okstra 0.146.1 → 0.148.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.
Files changed (137) hide show
  1. package/README.md +23 -9
  2. package/docs/architecture/storage-model.md +39 -65
  3. package/docs/architecture.md +68 -60
  4. package/docs/cli.md +40 -23
  5. package/docs/for-ai/skills/okstra-run.md +13 -34
  6. package/docs/performance-improvement-plan-v2.md +2 -2
  7. package/docs/pr-template-usage.md +1 -1
  8. package/docs/project-structure-overview.md +26 -22
  9. package/docs/task-process/README.md +4 -4
  10. package/docs/task-process/common-flow.md +12 -12
  11. package/docs/task-process/final-verification.md +2 -2
  12. package/docs/task-process/implementation.md +1 -1
  13. package/docs/task-process/release-handoff.md +1 -1
  14. package/package.json +2 -2
  15. package/runtime/BUILD.json +2 -2
  16. package/runtime/agents/workers/antigravity-worker.md +2 -2
  17. package/runtime/agents/workers/claude-worker.md +1 -1
  18. package/runtime/agents/workers/codex-worker.md +2 -2
  19. package/runtime/agents/workers/grok-worker.md +256 -0
  20. package/runtime/agents/workers/kimi-worker.md +256 -0
  21. package/runtime/agents/workers/report-writer-worker.md +12 -12
  22. package/runtime/bin/lib/okstra/cli.sh +13 -1
  23. package/runtime/bin/lib/okstra/globals.sh +3 -0
  24. package/runtime/bin/lib/okstra/usage.sh +17 -12
  25. package/runtime/bin/okstra-grok-exec.sh +5 -0
  26. package/runtime/bin/okstra-kimi-exec.sh +5 -0
  27. package/runtime/bin/okstra-provider-exec.py +235 -0
  28. package/runtime/bin/okstra-render-final-report.py +4 -4
  29. package/runtime/bin/okstra-render-report-views.py +100 -12
  30. package/runtime/bin/okstra.sh +3 -0
  31. package/runtime/prompts/lead/adapters/antigravity.md +48 -0
  32. package/runtime/prompts/lead/adapters/claude-code.md +13 -11
  33. package/runtime/prompts/lead/adapters/codex.md +7 -7
  34. package/runtime/prompts/lead/okstra-lead-contract.md +5 -5
  35. package/runtime/prompts/lead/report-writer.md +16 -12
  36. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  37. package/runtime/prompts/profiles/_common-contract.md +16 -10
  38. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  39. package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
  40. package/runtime/prompts/profiles/_implementation-executor.md +12 -12
  41. package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
  42. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  43. package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
  44. package/runtime/prompts/profiles/error-analysis.md +2 -0
  45. package/runtime/prompts/profiles/feature-analysis.md +2 -0
  46. package/runtime/prompts/profiles/final-verification.md +3 -1
  47. package/runtime/prompts/profiles/forbidden-actions.json +4 -4
  48. package/runtime/prompts/profiles/implementation-planning.md +3 -1
  49. package/runtime/prompts/profiles/implementation.md +2 -2
  50. package/runtime/prompts/profiles/improvement-discovery.md +6 -2
  51. package/runtime/prompts/profiles/project-analysis.md +2 -0
  52. package/runtime/prompts/profiles/release-handoff.md +7 -7
  53. package/runtime/prompts/profiles/requirements-discovery.md +2 -0
  54. package/runtime/prompts/wizard/prompts.ko.json +9 -1
  55. package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
  56. package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
  57. package/runtime/python/okstra_ctl/final_report_schema.py +37 -12
  58. package/runtime/python/okstra_ctl/lead_events.py +1 -1
  59. package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
  60. package/runtime/python/okstra_ctl/models.py +156 -8
  61. package/runtime/python/okstra_ctl/path_hints.py +9 -25
  62. package/runtime/python/okstra_ctl/paths.py +1 -1
  63. package/runtime/python/okstra_ctl/render.py +172 -74
  64. package/runtime/python/okstra_ctl/render_final_report.py +136 -28
  65. package/runtime/python/okstra_ctl/report_contract.py +124 -0
  66. package/runtime/python/okstra_ctl/report_finalize.py +1 -1
  67. package/runtime/python/okstra_ctl/report_html/__init__.py +10 -0
  68. package/runtime/python/okstra_ctl/report_html/common.py +86 -0
  69. package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
  70. package/runtime/python/okstra_ctl/report_html/models.py +59 -0
  71. package/runtime/python/okstra_ctl/report_html/render.py +76 -0
  72. package/runtime/python/okstra_ctl/report_html/router.py +40 -0
  73. package/runtime/python/okstra_ctl/report_html/view_models/__init__.py +1 -0
  74. package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +39 -0
  75. package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +49 -0
  76. package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +39 -0
  77. package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +47 -0
  78. package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +47 -0
  79. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +103 -0
  80. package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +43 -0
  81. package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +54 -0
  82. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +54 -0
  83. package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +55 -0
  84. package/runtime/python/okstra_ctl/report_html/visualizations.py +139 -0
  85. package/runtime/python/okstra_ctl/report_view_artifacts.py +4 -1
  86. package/runtime/python/okstra_ctl/report_views.py +15 -43
  87. package/runtime/python/okstra_ctl/run.py +276 -51
  88. package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
  89. package/runtime/python/okstra_ctl/schema_excerpt.py +7 -17
  90. package/runtime/python/okstra_ctl/team.py +2 -7
  91. package/runtime/python/okstra_ctl/wizard.py +194 -21
  92. package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
  93. package/runtime/python/okstra_ctl/workers.py +3 -1
  94. package/runtime/python/okstra_ctl/workflow.py +4 -2
  95. package/runtime/python/okstra_token_usage/__init__.py +1 -0
  96. package/runtime/python/okstra_token_usage/collect.py +32 -23
  97. package/runtime/python/okstra_token_usage/pricing.py +35 -3
  98. package/runtime/schemas/final-report-v2.0.schema.json +3923 -0
  99. package/runtime/skills/okstra-run/SKILL.md +31 -42
  100. package/runtime/templates/prd/pr-body.template.md +1 -1
  101. package/runtime/templates/reports/final-report-v2.template.md +66 -0
  102. package/runtime/templates/reports/html/assets/base.css +41 -0
  103. package/runtime/templates/reports/html/assets/base.js +5 -0
  104. package/runtime/templates/reports/html/base.template.html +79 -0
  105. package/runtime/templates/reports/html/macros/forms.html +47 -0
  106. package/runtime/templates/reports/html/macros/layout.html +19 -0
  107. package/runtime/templates/reports/html/macros/visualizations.html +27 -0
  108. package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +40 -0
  109. package/runtime/templates/reports/html/tasks/error-analysis.template.html +40 -0
  110. package/runtime/templates/reports/html/tasks/feature-analysis.template.html +40 -0
  111. package/runtime/templates/reports/html/tasks/final-verification.template.html +39 -0
  112. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +47 -0
  113. package/runtime/templates/reports/html/tasks/implementation.template.html +40 -0
  114. package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +29 -0
  115. package/runtime/templates/reports/html/tasks/project-analysis.template.html +57 -0
  116. package/runtime/templates/reports/html/tasks/release-handoff.template.html +36 -0
  117. package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +37 -0
  118. package/runtime/templates/reports/report.js +21 -4
  119. package/runtime/templates/reports/settings.template.json +4 -0
  120. package/runtime/templates/reports/task-brief.template.md +7 -7
  121. package/runtime/validators/validate-report-views.py +86 -4
  122. package/runtime/validators/validate-run.py +73 -15
  123. package/runtime/validators/validate_improvement_report.py +55 -0
  124. package/runtime/validators/validate_session_conformance.py +2 -1
  125. package/src/cli-registry.mjs +4 -4
  126. package/src/commands/execute/codex-dispatch.mjs +7 -10
  127. package/src/commands/execute/render-bundle.mjs +3 -3
  128. package/src/commands/execute/run.mjs +17 -52
  129. package/src/commands/execute/wizard.mjs +4 -1
  130. package/src/commands/lifecycle/doctor.mjs +6 -3
  131. package/src/commands/lifecycle/install.mjs +49 -21
  132. package/src/commands/report/finalize.mjs +2 -3
  133. package/src/commands/report/render-final-report.mjs +4 -2
  134. package/src/commands/report/render-views.mjs +8 -8
  135. package/src/lib/runtime-manifest.mjs +1 -1
  136. package/src/lib/runtime-resolver.mjs +2 -2
  137. package/src/lib/worker-agent-render.mjs +50 -0
