okstra 0.167.0 → 0.169.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 (102) hide show
  1. package/README.md +6 -5
  2. package/docs/architecture/storage-model.md +57 -1
  3. package/docs/architecture.md +70 -2
  4. package/docs/cli.md +8 -4
  5. package/docs/for-ai/skills/okstra-code-review.md +3 -2
  6. package/docs/for-ai/skills/okstra-schedule-gen.md +3 -1
  7. package/docs/pr-template-usage.md +10 -6
  8. package/docs/project-structure-overview.md +14 -11
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/agents/workers/claude-worker.md +6 -5
  12. package/runtime/agents/workers/report-writer-worker.md +9 -4
  13. package/runtime/agents/workers/translator-worker.md +6 -4
  14. package/runtime/prompts/coding-preflight/architectures/hexagonal.md +27 -1
  15. package/runtime/prompts/coding-preflight/clean-code.md +13 -0
  16. package/runtime/prompts/duties/acceptance-critic.md +24 -0
  17. package/runtime/prompts/duties/acceptance-verifier.md +24 -0
  18. package/runtime/prompts/duties/analysis-worker.md +24 -0
  19. package/runtime/prompts/duties/code-reviewer.md +24 -0
  20. package/runtime/prompts/duties/common.md +35 -0
  21. package/runtime/prompts/duties/implementation-executor.md +24 -0
  22. package/runtime/prompts/duties/implementation-verifier.md +24 -0
  23. package/runtime/prompts/duties/lead.md +24 -0
  24. package/runtime/prompts/duties/report-writer.md +24 -0
  25. package/runtime/prompts/duties/reverification-worker.md +24 -0
  26. package/runtime/prompts/duties/schedule-verifier.md +24 -0
  27. package/runtime/prompts/duties/scope-critic.md +24 -0
  28. package/runtime/prompts/duties/translator.md +24 -0
  29. package/runtime/prompts/lead/convergence.md +51 -7
  30. package/runtime/prompts/lead/okstra-lead-contract.md +10 -20
  31. package/runtime/prompts/lead/plan-body-verification.md +16 -1
  32. package/runtime/prompts/lead/report-writer.md +20 -5
  33. package/runtime/prompts/lead/team-contract.md +13 -13
  34. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  35. package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
  36. package/runtime/prompts/profiles/_implementation-executor.md +1 -1
  37. package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
  38. package/runtime/prompts/profiles/implementation.md +4 -2
  39. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +6 -0
  40. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +3 -2
  41. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +8 -0
  42. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +33 -0
  43. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +13 -12
  44. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +6 -0
  45. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +3 -2
  46. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +2 -0
  47. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -3
  48. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +6 -0
  49. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -1
  50. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +6 -0
  51. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -1
  52. package/runtime/python/okstra_ctl/agent_invocation.py +1502 -0
  53. package/runtime/python/okstra_ctl/agent_prompt_cli.py +788 -0
  54. package/runtime/python/okstra_ctl/codex_dispatch.py +2 -107
  55. package/runtime/python/okstra_ctl/context_cost.py +46 -5
  56. package/runtime/python/okstra_ctl/dispatch_core.py +312 -37
  57. package/runtime/python/okstra_ctl/dispatch_state.py +461 -36
  58. package/runtime/python/okstra_ctl/doctor.py +150 -16
  59. package/runtime/python/okstra_ctl/entrypoints/hosts.py +87 -9
  60. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +214 -23
  61. package/runtime/python/okstra_ctl/path_hints.py +26 -0
  62. package/runtime/python/okstra_ctl/paths.py +20 -0
  63. package/runtime/python/okstra_ctl/ports/__init__.py +8 -0
  64. package/runtime/python/okstra_ctl/ports/host.py +3 -0
  65. package/runtime/python/okstra_ctl/ports/host_model.py +60 -0
  66. package/runtime/python/okstra_ctl/pr_template.py +3 -6
  67. package/runtime/python/okstra_ctl/registry/host_registry.py +5 -0
  68. package/runtime/python/okstra_ctl/render.py +217 -12
  69. package/runtime/python/okstra_ctl/report_finalize.py +44 -0
  70. package/runtime/python/okstra_ctl/run.py +368 -51
  71. package/runtime/python/okstra_ctl/session.py +16 -12
  72. package/runtime/python/okstra_ctl/team.py +11 -11
  73. package/runtime/python/okstra_ctl/worker_dispatch.py +104 -0
  74. package/runtime/python/okstra_ctl/worker_prompt_body.py +5 -38
  75. package/runtime/python/okstra_ctl/worker_prompt_contract.py +32 -2
  76. package/runtime/python/okstra_ctl/worker_prompt_policy.py +38 -1
  77. package/runtime/skills/okstra-code-review/SKILL.md +22 -3
  78. package/runtime/skills/okstra-run/SKILL.md +16 -1
  79. package/runtime/skills/okstra-schedule-gen/SKILL.md +15 -1
  80. package/runtime/templates/implementation-worker-preamble.md +0 -10
  81. package/runtime/templates/report-writer-prompt-preamble.md +0 -9
  82. package/runtime/templates/reports/settings.template.json +0 -11
  83. package/runtime/templates/worker-prompt-preamble.md +0 -10
  84. package/runtime/validators/lib/fixtures.sh +93 -0
  85. package/runtime/validators/lib/validate-assets.sh +0 -8
  86. package/runtime/validators/validate-run.py +182 -0
  87. package/src/cli-registry.mjs +14 -0
  88. package/src/commands/execute/agent-prompt.mjs +25 -0
  89. package/src/commands/execute/codex-dispatch.mjs +6 -63
  90. package/src/commands/execute/worker-dispatch.mjs +76 -0
  91. package/src/commands/lifecycle/doctor.mjs +18 -3
  92. package/src/commands/lifecycle/install.mjs +33 -15
  93. package/src/commands/lifecycle/uninstall.mjs +4 -3
  94. package/src/lib/install-assets.mjs +9 -0
  95. package/runtime/agents/workers/antigravity-worker.md +0 -259
  96. package/runtime/agents/workers/codex-worker.md +0 -259
  97. package/runtime/agents/workers/grok-worker.md +0 -259
  98. package/runtime/agents/workers/kimi-worker.md +0 -259
  99. package/runtime/prompts/coding-preflight/scripts/preedit-check.sh +0 -79
  100. package/runtime/templates/operating-standard.md +0 -22
  101. package/src/lib/worker-agent-render.mjs +0 -50
  102. /package/runtime/templates/{prd → pr}/pr-body.template.md +0 -0
