okstra 0.178.0 → 0.179.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/dist/commands/execute/plan-verify.mjs +1 -1
- package/dist/commands/execute/worktree-status.mjs +8 -2
- package/dist/commands/execute/worktree-status.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +1 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/report/render-final-report.mjs +3 -3
- package/docs/architecture/storage-model.md +3 -3
- package/docs/architecture.md +10 -9
- package/docs/cli.md +11 -13
- package/docs/for-ai/skills/okstra-inspect.md +3 -3
- package/docs/for-ai/skills/okstra-schedule-gen.md +2 -2
- package/docs/for-ai/skills/okstra-user-response.md +2 -2
- package/docs/project-structure-overview.md +10 -11
- package/docs/task-process/implementation-planning.md +1 -1
- package/docs/task-process/implementation.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +11 -12
- package/runtime/bin/lib/okstra/globals.sh +2 -2
- package/runtime/bin/lib/okstra/interactive.sh +1 -1
- package/runtime/bin/lib/okstra/usage.sh +11 -9
- package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +1 -1
- package/runtime/bin/okstra-central.sh +2 -2
- package/runtime/bin/okstra-render-final-report.py +1 -1
- package/runtime/bin/okstra-token-usage.py +1 -1
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/adapters/cmux.md +6 -1
- package/runtime/prompts/lead/context-loader.md +3 -2
- package/runtime/prompts/lead/convergence.md +1 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +4 -4
- package/runtime/prompts/lead/plan-body-verification.md +3 -3
- package/runtime/prompts/lead/report-writer.md +21 -20
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +5 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -0
- package/runtime/prompts/profiles/_implementation-executor.md +2 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +9 -5
- package/runtime/prompts/profiles/implementation.md +4 -4
- package/runtime/prompts/profiles/improvement-discovery.md +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +5 -4
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +5 -4
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +71 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +5 -4
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +55 -3
- package/runtime/python/okstra_ctl/analysis_inputs.py +5 -3
- package/runtime/python/okstra_ctl/analysis_packet.py +21 -0
- package/runtime/python/okstra_ctl/backfill.py +12 -5
- package/runtime/python/okstra_ctl/consumers.py +70 -3
- package/runtime/python/okstra_ctl/convergence_engine.py +43 -17
- package/runtime/python/okstra_ctl/dispatch_core.py +47 -11
- package/runtime/python/okstra_ctl/dispatch_state.py +20 -19
- package/runtime/python/okstra_ctl/domain/worker_exec.py +13 -34
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +128 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +5 -0
- package/runtime/python/okstra_ctl/final_report_paths.py +77 -1
- package/runtime/python/okstra_ctl/handoff.py +1 -2
- package/runtime/python/okstra_ctl/implementation_outcome.py +1 -1
- package/runtime/python/okstra_ctl/index.py +4 -4
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +26 -12
- package/runtime/python/okstra_ctl/listing.py +4 -2
- package/runtime/python/okstra_ctl/manager_launch.py +1 -1
- package/runtime/python/okstra_ctl/manager_sync.py +1 -1
- package/runtime/python/okstra_ctl/path_hints.py +2 -2
- package/runtime/python/okstra_ctl/paths.py +13 -10
- package/runtime/python/okstra_ctl/plan_run_root.py +9 -5
- package/runtime/python/okstra_ctl/recap.py +3 -2
- package/runtime/python/okstra_ctl/reconcile.py +3 -1
- package/runtime/python/okstra_ctl/render.py +22 -22
- package/runtime/python/okstra_ctl/report_finalize.py +4 -4
- package/runtime/python/okstra_ctl/rollup.py +1 -1
- package/runtime/python/okstra_ctl/run.py +139 -284
- package/runtime/python/okstra_ctl/run_audit.py +5 -5
- package/runtime/python/okstra_ctl/run_index_row.py +2 -2
- package/runtime/python/okstra_ctl/session_transcript.py +89 -0
- package/runtime/python/okstra_ctl/stage_ledger.py +72 -0
- package/runtime/python/okstra_ctl/stage_map.py +28 -29
- package/runtime/python/okstra_ctl/stage_targets.py +61 -0
- package/runtime/python/okstra_ctl/user_response.py +97 -12
- package/runtime/python/okstra_ctl/wizard.py +43 -65
- package/runtime/python/okstra_ctl/worker_prompt_body.py +6 -7
- package/runtime/python/okstra_ctl/worker_runner.py +76 -213
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/wrapper_status.py +23 -0
- package/runtime/python/okstra_ctl/write_policy.py +45 -6
- package/runtime/python/okstra_project/state.py +2 -2
- package/runtime/python/okstra_token_usage/__init__.py +1 -1
- package/runtime/python/okstra_token_usage/cli.py +3 -3
- package/runtime/python/okstra_token_usage/report.py +7 -24
- package/runtime/schemas/convergence-groups-v1.0.schema.json +1 -1
- package/runtime/schemas/convergence-groups-v2.0.schema.json +1 -1
- package/runtime/schemas/final-report-v2.0.schema.json +11 -1
- package/runtime/skills/okstra-inspect/facets/history.md +3 -3
- package/runtime/skills/okstra-inspect/facets/recap.md +1 -1
- package/runtime/skills/okstra-inspect/facets/report.md +5 -5
- package/runtime/skills/okstra-inspect/facets/status.md +2 -2
- package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-schedule-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +3 -3
- package/runtime/templates/project-docs/task-index.template.md +1 -1
- package/runtime/templates/report-writer-prompt-preamble.md +1 -1
- package/runtime/validators/forbidden_actions.py +76 -5
- package/runtime/validators/lib/fixtures.sh +14 -10
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +3 -0
- package/runtime/validators/validate-report-views.py +1 -1
- package/runtime/validators/validate-run.py +95 -37
|
@@ -93,6 +93,7 @@ from okstra_ctl.md_table import ( # noqa: E402
|
|
|
93
93
|
split_pipe_row as _split_pipe_row,
|
|
94
94
|
)
|
|
95
95
|
from okstra_ctl.final_report_paths import final_report_data_path as _data_path_for # noqa: E402
|
|
96
|
+
from okstra_token_usage.report import _match_worker_index # noqa: E402
|
|
96
97
|
from okstra_ctl.improvement_assignment import ( # noqa: E402
|
|
97
98
|
validate_primary_lens_assignments,
|
|
98
99
|
)
|
|
@@ -743,8 +744,8 @@ def update_workflow_metadata(
|
|
|
743
744
|
"latestTeamStatePath": run_manifest.get(
|
|
744
745
|
"teamStatePath", ""
|
|
745
746
|
),
|
|
746
|
-
"
|
|
747
|
-
"
|
|
747
|
+
"latestReportRecordPath": task_manifest.get(
|
|
748
|
+
"latestReportRecordPath", ""
|
|
748
749
|
),
|
|
749
750
|
"latestResumeCommandPath": task_manifest.get(
|
|
750
751
|
"latestResumeCommandPath", ""
|
|
@@ -1660,39 +1661,51 @@ def _scan_token_usage_summary(
|
|
|
1660
1661
|
# a section heading line (not as inline text inside a paragraph or table).
|
|
1661
1662
|
_VERDICT_CARD_HEADING_RE = re.compile(r"^##[ \t]+Verdict Card\b", re.MULTILINE)
|
|
1662
1663
|
|
|
1664
|
+
def _team_state_worker_label(worker: Mapping[str, Any]) -> str:
|
|
1665
|
+
role = str(worker.get("role") or "").strip()
|
|
1666
|
+
worker_id = str(worker.get("workerId") or "").strip()
|
|
1667
|
+
if role and worker_id:
|
|
1668
|
+
return f"{role} (`{worker_id}`)"
|
|
1669
|
+
return role or worker_id or "<unnamed>"
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
def _unmatched_team_state_workers(
|
|
1673
|
+
report_data: Mapping[str, Any],
|
|
1674
|
+
team_state: Mapping[str, Any],
|
|
1675
|
+
) -> list[dict]:
|
|
1676
|
+
raw = team_state.get("workers")
|
|
1677
|
+
workers = [row for row in raw if isinstance(row, dict)] if isinstance(raw, list) else []
|
|
1678
|
+
used: set[int] = set()
|
|
1679
|
+
for row in report_data.get("executionStatus") or []:
|
|
1680
|
+
if not isinstance(row, dict):
|
|
1681
|
+
continue
|
|
1682
|
+
if "lead" in str(row.get("role") or "").lower():
|
|
1683
|
+
continue
|
|
1684
|
+
index = _match_worker_index(row, workers, used)
|
|
1685
|
+
if index is not None:
|
|
1686
|
+
used.add(index)
|
|
1687
|
+
return [worker for index, worker in enumerate(workers) if index not in used]
|
|
1688
|
+
|
|
1689
|
+
|
|
1663
1690
|
def _validate_v2_report(
|
|
1664
1691
|
report_data: Mapping[str, Any],
|
|
1665
|
-
required_agent_status_entries: list[str],
|
|
1666
1692
|
failures: list[str],
|
|
1693
|
+
*,
|
|
1694
|
+
team_state: Mapping[str, Any] | None = None,
|
|
1667
1695
|
) -> None:
|
|
1668
1696
|
"""Contract checks for a schema-v2 report, read from its data.json.
|
|
1669
1697
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
Schema enforcement in `render_final_report._enforce_schema` already answers
|
|
1675
|
-
the second question, and the first is settled by the template. What is left
|
|
1676
|
-
is the pair of facts the markdown could only ever carry second-hand: which
|
|
1677
|
-
agents the run must account for, and whether the token cells were filled.
|
|
1678
|
-
|
|
1679
|
-
Both are scanned over the serialized record, as text, because that is what
|
|
1680
|
-
the markdown scan was. Requiring each label to equal an
|
|
1681
|
-
`executionStatus[].role` would be a stricter rule than the contract states
|
|
1682
|
-
anywhere: the schema types `role` as a free string, no prompt or worker spec
|
|
1683
|
-
fixes its vocabulary, and `okstra_token_usage.report._match_worker_index`
|
|
1684
|
-
treats function-role spellings ("Analysis verifier", "Acceptance critic") as
|
|
1685
|
-
a shape reports do take — matching them by containment, never equality.
|
|
1686
|
-
Tightening this belongs with an authoring rule that says what to write, not
|
|
1687
|
-
on its own. Nothing is lost by reading the record instead of its rendering:
|
|
1688
|
-
the labels never come from the template or the i18n dictionaries.
|
|
1698
|
+
Agent completeness is the team-state worker set versus `executionStatus`
|
|
1699
|
+
rows, matched by `_match_worker_index` (role containment, then a unique
|
|
1700
|
+
provider+model fallback). Token placeholders stay a serialized-record
|
|
1701
|
+
scan. The full reading copy is not read.
|
|
1689
1702
|
"""
|
|
1703
|
+
for worker in _unmatched_team_state_workers(report_data, team_state or {}):
|
|
1704
|
+
failures.append(
|
|
1705
|
+
"executionStatus is missing team-state worker "
|
|
1706
|
+
f"{_team_state_worker_label(worker)}"
|
|
1707
|
+
)
|
|
1690
1708
|
serialized = json.dumps(report_data, ensure_ascii=False)
|
|
1691
|
-
for label in required_agent_status_entries:
|
|
1692
|
-
if label not in serialized:
|
|
1693
|
-
failures.append(
|
|
1694
|
-
f"final report does not include required agent status entry: {label}"
|
|
1695
|
-
)
|
|
1696
1709
|
for placeholder in TOKEN_PLACEHOLDERS:
|
|
1697
1710
|
if placeholder in serialized:
|
|
1698
1711
|
failures.append(
|
|
@@ -2875,13 +2888,14 @@ def validate_report(
|
|
|
2875
2888
|
allow_unavailable_token_usage: bool = False,
|
|
2876
2889
|
unavailable_ok_labels: frozenset[str] = frozenset(),
|
|
2877
2890
|
report_data: Mapping[str, Any] | None = None,
|
|
2891
|
+
team_state: Mapping[str, Any] | None = None,
|
|
2878
2892
|
) -> None:
|
|
2879
|
-
if
|
|
2880
|
-
|
|
2893
|
+
if (report_data or {}).get("schemaVersion") == "2.0":
|
|
2894
|
+
_validate_v2_report(report_data or {}, failures, team_state=team_state)
|
|
2881
2895
|
return
|
|
2882
2896
|
|
|
2883
|
-
if
|
|
2884
|
-
|
|
2897
|
+
if not report_path.exists():
|
|
2898
|
+
failures.append(f"final report is missing: {report_path}")
|
|
2885
2899
|
return
|
|
2886
2900
|
|
|
2887
2901
|
content = report_path.read_text()
|
|
@@ -3475,6 +3489,7 @@ def validate_final_report_data(
|
|
|
3475
3489
|
# Called here rather than from a task-type branch: four profiles raise
|
|
3476
3490
|
# clarification rows, and the gate scopes itself by task type internally.
|
|
3477
3491
|
_validate_clarification_options(data, failures)
|
|
3492
|
+
_validate_clarification_record_coordinates(data, failures)
|
|
3478
3493
|
_validate_open_approval_blocker_provenance(data, failures)
|
|
3479
3494
|
|
|
3480
3495
|
task_type = (data.get("header") or {}).get("taskType")
|
|
@@ -5413,6 +5428,48 @@ _REACH_TOKENS = frozenset({"in-repo", "cross-repo"})
|
|
|
5413
5428
|
_LEGACY_EXPECTED_FORM_RE = re.compile(r"\b(?:Recommended|Alternatives):")
|
|
5414
5429
|
|
|
5415
5430
|
|
|
5431
|
+
_CLARIFICATION_SECTION_CITE_RE = re.compile(r"§\d")
|
|
5432
|
+
|
|
5433
|
+
|
|
5434
|
+
def _validate_clarification_record_coordinates(
|
|
5435
|
+
data: dict, failures: list[str]
|
|
5436
|
+
) -> None:
|
|
5437
|
+
"""A clarification row may cite a record coordinate, not a section number.
|
|
5438
|
+
|
|
5439
|
+
`§4.7` exists only on one full reading copy. The report record identifies
|
|
5440
|
+
a row by `id` (`RB-002`). schema-v1 has no record and is exempt.
|
|
5441
|
+
"""
|
|
5442
|
+
if data.get("schemaVersion") != "2.0":
|
|
5443
|
+
return
|
|
5444
|
+
texts: list[tuple[str, str]] = []
|
|
5445
|
+
for row in data.get("clarificationItems") or []:
|
|
5446
|
+
if not isinstance(row, dict):
|
|
5447
|
+
continue
|
|
5448
|
+
row_id = str(row.get("id") or "<unknown>")
|
|
5449
|
+
for field in ("statement", "expectedForm"):
|
|
5450
|
+
texts.append((f"`{row_id}` {field}", str(row.get(field) or "")))
|
|
5451
|
+
options = row.get("options") or []
|
|
5452
|
+
if isinstance(options, list):
|
|
5453
|
+
for index, option in enumerate(options):
|
|
5454
|
+
if not isinstance(option, dict):
|
|
5455
|
+
continue
|
|
5456
|
+
for field in ("answer", "rationale"):
|
|
5457
|
+
texts.append(
|
|
5458
|
+
(
|
|
5459
|
+
f"`{row_id}` options[{index}].{field}",
|
|
5460
|
+
str(option.get(field) or ""),
|
|
5461
|
+
)
|
|
5462
|
+
)
|
|
5463
|
+
for label, text in texts:
|
|
5464
|
+
if _CLARIFICATION_SECTION_CITE_RE.search(text):
|
|
5465
|
+
failures.append(
|
|
5466
|
+
f"final-report data.json: clarification {label} cites a "
|
|
5467
|
+
"section number (`§…`). Cite a record coordinate (`RB-002`) "
|
|
5468
|
+
"instead — a section number exists only on one full reading "
|
|
5469
|
+
"copy."
|
|
5470
|
+
)
|
|
5471
|
+
|
|
5472
|
+
|
|
5416
5473
|
def _validate_clarification_options(data: dict, failures: list[str]) -> None:
|
|
5417
5474
|
"""A `decision` row must carry its choices as data, not as prose.
|
|
5418
5475
|
|
|
@@ -8922,8 +8979,8 @@ def _import_token_usage():
|
|
|
8922
8979
|
sys.path.insert(0, str(candidate))
|
|
8923
8980
|
break
|
|
8924
8981
|
from okstra_token_usage.collect import collect # noqa: E402
|
|
8925
|
-
from okstra_token_usage.report import
|
|
8926
|
-
return collect,
|
|
8982
|
+
from okstra_token_usage.report import populate_token_cells # noqa: E402
|
|
8983
|
+
return collect, populate_token_cells
|
|
8927
8984
|
|
|
8928
8985
|
|
|
8929
8986
|
def _needs_token_autofix(team_state: dict, report_path: Path) -> bool:
|
|
@@ -9018,7 +9075,7 @@ def attempt_token_usage_autofix(
|
|
|
9018
9075
|
if not _needs_token_autofix(team_state, report_path):
|
|
9019
9076
|
return "skipped", []
|
|
9020
9077
|
try:
|
|
9021
|
-
collect,
|
|
9078
|
+
collect, populate_token_cells = _import_token_usage()
|
|
9022
9079
|
except Exception as exc: # noqa: BLE001
|
|
9023
9080
|
return "import-failed", [f"okstra_token_usage import failed: {exc}"]
|
|
9024
9081
|
try:
|
|
@@ -9041,7 +9098,7 @@ def attempt_token_usage_autofix(
|
|
|
9041
9098
|
)
|
|
9042
9099
|
data_path = _data_path_for(report_path)
|
|
9043
9100
|
try:
|
|
9044
|
-
replaced
|
|
9101
|
+
replaced = populate_token_cells(data_path, updated)
|
|
9045
9102
|
except Exception as exc: # noqa: BLE001
|
|
9046
9103
|
# `SubstituteRefusedError` (or any unexpected substitution
|
|
9047
9104
|
# failure) — report it as an accuracy failure so the validator
|
|
@@ -9052,7 +9109,7 @@ def attempt_token_usage_autofix(
|
|
|
9052
9109
|
]
|
|
9053
9110
|
|
|
9054
9111
|
# Phase 7 step 1.5 is BLOCKING and the autofix just mutated the
|
|
9055
|
-
#
|
|
9112
|
+
# report record — any pre-existing html sibling is now stale by
|
|
9056
9113
|
# construction. Re-render the html view in lock-step so the
|
|
9057
9114
|
# downstream report-views validator does not trip over the
|
|
9058
9115
|
# autofix's own side effect.
|
|
@@ -9203,7 +9260,7 @@ def main() -> int:
|
|
|
9203
9260
|
parser.add_argument(
|
|
9204
9261
|
"--report",
|
|
9205
9262
|
required=True,
|
|
9206
|
-
help="Project-relative or absolute path to the
|
|
9263
|
+
help="Project-relative or absolute path to the report record (.data.json). Schema-v1 reports still use the Markdown file.",
|
|
9207
9264
|
)
|
|
9208
9265
|
parser.add_argument(
|
|
9209
9266
|
"--run-manifest",
|
|
@@ -9344,6 +9401,7 @@ def main() -> int:
|
|
|
9344
9401
|
allow_unavailable_token_usage=_session_accounting(team_state) == "artifact-only",
|
|
9345
9402
|
unavailable_ok_labels=_unavailable_usage_worker_labels(team_state),
|
|
9346
9403
|
report_data=validation_data,
|
|
9404
|
+
team_state=team_state,
|
|
9347
9405
|
)
|
|
9348
9406
|
validate_team_state_usage(team_state, failures)
|
|
9349
9407
|
|