@@ -30,12 +30,12 @@ for _ssot_dir in (_VALIDATORS_DIR.parent / "scripts", _VALIDATORS_DIR.parent / "
30
30
  try:
31
31
  from okstra_ctl.final_report_schema import (
32
32
  SchemaError,
33
- load_schema,
33
+ load_schema_for_data,
34
34
  validate as schema_validate,
35
35
  )
36
36
  except ImportError: # pragma: no cover — runtime guarantees this import
37
37
  SchemaError = None # type: ignore[assignment]
38
- load_schema = None # type: ignore[assignment]
38
+ load_schema_for_data = None # type: ignore[assignment]
39
39
  schema_validate = None # type: ignore[assignment]
40
40
 
41
41
  from okstra_project import project_json_path # noqa: E402
@@ -51,6 +51,7 @@ from okstra_ctl.conformance import ( # noqa: E402
51
51
  validate_conformance_manifest,
52
52
  )
53
53
  from okstra_ctl.paths import RunRef # noqa: E402
54
+ from okstra_ctl.lead_runtime import ARTIFACT_ONLY_LEAD_RUNTIMES # noqa: E402
54
55
  from okstra_ctl.build_tools import ( # noqa: E402
55
56
  command_invokes_build_tool,
56
57
  resolve_build_tool_tokens,
@@ -156,7 +157,7 @@ def _session_accounting(team_state: dict) -> str:
156
157
  return value
157
158
  return (
158
159
  "artifact-only"
159
- if team_state.get("leadRuntime") in {"codex", "external"}
160
+ if team_state.get("leadRuntime") in ARTIFACT_ONLY_LEAD_RUNTIMES
160
161
  else "claude-jsonl"
161
162
  )
162
163
 
@@ -415,20 +416,24 @@ def extract_contract(
415
416
  required_worker_roles = []
416
417
  failures.append("requiredWorkerRoles is missing from run/task manifest")
417
418
 
419
+ lead_role = (
420
+ run_contract.get("leadRole")
421
+ or task_contract.get("leadRole")
422
+ or "Okstra lead"
423
+ )
424
+
418
425
  required_agent_status_entries = run_contract.get("requiredAgentStatusEntries")
419
426
  if not isinstance(required_agent_status_entries, list):
420
427
  required_agent_status_entries = task_contract.get("requiredAgentStatusEntries")
421
428
  if not isinstance(required_agent_status_entries, list):
422
- required_agent_status_entries = ["Claude lead"] + [
429
+ required_agent_status_entries = [lead_role] + [
423
430
  item.get("role", "")
424
431
  for item in required_worker_roles
425
432
  if isinstance(item, dict) and item.get("role")
426
433
  ]
427
434
 
428
435
  return {
429
- "lead_role": run_contract.get("leadRole")
430
- or task_contract.get("leadRole")
431
- or "Claude lead",
436
+ "lead_role": lead_role,
432
437
  "lead_agent": run_contract.get("leadAgent")
433
438
  or task_contract.get("leadAgent")
434
439
  or "claude",
@@ -722,7 +727,7 @@ def validate_team_state(
722
727
  dispatch_mode = str(team_state.get("dispatchMode", "")).strip()
723
728
  if (
724
729
  dispatch_mode in WORKER_DISPATCH_MODES
725
- or team_state.get("leadRuntime") in {"codex", "external"}
730
+ or team_state.get("leadRuntime") in ARTIFACT_ONLY_LEAD_RUNTIMES
726
731
  ):
727
732
  if dispatch_mode not in WORKER_DISPATCH_MODES:
728
733
  expected = ", ".join(sorted(WORKER_DISPATCH_MODES))
@@ -1021,6 +1026,48 @@ def _scan_token_usage_summary(
1021
1026
  # a section heading line (not as inline text inside a paragraph or table).
1022
1027
  _VERDICT_CARD_HEADING_RE = re.compile(r"^##[ \t]+Verdict Card\b", re.MULTILINE)
1023
1028
 
1029
+ _V2_AI_HANDOFF_HEADINGS = (
1030
+ "## AI Handoff Summary",
1031
+ "## Clarification and User Decisions",
1032
+ "## Evidence Ledger",
1033
+ "## Task Deliverable:",
1034
+ "## Cross Verification Audit",
1035
+ "## Execution Audit",
1036
+ "## Token and Cost Audit",
1037
+ )
1038
+
1039
+
1040
+ def _validate_v2_ai_handoff(content: str, failures: list[str]) -> None:
1041
+ positions: list[int] = []
1042
+ for heading in _V2_AI_HANDOFF_HEADINGS:
1043
+ count = content.count(heading)
1044
+ if count != 1:
1045
+ failures.append(
1046
+ "schema-v2 AI handoff markdown requires exactly one "
1047
+ f"`{heading}` heading; found {count}."
1048
+ )
1049
+ continue
1050
+ positions.append(content.index(heading))
1051
+ if len(positions) == len(_V2_AI_HANDOFF_HEADINGS) and positions != sorted(
1052
+ positions
1053
+ ):
1054
+ failures.append(
1055
+ "schema-v2 AI handoff markdown heading order does not match "
1056
+ "templates/reports/final-report-v2.template.md."
1057
+ )
1058
+ for human_only_field in ('"humanSummary"', '"userNarrative"'):
1059
+ if human_only_field in content:
1060
+ failures.append(
1061
+ "schema-v2 AI handoff markdown contains human-only field "
1062
+ f"{human_only_field}; render it only in the task-specific HTML."
1063
+ )
1064
+ if _READING_CONFIRMATION_HEADING_RE.search(content) is not None:
1065
+ failures.append(
1066
+ "final report contains a `## 0. Reading Confirmation` heading — "
1067
+ "Reading Confirmation lives in the worker audit sidecar, never "
1068
+ "in the AI handoff markdown."
1069
+ )
1070
+
1024
1071
  # Top-of-report Index block. The renderer
1025
1072
  # (scripts/okstra_ctl/render_final_report.py) injects `<a id="report-index">`
1026
1073
  # into the index heading; a missing anchor means the markdown was produced
@@ -2229,6 +2276,10 @@ def validate_report(
2229
2276
  "run `okstra-token-usage.py ... --substitute-data <report-path>` during Phase 7"
2230
2277
  )
2231
2278
 
2279
+ if (report_data or {}).get("schemaVersion") == "2.0":
2280
+ _validate_v2_ai_handoff(content, failures)
2281
+ return
2282
+
2232
2283
  # Catch the "workers typed `0` / `pending` instead of the placeholder"
2233
2284
  # failure mode that bypasses the placeholder check above.
2234
2285
  _scan_token_usage_summary(
@@ -2995,7 +3046,7 @@ def validate_final_report_data(
2995
3046
  project_root: Path | None = None,
2996
3047
  clarification_text: str = "",
2997
3048
  ) -> Mapping[str, Any] | None:
2998
- """Validate the final-report data.json against the v1.0 schema.
3049
+ """Validate final-report data.json against its declared versioned schema.
2999
3050
 
3000
3051
  The data.json is the source-of-truth that the renderer reads to
3001
3052
  produce the markdown. If schema validation passes here, the rendered
@@ -3010,7 +3061,7 @@ def validate_final_report_data(
3010
3061
  The returned mapping is the exact loaded snapshot consumed by later
3011
3062
  finalization checks and workflow persistence.
3012
3063
  """
3013
- if schema_validate is None or load_schema is None:
3064
+ if schema_validate is None or load_schema_for_data is None:
3014
3065
  # Module-load fallback path; should never fire in a real install.
3015
3066
  failures.append(
3016
3067
  "validate-run: okstra_ctl.final_report_schema is not importable — "
@@ -3028,13 +3079,11 @@ def validate_final_report_data(
3028
3079
  return
3029
3080
 
3030
3081
  try:
3031
- schema = load_schema()
3082
+ data = json.loads(data_path.read_text(encoding="utf-8"))
3083
+ schema = load_schema_for_data(data)
3032
3084
  except SchemaError as exc:
3033
3085
  failures.append(f"final-report schema could not be loaded: {exc}")
3034
3086
  return
3035
-
3036
- try:
3037
- data = json.loads(data_path.read_text(encoding="utf-8"))
3038
3087
  except json.JSONDecodeError as exc:
3039
3088
  failures.append(f"final-report data.json is not valid JSON: {exc}")
3040
3089
  return
@@ -6035,6 +6084,8 @@ def validate_phase_boundary(
6035
6084
  task_type: str,
6036
6085
  report_path: Path,
6037
6086
  failures: list[str],
6087
+ *,
6088
+ report_data: Mapping[str, Any] | None = None,
6038
6089
  ) -> None:
6039
6090
  """Phase-specific contract checks.
6040
6091
 
@@ -6050,6 +6101,8 @@ def validate_phase_boundary(
6050
6101
  MUST be absent (lead converted findings into Clarification rows
6051
6102
  instead of opening the gate).
6052
6103
  """
6104
+ if (report_data or {}).get("schemaVersion") == "2.0":
6105
+ return
6053
6106
  if not report_path.exists():
6054
6107
  return
6055
6108
  content = report_path.read_text()
@@ -7167,7 +7220,12 @@ def main() -> int:
7167
7220
  )
7168
7221
  validate_team_state_usage(team_state, failures)
7169
7222
 
7170
- validate_phase_boundary(task_type, report_path, failures)
7223
+ validate_phase_boundary(
7224
+ task_type,
7225
+ report_path,
7226
+ failures,
7227
+ report_data=validation_data,
7228
+ )
7171
7229
  _validate_phase_boundary_error_log(
7172
7230
  report_path.parent.parent,
7173
7231
  task_type,
@@ -7,6 +7,7 @@ Called by validators/validate-run.py when task_type == "improvement-discovery".
7
7
  """
8
8
  from __future__ import annotations
9
9
 
10
+ import json
10
11
  import re
11
12
  import sys
12
13
  from dataclasses import dataclass, field
@@ -25,6 +26,10 @@ from okstra_ctl.improvement_lenses import (
25
26
  ABSOLUTE_CANDIDATE_CAP,
26
27
  SOURCE_WORKERS,
27
28
  )
29
+ from okstra_ctl.final_report_schema import (
30
+ load_schema_version,
31
+ validate as validate_final_report_data,
32
+ )
28
33
  from okstra_ctl.md_table import split_pipe_row
29
34
 
30
35
 
@@ -290,6 +295,47 @@ def _check_verdict_card(
290
295
  errors.append("Verdict Card row must byte-match `## 7. Final Verdict` row")
291
296
 
292
297
 
298
+ def _v2_candidate_rows(data: dict) -> list[list[str]]:
299
+ candidates = data["improvementDiscovery"]["candidates"]
300
+ return [
301
+ [
302
+ row["id"],
303
+ ",".join(row["lens"]),
304
+ row["title"],
305
+ ",".join(row["scope"]),
306
+ row["severity"],
307
+ row["effort"],
308
+ row["consensus"],
309
+ ",".join(row["sourceWorkers"]),
310
+ row["recommendedNextPhase"],
311
+ row["expectedBehaviorAfter"],
312
+ ",".join(row["evidence"]),
313
+ ]
314
+ for row in candidates
315
+ ]
316
+
317
+
318
+ def _validate_v2_report(
319
+ data: dict,
320
+ run_dir: Path,
321
+ brief_frontmatter: dict,
322
+ ) -> ValidationResult:
323
+ errors = validate_final_report_data(data, load_schema_version("2.0"))
324
+ _check_grilling_log(run_dir, errors)
325
+ if errors:
326
+ return ValidationResult(ok=False, errors=errors)
327
+
328
+ rows = _v2_candidate_rows(data)
329
+ _check_candidates_cap(len(rows), brief_frontmatter, errors)
330
+ _check_candidate_rows(
331
+ rows,
332
+ brief_frontmatter.get("scan-scope") or [],
333
+ brief_frontmatter.get("out-of-scope") or [],
334
+ errors,
335
+ )
336
+ return ValidationResult(ok=not errors, errors=errors)
337
+
338
+
293
339
  def validate_improvement_report(
294
340
  report_path: Path, run_dir: Path, brief_frontmatter: dict
295
341
  ) -> ValidationResult:
@@ -299,6 +345,15 @@ def validate_improvement_report(
299
345
  errors.append(f"report file not found: {report_path}")
300
346
  return ValidationResult(ok=False, errors=errors)
301
347
 
348
+ data_path = report_path.with_suffix(".data.json")
349
+ if data_path.exists():
350
+ try:
351
+ data = json.loads(data_path.read_text(encoding="utf-8"))
352
+ except (OSError, json.JSONDecodeError) as exc:
353
+ return ValidationResult(ok=False, errors=[f"invalid report data: {exc}"])
354
+ if data.get("schemaVersion") == "2.0":
355
+ return _validate_v2_report(data, run_dir, brief_frontmatter)
356
+
302
357
  body = report_path.read_text(encoding="utf-8")
303
358
 
304
359
  _check_grilling_log(run_dir, errors)
@@ -38,6 +38,7 @@ from okstra_ctl.worker_heartbeat import ( # noqa: E402
38
38
  HEARTBEAT_LINE_RE,
39
39
  max_gap_seconds_after,
40
40
  )
41
+ from okstra_ctl.lead_runtime import ARTIFACT_ONLY_LEAD_RUNTIMES # noqa: E402
41
42
  from okstra_ctl.wrapper_status import read_wrapper_status # noqa: E402
42
43
 
43
44
  _DISPATCHED_STATUSES = {"completed", "timeout", "error", "in-progress"}
@@ -366,7 +367,7 @@ def _conformance_evidence_source(
366
367
  return None, f"unsupported sessionAccounting: `{accounting}`"
367
368
 
368
369
  legacy_runtime = str(team_state.get("leadRuntime", "") or "claude-code")
369
- if legacy_runtime in {"codex", "external"}:
370
+ if legacy_runtime in ARTIFACT_ONLY_LEAD_RUNTIMES:
370
371
  return "artifact-only", None
371
372
  return "claude-jsonl", None
372
373
 
@@ -415,18 +415,18 @@ export const COMMANDS = new Map(
415
415
  ]),
416
416
  );
417
417
 
418
- const PREAMBLE = `okstra — multi-agent cross-verification orchestrator for Claude Code
418
+ const PREAMBLE = `okstra — host-aware multi-provider cross-verification orchestrator
419
419
 
420
420
  This CLI is the installer/admin tool. Day-to-day usage happens inside a
421
- Claude Code session via slash commands (/okstra-setup, /okstra-run, ...).
421
+ supported agent host through installed skills (okstra-setup, okstra-run, ...).
422
422
 
423
423
  Quick start (CLI):
424
424
  1. npx -y okstra@latest install # one-time, this machine
425
425
  2. cd <your project>
426
426
  3. npx -y okstra@latest setup --project-id <id> # one-time, this project
427
- 4. open a Claude Code session and run /okstra-run # start a task
427
+ 4. open Claude Code, Codex, or Antigravity and run okstra-run # start a task
428
428
 
429
- Inside a Claude Code session you can replace step 3 with /okstra-setup —
429
+ Inside a supported agent host you can replace step 3 with okstra-setup —
430
430
  both write the same <PROJECT_ROOT>/.okstra/project.json.
431
431
 
432
432
  Usage:
@@ -5,18 +5,15 @@ const USAGE = `okstra codex-dispatch — dispatch CLI-backed workers for a prepa
5
5
 
6
6
  Usage:
7
7
  okstra codex-dispatch --project-root <dir> --run-manifest <path> \\
8
- [--workers codex[,antigravity[,report-writer]]] [--dry-run] \\
9
- [--idle-timeout-seconds <n>] \\
10
- [--enable-codex-report-writer --report-writer-codex-model <model>]
8
+ [--workers <cli-assigned-worker-ids>] [--dry-run] \\
9
+ [--idle-timeout-seconds <n>]
11
10
 
12
11
  This command reads an existing run manifest produced by 'okstra codex-run' and
13
- dispatches only Codex-side supported workers. When --workers is omitted, the
14
- command selects the supported subset of the run roster (codex/antigravity, plus
15
- report-writer only when explicitly opted in). Codex and antigravity use their
16
- existing wrappers. The report-writer role is available only through explicit
17
- Codex opt-in because the prepared manifest still records the Claude-side report
18
- writer model by default. Explicitly requesting unsupported workers such as
19
- claude fails.
12
+ dispatches only assignments whose persisted runner is 'cli-wrapper'. When
13
+ --workers is omitted, it selects that subset from the run roster. Each worker,
14
+ including report-writer, uses its persisted provider, model, and registered
15
+ wrapper. Native-session assignments remain owned by the current Codex host;
16
+ explicitly requesting one here fails.
20
17
 
21
18
  Missing worker prompt files are generated automatically from the run manifest
22
19
  and active-run-context. Existing prompt files are never overwritten.
@@ -2,7 +2,7 @@ import { runPythonModule } from "../../lib/python-helper.mjs";
2
2
  import { resolvePaths } from "../../lib/paths.mjs";
3
3
  import { resolveRuntime } from "../../lib/runtime-resolver.mjs";
4
4
 
5
- const USAGE = `okstra render-bundle — preview the task bundle without launching claude
5
+ const USAGE = `okstra render-bundle — preview the task bundle without launching a lead
6
6
 
7
7
  This is a thin shim over \`python3 -m okstra_ctl.run\` that forces
8
8
  \`--render-only\`. Use it to preview where prepare_task_bundle() would
@@ -18,7 +18,7 @@ Usage:
18
18
  [--analysis-target <text>] [--evidence-inputs <csv>] \\
19
19
  [--lead-model <m>] [--claude-model <m>] [--codex-model <m>] \\
20
20
  [--antigravity-model <m>] [--report-writer-model <m>] \\
21
- [--lead-runtime claude-code|codex] \\
21
+ [--lead-runtime claude-code|codex|antigravity|external] \\
22
22
  [--related-tasks <list>] [--base-ref <ref>] \\
23
23
  [--clarification-response <path>] [--work-category <cat>] \\
24
24
  [--stage <auto|N>] [--stages <csv>] [--pr-template-path <path>] \\
@@ -26,7 +26,7 @@ Usage:
26
26
 
27
27
  --stage implementation / final-verification only
28
28
  --stages release-handoff only: PR stage bundle csv (empty = whole-task)
29
- --lead-runtime lead adapter marker; codex is render-only in this version
29
+ --lead-runtime host-native lead adapter marker
30
30
  --pr-template-path release-handoff only
31
31
  --fix-cycle entry phase only: record this re-entry of a done task as a
32
32
  bug-fix cycle (yes opens/continues the cycle)
@@ -7,10 +7,10 @@ const USAGE = `okstra run — host-aware okstra execution front door
7
7
 
8
8
  Usage:
9
9
  okstra run --project-root <dir> --project-id <id> --task-group <tg> \\
10
- --task-id <tid> --task-type <type> [--lead-runtime auto|claude-code|codex|external]
10
+ --task-id <tid> --task-type <type> [--lead-runtime auto|claude-code|codex|antigravity|external]
11
11
 
12
- Claude Code native execution remains the /okstra-run skill front door. Bare
13
- 'okstra run' orchestrates Codex and external/tmux CLI execution.
12
+ Host-native Claude Code, Codex, and Antigravity execution use the okstra-run
13
+ skill front door. Bare 'okstra run' orchestrates only external/tmux all-CLI execution.
14
14
  `;
15
15
 
16
16
  function flagValue(args, flag) {
@@ -48,55 +48,22 @@ function projectRootFromArgs(args) {
48
48
  return flagValue(args, "--project-root");
49
49
  }
50
50
 
51
- function withoutDispatchOnlyFlags(args) {
52
- const out = [];
53
- for (let i = 0; i < args.length; i += 1) {
54
- const arg = args[i];
55
- if (arg === "--enable-codex-report-writer") continue;
56
- if (arg === "--report-writer-codex-model") {
57
- i += 1;
58
- continue;
59
- }
60
- if (arg.startsWith("--report-writer-codex-model=")) continue;
61
- out.push(arg);
62
- }
63
- return out;
64
- }
65
-
66
51
  function renderArgs(args, resolution) {
67
52
  return [
68
53
  "--lead-runtime", resolution.resolvedRuntime,
69
54
  "--lead-runtime-request", resolution.requestedRuntime,
70
55
  "--runtime-resolution-json", JSON.stringify(resolution),
71
- ...withoutDispatchOnlyFlags(withoutLeadRuntimeFlags(args)),
56
+ ...withoutLeadRuntimeFlags(args),
72
57
  ];
73
58
  }
74
59
 
75
- function codexDispatchArgs(args, projectRoot, runManifestPath) {
76
- const out = ["--project-root", projectRoot, "--run-manifest", runManifestPath];
77
- for (let i = 0; i < args.length; i += 1) {
78
- const arg = args[i];
79
- if (arg === "--enable-codex-report-writer") {
80
- out.push(arg);
81
- continue;
82
- }
83
- if (arg === "--report-writer-codex-model") {
84
- out.push(arg, args[i + 1] ?? "");
85
- i += 1;
86
- continue;
87
- }
88
- if (arg.startsWith("--report-writer-codex-model=")) out.push(arg);
89
- }
90
- return out;
91
- }
92
-
93
60
  export function buildRunPlan({ args, paths, resolution, runManifestPath = "" }) {
94
61
  void paths;
95
62
  const projectRoot = projectRootFromArgs(args);
96
63
  if (resolution.resolvedRuntime === "claude-code") {
97
64
  return {
98
65
  ok: false,
99
- reason: "Bare okstra run cannot invoke Claude Code TeamCreate / Agent(...). Use /okstra-run in Claude Code or pass --lead-runtime external|codex.",
66
+ reason: "Bare okstra run cannot invoke host-native Claude workers. Use the okstra-run skill in Claude Code or pass --lead-runtime external.",
100
67
  commands: [],
101
68
  };
102
69
  }
@@ -115,12 +82,17 @@ export function buildRunPlan({ args, paths, resolution, runManifestPath = "" })
115
82
 
116
83
  if (resolution.resolvedRuntime === "codex") {
117
84
  return {
118
- ok: true,
119
- reason: resolution.reason,
120
- commands: [
121
- { name: "codex-run", args: renderArgs(args, resolution), capture: true },
122
- { name: "codex-dispatch", args: codexDispatchArgs(args, projectRoot, runManifestPath), capture: false },
123
- ],
85
+ ok: false,
86
+ reason: "Bare okstra run cannot invoke host-native Codex workers. Use the okstra-run skill in Codex or pass --lead-runtime external for an all-CLI run.",
87
+ commands: [],
88
+ };
89
+ }
90
+
91
+ if (resolution.resolvedRuntime === "antigravity") {
92
+ return {
93
+ ok: false,
94
+ reason: "Bare okstra run cannot invoke host-native Antigravity workers. Use the okstra-run skill in Antigravity or pass --lead-runtime external for an all-CLI run.",
95
+ commands: [],
124
96
  };
125
97
  }
126
98
 
@@ -147,11 +119,6 @@ async function runTeam(args) {
147
119
  return await runTeamCommand(args);
148
120
  }
149
121
 
150
- async function runCodexDispatch(args) {
151
- const { run: runCodexDispatchCommand } = await import("./codex-dispatch.mjs");
152
- return await runCodexDispatchCommand(args);
153
- }
154
-
155
122
  export async function run(args) {
156
123
  if (args.includes("--help") || args.includes("-h")) {
157
124
  process.stdout.write(USAGE);
@@ -192,9 +159,7 @@ export async function run(args) {
192
159
 
193
160
  const plan = buildRunPlan({ args, paths, resolution, runManifestPath });
194
161
  for (const command of plan.commands.slice(1)) {
195
- const code = command.name.startsWith("team ")
196
- ? await runTeam(command.args)
197
- : await runCodexDispatch(command.args);
162
+ const code = await runTeam(command.args);
198
163
  if (code !== 0) return code;
199
164
  }
200
165
  return 0;
@@ -20,7 +20,7 @@ Subcommands:
20
20
 
21
21
  Usage:
22
22
  okstra wizard new-state-file
23
- okstra wizard init --state-file <path> --project-root <p> --project-id <id>
23
+ okstra wizard init --state-file <path> --project-root <p> --project-id <id> --host-runtime <runtime>
24
24
  okstra wizard step --state-file <path> [--answer <value>]
25
25
  okstra wizard render-args --state-file <path>
26
26
  okstra wizard confirmation --state-file <path>
@@ -106,6 +106,9 @@ export async function run(args) {
106
106
  pyArgs.push("--workspace-root", paths.workspace);
107
107
  pyArgs.push("--project-root", flags["project-root"]);
108
108
  pyArgs.push("--project-id", flags["project-id"]);
109
+ if (flags["host-runtime"]) {
110
+ pyArgs.push("--host-runtime", flags["host-runtime"]);
111
+ }
109
112
  } else if (sub === "step" && flags.answer !== undefined) {
110
113
  pyArgs.push("--answer", flags.answer);
111
114
  }
@@ -14,7 +14,7 @@ const USAGE = `okstra doctor — diagnose the installed runtime
14
14
 
15
15
  Usage:
16
16
  okstra doctor Run all checks and print a summary
17
- okstra doctor --runtime <claude-code|codex|external|all>
17
+ okstra doctor --runtime <claude-code|codex|antigravity|external|all>
18
18
  okstra doctor --phase <phase>
19
19
  okstra doctor --json Machine-readable result
20
20
  Checks:
@@ -48,7 +48,7 @@ export function parseDoctorArgs(args) {
48
48
  } else if (arg === "--runtime" || arg.startsWith("--runtime=")) {
49
49
  const value = arg.startsWith("--runtime=") ? arg.slice("--runtime=".length) : args[i + 1];
50
50
  if (!value || value.startsWith("--")) {
51
- throw new Error("--runtime requires one of: claude-code, codex, external, all");
51
+ throw new Error("--runtime requires one of: claude-code, codex, antigravity, external, all");
52
52
  }
53
53
  result.runtime = validateInstallRuntime(value);
54
54
  if (arg === "--runtime") i++;
@@ -226,6 +226,9 @@ export async function run(args) {
226
226
  await check("bin: okstra.sh", () => checkBashEntry(paths.bin, "okstra.sh")),
227
227
  await check("bin: okstra-codex-exec.sh", () => checkBashEntry(paths.bin, "okstra-codex-exec.sh")),
228
228
  await check("bin: okstra-antigravity-exec.sh", () => checkBashEntry(paths.bin, "okstra-antigravity-exec.sh")),
229
+ await check("bin: okstra-grok-exec.sh", () => checkBashEntry(paths.bin, "okstra-grok-exec.sh")),
230
+ await check("bin: okstra-kimi-exec.sh", () => checkBashEntry(paths.bin, "okstra-kimi-exec.sh")),
231
+ await check("bin: okstra-provider-exec.py", () => checkBashEntry(paths.bin, "okstra-provider-exec.py")),
229
232
  await check("bin: okstra-central.sh", () => checkBashEntry(paths.bin, "okstra-central.sh")),
230
233
  ...(await Promise.all(
231
234
  requiredSkillNamesForRuntime(resolvedRuntime).map((name) =>
@@ -275,7 +278,7 @@ export async function run(args) {
275
278
 
276
279
  if (!runtimeResolution.ok) {
277
280
  process.stdout.write(
278
- ` [WARN] runtime host: ${runtimeResolution.reason} skill checks skipped — pass --runtime <claude-code|codex|external> to include them.\n`,
281
+ ` [WARN] runtime host: ${runtimeResolution.reason} skill checks skipped — pass --runtime <claude-code|codex|antigravity|external> to include them.\n`,
279
282
  );
280
283
  }
281
284
  if (dockerWarning) {