okstra 0.146.0 → 0.147.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.md +2 -2
- package/docs/architecture/storage-model.md +8 -7
- package/docs/architecture.md +18 -12
- package/docs/cli.md +3 -3
- package/docs/project-structure-overview.md +16 -14
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +10 -10
- package/runtime/bin/okstra-render-final-report.py +4 -4
- package/runtime/bin/okstra-render-report-views.py +100 -12
- package/runtime/bin/okstra-trace-cleanup.sh +13 -9
- package/runtime/prompts/lead/okstra-lead-contract.md +4 -4
- package/runtime/prompts/lead/report-writer.md +15 -11
- package/runtime/prompts/profiles/_common-contract.md +13 -7
- package/runtime/prompts/profiles/improvement-discovery.md +3 -1
- package/runtime/python/okstra_ctl/final_report_schema.py +37 -12
- package/runtime/python/okstra_ctl/render_final_report.py +136 -28
- package/runtime/python/okstra_ctl/report_contract.py +124 -0
- package/runtime/python/okstra_ctl/report_finalize.py +1 -1
- package/runtime/python/okstra_ctl/report_html/__init__.py +10 -0
- package/runtime/python/okstra_ctl/report_html/common.py +50 -0
- package/runtime/python/okstra_ctl/report_html/models.py +59 -0
- package/runtime/python/okstra_ctl/report_html/render.py +69 -0
- package/runtime/python/okstra_ctl/report_html/router.py +40 -0
- package/runtime/python/okstra_ctl/report_html/view_models/__init__.py +1 -0
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +38 -0
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +48 -0
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +38 -0
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +46 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +46 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +102 -0
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +42 -0
- package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +54 -0
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +53 -0
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +54 -0
- package/runtime/python/okstra_ctl/report_html/visualizations.py +113 -0
- package/runtime/python/okstra_ctl/report_view_artifacts.py +4 -1
- package/runtime/python/okstra_ctl/report_views.py +15 -43
- package/runtime/python/okstra_ctl/run.py +12 -6
- package/runtime/python/okstra_ctl/schema_excerpt.py +7 -17
- package/runtime/schemas/final-report-v2.0.schema.json +3923 -0
- package/runtime/templates/reports/final-report-v2.template.md +66 -0
- package/runtime/templates/reports/html/assets/base.css +38 -0
- package/runtime/templates/reports/html/assets/base.js +5 -0
- package/runtime/templates/reports/html/base.template.html +65 -0
- package/runtime/templates/reports/html/macros/forms.html +47 -0
- package/runtime/templates/reports/html/macros/layout.html +19 -0
- package/runtime/templates/reports/html/macros/visualizations.html +17 -0
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +40 -0
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +40 -0
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +40 -0
- package/runtime/templates/reports/html/tasks/final-verification.template.html +39 -0
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +47 -0
- package/runtime/templates/reports/html/tasks/implementation.template.html +40 -0
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +29 -0
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +57 -0
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +36 -0
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +37 -0
- package/runtime/validators/validate-report-views.py +86 -4
- package/runtime/validators/validate-run.py +62 -9
- package/runtime/validators/validate_improvement_report.py +55 -0
- package/src/commands/lifecycle/install.mjs +18 -13
- package/src/commands/report/finalize.mjs +2 -3
- package/src/commands/report/render-final-report.mjs +4 -2
- package/src/commands/report/render-views.mjs +8 -8
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""CLI entrypoint for Phase 7 step 1.5 — render
|
|
3
|
-
view of an okstra final-report markdown.
|
|
2
|
+
"""CLI entrypoint for Phase 7 step 1.5 — render a final-report HTML view.
|
|
4
3
|
|
|
5
4
|
Usage:
|
|
6
|
-
okstra-render-report-views.py <path-to-final-report.md>
|
|
5
|
+
okstra-render-report-views.py <path-to-final-report.data.json|final-report.md>
|
|
7
6
|
[--task-key <task-group/task-id>]
|
|
8
7
|
[--task-type <profile>]
|
|
9
8
|
[--seq <NNN>]
|
|
10
9
|
[--source-report <relative-path>]
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
Schema-v2 ``.data.json`` input is rendered directly into the dedicated task
|
|
12
|
+
HTML template and always produces an HTML sibling. Markdown input resolves its
|
|
13
|
+
data sibling first; schema-v1 and quick reports keep the legacy conditional
|
|
14
|
+
Markdown renderer.
|
|
15
15
|
|
|
16
16
|
Output (idempotent — overwrites):
|
|
17
17
|
- <stem>.html — single-file self-contained HTML view
|
|
@@ -22,13 +22,19 @@ This script is the canonical single-reference-point. The Node CLI
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
24
|
import argparse
|
|
25
|
+
import json
|
|
25
26
|
import os
|
|
27
|
+
import re
|
|
26
28
|
import sys
|
|
27
29
|
from pathlib import Path
|
|
28
30
|
|
|
29
31
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
30
32
|
SCRIPTS_DIR = REPO_ROOT / "scripts"
|
|
31
|
-
HOME_LIB =
|
|
33
|
+
HOME_LIB = (
|
|
34
|
+
Path(os.environ.get("OKSTRA_HOME", str(Path.home() / ".okstra")))
|
|
35
|
+
/ "lib"
|
|
36
|
+
/ "python"
|
|
37
|
+
)
|
|
32
38
|
|
|
33
39
|
# Prefer dev sources when present, fall back to install. Without this
|
|
34
40
|
# order, a stale install (~/.okstra/lib/python/okstra_ctl) without the
|
|
@@ -43,6 +49,10 @@ elif HOME_LIB.is_dir() and str(HOME_LIB) not in sys.path:
|
|
|
43
49
|
sys.path.insert(0, str(HOME_LIB))
|
|
44
50
|
|
|
45
51
|
from okstra_ctl.report_views import infer_run_meta, render_html_view # noqa: E402
|
|
52
|
+
from okstra_ctl.final_report_paths import ( # noqa: E402
|
|
53
|
+
final_report_data_path,
|
|
54
|
+
final_report_markdown_path,
|
|
55
|
+
)
|
|
46
56
|
|
|
47
57
|
|
|
48
58
|
_OKSTRA_HOME = Path(os.environ.get("OKSTRA_HOME", str(Path.home() / ".okstra")))
|
|
@@ -77,6 +87,60 @@ def _load_assets() -> tuple[str, str]:
|
|
|
77
87
|
return css_text, js_text
|
|
78
88
|
|
|
79
89
|
|
|
90
|
+
def _templates_root() -> Path:
|
|
91
|
+
for directory in _TEMPLATES_DIRS:
|
|
92
|
+
if (directory / "html" / "base.template.html").is_file():
|
|
93
|
+
return directory
|
|
94
|
+
raise SystemExit(
|
|
95
|
+
"schema-v2 HTML templates not found. Looked under: "
|
|
96
|
+
+ ", ".join(str(directory) for directory in _TEMPLATES_DIRS)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _report_pair(report_path: Path) -> tuple[Path, Path]:
|
|
101
|
+
if report_path.name.endswith(".data.json"):
|
|
102
|
+
return report_path, final_report_markdown_path(report_path)
|
|
103
|
+
return final_report_data_path(report_path), report_path
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _load_data_if_present(data_path: Path) -> dict | None:
|
|
107
|
+
if not data_path.is_file():
|
|
108
|
+
return None
|
|
109
|
+
try:
|
|
110
|
+
data = json.loads(data_path.read_text(encoding="utf-8"))
|
|
111
|
+
except json.JSONDecodeError as exc:
|
|
112
|
+
raise SystemExit(
|
|
113
|
+
f"final-report data is not valid JSON: {data_path} ({exc})"
|
|
114
|
+
) from exc
|
|
115
|
+
if not isinstance(data, dict):
|
|
116
|
+
raise SystemExit(f"final-report data must be a JSON object: {data_path}")
|
|
117
|
+
return data
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _v2_run_meta(data: dict, markdown_path: Path, args: argparse.Namespace):
|
|
121
|
+
from okstra_ctl.report_html.models import HtmlRunMeta
|
|
122
|
+
|
|
123
|
+
header = data.get("header", {})
|
|
124
|
+
task_key = args.task_key or header.get("taskKey")
|
|
125
|
+
task_type = args.task_type or header.get("taskType")
|
|
126
|
+
match = re.search(r"-(\d+)\.md$", markdown_path.name)
|
|
127
|
+
seq = (
|
|
128
|
+
args.seq
|
|
129
|
+
or data.get("analysisCommon", {}).get("runSeq")
|
|
130
|
+
or (match.group(1) if match else None)
|
|
131
|
+
)
|
|
132
|
+
if not all(
|
|
133
|
+
isinstance(value, str) and value for value in (task_key, task_type, seq)
|
|
134
|
+
):
|
|
135
|
+
raise SystemExit("schema-v2 report metadata requires task-key, task-type, and seq")
|
|
136
|
+
return HtmlRunMeta(
|
|
137
|
+
task_key,
|
|
138
|
+
task_type,
|
|
139
|
+
seq,
|
|
140
|
+
args.source_report or markdown_path.name,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
80
144
|
def main(argv: list[str] | None = None) -> int:
|
|
81
145
|
parser = argparse.ArgumentParser(
|
|
82
146
|
description="Render the self-contained HTML view of an okstra final-report."
|
|
@@ -88,18 +152,42 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
88
152
|
parser.add_argument("--source-report", default=None)
|
|
89
153
|
args = parser.parse_args(argv)
|
|
90
154
|
|
|
91
|
-
report_path
|
|
155
|
+
report_path = args.report_path.resolve()
|
|
92
156
|
if not report_path.is_file():
|
|
93
157
|
parser.error(f"final-report not found: {report_path}")
|
|
158
|
+
data_path, markdown_path = _report_pair(report_path)
|
|
159
|
+
data = _load_data_if_present(data_path)
|
|
160
|
+
|
|
161
|
+
if data is not None and data.get("schemaVersion") == "2.0":
|
|
162
|
+
if not markdown_path.is_file():
|
|
163
|
+
parser.error(f"schema-v2 markdown sibling not found: {markdown_path}")
|
|
164
|
+
from okstra_ctl.report_html.render import render_v2_html_view
|
|
165
|
+
|
|
166
|
+
html_path = render_v2_html_view(
|
|
167
|
+
data_path,
|
|
168
|
+
markdown_path,
|
|
169
|
+
run_meta=_v2_run_meta(data, markdown_path, args),
|
|
170
|
+
templates_root=_templates_root(),
|
|
171
|
+
)
|
|
172
|
+
print(f"html: {html_path}")
|
|
173
|
+
return 0
|
|
94
174
|
|
|
175
|
+
if not markdown_path.is_file():
|
|
176
|
+
parser.error(f"legacy markdown sibling not found: {markdown_path}")
|
|
95
177
|
meta = infer_run_meta(
|
|
96
|
-
|
|
97
|
-
|
|
178
|
+
markdown_path,
|
|
179
|
+
task_key=args.task_key,
|
|
180
|
+
task_type=args.task_type,
|
|
181
|
+
seq=args.seq,
|
|
182
|
+
source_report=args.source_report,
|
|
98
183
|
)
|
|
99
184
|
css, js = _load_assets()
|
|
100
|
-
html_path = render_html_view(
|
|
185
|
+
html_path = render_html_view(markdown_path, run_meta=meta, css=css, js=js)
|
|
101
186
|
if html_path is None:
|
|
102
|
-
print(
|
|
187
|
+
print(
|
|
188
|
+
"html: skipped (no §1 clarification rows — html view carries no "
|
|
189
|
+
"interactive forms for this report)"
|
|
190
|
+
)
|
|
103
191
|
else:
|
|
104
192
|
print(f"html: {html_path}")
|
|
105
193
|
return 0
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
# (b) worker-agent panes the harness gives to dispatched
|
|
18
18
|
# subagents (`claude-worker` / `codex-worker` /
|
|
19
19
|
# `antigravity-worker` / `report-writer-worker`), identified
|
|
20
|
-
# by a title allowlist scoped to the LEAD's
|
|
20
|
+
# by a title allowlist scoped to the LEAD's WINDOW. The
|
|
21
21
|
# lead pane is read from `<RUN_DIR>/state/lead-pane.id`
|
|
22
22
|
# (recorded once by the lead in its own foreground pane —
|
|
23
23
|
# reliable even though Claude Code's Bash tool strips
|
|
24
24
|
# `$TMUX`/`$TMUX_PANE`); it scopes the title scan and is
|
|
25
|
-
# NEVER killed.
|
|
25
|
+
# NEVER killed. Window scope keeps a second lead running
|
|
26
|
+
# in another window of the same session out of range.
|
|
26
27
|
#
|
|
27
28
|
# --reap Used by the `SessionEnd` hook, where no single run-dir
|
|
28
29
|
# applies. Closes every trace pane whose tag points under
|
|
@@ -199,12 +200,15 @@ collect_okstra_panes() {
|
|
|
199
200
|
done < <(tmux list-panes -a \
|
|
200
201
|
-F '#{pane_id}'$'\t''x#{@okstra_trace_run}'$'\t''x#{@okstra_worker_run}'$'\t''x#{@okstra_status}' \
|
|
201
202
|
2>/dev/null || true)
|
|
202
|
-
# (2) Title-allowlisted worker-agent panes in the lead's
|
|
203
|
-
# run (reap leaves these harness-owned panes to the harness). `list-panes -
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
203
|
+
# (2) Title-allowlisted worker-agent panes in the lead's WINDOW. Only for a
|
|
204
|
+
# run (reap leaves these harness-owned panes to the harness). `list-panes -t
|
|
205
|
+
# <pane>` (no `-s`, no `-a`) resolves the window containing that pane and
|
|
206
|
+
# lists only that window's panes. Split-pane teammates always land in the
|
|
207
|
+
# lead's window, so this catches all of THIS run's worker panes while a second
|
|
208
|
+
# okstra lead in another window of the same tmux session — whose in-flight
|
|
209
|
+
# worker panes are untagged and title-only — is left untouched. Skipped when
|
|
210
|
+
# the lead pane is unknown. reclaim 모드는 teammate pane 을 회수하지 않으므로
|
|
211
|
+
# (완료 판정 불가, trace-only) 이 스캔을 건너뛴다.
|
|
208
212
|
if [[ "$REAP" -eq 0 && "$RECLAIM" -eq 0 && -n "$lead_pane" ]]; then
|
|
209
213
|
while IFS=$'\t' read -r pid title; do
|
|
210
214
|
[[ -n "$pid" ]] || continue
|
|
@@ -212,7 +216,7 @@ collect_okstra_panes() {
|
|
|
212
216
|
if _title_in_okstra_scope "$title"; then
|
|
213
217
|
panes+=("$pid")
|
|
214
218
|
fi
|
|
215
|
-
done < <(tmux list-panes -
|
|
219
|
+
done < <(tmux list-panes -t "$lead_pane" \
|
|
216
220
|
-F '#{pane_id}'$'\t''#{pane_title}' 2>/dev/null || true)
|
|
217
221
|
fi
|
|
218
222
|
|
|
@@ -202,7 +202,7 @@ If previous run reports exist, use as historical context only. If discovery meta
|
|
|
202
202
|
|
|
203
203
|
For `project-analysis`, `feature-analysis`, and `change-impact-analysis`, Lead MUST use the run manifest's immutable pre-dispatch `analysisScopeConfirmation` snapshot as the structured reporter-confirmation evidence before Phase 4 worker dispatch. Its `status` MUST be `complete`; its `taskBriefPath` and `briefSha256` bind that status to the exact brief bytes captured when the run manifest was created. A later edit to the live brief, clarification prose, or inferred consent cannot substitute for this snapshot. `project-analysis` confirms which areas remain shallow; `feature-analysis` confirms the exact feature target and covered flows; `change-impact-analysis` confirms the proposed change, preserved behavior, and dependency boundary.
|
|
204
204
|
|
|
205
|
-
If that snapshot is incomplete, missing, or malformed, Lead MUST follow the shared Reporter Confirmation Required / Clarification Items contract
|
|
205
|
+
If that snapshot is incomplete, missing, or malformed, Lead MUST follow the shared Reporter Confirmation Required / Clarification Items contract and stop before dispatch. A scope reduction must be explicitly confirmed in the brief's `## Reporter Confirmations` before a fresh bundle records `analysisScopeConfirmation.status=complete`. Do not dispatch workers and then defer a foreseeable scope conflict to a final HTML question. **Enforcement:** `scripts/okstra_ctl/render.py` records the brief path, reporter-confirmation status, and brief byte digest in the run manifest before the lead can dispatch workers; `validators/validate_analysis_report.py` validates only that immutable snapshot, rejects required-worker execution before a complete snapshot, and recomputes the analysis verdict; `validators/validate-run.py` runs that check only after schema validation succeeds.
|
|
206
206
|
|
|
207
207
|
## Phase 2 — Phase 5: Prompt preparation, teammate setup, execution, completion poll
|
|
208
208
|
|
|
@@ -308,7 +308,7 @@ If convergence is disabled, `seed`/`finalize` produce the auto-disabled final st
|
|
|
308
308
|
|
|
309
309
|
### Authoring ownership (BLOCKING)
|
|
310
310
|
|
|
311
|
-
If `Report writer worker` is in the selected roster (`recommendedWorkers` / `resultContract.requiredWorkerRoles`), **Lead MUST dispatch it to author the final report**. The worker writes the JSON SSOT at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` and invokes `scripts/okstra-render-final-report.py` to produce the sibling
|
|
311
|
+
If `Report writer worker` is in the selected roster (`recommendedWorkers` / `resultContract.requiredWorkerRoles`), **Lead MUST dispatch it to author the final report data.json**. The worker writes the JSON SSOT at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` and invokes `scripts/okstra-render-final-report.py` to produce the sibling AI handoff Markdown. Phase 7 renders human HTML directly from the same data.json. Lead writes none of these files; it prepares the prompt, dispatches, and reviews both audience artifacts. See [report-writer](./report-writer.md) "File-author ownership".
|
|
312
312
|
|
|
313
313
|
Before constructing the dispatch prompt, the lead MUST:
|
|
314
314
|
|
|
@@ -358,8 +358,8 @@ Lead's responsibilities in this sub-step (in order):
|
|
|
358
358
|
2. Dispatch a single plan-body reverify round to every analyser worker in the roster (`claude`, `codex`, and `antigravity` when opted in). `Report writer worker` is NOT a participant in this round.
|
|
359
359
|
3. Aggregate verdicts and resolve the gate result to one of `passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`.
|
|
360
360
|
4. Write `runs/<task-type>/state/plan-body-verification.json` (schema in the plan-body-verification contract), appending one `roundHistory[]` entry per round — including every self-fix re-verification round, since data.json keeps only the final verdicts.
|
|
361
|
-
5. Populate
|
|
362
|
-
6. For every `majority-disagree` plan item, append
|
|
361
|
+
5. Populate `implementationPlanning.planBodyVerification` in data.json with round count, gate result, per-item verdicts, and dissent log. The AI handoff task-deliverable block carries this structure without a second prose rendering.
|
|
362
|
+
6. For every `majority-disagree` plan item, append one `clarificationItems[]` row with `blocks=approval` and the 1:1 ID match in the verdict classification (`majority-disagree → C-<N>`). Do not create a parallel open-questions structure.
|
|
363
363
|
7. Publish the YAML frontmatter `approved:` field as `false`. There is no in-body `- [ ] Approved` marker line — approval lives only in the frontmatter (see [plan-body-verification](./plan-body-verification.md) §"Round protocol" step 9). The user may flip it to `true` only when the gate is `passed` or `passed-with-dissent`. **Enforced:** `validators/validate-run.py` `validate_phase_boundary` fails a report shipping `approved: true` under `blocked-by-disagreement` / `aborted-non-result`, and run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan`) fail-closes the same case. Manually flipping a blocked gate to passing is a contract violation.
|
|
364
364
|
|
|
365
365
|
If `convergence.planBodyVerification.enabled == false` (set by `--no-plan-verification` or by `okstra config set plan-verification off`), the entire sub-step is skipped and the top-of-report Approval marker is rendered unconditionally (legacy behaviour). This opt-out is intended for fast iteration only and is not recommended for handoff-ready plans.
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
The final-report data.json is authored by `Report writer worker` when that role is in the roster. The lead reviews both rendered artifacts but does not write them. Lead-authored fallback is legal only after a real `dispatch_worker` attempt records `error`, `timeout`, or `not-run` with a concrete reason. `release-handoff` remains the intentional single-lead exception.
|
|
6
6
|
|
|
7
|
-
The JSON SSOT path is `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`.
|
|
7
|
+
The JSON SSOT path is `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`. Two audience artifacts are independently derived from the data.json: `scripts/okstra-render-final-report.py` produces the AI handoff Markdown sibling, and Phase 7 produces the task-specific human HTML sibling directly from data.json. The worker-result pointer at `**Worker Result Path:**` records the data and Markdown paths plus reconciled convergence input. These three completion artifacts land on disk before the worker returns; HTML follows during finalization and the heartbeat sidecar remains a separate audit artifact.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
New bundles use `schemas/final-report-v2.0.schema.json`. The Markdown keeps verdict, routing, evidence, one structured task deliverable, and audit data for the next agent. The HTML uses `humanSummary`, task `userNarrative`, and structured facts for the user. Raw worker discussion, convergence mechanics, and usage belong to audit structures and never to the HTML human main body.
|
|
10
10
|
|
|
11
11
|
Two `frontmatter` approval fields are always emitted with their unset default — never pre-fill them: `frontmatter.approved` is emitted as `false`, and `frontmatter.implementationOption` is emitted as an empty string `""`. The user later flips `approved` to `true` (via `--approve` or manual edit) and fills `implementationOption` with the chosen Option Candidate name (via `--implementation-option <name>` or manual edit) to authorise and scope the next `implementation` run.
|
|
12
12
|
|
|
@@ -46,8 +46,8 @@ The prompt MUST include, in this order at the top:
|
|
|
46
46
|
8. `**Prompt Delivery Mode:** <mode>` — the selected adapter's declared `initialPromptDeliveryMode`.
|
|
47
47
|
9. `**Model:** Report writer worker, <modelExecutionValue>` (resolved per Phase 5.5 anchor-header rules)
|
|
48
48
|
10. The full `[Required reading]` clause (see [team-contract](./team-contract.md)) — for Phase 6 it adds two **per-task-type, instruction-set-local** read-only files, both scoped to this run's task-type by `okstra-ctl` at prep time:
|
|
49
|
-
- `<instruction-set>/final-report-schema.json` — a task-type excerpt of
|
|
50
|
-
- `<instruction-set>/final-report-template.md` — the
|
|
49
|
+
- `<instruction-set>/final-report-schema.json` — a task-type excerpt of schema v2. This is the binding authoring shape; the installed full schema is what the run is judged against. Do **NOT** pull the full repository schema because it is outside the task bundle.
|
|
50
|
+
- `<instruction-set>/final-report-template.md` — the AI handoff Markdown template. It shows the agent-facing ledger shape, not the human presentation. The task-specific HTML renderer reads data.json separately.
|
|
51
51
|
11. A one-line MCP pointer instead of the verbatim block (redundant — the brief is already in the report-writer's Required reading, item 10): `**MCP servers:** follow the task brief's "## Available MCP Servers" section (already in your Required reading).`
|
|
52
52
|
12. `Convergence state: runs/<task-type>/state/convergence-<task-type>-<seq>.json`, followed by pointers to all analysis-worker result files under `worker-results/`. The convergence path is deterministic and is listed even before Phase 5.5 creates the file. Read its classifications (Full/Partial/Contested/Worker-Unique), `roundHistory[]`, `round2SkippedReason`, and `finalClassificationCounts`; populate `crossVerification.roundHistory` in data.json so Section 6 can show which rounds executed, queue sizes, and why Round 2 was (or was not) skipped. The renderer prints the full per-round table only when more than one round ran; single-round or zero-round histories are auto-collapsed to a one-line summary.
|
|
53
53
|
13. `**Report Language:** <en|ko>` — must be either `en` or `ko`; `auto`
|
|
@@ -56,7 +56,7 @@ The prompt MUST include, in this order at the top:
|
|
|
56
56
|
into `data.json.meta.reportLanguage`.
|
|
57
57
|
14. For implementation-planning runs: a literal block listing the 12 required English section headings — `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. This list is `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`; that tuple is the SSOT and this block must match it exactly. The writer uses these exact substrings as section headings (Korean translation in parentheses is allowed), and the `Plan Body Verification` section carries its required `Gate result:` line.
|
|
58
58
|
15. An explicit instruction: `You are the author of THREE files: (a) the final-report data.json at <Result Path>, (b) its rendered Markdown sibling produced through "okstra render-final-report <Result Path>", and (c) the worker-result pointer at <Worker Result Path>. Maintain the separate heartbeat audit sidecar at <Audit sidecar path>. Do not return the report inline. The dispatch fails when any of the three completion artifacts is missing, and session conformance fails when the audit sidecar is missing or invalid.`
|
|
59
|
-
16. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT — at most 3 sentences. `rationale.*` fields stay within 2 sentences each
|
|
59
|
+
16. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT — at most 3 sentences. `rationale.*` fields stay within 2 sentences each; `humanSummary` entries stay concise; task `userNarrative` explains each user-facing section once with evidence references. Do not copy these narratives into the AI Markdown. `summary` stays at 3-5 rows unless the run covers multiple tickets. Generation time scales with output volume, so exceeding the budget is a cost bug, not extra diligence.
|
|
60
60
|
|
|
61
61
|
**Fix-run incremental authoring (applies when the run's profile carries a "Fix-Run Carry" block).** Do not author the data.json from scratch. Start by copying the previous run's data.json (the `Previous report` path in the Fix-Run Carry block) to this run's Result Path, then update ONLY the blocks the fix run changed: `meta`/`header` (run seq, dates), `executionStatus`, `implementation.verifierResults`, `implementation.validationEvidence`, `implementation.commitList` / `diffSummary`, `crossVerification`, `verdictCard`, `finalVerdict`, and any `evidence` rows the fix touched. Deliverable prose for unchanged sections is carried forward verbatim — do not re-generate it. Then invoke the renderer exactly as in a full run. The schema validation and renderer contract are unchanged, so an incrementally-authored data.json passes the same post-hoc gates. The lead's dispatch prompt MUST include the previous data.json path when the carry block is present.
|
|
62
62
|
|
|
@@ -81,7 +81,7 @@ Speculative reasons such as "session resume constraint", "runtime state is unava
|
|
|
81
81
|
|
|
82
82
|
Phase 6 first produces the final-report data.json at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` and its rendered markdown sibling. Token Usage cells are `null` at this point, and Section 3 does not yet include auto-spawned follow-ups.
|
|
83
83
|
|
|
84
|
-
For an implementation-planning run, the Report writer worker owns the Phase 6 design assessment snapshot: it writes `designPreparation` and every stage's `designSurfaceCoverage` into data.json from the detector output and consolidated plan. It does not create user inputs, consume a user answer as if it were part of that snapshot, or materialize `design-prep-requests/`; `schemas/final-report-
|
|
84
|
+
For an implementation-planning run, the Report writer worker owns the Phase 6 design assessment snapshot: it writes `designPreparation` and every stage's `designSurfaceCoverage` into data.json from the detector output and consolidated plan. It does not create user inputs, consume a user answer as if it were part of that snapshot, or materialize `design-prep-requests/`; `schemas/final-report-v2.0.schema.json` and `validators/validate-run.py` `_validate_design_prep_contract` enforce the snapshot shape, detector coverage, and references.
|
|
85
85
|
|
|
86
86
|
Phase 7 post-processing is **one command**. `okstra report-finalize` owns the ordered sequence — it is the same code path the Codex lead adapter runs automatically, so a Claude-led run and a Codex-led run finalize identically:
|
|
87
87
|
|
|
@@ -101,12 +101,12 @@ The steps it executes, in this contractual order, and the contract each one carr
|
|
|
101
101
|
The data.json paths populated: `tokenUsage.lead.{totalTokens,billableTokens,costUsd}`, the `worker` / `grand` rows, `tokenUsage.cli.costUsd`, and each `executionStatus[].{totalTokens,billableTokens,costUsd,durationMs,cliTotalTokens,cliCostUsd}` for rows whose role matches a team-state worker. The data.json MUST already exist (Phase 6 output).
|
|
102
102
|
|
|
103
103
|
For implementation-planning, this Phase 7 canonical render calls `materialize_design_prep_requests()` after token substitution and creates deterministic request files only for `provisional` / `blocked` items. Later answers are append-only user-input sidecars; request generation and user input never rewrite the assessment fields, so the source report remains immutable as the design-input snapshot after this render. `validators/validate-run.py` `_validate_design_prep_requests` enforces request existence, canonical path, content, and assessment fingerprint.
|
|
104
|
-
2. **`render-views` — render the human report artifact.** Runs against the substituted
|
|
104
|
+
2. **`render-views` — render the human report artifact.** Runs against the substituted v2 data.json and its Markdown sibling.
|
|
105
105
|
|
|
106
106
|
Output (idempotent — re-running overwrites):
|
|
107
|
-
- `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view,
|
|
107
|
+
- `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — single-file self-contained human view, always generated for schema v2 from the dedicated template registered for that task type. Clarification rows with `Status` ∈ {`open`, `answered`} embed response controls and export a `user-response-<task-type>-<seq>.md` sidecar. The original data and Markdown artifacts are never mutated by user input.
|
|
108
108
|
- the implementation-planning report renders a **Plan Approval** section at the end of the body (implementation-option `<select>` + an approval checkbox) — disabled while any §1 `Blocks: approval` row is unresolved. Checking approval and exporting embeds a `## APPROVAL` block in the sidecar body, and the implementation-start wizard's approve-confirm step detects it and, after user confirmation, applies it through the existing `--approve` / `--implementation-option` path.
|
|
109
|
-
-
|
|
109
|
+
- Schema-v1 and quick compatibility reports retain the legacy conditional HTML path; this does not change the schema-v2 always-generated contract.
|
|
110
110
|
|
|
111
111
|
It runs after usage collection so token placeholders are substituted in any rendered html, and before routing persistence so the html artifact, when generated, exists for the validator step that checks it.
|
|
112
112
|
3. **`spawn-followups` — routing and follow-up persistence.** Turns the report's `## 4. Follow-up Tasks` rows into `tasks/<task-group>/<new-task-id>/` stubs.
|
|
@@ -128,9 +128,13 @@ The steps it executes, in this contractual order, and the contract each one carr
|
|
|
128
128
|
|
|
129
129
|
After `okstra report-finalize` reports `"ok": true`, **execute the run-scoped cleanup gate.** Call `shutdown_workers` only after that success, all persistence work, and explicit user approval under [okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped worker-resource lifecycle". If the user keeps resources, leave the selected adapter's resources intact and surface its manual cleanup guidance.
|
|
130
130
|
|
|
131
|
-
##
|
|
131
|
+
## Schema-v2 report data responsibilities
|
|
132
132
|
|
|
133
|
-
The
|
|
133
|
+
The binding authoring shape is the task bundle's `instruction-set/final-report-schema.json`. Populate both audiences in data.json: agent-facing verdict, routing, evidence, task facts, and audits; user-facing `humanSummary` and task `userNarrative`. The AI handoff template deliberately omits the full user narrative, while the task-specific HTML deliberately moves `crossVerification`, `executionStatus`, and `tokenUsage` into collapsed audit details.
|
|
134
|
+
|
|
135
|
+
## Legacy schema-v1 Markdown structure reference
|
|
136
|
+
|
|
137
|
+
The remaining numbered-section guide exists only for rendering or diagnosing historical schema-v1 data. New report-writer runs do not author against it; their instruction-set schema and AI handoff template are authoritative.
|
|
134
138
|
|
|
135
139
|
### Report Header
|
|
136
140
|
|
|
@@ -12,6 +12,11 @@ profile document.
|
|
|
12
12
|
- **Phase 5.5 (convergence — peer review by workers)**: workers peer-review each other's findings across up to `effectiveMaxRounds` rounds; the lead mediates but does not vote. See `prompts/lead/convergence.md` for the round protocol (replay of findings, `AGREE` / `DISAGREE` / `SUPPLEMENT` verdicts), queue invariants, and final classification (`full-consensus` / `partial-consensus` / `contested` / `worker-unique`). For `requirements-discovery`, `error-analysis`, `implementation-planning`, `project-analysis`, `feature-analysis`, and `change-impact-analysis` this phase runs in **adversarial mode** (`convergence.adversarial=true`): verifiers try to refute each finding against its cited evidence and the burden of proof sits on the claim — see that skill's §"Adversarial Verification Mode".
|
|
13
13
|
- Do NOT conclude "no peer review happens" from the roster alone — every profile that lists ≥2 analyser workers runs convergence by default (`convergence.enabled=true` in `task-manifest.json`).
|
|
14
14
|
- **provider-unavailable fallback (tolerance).** A worker dispatch can fail to produce a result for two distinct reasons, and both take the same recovery path. (1) **Pane budget:** the dispatch is rejected with `no room for another tmux split` (or an equivalent teammate-pane creation failure). (2) **Sandbox CLI-start failure (non-tmux path):** an external CLI worker wrapper exits non-zero within seconds with empty stdout and its live-log shows `operation not permitted` (e.g. `agy`'s `listen tcp 127.0.0.1:0: bind` under a seatbelt sandbox around a non-tmux subagent's Bash tool). In either case the lead retries that worker in-process without `run_in_background`; if it was an external CLI worker, the lead instead **substitutes** an in-process `claude` analysis. Either way the lead records the substitution as provider unavailable in the run log and the convergence notes. Completed external-CLI worker trace panes are reclaimed automatically by the `SubagentStop` hook, but okstra cannot directly reclaim the teammate panes the harness creates, so this fallback is the last line of defence against pane-budget exhaustion and sandbox-blocked CLI workers. (This is a prompt instruction, not a code-enforced gate.)
|
|
15
|
+
- Dual-audience final-report contract (shared):
|
|
16
|
+
- data.json is the sole authored report artifact. AI handoff Markdown and human HTML are independently derived from it; neither derived artifact is the other's source.
|
|
17
|
+
- User-facing information belongs in `humanSummary` and the selected task block's `userNarrative`; it must not exist only in Markdown. The HTML human main body explains the result with those fields plus task facts.
|
|
18
|
+
- Agent coordination and audit details belong in `crossVerification`, `executionStatus`, and `tokenUsage`. HTML may expose them only inside collapsed audit details, never as the primary result.
|
|
19
|
+
- Clarification and approval controls are rendered from data.json IDs. A question that could have been resolved before dispatch through the profile or Reporter Confirmations is an intake failure, not a final-report question.
|
|
15
20
|
- Tooling — read-only MCP availability (shared):
|
|
16
21
|
- MCP is not implicit context; query a server only when the task brief explicitly lists it as source material for this run. Any MCP-derived finding MUST cite server, table, and the SELECT used. MCP MUST NEVER be a write path — schema/data mutations go through repository migration files reviewed by humans.
|
|
17
22
|
- Resource boundary (shared — artifact-home rule):
|
|
@@ -54,7 +59,8 @@ profile document.
|
|
|
54
59
|
- Any decision in this run that contradicts the brief's `Source Material` must be raised back to the reporter via a `Clarification Items` row; it must NOT be silently overridden. Disagreement with the reporter is allowed only after the row is resolved.
|
|
55
60
|
- This contract is the single authority on brief consumption. Phase-specific addenda may *tighten* these rules but may not relax them.
|
|
56
61
|
- Clarification request policy (shared — applies whenever a profile uses `## 1. Clarification Items`):
|
|
57
|
-
-
|
|
62
|
+
- Schema-v2 final reports author `clarificationItems[]` in data.json; task-specific HTML renders the question and response controls directly from those IDs, and AI handoff Markdown serializes the same array for the next agent. The remaining table-layout rules describe schema-v1 compatibility and analysis-worker result tables only.
|
|
63
|
+
- **Legacy canonical column schema (must match `templates/reports/final-report.template.md` §1 exactly):** every `## 1. Clarification Items` table has exactly these 4 columns, in this order:
|
|
58
64
|
`| <record-meta> | Statement | Expected form | User input |` (the first header is the i18n `columns.recordMeta` label — `Record`).
|
|
59
65
|
The five short fields (ID, Ticket ID, Kind, Blocks, Status) are stacked inside the single record-meta cell, one per line separated by `<br>`, in this fixed order (mirrors the §2.1 Primary-Evidence meta column):
|
|
60
66
|
|
|
@@ -64,19 +70,19 @@ profile document.
|
|
|
64
70
|
|
|
65
71
|
The labels `Ticket:` / `Kind:` / `Blocks:` / `Status:` stay English in every locale so the approval-gate parser (`clarification_items.parse_meta_cell`) reads them regardless of report language.
|
|
66
72
|
Profile-specific addenda may tighten cell content but MUST NOT add, remove, rename, or reorder columns, nor change the meta-cell field order. The `ID` is `C-NNN` (3-digit zero-padded), the `Status` ∈ `{open, answered, resolved, obsolete}`, and the `Kind` / `Blocks` legal values are listed below.
|
|
67
|
-
- section 1 is a **single unified table** per `final-report-template.md`. Every clarification item
|
|
73
|
+
- In schema-v1 Markdown and worker-result tables, section 1 is a **single unified table** per `final-report-template.md`. Every clarification item is one row. Do not split it into sub-sections or create a parallel question table.
|
|
68
74
|
- each row's `Kind` column picks one of `{material, decision, data-point}`: `material` for files / snapshots / logs / screenshots the user must attach (the `User input` cell will hold a path or URL); `decision` for choices and yes/no confirmations only the user can make; `data-point` for a single number, ID, date, or short string the user can answer inline. A `decision` alternative must be a terminal choice the user can pick as-is; if acting on an alternative still requires the user to supply a concrete value (a path, string, number, or file), that value is its own `data-point` / `material` row — never phrase a data-entry action (e.g. "specify the path", "enter a value") as a selectable `decision` option, because the rendered `<select>` cannot capture the value the option demands. Items that mix "yes/no + file path if yes" are one row of `Kind=material` with the combined expectation written into `Expected form`.
|
|
69
75
|
- each row's `Blocks` column picks one of `{approval, next-phase, none}`. `approval` is reserved for items that gate an approval action, especially the `implementation-planning` `approved:` frontmatter flip; outside `implementation-planning`, unresolved brief reporter-confirmation rows use `next-phase` instead. `next-phase` blocks the next run from starting cleanly. `none` is informational/audit-only.
|
|
70
76
|
- write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the answer shape (yes/no, one of the options, number/date, file path, short description, etc.); supply concrete option choices when applicable.
|
|
71
77
|
- if a phase requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: (a) <answer> — <rationale>; Alternatives: (b) <option> (c) <option>`. The recommended answer is always the first option and MUST carry the `(a)` label; alternatives continue the same letter sequence from `(b)` (a lone alternative is `(b) <option>`, never restart at `(a)`), so the full option set reads `(a) (b) (c) …` in order and renders each as its own selectable option. Do **not** append a pick-one answer-space summary such as `(pick 1 of A / B)` or `(pick N of …)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
|
|
72
|
-
-
|
|
73
|
-
-
|
|
78
|
+
- For schema v2, data.json is canonical and the HTML exports answers to a user-response sidecar; the source report is never edited. `--resume-clarification` carries those answers into the next run. The lower-level `--clarification-response <path>` remains available for scripted runs.
|
|
79
|
+
- When a response is carried in, reconcile every prior `clarificationItems[]` row against new evidence and update its status to `resolved` or `obsolete` before issuing the next verdict. Schema-v1 compatibility Markdown may additionally render its conditional Section 0; schema-v2 AI Markdown records decisions under `## Clarification and User Decisions`.
|
|
74
80
|
- **Supersession (BLOCKING).** Reconciling the `C-*` row is only half of incorporating an answer. An answer does not merely *add* a decision — it *invalidates* whatever the previous run wrote under the opposite assumption. Before issuing the next decision, walk the prior deliverable prose for every statement the answer makes false and **delete or rewrite it**, then record the retirement. Adding the new decision while leaving the contradicting sentence in place puts two opposite instructions for the same symbol in one document; the implementer must then guess which is live, and the next verification round correctly blocks on it. In `implementation-planning` this record is `implementationPlanning.supersessionLedger[]` — one entry per answered clarification, either `disposition: superseded` (with the retired statement, its replacement, and the sections revised) or `disposition: no-dependent-statement` (with a rationale). **Enforced:** `validators/validate-run.py` `_validate_supersession_ledger` requires an entry per answered clarification; whether the claim is *true* is what the §5.5.9 adversarial round tests.
|
|
75
|
-
- Verdict Card (shared
|
|
76
|
-
-
|
|
81
|
+
- Verdict Card data consistency (shared; schema-v1 Markdown keeps the legacy visible card):
|
|
82
|
+
- `verdictCard.verdictToken` and `.direction` MUST byte-match `finalVerdict`; next-step routing must agree with `recommendedNextSteps[0]`. Schema-v1 Markdown renders the visible `## Verdict Card` / `## 7. Final Verdict` pair. Schema-v2 derives the AI handoff and human summary from the data fields without repeating both visible sections. **Enforced:** `validators/validate-run.py` `_validate_verdict_card_consistency` and `_validate_verdict_card_fields`.
|
|
77
83
|
- Cross-worker traceability (shared — applies to every analysis worker output and to the lead's `## 6.` / `## 2.` tables in the final-report):
|
|
78
84
|
- **Worker-side item IDs (free-form but unique within the worker).** Every row item in sections 1–5 (and any optional section 6) of an analysis worker's output MUST carry an item ID that is unique within that one worker's result file. The ID convention is the worker's choice — `F-001` / `F-002` per the suggested schema, `1.1` / `1.2` / `1.3` as Codex tends to use, or any other shape — but it MUST appear as the leading column of the row (for table-form items) or as a `[<ID>]` prefix (for bullet/numbered items). Workers that emit findings without IDs make cross-worker reconciliation impossible.
|
|
79
|
-
- **Lead-side ID assignment + source preservation.** When the lead (or `report-writer-worker`) synthesises
|
|
85
|
+
- **Lead-side ID assignment + source preservation.** When the lead (or `report-writer-worker`) synthesises consensus, difference, or primary-evidence rows from worker outputs, the lead assigns a fresh `C-NNN` / `D-NNN` / `E-NNN` row ID. Each `sourceItems` field MUST list every contributing worker:item pair (e.g. `claude:F-001`, `codex:1.1`, `antigravity:F-3`) so an agent can trace the synthesised row to the worker result. Bare worker names are rejected. **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.SourceItem` pins each entry to `^[a-z][a-z-]*:[A-Za-z0-9._-]+$`, and `ConsensusRow` / `PrimaryEvidenceRow` require non-empty `sourceItems`.
|
|
80
86
|
- Audit sidecar (shared): Reading Confirmation placement follows the audience-selected preamble named by `**Worker Preamble Path:**`. Profiles do not restate it; the main worker-results body starts at section 1.
|
|
81
87
|
|
|
82
88
|
- Markdown authoring (shared — applies to markdown documents not already governed by an okstra template/schema):
|
|
@@ -48,11 +48,13 @@
|
|
|
48
48
|
- uncertainty and overlap relationships kept explicit for downstream consensus classification
|
|
49
49
|
- every candidate's `Expected behavior after` is checked against the brief's `## Preserved Behavior` items before the row is written. A candidate whose observable change contradicts a `PB-NNN` item is dropped, or raised as a `## 1. Clarification Items` row — never softened into a vaguer cell. The scan brief's PB set is the upper bound on what this phase may propose changing.
|
|
50
50
|
- Report assembly instructions:
|
|
51
|
+
- current branch — `schemaVersion: 2.0`: author the structured data contract below and let the independent renderers produce AI handoff Markdown and task-specific human HTML.
|
|
52
|
+
- v1 legacy branch: when validating or rerendering an existing schema-v1 report, preserve its `## 5.9 Improvement Candidates` table and legacy Markdown contract; do not rewrite that historical data into v2 implicitly.
|
|
51
53
|
- the `## 5.9 Improvement Candidates` table populated with rows that obey the 11-column schema from `validators/validate_improvement_report.py` (Cand ID `I-NNN`, Lens from whitelist, Title, Scope ⊆ scan-scope, Severity, Effort, Consensus, Source workers `<worker>:<id>` from {claude, codex, antigravity}, Recommended next-phase ∈ {requirements-discovery, implementation-planning, error-analysis}, Expected behavior after, Evidence as path:line list). `Expected behavior after` states, in one observable sentence, what becomes different once the candidate is applied — it is the seed of the downstream brief's `EB-NNN` / `EO-NNN`. A candidate you cannot write this cell for is a preference, not a finding: drop it rather than filling the cell with a restatement of the title.
|
|
52
54
|
- `Consensus` cells in `## 5.9 Improvement Candidates` use the table enum exactly: `full`, `partial`, `contested`, `worker-unique`. Map convergence's `full-consensus` / `partial-consensus` labels to `full` / `partial` before writing the table.
|
|
53
55
|
- `## 7. Final Verdict` Verdict Token ∈ {`candidates-ready`, `no-candidates`, `blocked`}; Direction `routing`; Next Step "ask the user to select K candidates (see the ## 5.9 table)"
|
|
54
56
|
- `## 3. Recommended Next Steps` first entry summarises per-candidate routing and proposes new task-key names of the form `<task-group>/imp-<Cand-ID>`
|
|
55
|
-
-
|
|
57
|
+
- author the shared schema-v2 report fields plus `improvementDiscovery.candidates[]`, `improvementDiscovery.lensCoverage[]`, `improvementDiscovery.selectionLimit`, and `improvementDiscovery.userNarrative` in data.json. `candidates[]` carries the same 11 logical fields described above; `lensCoverage[]` records either candidate IDs or an evidence-backed no-candidate rationale for every resolved lens. `schemas/final-report-v2.0.schema.json` and `validators/validate_improvement_report.py` enforce this contract. The renderers independently derive AI handoff Markdown and human HTML; never author a free-form report.
|
|
56
58
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
57
59
|
- if scan-scope or priority-lenses cannot be made concrete during Phase 1.5, end the run with Verdict Token `blocked`, populate `## 1. Clarification Items` with `Blocks=next-phase` rows, and do not run worker dispatch
|
|
58
60
|
{{INCLUDE:_clarification-recommendation.md}}
|
|
@@ -259,20 +259,45 @@ def validate(data: Any, schema: dict) -> list[str]:
|
|
|
259
259
|
return v.errors
|
|
260
260
|
|
|
261
261
|
|
|
262
|
+
SCHEMA_FILENAMES = {
|
|
263
|
+
"1.0": "final-report-v1.0.schema.json",
|
|
264
|
+
"2.0": "final-report-v2.0.schema.json",
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _locate_and_load_schema(filename: str, start: Path | None = None) -> dict:
|
|
269
|
+
here = (start or Path(__file__)).resolve()
|
|
270
|
+
if here.is_file():
|
|
271
|
+
here = here.parent
|
|
272
|
+
for parent in [here, *here.parents]:
|
|
273
|
+
candidate = parent / "schemas" / filename
|
|
274
|
+
if candidate.is_file():
|
|
275
|
+
return json.loads(candidate.read_text(encoding="utf-8"))
|
|
276
|
+
raise SchemaError(f"could not locate schemas/{filename}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def load_schema_version(version: str, start: Path | None = None) -> dict:
|
|
280
|
+
"""Load the final-report schema identified by its data version."""
|
|
281
|
+
try:
|
|
282
|
+
filename = SCHEMA_FILENAMES[version]
|
|
283
|
+
except KeyError as exc:
|
|
284
|
+
raise SchemaError(f"unsupported final-report schemaVersion: {version}") from exc
|
|
285
|
+
return _locate_and_load_schema(filename, start=start)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def load_schema_for_data(data: dict, start: Path | None = None) -> dict:
|
|
289
|
+
"""Select a schema from the explicit ``schemaVersion`` in *data*."""
|
|
290
|
+
version = data.get("schemaVersion")
|
|
291
|
+
if not isinstance(version, str) or not version:
|
|
292
|
+
raise SchemaError("final-report data has no schemaVersion")
|
|
293
|
+
return load_schema_version(version, start=start)
|
|
294
|
+
|
|
295
|
+
|
|
262
296
|
def load_schema(schema_path: Path | None = None) -> dict:
|
|
263
297
|
"""Load the final-report schema. If ``schema_path`` is None, locate
|
|
264
298
|
``schemas/final-report-v1.0.schema.json`` relative to this file's
|
|
265
299
|
repo root.
|
|
266
300
|
"""
|
|
267
|
-
if schema_path is None:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
candidate = parent / "schemas" / "final-report-v1.0.schema.json"
|
|
271
|
-
if candidate.is_file():
|
|
272
|
-
schema_path = candidate
|
|
273
|
-
break
|
|
274
|
-
if schema_path is None:
|
|
275
|
-
raise SchemaError(
|
|
276
|
-
"could not locate schemas/final-report-v1.0.schema.json"
|
|
277
|
-
)
|
|
278
|
-
return json.loads(Path(schema_path).read_text(encoding="utf-8"))
|
|
301
|
+
if schema_path is not None:
|
|
302
|
+
return json.loads(Path(schema_path).read_text(encoding="utf-8"))
|
|
303
|
+
return load_schema_version("1.0")
|