@@ -1,114 +1,9 @@
1
- """CLI-wrapper dispatcher for a Codex-hosted Okstra lead.
2
-
3
- The prepared assignment's ``runner`` field is authoritative. Native Codex
4
- assignments stay in the host session; this entrypoint executes only the backend
5
- plan returned by the worker-dispatch application use case.
6
- """
1
+ """Compatibility alias for :mod:`okstra_ctl.worker_dispatch`."""
7
2
  from __future__ import annotations
8
3
 
9
- import argparse
10
- import json
11
4
  import sys
12
- from pathlib import Path
13
- from typing import Any, Mapping, Sequence
14
-
15
- from . import dispatch_core
16
- from .adapters.dispatch import with_worker_dispatch
17
- from .adapters.dispatch.cli_wrapper import CliWrapperDispatchPort
18
- from .application.dispatch_assignments import dispatch_assignments
19
- from .dispatch_state import DispatchError
20
- from .models import provider_wrappers
21
- from .ports.worker_dispatch import WorkerDispatchRequest
22
- from .registry.host_registry import default_host_registry
23
-
24
-
25
- SUPPORTED_CLI_WORKERS = provider_wrappers("analyser")
26
-
27
-
28
- def main(argv: Sequence[str] | None = None) -> int:
29
- args = _parser().parse_args(argv)
30
- try:
31
- if args.jobs_file and args.workers:
32
- raise DispatchError("--jobs-file and --workers cannot be combined")
33
- request = _dispatch_request(args)
34
- port = CliWrapperDispatchPort(
35
- supported_worker_wrappers=SUPPORTED_CLI_WORKERS,
36
- unsupported_worker_label="Codex lead",
37
- required_lead_runtime="codex",
38
- cli_wrapper_assignments_only=True,
39
- default_provider_by_worker_id={"report-writer": "codex"},
40
- )
41
- host_adapter = with_worker_dispatch(
42
- default_host_registry().resolve("codex"),
43
- port,
44
- )
45
- plan = dispatch_assignments(request, host_adapter.worker_dispatch())
46
- backend_plan = plan.backend_plan
47
- if backend_plan is None:
48
- raise DispatchError("CLI wrapper dispatch did not produce a backend plan")
49
- if args.dry_run:
50
- _print_json(_backend_payload(backend_plan, dry_run=True))
51
- return 0
52
- code = dispatch_core.dispatch_plan(backend_plan)
53
- _print_json({**_backend_payload(backend_plan, dry_run=False), "exitCode": code})
54
- return code
55
- except DispatchError as exc:
56
- print(f"error: {exc}", file=sys.stderr)
57
- return 2
58
-
59
-
60
- def _dispatch_request(args: argparse.Namespace) -> WorkerDispatchRequest:
61
- return WorkerDispatchRequest(
62
- project_root=Path(args.project_root),
63
- run_manifest=Path(args.run_manifest),
64
- workspace_root=Path(args.workspace_root),
65
- okstra_bin=Path(args.okstra_bin) if args.okstra_bin else None,
66
- requested_workers=tuple(_parse_workers(args.workers)),
67
- idle_timeout_seconds=_parse_idle_timeout(args.idle_timeout_seconds),
68
- dispatch_kind=args.dispatch_kind,
69
- jobs_file=Path(args.jobs_file) if args.jobs_file else None,
70
- )
71
-
72
-
73
- def _backend_payload(backend_plan, *, dry_run: bool) -> dict[str, Any]:
74
- payload = backend_plan.to_payload(dry_run=dry_run)
75
- payload.pop("workerDispatches", None)
76
- return payload
77
-
78
-
79
- def _parser() -> argparse.ArgumentParser:
80
- parser = argparse.ArgumentParser(
81
- prog="okstra codex-dispatch",
82
- description="Dispatch CLI-backed workers for a prepared Codex lead run.",
83
- )
84
- parser.add_argument("--project-root", required=True)
85
- parser.add_argument("--run-manifest", required=True)
86
- parser.add_argument("--workspace-root", required=True)
87
- parser.add_argument("--okstra-bin", default="")
88
- parser.add_argument("--workers", default="")
89
- parser.add_argument("--dry-run", action="store_true")
90
- parser.add_argument("--idle-timeout-seconds", default="600")
91
- parser.add_argument("--dispatch-kind", default="initial")
92
- parser.add_argument("--jobs-file", default="")
93
- return parser
94
-
95
-
96
- def _parse_workers(raw: str) -> list[str]:
97
- return [item.strip() for item in raw.split(",") if item.strip()]
98
-
99
-
100
- def _parse_idle_timeout(raw: str) -> int:
101
- try:
102
- value = int(raw)
103
- except ValueError as exc:
104
- raise DispatchError("--idle-timeout-seconds must be an integer") from exc
105
- if value < 0:
106
- raise DispatchError("--idle-timeout-seconds must be non-negative")
107
- return value
108
-
109
5
 
