okstra 0.151.0 → 0.152.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/docs/project-structure-overview.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +2 -1
- package/runtime/agents/workers/claude-worker.md +2 -1
- package/runtime/agents/workers/codex-worker.md +2 -1
- package/runtime/agents/workers/grok-worker.md +2 -1
- package/runtime/agents/workers/kimi-worker.md +2 -1
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/okstra-report-translate.py +32 -11
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/convergence.md +16 -4
- package/runtime/prompts/lead/report-writer.md +26 -14
- package/runtime/prompts/lead/team-contract.md +2 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +12 -7
- package/runtime/python/okstra_ctl/analysis_packet.py +1 -0
- package/runtime/python/okstra_ctl/dispatch_state.py +5 -1
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +7 -0
- package/runtime/python/okstra_ctl/report_finalize.py +9 -4
- package/runtime/python/okstra_ctl/worker_prompt_body.py +4 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +41 -1
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +3 -0
- package/runtime/templates/implementation-worker-preamble.md +12 -3
- package/runtime/templates/report-writer-prompt-preamble.md +5 -1
- package/runtime/templates/worker-prompt-preamble.md +12 -3
- package/runtime/validators/validate-implementation-plan-stages.py +170 -28
- package/runtime/validators/validate-run.py +137 -0
|
@@ -103,6 +103,7 @@ from okstra_ctl.worker_prompt_contract import ( # noqa: E402
|
|
|
103
103
|
PromptRecord,
|
|
104
104
|
validate_initial_prompt_records,
|
|
105
105
|
)
|
|
106
|
+
from okstra_ctl.worker_prompt_headers import EVIDENCE_LEDGER_HEADER # noqa: E402
|
|
106
107
|
from validate_analysis_report import validate_analysis_report # noqa: E402
|
|
107
108
|
from okstra_ctl.convergence_engine import ( # noqa: E402
|
|
108
109
|
grouped_input_digest,
|
|
@@ -2378,6 +2379,16 @@ def validate_report(
|
|
|
2378
2379
|
_WORKER_RESULT_BASENAME_RE = re.compile(
|
|
2379
2380
|
r"^(?P<worker>[a-z][a-z0-9-]*-worker)-(?P<task_type>[a-z][a-z-]*?)-(?P<seq>\d{3})\.md$"
|
|
2380
2381
|
)
|
|
2382
|
+
_EVIDENCE_READ_RE = re.compile(
|
|
2383
|
+
r"^- Evidence read: `(?P<path>[^`\n]+)`\s*$",
|
|
2384
|
+
re.MULTILINE,
|
|
2385
|
+
)
|
|
2386
|
+
_FILE_LINE_CITATION_RE = re.compile(
|
|
2387
|
+
r"`(?P<path>(?!https?://)[^`\n]+?):(?P<line>\d+(?:-\d+)?)`"
|
|
2388
|
+
)
|
|
2389
|
+
_EXTENSIONLESS_SOURCE_FILENAMES = frozenset(
|
|
2390
|
+
{"Dockerfile", "Justfile", "Makefile", "Procfile", "Rakefile"}
|
|
2391
|
+
)
|
|
2381
2392
|
|
|
2382
2393
|
_REPORT_BASENAME_SEQ_RE = re.compile(r"-(?P<seq>\d{3})(?:\.data)?\.(?:md|json)$")
|
|
2383
2394
|
|
|
@@ -2390,6 +2401,90 @@ def _report_run_seq(report_path: Path) -> str | None:
|
|
|
2390
2401
|
return match.group("seq") if match else None
|
|
2391
2402
|
|
|
2392
2403
|
|
|
2404
|
+
def _cited_file_paths(content: str) -> set[str]:
|
|
2405
|
+
paths: set[str] = set()
|
|
2406
|
+
for match in _FILE_LINE_CITATION_RE.finditer(content):
|
|
2407
|
+
path = match.group("path")
|
|
2408
|
+
if _looks_like_file_path(path):
|
|
2409
|
+
paths.add(path)
|
|
2410
|
+
return paths
|
|
2411
|
+
|
|
2412
|
+
|
|
2413
|
+
def _looks_like_file_path(path: str) -> bool:
|
|
2414
|
+
if (
|
|
2415
|
+
not path
|
|
2416
|
+
or path.startswith(("-", "$"))
|
|
2417
|
+
or any(char.isspace() for char in path)
|
|
2418
|
+
):
|
|
2419
|
+
return False
|
|
2420
|
+
if re.fullmatch(r"[0-9a-fA-F]{7,64}", path):
|
|
2421
|
+
return False
|
|
2422
|
+
return (
|
|
2423
|
+
"/" in path
|
|
2424
|
+
or "." in Path(path).name
|
|
2425
|
+
or Path(path).name in _EXTENSIONLESS_SOURCE_FILENAMES
|
|
2426
|
+
)
|
|
2427
|
+
|
|
2428
|
+
|
|
2429
|
+
def _audit_evidence_read_paths(content: str) -> set[str]:
|
|
2430
|
+
return {
|
|
2431
|
+
match.group("path")
|
|
2432
|
+
for match in _EVIDENCE_READ_RE.finditer(content)
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
|
|
2436
|
+
def _worker_prompt_path(
|
|
2437
|
+
report_path: Path,
|
|
2438
|
+
worker_role: str,
|
|
2439
|
+
task_type: str,
|
|
2440
|
+
seq: str,
|
|
2441
|
+
) -> Path:
|
|
2442
|
+
return (
|
|
2443
|
+
report_path.parent.parent
|
|
2444
|
+
/ "prompts"
|
|
2445
|
+
/ f"{worker_role}-prompt-{task_type}-{seq}.md"
|
|
2446
|
+
)
|
|
2447
|
+
|
|
2448
|
+
|
|
2449
|
+
def _validate_worker_evidence_read_ledger(
|
|
2450
|
+
*,
|
|
2451
|
+
report_path: Path,
|
|
2452
|
+
worker_role: str,
|
|
2453
|
+
task_type: str,
|
|
2454
|
+
seq: str,
|
|
2455
|
+
result_name: str,
|
|
2456
|
+
result_content: str,
|
|
2457
|
+
audit_path: Path,
|
|
2458
|
+
failures: list[str],
|
|
2459
|
+
) -> None:
|
|
2460
|
+
if worker_role == "report-writer-worker":
|
|
2461
|
+
return
|
|
2462
|
+
prompt_path = _worker_prompt_path(report_path, worker_role, task_type, seq)
|
|
2463
|
+
try:
|
|
2464
|
+
prompt_content = prompt_path.read_text(encoding="utf-8")
|
|
2465
|
+
except OSError:
|
|
2466
|
+
return
|
|
2467
|
+
if EVIDENCE_LEDGER_HEADER not in prompt_content.splitlines():
|
|
2468
|
+
return
|
|
2469
|
+
try:
|
|
2470
|
+
audit_content = audit_path.read_text(encoding="utf-8")
|
|
2471
|
+
except OSError as exc:
|
|
2472
|
+
failures.append(
|
|
2473
|
+
f"worker audit sidecar unreadable: {audit_path.name} ({exc})"
|
|
2474
|
+
)
|
|
2475
|
+
return
|
|
2476
|
+
|
|
2477
|
+
missing_paths = sorted(
|
|
2478
|
+
_cited_file_paths(result_content) - _audit_evidence_read_paths(audit_content)
|
|
2479
|
+
)
|
|
2480
|
+
for missing_path in missing_paths:
|
|
2481
|
+
failures.append(
|
|
2482
|
+
f"worker `{worker_role}` result `{result_name}` cites "
|
|
2483
|
+
f"`{missing_path}:line` without an Evidence read row for "
|
|
2484
|
+
f"`{missing_path}` in `{audit_path.name}`"
|
|
2485
|
+
)
|
|
2486
|
+
|
|
2487
|
+
|
|
2393
2488
|
def validate_worker_results_audit(
|
|
2394
2489
|
report_path: Path, task_type: str, failures: list[str]
|
|
2395
2490
|
) -> None:
|
|
@@ -2404,6 +2499,10 @@ def validate_worker_results_audit(
|
|
|
2404
2499
|
2. The matching audit sidecar exists at
|
|
2405
2500
|
`<worker>-audit-<task-type>-<seq>.md`. Missing sidecar means the
|
|
2406
2501
|
worker silently skipped the reading-confirmation step.
|
|
2502
|
+
3. For new prompts carrying the required-v1 marker, every canonical
|
|
2503
|
+
backticked `path:line` citation has a matching Evidence read row in
|
|
2504
|
+
that audit sidecar. Historical prompts without the marker retain the
|
|
2505
|
+
existence-only contract.
|
|
2407
2506
|
|
|
2408
2507
|
Scoped to this run's seq. `worker-results/` accumulates every run's
|
|
2409
2508
|
artifacts, so scanning the whole directory judged a run by files it did
|
|
@@ -2465,6 +2564,18 @@ def validate_worker_results_audit(
|
|
|
2465
2564
|
f"Confirmation block (one short line per input file). Workers "
|
|
2466
2565
|
f"write this in the same step as the main worker-results file."
|
|
2467
2566
|
)
|
|
2567
|
+
continue
|
|
2568
|
+
|
|
2569
|
+
_validate_worker_evidence_read_ledger(
|
|
2570
|
+
report_path=report_path,
|
|
2571
|
+
worker_role=worker_role,
|
|
2572
|
+
task_type=task_type,
|
|
2573
|
+
seq=seq,
|
|
2574
|
+
result_name=rel,
|
|
2575
|
+
result_content=content,
|
|
2576
|
+
audit_path=audit_path,
|
|
2577
|
+
failures=failures,
|
|
2578
|
+
)
|
|
2468
2579
|
|
|
2469
2580
|
|
|
2470
2581
|
def validate_team_state_usage(team_state: dict, failures: list[str]) -> None:
|
|
@@ -6066,6 +6177,31 @@ def _load_stage_validator():
|
|
|
6066
6177
|
return mod
|
|
6067
6178
|
|
|
6068
6179
|
|
|
6180
|
+
def _append_stage_data_failures(data: Mapping[str, Any], failures: list[str]) -> None:
|
|
6181
|
+
"""Run the stage relationship checks that schema v2 cannot express.
|
|
6182
|
+
|
|
6183
|
+
`_append_stage_structure_failures` scans rendered Markdown and sits after
|
|
6184
|
+
the v2 early return in `validate_phase_boundary`, so for a v2 report the
|
|
6185
|
+
depends-on DAG, parallel-stage file safety, RED→GREEN ordering, and the
|
|
6186
|
+
TDD-exemption vocabulary had nothing enforcing them. The same validator
|
|
6187
|
+
owns both modes so the rule vocabulary stays defined once.
|
|
6188
|
+
"""
|
|
6189
|
+
if (data or {}).get("schemaVersion") != "2.0":
|
|
6190
|
+
return # v1 reports are covered by the Markdown scan.
|
|
6191
|
+
planning = (data or {}).get("implementationPlanning")
|
|
6192
|
+
if not isinstance(planning, Mapping):
|
|
6193
|
+
return # Schema validation already reported the missing block.
|
|
6194
|
+
mod = _load_stage_validator()
|
|
6195
|
+
if mod is None: # pragma: no cover — repo/runtime always ship the file
|
|
6196
|
+
failures.append(f"cannot load Stage Map validator at {_STAGE_VALIDATOR_PATH}")
|
|
6197
|
+
return
|
|
6198
|
+
for e in mod.collect_data_validation_errors(dict(planning)):
|
|
6199
|
+
failures.append(
|
|
6200
|
+
f"implementation-planning stage contract invalid "
|
|
6201
|
+
f"[{e.code} stage={e.stage}]: {e.message}"
|
|
6202
|
+
)
|
|
6203
|
+
|
|
6204
|
+
|
|
6069
6205
|
def _append_stage_structure_failures(content: str, failures: list[str]) -> None:
|
|
6070
6206
|
"""Enforce the Stage Map structural contract at the implementation-planning
|
|
6071
6207
|
boundary. Without this, a plan missing `## 5.5 Stage Map` passes the
|
|
@@ -7309,6 +7445,7 @@ def main() -> int:
|
|
|
7309
7445
|
validation_data, brief_path, failures
|
|
7310
7446
|
)
|
|
7311
7447
|
_validate_stage_has_requirement(validation_data, failures)
|
|
7448
|
+
_append_stage_data_failures(validation_data, failures)
|
|
7312
7449
|
if task_type == "improvement-discovery":
|
|
7313
7450
|
run_dir = report_path.parent.parent
|
|
7314
7451
|
_validate_improvement_discovery(report_path, run_dir, brief_path, failures)
|