okstra 0.140.0 → 0.141.1
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 +1 -1
- package/docs/architecture.md +1 -1
- package/docs/cli.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +4 -2
- package/runtime/prompts/launch.template.md +7 -5
- package/runtime/prompts/lead/convergence.md +22 -0
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/plan-body-verification.md +97 -26
- package/runtime/prompts/lead/report-writer.md +2 -0
- package/runtime/prompts/profiles/error-analysis.md +1 -0
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +6 -4
- package/runtime/prompts/profiles/improvement-discovery.md +2 -1
- package/runtime/prompts/profiles/requirements-discovery.md +2 -1
- package/runtime/python/okstra_ctl/build_tools.py +95 -0
- package/runtime/python/okstra_ctl/incremental_scope.py +95 -0
- package/runtime/python/okstra_ctl/render.py +26 -11
- package/runtime/python/okstra_ctl/render_final_report.py +24 -1
- package/runtime/python/okstra_ctl/rollup.py +49 -0
- package/runtime/python/okstra_ctl/scope_provenance.py +98 -1
- package/runtime/python/okstra_ctl/stage_citations.py +47 -0
- package/runtime/python/okstra_ctl/workflow.py +7 -4
- package/runtime/schemas/final-report-v1.0.schema.json +60 -1
- package/runtime/skills/okstra-brief-gen/SKILL.md +38 -11
- package/runtime/skills/okstra-rollup/SKILL.md +3 -2
- package/runtime/templates/reports/brief.template.md +42 -11
- package/runtime/templates/reports/fan-out-unit.template.md +9 -4
- package/runtime/templates/reports/final-report.template.md +20 -0
- package/runtime/templates/reports/final-verification-input.template.md +4 -2
- package/runtime/templates/reports/i18n/en.json +11 -4
- package/runtime/templates/reports/i18n/ko.json +11 -4
- package/runtime/templates/reports/improvement-discovery-input.template.md +2 -2
- package/runtime/validators/validate-brief.py +171 -26
- package/runtime/validators/validate-run.py +528 -86
- package/runtime/validators/validate_fanout.py +19 -10
- package/runtime/validators/validate_improvement_report.py +17 -4
|
@@ -18,8 +18,9 @@ for _ssot_dir in (_VALIDATORS_DIR.parent / "scripts", _VALIDATORS_DIR.parent / "
|
|
|
18
18
|
from okstra_ctl.work_categories import WORK_CATEGORIES # noqa: E402
|
|
19
19
|
from okstra_ctl.fanout import topological_order, CycleError # noqa: E402
|
|
20
20
|
from okstra_ctl.scope_provenance import ( # noqa: E402
|
|
21
|
+
brief_citation_problem,
|
|
22
|
+
brief_end_state_ids,
|
|
21
23
|
brief_headings,
|
|
22
|
-
normalize_heading,
|
|
23
24
|
parse_source,
|
|
24
25
|
)
|
|
25
26
|
|
|
@@ -37,7 +38,11 @@ _PROVENANCE_BULLET_RE = re.compile(r"^\s*[-*]\s+(?P<src>.+?)\s*$", re.MULTILINE)
|
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def _check_provenance(
|
|
40
|
-
pkt_name: str,
|
|
41
|
+
pkt_name: str,
|
|
42
|
+
text: str,
|
|
43
|
+
errors: list[str],
|
|
44
|
+
headings: set[str],
|
|
45
|
+
end_state: set[str],
|
|
41
46
|
) -> None:
|
|
42
47
|
"""A fan-out unit must cite the brief line that demanded it.
|
|
43
48
|
|
|
@@ -47,6 +52,8 @@ def _check_provenance(
|
|
|
47
52
|
|
|
48
53
|
`headings` empty means the brief was unavailable — the grammar still
|
|
49
54
|
applies, only the heading-existence half degrades to unverifiable.
|
|
55
|
+
`end_state` empty means the brief predates the end-state sections, so the
|
|
56
|
+
older heading form is still the admissible one.
|
|
50
57
|
"""
|
|
51
58
|
section = _PROVENANCE_SECTION_RE.search(text)
|
|
52
59
|
if not section:
|
|
@@ -61,7 +68,7 @@ def _check_provenance(
|
|
|
61
68
|
if not bullets:
|
|
62
69
|
errors.append(
|
|
63
70
|
f"{pkt_name}: `## Requirement Provenance` is empty — cite at least one "
|
|
64
|
-
"`brief:<heading>` or `contract:<rule>`"
|
|
71
|
+
"`brief:EB-001` (or the legacy `brief:<heading>`) or `contract:<rule>`"
|
|
65
72
|
)
|
|
66
73
|
return
|
|
67
74
|
for raw in bullets:
|
|
@@ -69,14 +76,15 @@ def _check_provenance(
|
|
|
69
76
|
if ref.kind not in ("brief", "contract"):
|
|
70
77
|
errors.append(
|
|
71
78
|
f"{pkt_name}: unrecognized provenance `{raw}` — a fan-out unit's "
|
|
72
|
-
"source must be `brief
|
|
79
|
+
"source must be `brief:EB-001` (or the legacy `brief:<heading>`) "
|
|
80
|
+
"or `contract:<rule>`"
|
|
73
81
|
)
|
|
74
|
-
elif ref.kind == "brief"
|
|
75
|
-
|
|
82
|
+
elif ref.kind == "brief":
|
|
83
|
+
problem = brief_citation_problem(ref, headings, end_state)
|
|
84
|
+
if problem:
|
|
76
85
|
errors.append(
|
|
77
|
-
f"{pkt_name}:
|
|
78
|
-
"
|
|
79
|
-
"downstream task — it must trace to a line the reporter wrote."
|
|
86
|
+
f"{pkt_name}: {problem}. A fan-out unit becomes the brief for a "
|
|
87
|
+
"whole downstream task — it must trace to a line the reporter wrote."
|
|
80
88
|
)
|
|
81
89
|
|
|
82
90
|
|
|
@@ -117,13 +125,14 @@ def validate_fanout(run_dir: Path, brief_path: Path | None = None) -> Validation
|
|
|
117
125
|
return ValidationResult(ok=True)
|
|
118
126
|
|
|
119
127
|
headings = brief_headings(brief_path) if brief_path is not None else set()
|
|
128
|
+
end_state = brief_end_state_ids(brief_path) if brief_path is not None else set()
|
|
120
129
|
|
|
121
130
|
errors: list[str] = []
|
|
122
131
|
units: dict[str, list[str]] = {}
|
|
123
132
|
for pkt in sorted(fo.glob("unit-*.md")):
|
|
124
133
|
text = pkt.read_text(encoding="utf-8")
|
|
125
134
|
fm = _frontmatter(text)
|
|
126
|
-
_check_provenance(pkt.name, text, errors, headings)
|
|
135
|
+
_check_provenance(pkt.name, text, errors, headings, end_state)
|
|
127
136
|
uid = fm.get("unit-id", "")
|
|
128
137
|
if uid != pkt.stem:
|
|
129
138
|
errors.append(f"{pkt.name}: unit-id {uid!r} != filename stem {pkt.stem!r}")
|
|
@@ -181,10 +181,13 @@ def _check_candidate_row(
|
|
|
181
181
|
errors: list[str],
|
|
182
182
|
) -> None:
|
|
183
183
|
"""Items 2, 3, 4, 6, 7 — validate a single candidate row."""
|
|
184
|
-
if len(row) !=
|
|
185
|
-
errors.append(f"row {idx} has {len(row)} columns, expected
|
|
184
|
+
if len(row) != 11:
|
|
185
|
+
errors.append(f"row {idx} has {len(row)} columns, expected 11")
|
|
186
186
|
return
|
|
187
|
-
|
|
187
|
+
(
|
|
188
|
+
cand_id, lens_cell, _title, scope, _sev, _eff,
|
|
189
|
+
consensus, source_workers, next_phase, expected_after, _evidence,
|
|
190
|
+
) = row
|
|
188
191
|
|
|
189
192
|
if not _CAND_ID_RE.match(cand_id):
|
|
190
193
|
errors.append(f"row {idx}: Cand ID '{cand_id}' must match I-NNN")
|
|
@@ -209,6 +212,15 @@ def _check_candidate_row(
|
|
|
209
212
|
if next_phase not in _NEXT_PHASES:
|
|
210
213
|
errors.append(f"row {idx}: Recommended next-phase '{next_phase}' must be one of {_NEXT_PHASES}")
|
|
211
214
|
|
|
215
|
+
# A candidate nobody can describe an observable change for is not a finding;
|
|
216
|
+
# it is a preference. Making this a cell rather than prose is what stops the
|
|
217
|
+
# candidate list from growing past what a downstream brief can pin.
|
|
218
|
+
if not expected_after.strip():
|
|
219
|
+
errors.append(
|
|
220
|
+
f"row {idx}: `Expected behavior after` is empty — state what becomes "
|
|
221
|
+
"observably different once this candidate is applied, or drop the row"
|
|
222
|
+
)
|
|
223
|
+
|
|
212
224
|
|
|
213
225
|
def _check_candidate_rows(
|
|
214
226
|
data_rows: list[list[str]],
|
|
@@ -233,7 +245,8 @@ def _check_candidates_table(
|
|
|
233
245
|
header, *data = rows
|
|
234
246
|
expected_columns = [
|
|
235
247
|
"Cand ID", "Lens", "Title", "Scope", "Severity", "Effort",
|
|
236
|
-
"Consensus", "Source workers", "Recommended next-phase",
|
|
248
|
+
"Consensus", "Source workers", "Recommended next-phase",
|
|
249
|
+
"Expected behavior after", "Evidence",
|
|
237
250
|
]
|
|
238
251
|
if header != expected_columns:
|
|
239
252
|
errors.append(
|