110
- def _print_json(payload: Mapping[str, Any]) -> None:
111
- print(json.dumps(payload, ensure_ascii=False, indent=2))
6
+ from .worker_dispatch import main
112
7
 
113
8
 
114
9
  if __name__ == "__main__":
@@ -25,8 +25,8 @@ INPUT_FILES = (
25
25
  "clarification-response.md",
26
26
  )
27
27
  # Per-run hot-path instruction assets the lead loads OUTSIDE the task bundle:
28
- # the lifecycle lead contracts (Phase 1 / 2-5 / 5.5 / 6-7) plus the worker
29
- # agent specs. These are the prompt-diet (perf plan v2 P2) targets; the
28
+ # the lifecycle lead contracts (Phase 1 / 2-5 / 5.5 / 6-7) plus the host-native
29
+ # functional agent specs. These are the prompt-diet (perf plan v2 P2) targets; the
30
30
  # bundle-local metrics below cannot see them, so they get their own metric.
31
31
  # The contracts ship as runtime resources under ~/.okstra/prompts/lead/, not
32
32
  # as agent skills.
@@ -38,9 +38,8 @@ HOT_PATH_LEAD_RESOURCES = (
38
38
  )
39
39
  WORKER_AGENT_FILES = (
40
40
  "claude-worker.md",
41
- "codex-worker.md",
42
- "antigravity-worker.md",
43
41
  "report-writer-worker.md",
42
+ "translator-worker.md",
44
43
  )
45
44
  TIMESTAMPED_ARTIFACT_RE = re.compile(r"\d{4}-\d{2}-\d{2}[_T]\d{2}-\d{2}-\d{2}")
46
45
 
@@ -114,6 +113,31 @@ def _latest_matching_file(directory: Path, pattern: str) -> Path | None:
114
113
  return max(matches, key=lambda path: path.stat().st_mtime)
115
114
 
116
115
 
