okstra 0.150.0 → 0.151.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/docs/cli.md +1 -1
- package/package.json +3 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +8 -0
- package/runtime/bin/okstra-report-translate.py +34 -1
- package/runtime/prompts/lead/plan-body-verification.md +1 -1
- package/runtime/prompts/lead/report-writer.md +2 -2
- package/runtime/python/okstra_ctl/convergence.py +15 -1
- package/runtime/python/okstra_ctl/report_finalize.py +18 -0
- package/runtime/python/okstra_ctl/report_translation.py +29 -0
- package/runtime/validators/validate-run.py +31 -0
package/docs/cli.md
CHANGED
|
@@ -720,7 +720,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
720
720
|
| `okstra convergence apply-critic-gaps --work-state <path> --results <path>` | Apply one verified coverage-critic batch after the main queue reaches a terminal state |
|
|
721
721
|
| `okstra convergence finalize --work-state <path> --output <path>` | Materialize the terminal schema v1.3 convergence state |
|
|
722
722
|
| `okstra convergence validate --state <path> --kind <working\|final>` | Validate replayable working state or a terminal final state |
|
|
723
|
-
| `okstra convergence example --kind <groups\|round-results\|critic-results>` | Print one deterministic valid input example as JSON |
|
|
723
|
+
| `okstra convergence example --kind <groups\|round-results\|critic-results>` | Print one deterministic valid input example as JSON. `groups` feeds `seed --groups` and `round-results` feeds `apply-round --results`; `critic-results` is the critic worker's own result document and is **not** the `apply-critic-gaps --results` input — that input is the coverage batch the lead assembles from those candidates plus each analyser's vote (`dispatches[]` / `gaps[]` / `modelExecutionValue`) |
|
|
724
724
|
| `okstra plan-items extract --data <data.json> --output <items.json>` | Deterministically extract the complete implementation-planning `P-*` queue from report-writer data.json |
|
|
725
725
|
| `okstra plan-items validate --data <data.json> --items <items.json>` | Require the persisted `P-*` queue to match a fresh deterministic extraction exactly |
|
|
726
726
|
| `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | Manage persistent settings such as `pr-template-path` with atomic JSON writes |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okstra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.151.0",
|
|
4
4
|
"description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "devonshin",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"test:js": "node --test tests-js/*.test.mjs",
|
|
39
39
|
"test:py": "python3 -m pytest tests/",
|
|
40
40
|
"test:workflow": "bash validators/validate-workflow.sh",
|
|
41
|
-
"
|
|
41
|
+
"test:e2e": "bash tests-e2e/run-all.sh",
|
|
42
|
+
"check": "npm run build && npm run test:js && npm run test:py && npm run test:workflow && npm run test:e2e"
|
|
42
43
|
}
|
|
43
44
|
}
|
package/runtime/BUILD.json
CHANGED
|
@@ -19,6 +19,14 @@ tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "TodoWrite", "WebFetch"
|
|
|
19
19
|
- The `**Report Language:**` header in your dispatch prompt is already
|
|
20
20
|
resolved to `en` or `ko` by the lead. Copy it verbatim into
|
|
21
21
|
`data.json.meta.reportLanguage`. Never write `auto` here.
|
|
22
|
+
- **That header is not the language you write in — you always write English.**
|
|
23
|
+
It names the language the human HTML renders in, and Phase 7 reads it to
|
|
24
|
+
decide whether to dispatch a translator. The data.json is the SSOT every
|
|
25
|
+
later phase, validator and agent reads, so authoring it in anything but
|
|
26
|
+
English splits the record. **Enforced:** `okstra report-finalize` runs
|
|
27
|
+
`report-translate check-source` as its first Phase 7 step and fails the run
|
|
28
|
+
when the data.json's authored prose is not English. You can run that check
|
|
29
|
+
yourself before returning.
|
|
22
30
|
|
|
23
31
|
## Authority
|
|
24
32
|
|
|
@@ -44,7 +44,12 @@ if (SCRIPTS_DIR / "okstra_ctl" / "report_translation.py").is_file():
|
|
|
44
44
|
elif HOME_LIB.is_dir() and str(HOME_LIB) not in sys.path:
|
|
45
45
|
sys.path.insert(0, str(HOME_LIB))
|
|
46
46
|
|
|
47
|
-
from okstra_ctl.report_translation import
|
|
47
|
+
from okstra_ctl.report_translation import ( # noqa: E402
|
|
48
|
+
HANGUL_PROSE_LIMIT,
|
|
49
|
+
extract,
|
|
50
|
+
hangul_share,
|
|
51
|
+
overlay,
|
|
52
|
+
)
|
|
48
53
|
from okstra_ctl.final_report_paths import ( # noqa: E402
|
|
49
54
|
translation_sidecar_path,
|
|
50
55
|
translation_source_path,
|
|
@@ -132,6 +137,28 @@ def cmd_check(args: argparse.Namespace) -> int:
|
|
|
132
137
|
return 0
|
|
133
138
|
|
|
134
139
|
|
|
140
|
+
def cmd_check_source(args: argparse.Namespace) -> int:
|
|
141
|
+
data_path = Path(args.data).resolve()
|
|
142
|
+
share, length = hangul_share(_load(data_path))
|
|
143
|
+
payload = {
|
|
144
|
+
"ok": share < HANGUL_PROSE_LIMIT,
|
|
145
|
+
"hangulShare": round(share, 4),
|
|
146
|
+
"limit": HANGUL_PROSE_LIMIT,
|
|
147
|
+
"proseChars": length,
|
|
148
|
+
}
|
|
149
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
150
|
+
if not payload["ok"]:
|
|
151
|
+
sys.stderr.write(
|
|
152
|
+
f"error: {data_path.name} was authored in Korean "
|
|
153
|
+
f"({share:.0%} of its prose, limit {HANGUL_PROSE_LIMIT:.0%}). "
|
|
154
|
+
"The data.json is the English SSOT every later phase reads; the "
|
|
155
|
+
"report language selects the human HTML's language and is served "
|
|
156
|
+
"by the Phase 7 translator, not by authoring the SSOT in it.\n"
|
|
157
|
+
)
|
|
158
|
+
return 1
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
135
162
|
def _parser() -> argparse.ArgumentParser:
|
|
136
163
|
parser = argparse.ArgumentParser(
|
|
137
164
|
prog="okstra-report-translate.py",
|
|
@@ -146,6 +173,12 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
146
173
|
check_cmd = sub.add_parser("check", help="verify a filled sidecar against its report")
|
|
147
174
|
check_cmd.add_argument("sidecar", help="path to final-report-<type>-<seq>.i18n.<lang>.json")
|
|
148
175
|
check_cmd.set_defaults(func=cmd_check)
|
|
176
|
+
|
|
177
|
+
source_cmd = sub.add_parser(
|
|
178
|
+
"check-source", help="verify the data.json itself was authored in English"
|
|
179
|
+
)
|
|
180
|
+
source_cmd.add_argument("data", help="path to final-report-<type>-<seq>.data.json")
|
|
181
|
+
source_cmd.set_defaults(func=cmd_check_source)
|
|
149
182
|
return parser
|
|
150
183
|
|
|
151
184
|
|
|
@@ -175,7 +175,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
175
175
|
|
|
176
176
|
1. Lead runs `okstra plan-items extract --data <data.json> --output <state>/plan-items-....json`, places the persisted `items[]` verbatim in every verifier prompt with the compact `subject` and lossless `payload`, then runs `okstra plan-items validate --data <data.json> --items <state>/plan-items-....json`. Dispatch only after that exact-match validation succeeds.
|
|
177
177
|
2. For each analyser worker in the roster (`claude`, `codex`, and `antigravity` if opted in), lead constructs a reverify prompt using the template in §"Plan-body reverify prompt" below.
|
|
178
|
-
3. Dispatch uses the same wrapper infrastructure as finding convergence
|
|
178
|
+
3. Dispatch uses the same wrapper infrastructure as finding convergence, so the `--role-slug` is the same canonical `<role>-worker` that convergence uses — not a round-specific slug. Result file path: `runs/<task-type>/worker-results/<role>-worker-plan-verify-r<N>-implementation-planning-<seq>.md` (e.g. `codex-worker-plan-verify-r1-implementation-planning-003.md`). The `-worker-` token is load-bearing twice over: §"Plan-body reverify prompt" requires the same anchor headers as convergence, whose `**Audit sidecar path:**` is derived by `okstra_ctl.worker_artifact_paths.audit_sidecar_rel()` inserting `-audit-` after that token — a slug without it makes the header underivable and the helper raises. Record each `planItems[].verdicts[].worker` as the same `<role>-worker` string, because provenance compares it to this filename's prefix. **Enforced:** `tests/contract/test_reverify_dispatch_anchors.py` derives the sidecar from the documented name and re-extracts the prefix the provenance resolver uses.
|
|
179
179
|
**Verdict provenance (BLOCKING).** Every verdict recorded in `planItems[].verdicts[]` MUST trace back to a dispatch that actually returned a result file at the path above. The whole gate — classification, self-fix eligibility, promotion, `gateBlockedBy` — is computed from these votes, so an unbacked vote lets the round be skipped while the gate still reads `passed`. **Enforced:** `validators/validate-run.py` `_validate_plan_body_verdict_provenance` fails any `verdicts[].worker` with no matching `<worker>-plan-verify-r<N>-<task-type>-<seq>.md` result file. Recording a `verification-error` for a dispatch that produced no result is the correct way to represent a failed worker — inventing an `AGREE` is a contract violation.
|
|
180
180
|
|
|
181
181
|
4. After all dispatches return, lead aggregates verdicts per `P-*` item across workers and classifies each:
|
|
@@ -176,7 +176,7 @@ Table Generation Rules:
|
|
|
176
176
|
|
|
177
177
|
Place this section immediately after the execution status table.
|
|
178
178
|
|
|
179
|
-
Example (
|
|
179
|
+
Example (the Markdown renders in English whatever `meta.reportLanguage` says):
|
|
180
180
|
|
|
181
181
|
```markdown
|
|
182
182
|
### Token Usage Summary
|
|
@@ -233,7 +233,7 @@ The rows below mirror `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.p
|
|
|
233
233
|
|
|
234
234
|
`Implementation Design Preparation` is NOT in the substring list: `templates/reports/final-report.template.md` §5.5.10 renders that heading from `implementationPlanning.designPreparation`, so it is enforced by the schema + renderer rather than by a heading scan.
|
|
235
235
|
|
|
236
|
-
The
|
|
236
|
+
The English keyword is mandatory and the body of each section is written in English, like everything else you author — see "Writing Guidelines" below. For non-`implementation-planning` runs, omit this entire block — these headings are NOT validator-checked for other task-types.
|
|
237
237
|
|
|
238
238
|
The final-report template `templates/reports/final-report.template.md` Section 5.5 already encodes this contract — copy that block verbatim and fill in.
|
|
239
239
|
|
|
@@ -123,7 +123,21 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
123
123
|
subparsers = parser.add_subparsers(dest="operation", required=True)
|
|
124
124
|
|
|
125
125
|
example = subparsers.add_parser(
|
|
126
|
-
"example",
|
|
126
|
+
"example",
|
|
127
|
+
help="print a deterministic input artifact example",
|
|
128
|
+
description=(
|
|
129
|
+
"Print one deterministic valid example as JSON.\n\n"
|
|
130
|
+
"`groups` feeds `seed --groups` and `round-results` feeds "
|
|
131
|
+
"`apply-round --results`, but `critic-results` is the critic "
|
|
132
|
+
"worker's own result document — NOT the `apply-critic-gaps "
|
|
133
|
+
"--results` input. That input is the coverage batch the lead "
|
|
134
|
+
"assembles from those candidates plus each analyser's vote "
|
|
135
|
+
'({schemaVersion, taskKey, mode: "coverage", provider, '
|
|
136
|
+
"modelExecutionValue, dispatches[], gaps[]}); see "
|
|
137
|
+
'prompts/lead/convergence.md §"Coverage critic". Feeding this '
|
|
138
|
+
"example straight into `apply-critic-gaps` is rejected, by design."
|
|
139
|
+
),
|
|
140
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
127
141
|
)
|
|
128
142
|
example.add_argument(
|
|
129
143
|
"--kind",
|
|
@@ -24,12 +24,17 @@ from .final_report_paths import final_report_data_path, final_report_markdown_pa
|
|
|
24
24
|
from .paths import task_dir, task_manifest_file
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
STEP_CHECK_SOURCE = "check-source"
|
|
27
28
|
STEP_TOKEN_USAGE = "token-usage"
|
|
28
29
|
STEP_RENDER_VIEWS = "render-views"
|
|
29
30
|
STEP_SPAWN_FOLLOWUPS = "spawn-followups"
|
|
30
31
|
STEP_VALIDATE_RUN = "validate-run"
|
|
31
32
|
|
|
32
33
|
STEP_ORDER = (
|
|
34
|
+
# First, because everything after it derives from the data.json: rendering
|
|
35
|
+
# a Korean SSOT into English chrome, spawning follow-ups from it, and
|
|
36
|
+
# validating it all succeed on a record the next phase cannot read.
|
|
37
|
+
STEP_CHECK_SOURCE,
|
|
33
38
|
STEP_TOKEN_USAGE,
|
|
34
39
|
STEP_RENDER_VIEWS,
|
|
35
40
|
STEP_SPAWN_FOLLOWUPS,
|
|
@@ -205,6 +210,19 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
|
|
|
205
210
|
"""Assemble the ordered Phase 7 argv list. Order is contractual."""
|
|
206
211
|
markdown_path = ctx.markdown_path
|
|
207
212
|
return [
|
|
213
|
+
(
|
|
214
|
+
STEP_CHECK_SOURCE,
|
|
215
|
+
[
|
|
216
|
+
sys.executable,
|
|
217
|
+
str(
|
|
218
|
+
resolve_workspace_script(
|
|
219
|
+
ctx.workspace_root, "okstra-report-translate.py"
|
|
220
|
+
)
|
|
221
|
+
),
|
|
222
|
+
"check-source",
|
|
223
|
+
str(ctx.data_path),
|
|
224
|
+
],
|
|
225
|
+
),
|
|
208
226
|
(
|
|
209
227
|
STEP_TOKEN_USAGE,
|
|
210
228
|
[
|
|
@@ -393,6 +393,35 @@ def overlay(
|
|
|
393
393
|
return out, OverlayReport(applied, untranslated, tuple(sorted(unresolved)))
|
|
394
394
|
|
|
395
395
|
|
|
396
|
+
# Above this share of Hangul, the prose was authored in Korean rather than
|
|
397
|
+
# quoting some. Measured across real reports: Korean-authored ones sit at
|
|
398
|
+
# 35-38%, English ones that quote a Korean brief or a worker's Korean phrase
|
|
399
|
+
# reach 8% at most. The gap is wide enough that no threshold inside it is
|
|
400
|
+
# delicate. Only the strings `extract` offers are counted — a verbatim quote of
|
|
401
|
+
# the user's request is structural and never reaches this.
|
|
402
|
+
HANGUL_PROSE_LIMIT = 0.20
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def hangul_share(data: Mapping[str, Any]) -> tuple[float, int]:
|
|
406
|
+
"""Return the Hangul share of the report's authored prose, and its length."""
|
|
407
|
+
text = "".join(extract(data).values())
|
|
408
|
+
if not text:
|
|
409
|
+
return 0.0, 0
|
|
410
|
+
hangul = sum(1 for char in text if "가" <= char <= "힣")
|
|
411
|
+
return hangul / len(text), len(text)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def prose_is_english(data: Mapping[str, Any]) -> bool:
|
|
415
|
+
"""Whether the data.json was authored in English, as the SSOT contract requires.
|
|
416
|
+
|
|
417
|
+
The report language names what the human HTML renders in, not what the
|
|
418
|
+
worker writes. A worker that authors Korean anyway hands every later phase,
|
|
419
|
+
validator and agent a record in a language they do not read.
|
|
420
|
+
"""
|
|
421
|
+
share, _ = hangul_share(data)
|
|
422
|
+
return share < HANGUL_PROSE_LIMIT
|
|
423
|
+
|
|
424
|
+
|
|
396
425
|
def _schema_string_keys(schema: Mapping[str, Any], *, enums: bool) -> set[str]:
|
|
397
426
|
found: set[str] = set()
|
|
398
427
|
|
|
@@ -61,6 +61,10 @@ from okstra_ctl.mutation_probe import ( # noqa: E402
|
|
|
61
61
|
classify_reason,
|
|
62
62
|
)
|
|
63
63
|
from okstra_ctl.self_mock_signals import selfmock_path_key # noqa: E402
|
|
64
|
+
from okstra_ctl.report_translation import ( # noqa: E402
|
|
65
|
+
HANGUL_PROSE_LIMIT,
|
|
66
|
+
hangul_share,
|
|
67
|
+
)
|
|
64
68
|
from okstra_ctl.stage_citations import cited_stage_numbers # noqa: E402
|
|
65
69
|
from okstra_ctl.workflow import DEFAULT_NEXT_PHASE, PHASE_SEQUENCE # noqa: E402
|
|
66
70
|
from okstra_ctl.md_table import ( # noqa: E402
|
|
@@ -6316,6 +6320,32 @@ def _validate_improvement_discovery(
|
|
|
6316
6320
|
failures.append(f"improvement-discovery: {err}")
|
|
6317
6321
|
|
|
6318
6322
|
|
|
6323
|
+
def _validate_ssot_is_english(data: dict, failures: list[str]) -> None:
|
|
6324
|
+
"""Enforce: the final-report data.json is authored in English.
|
|
6325
|
+
|
|
6326
|
+
`meta.reportLanguage` names the language the human HTML renders in, and
|
|
6327
|
+
Phase 7's translator serves it from a sidecar. The data.json itself is the
|
|
6328
|
+
record every later phase, validator and agent reads, so a worker that
|
|
6329
|
+
authors it in the reader's language instead splits the record — and does so
|
|
6330
|
+
silently, because rendering, follow-up spawning and validation all succeed
|
|
6331
|
+
on it.
|
|
6332
|
+
|
|
6333
|
+
`report-finalize` runs the same check as its first step, before anything
|
|
6334
|
+
derives from the report. This is the second gate, for a report that reached
|
|
6335
|
+
validation by some other path.
|
|
6336
|
+
"""
|
|
6337
|
+
if not data:
|
|
6338
|
+
return
|
|
6339
|
+
share, length = hangul_share(data)
|
|
6340
|
+
if length and share >= HANGUL_PROSE_LIMIT:
|
|
6341
|
+
failures.append(
|
|
6342
|
+
f"final-report data.json was authored in Korean ({share:.0%} of its "
|
|
6343
|
+
f"prose, limit {HANGUL_PROSE_LIMIT:.0%}). The data.json is the "
|
|
6344
|
+
"English SSOT; meta.reportLanguage selects the human HTML's "
|
|
6345
|
+
"language and is served by the Phase 7 translator sidecar."
|
|
6346
|
+
)
|
|
6347
|
+
|
|
6348
|
+
|
|
6319
6349
|
def _validate_fix_cycle(run_manifest: dict, data: dict, failures: list[str]) -> None:
|
|
6320
6350
|
"""Enforce: when the run-manifest carries a fixCycleId, the final-report
|
|
6321
6351
|
data.json MUST contain a fixCycle block whose ``cycle`` matches it.
|
|
@@ -7206,6 +7236,7 @@ def main() -> int:
|
|
|
7206
7236
|
),
|
|
7207
7237
|
)
|
|
7208
7238
|
validation_data = report_data if isinstance(report_data, Mapping) else {}
|
|
7239
|
+
_validate_ssot_is_english(validation_data, failures)
|
|
7209
7240
|
_validate_fix_cycle(run_manifest, validation_data, failures)
|
|
7210
7241
|
validate_report(
|
|
7211
7242
|
report_path,
|