116
+ def _duty_contract_files(
117
+ run_dir: Path | None,
118
+ project_root: Path,
119
+ audience: str,
120
+ ) -> list[Path]:
121
+ if run_dir is None:
122
+ return []
123
+ manifest_path = _latest_matching_file(
124
+ run_dir / "manifests",
125
+ "run-manifest-*.json",
126
+ )
127
+ manifest = _load_json(manifest_path) if manifest_path else {}
128
+ contract = manifest.get("agentContract")
129
+ duty_root_value = (
130
+ contract.get("dutyRootPath")
131
+ if isinstance(contract, dict)
132
+ else None
133
+ )
134
+ if not isinstance(duty_root_value, str) or not duty_root_value:
135
+ return []
136
+ duty_root = project_root / duty_root_value
137
+ files = [duty_root / "common.md", duty_root / f"{audience}.md"]
138
+ return [path for path in files if path.is_file()]
139
+
140
+
117
141
  def _is_timestamped_legacy_artifact(path: Path) -> bool:
118
142
  return bool(TIMESTAMPED_ARTIFACT_RE.search(path.name))
119
143
 
@@ -275,12 +299,15 @@ def _lead_phase1_metric(
275
299
  files.append(team_state)
276
300
  mode = "legacy-five-file"
277
301
 
302
+ duty_files = _duty_contract_files(run_dir, project_root, "lead")
303
+ files.extend(duty_files)
278
304
  file_count, byte_count = _count_files(files)
279
305
  return {
280
306
  "mode": mode,
281
307
  "fileCount": file_count,
282
308
  "bytes": byte_count,
283
309
  "estimatedTokens": _estimate_tokens(files),
310
+ "dutyContractBytes": sum(_file_size(path) for path in duty_files),
284
311
  "files": [project_rel(path, project_root) for path in files if path.is_file()],
285
312
  }
286
313
 
@@ -293,6 +320,12 @@ def _analysis_worker_metric(
293
320
  ) -> dict:
294
321
  instruction_set = task_root / "instruction-set"
295
322
  full_contract_files = [instruction_set / name for name in INPUT_FILES]
323
+ duty_files = _duty_contract_files(
324
+ run_dir,
325
+ project_root,
326
+ "analysis-worker",
327
+ )
328
+ full_contract_files.extend(duty_files)
296
329
  default_preamble = (
297
330
  "implementation-worker-preamble.md"
298
331
  if task_type == "implementation"
@@ -309,7 +342,11 @@ def _analysis_worker_metric(
309
342
  legacy_packet = instruction_set / "task-packet.md"
310
343
  primary_packet = packet if packet.is_file() else legacy_packet
311
344
  has_packet = primary_packet.is_file()
312
- packet_files = [primary_packet, preamble, error_contract] if has_packet else []
345
+ packet_files = (
346
+ [primary_packet, preamble, error_contract, *duty_files]
347
+ if has_packet
348
+ else []
349
+ )
313
350
  packet_file_count, packet_byte_count = _count_files(packet_files)
314
351
  mode = "analysis-packet-primary" if packet.is_file() else "full-input-contract"
315
352
  byte_count = packet_byte_count if packet.is_file() else full_byte_count
@@ -324,6 +361,7 @@ def _analysis_worker_metric(
324
361
  "fileCount": file_count,
325
362
  "bytesPerWorker": byte_count,
326
363
  "estimatedTokensPerWorker": _estimate_tokens(current_files),
364
+ "dutyContractBytes": sum(_file_size(path) for path in duty_files),
327
365
  "legacyFullContractBytesPerWorker": full_byte_count,
328
366
  "legacyFullContractFileCount": full_file_count,
329
367
  "estimatedPacketModeBytesPerWorker": packet_byte_count,
@@ -382,6 +420,8 @@ def _report_writer_metric(run_dir: Path | None, task_root: Path, project_root: P
382
420
  "report-writer-prompt-preamble.md",
383
421
  )
384
422
  files.extend((preamble, error_contract))
423
+ duty_files = _duty_contract_files(run_dir, project_root, "report-writer")
424
+ files.extend(duty_files)
385
425
  if run_dir is not None:
386
426
  files.extend(_current_seq_worker_results(run_dir))
387
427
  convergence = _latest_matching_file(
@@ -395,6 +435,7 @@ def _report_writer_metric(run_dir: Path | None, task_root: Path, project_root: P
395
435
  "fileCount": file_count,
396
436
  "bytes": byte_count,
397
437
  "estimatedTokens": _estimate_tokens(files),
438
+ "dutyContractBytes": sum(_file_size(path) for path in duty_files),
398
439
  "promptPreamblePath": str(preamble),
399
440
  "workerErrorContractPath": str(error_contract),
400
441
  "files": [project_rel(path, project_root) for path in files if path.is_file()],