okstra 0.224.0 → 0.225.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 (65) hide show
  1. package/dist/commands/execute/render-bundle.mjs +7 -5
  2. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  3. package/docs/cli.md +10 -2
  4. package/package.json +1 -1
  5. package/runtime/BUILD.json +2 -2
  6. package/runtime/bin/lib/okstra/cli.sh +5 -1
  7. package/runtime/bin/lib/okstra/globals.sh +1 -0
  8. package/runtime/bin/lib/okstra/usage.sh +4 -1
  9. package/runtime/bin/okstra-provider-exec.py +32 -16
  10. package/runtime/bin/okstra.sh +1 -0
  11. package/runtime/prompts/duties/code-reviewer.json +5 -3
  12. package/runtime/prompts/wizard/prompts.ko.json +10 -0
  13. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +2 -0
  14. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +3 -1
  15. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +6 -0
  16. package/runtime/python/okstra_ctl/adapters/providers/zai/adapter.py +5 -1
  17. package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +25 -0
  18. package/runtime/python/okstra_ctl/code_review_coverage.py +213 -0
  19. package/runtime/python/okstra_ctl/code_review_target.py +31 -2
  20. package/runtime/python/okstra_ctl/coverage_census.py +2 -2
  21. package/runtime/python/okstra_ctl/dispatch_core.py +15 -0
  22. package/runtime/python/okstra_ctl/domain/worker_exec.py +6 -0
  23. package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +66 -0
  24. package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +5 -1
  25. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +1 -0
  26. package/runtime/python/okstra_ctl/phases/implementation_planning/round_completion.py +177 -0
  27. package/runtime/python/okstra_ctl/render.py +2 -0
  28. package/runtime/python/okstra_ctl/run.py +23 -2
  29. package/runtime/python/okstra_ctl/team.py +12 -18
  30. package/runtime/python/okstra_ctl/wizard/__init__.py +1 -0
  31. package/runtime/python/okstra_ctl/wizard/confirmation.py +2 -0
  32. package/runtime/python/okstra_ctl/wizard/ids.py +3 -0
  33. package/runtime/python/okstra_ctl/wizard/registry.py +11 -0
  34. package/runtime/python/okstra_ctl/wizard/render.py +1 -0
  35. package/runtime/python/okstra_ctl/wizard/state.py +3 -0
  36. package/runtime/python/okstra_ctl/wizard/steps_options.py +41 -1
  37. package/runtime/python/okstra_ctl/worker_dispatch.py +9 -0
  38. package/runtime/python/okstra_ctl/worker_request.py +12 -1
  39. package/runtime/schemas/execution-manifest-v2.schema.json +1 -0
  40. package/runtime/skills/_fragments/bash-invocation-rule.md +1 -1
  41. package/runtime/skills/_fragments/host-question-interface.md +1 -0
  42. package/runtime/skills/_fragments/host-runtime-selection.md +1 -0
  43. package/runtime/skills/_fragments/wizard-bash-invocation-rule.md +1 -1
  44. package/runtime/skills/_fragments/wizard-preflight.md +1 -1
  45. package/runtime/skills/_fragments/wizard-render-bundle.md +4 -4
  46. package/runtime/skills/okstra-brief-gen/SKILL.md +45 -47
  47. package/runtime/skills/okstra-brief-gen/references/reporter-confirmations.md +3 -3
  48. package/runtime/skills/okstra-brief-gen/references/tracker-recursion.md +1 -1
  49. package/runtime/skills/okstra-chat/SKILL.md +1 -1
  50. package/runtime/skills/okstra-code-review/SKILL.md +26 -15
  51. package/runtime/skills/okstra-code-review/references/census-rules.md +38 -2
  52. package/runtime/skills/okstra-code-review/references/review-calibration.md +39 -15
  53. package/runtime/skills/okstra-container-build/SKILL.md +6 -2
  54. package/runtime/skills/okstra-discussion/SKILL.md +6 -6
  55. package/runtime/skills/okstra-inspect/SKILL.md +5 -3
  56. package/runtime/skills/okstra-manager/SKILL.md +8 -4
  57. package/runtime/skills/okstra-memory/SKILL.md +1 -1
  58. package/runtime/skills/okstra-pr-gen/SKILL.md +10 -6
  59. package/runtime/skills/okstra-rollup/SKILL.md +6 -2
  60. package/runtime/skills/okstra-run/SKILL.md +8 -7
  61. package/runtime/skills/okstra-schedule-gen/SKILL.md +13 -6
  62. package/runtime/skills/okstra-setup/SKILL.md +8 -4
  63. package/runtime/skills/okstra-setup/references/project-config.md +3 -3
  64. package/runtime/skills/okstra-usage/SKILL.md +5 -1
  65. package/runtime/skills/okstra-user-response/SKILL.md +7 -3
@@ -130,6 +130,7 @@ from .domain.host import (
130
130
  ProviderUnavailable,
131
131
  )
132
132
  from .domain.provider import ServedModelAttestation
133
+ from .domain.worker_exec import REASONING_EFFORTS
133
134
  from .execution_identity import (
134
135
  ExecutionManifest,
135
136
  ParticipantAssignment,
@@ -634,6 +635,9 @@ class PrepareInputs:
634
635
  # discussion 전용: 링크·짧은 설명·이 task 의 key. brief 자리는 prepare 가
635
636
  # 이 값으로 만든 입력 문서가 채운다.
636
637
  discussion_start: str = ""
638
+ # 이 run 이 띄우는 모든 워커 CLI 의 추론 강도(`REASONING_EFFORTS`). 빈 값이면
639
+ # 각 CLI 가 자기 설정을 따른다.
640
+ reasoning_effort: str = ""
637
641
 
638
642
 
639
643
  @dataclass
@@ -4257,6 +4261,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
4257
4261
  "OKSTRA_PLAN_VERIFICATION": (
4258
4262
  "false" if not inp.plan_verification_enabled else ""
4259
4263
  ),
4264
+ "REASONING_EFFORT": inp.reasoning_effort,
4260
4265
  "ANALYSIS_SOURCE_COMMIT": analysis_source_commit,
4261
4266
  "ANALYSIS_TARGET_JSON": json.dumps(resolved_target, ensure_ascii=False),
4262
4267
  "EVIDENCE_INPUTS_JSON": json.dumps(
@@ -4687,6 +4692,10 @@ def build_prepare_argument_parser():
4687
4692
  ),
4688
4693
  )
4689
4694
  p.add_argument("--render-only", action="store_true", dest="render_only")
4695
+ p.add_argument(
4696
+ "--summary-only", action="store_true",
4697
+ help="Omit the lead prompt body from render-only output; still prepare all artifacts.",
4698
+ )
4690
4699
  p.add_argument(
4691
4700
  "--terminal-backend",
4692
4701
  dest="terminal_backend",
@@ -4698,6 +4707,16 @@ def build_prepare_argument_parser():
4698
4707
  "않고 멈춘다."
4699
4708
  ),
4700
4709
  )
4710
+ p.add_argument(
4711
+ "--reasoning-effort",
4712
+ default="",
4713
+ choices=["", *REASONING_EFFORTS],
4714
+ dest="reasoning_effort",
4715
+ help=(
4716
+ "Reasoning effort every worker CLI in this run is started with. "
4717
+ "Empty leaves each CLI on its own configuration."
4718
+ ),
4719
+ )
4701
4720
  p.add_argument(
4702
4721
  "--no-plan-verification",
4703
4722
  action="store_false",
@@ -4897,6 +4916,7 @@ def main(argv: list[str]) -> int:
4897
4916
  plan_verification_enabled=args.plan_verification_enabled,
4898
4917
  fix_cycle=args.fix_cycle,
4899
4918
  discussion_start=args.discussion_start,
4919
+ reasoning_effort=args.reasoning_effort,
4900
4920
  )
4901
4921
  try:
4902
4922
  out = prepare_task_bundle(inputs)
@@ -4919,8 +4939,9 @@ def main(argv: list[str]) -> int:
4919
4939
  stages_csv = ",".join(str(s) for s in cr["active_stages"])
4920
4940
  print(f"okstra concurrent-run stages: {stages_csv}")
4921
4941
  if inputs.render_only:
4922
- print()
4923
- print(out.prompt_text, end="")
4942
+ if not args.summary_only:
4943
+ print()
4944
+ print(out.prompt_text, end="")
4924
4945
  else:
4925
4946
  print(f"okstra current run dir: {ctx['RUN_DIR']}")
4926
4947
  print(f"final report path: {ctx['FINAL_REPORT_PATH']}")
@@ -35,6 +35,7 @@ from .dispatch_core import (
35
35
  DispatchPlan,
36
36
  await_dispatches,
37
37
  dispatch_plan,
38
+ existing_dispatch_plan as _plan_for_existing,
38
39
  )
39
40
  from .dispatch_checkpoints import (
40
41
  record_collect_checkpoints,
@@ -257,6 +258,11 @@ def _await(args) -> int:
257
258
  timeout_seconds=args.timeout_seconds,
258
259
  heartbeat_seconds=0 if args.json else args.heartbeat_seconds,
259
260
  )
261
+ from .phases.implementation_planning.round_completion import complete_ready_plan_round
262
+
263
+ completion = complete_ready_plan_round(plan) if code == 0 else None
264
+ if completion and completion["status"] == "failed":
265
+ code = 2
260
266
  if should_record:
261
267
  exit_state = _load_json(plan.team_state_path, "team-state")
262
268
  lines.extend(record_collect_checkpoints(
@@ -272,11 +278,16 @@ def _await(args) -> int:
272
278
  if args.json:
273
279
  payload = _await_payload(plan, code == 0)
274
280
  payload["progressLines"] = lines
281
+ if completion:
282
+ payload["planVerification"] = completion
275
283
  _print_json(payload)
276
284
  else:
277
285
  for line in lines:
278
286
  print(line)
279
- print("ALL_WORKERS_DONE" if code == 0 else "POLL_TIMEOUT")
287
+ if completion:
288
+ print(json.dumps({"planVerification": completion}, ensure_ascii=False))
289
+ print("PLAN_VERIFICATION_FAILED" if completion and completion["status"] == "failed"
290
+ else "ALL_WORKERS_DONE" if code == 0 else "POLL_TIMEOUT")
280
291
  return code
281
292
 
282
293
 
@@ -402,23 +413,6 @@ def _observe_lead_session_from_manifest(
402
413
  observe_lead_session(project_root, team_state_path)
403
414
 
404
415
 
405
- def _plan_for_existing(
406
- project_root: Path, workspace_root: Path, run_manifest_path: Path, manifest: Mapping[str, Any]
407
- ) -> DispatchPlan:
408
- root = project_root.resolve()
409
- manifest_path = _resolve_project_path(root, str(run_manifest_path))
410
- return DispatchPlan(
411
- project_root=root,
412
- workspace_root=workspace_root.resolve(),
413
- manifest_path=manifest_path,
414
- team_state_path=_resolve_project_path(root, _require_string(manifest, "teamStatePath")),
415
- lead_events_path=_resolve_project_path(root, _require_string(manifest, "leadEventsPath")),
416
- manifest=manifest,
417
- jobs=(),
418
- default_backend=_manifest_backend(manifest),
419
- )
420
-
421
-
422
416
  def _await_payload(plan: DispatchPlan, completed: bool) -> dict[str, Any]:
423
417
  team_state = _load_json(plan.team_state_path, "team-state")
424
418
  dispatches = [d for d in team_state.get("workerDispatches", []) if isinstance(d, dict)]
@@ -68,6 +68,7 @@ from .ids import (
68
68
  S_HANDOFF_STAGE_PICK,
69
69
  S_PROJECT_EVIDENCE,
70
70
  S_PROJECT_EVIDENCE_PICK,
71
+ S_REASONING_EFFORT,
71
72
  S_PR_TEMPLATE,
72
73
  S_PR_TEMPLATE_PICK,
73
74
  S_PR_TEMPLATE_SCOPE,
@@ -333,6 +333,8 @@ def confirmation_block(state: WizardState) -> str:
333
333
  lines.append(f" pr-template : {state.pr_template_path} ({state.pr_template_scope or 'once'})")
334
334
  if state.fix_cycle:
335
335
  lines.append(f" fix-cycle : {state.fix_cycle}")
336
+ if state.reasoning_effort:
337
+ lines.append(f" reasoning : {state.reasoning_effort}")
336
338
  lines.append(_msg(
337
339
  state.workspace_root, "confirmation", "provider_data_scope",
338
340
  project_root=state.project_root,
@@ -238,6 +238,9 @@ S_PR_TEMPLATE_SCOPE = "pr_template_scope"
238
238
  S_FIX_CYCLE_CONFIRM = "fix_cycle_confirm"
239
239
 
240
240
 
241
+ S_REASONING_EFFORT = "reasoning_effort"
242
+
243
+
241
244
  S_CONFIRM = "confirm"
242
245
 
243
246
 
@@ -58,6 +58,7 @@ from .ids import (
58
58
  S_PROJECT_EVIDENCE_PICK,
59
59
  S_PR_TEMPLATE,
60
60
  S_PR_TEMPLATE_PICK,
61
+ S_REASONING_EFFORT,
61
62
  S_PR_TEMPLATE_SCOPE,
62
63
  S_RELATED_TASKS,
63
64
  S_RELATED_TASKS_PICK,
@@ -169,6 +170,9 @@ from .steps_plan import (
169
170
  _submit_stage_pick,
170
171
  )
171
172
  from .steps_options import (
173
+ _build_reasoning_effort,
174
+ _reasoning_effort_required,
175
+ _submit_reasoning_effort,
172
176
  _report_language_required,
173
177
  _build_report_language,
174
178
  _submit_report_language,
@@ -516,6 +520,13 @@ STEPS: list[Step] = [
516
520
  and S_DIRECTIVE not in s.answered),
517
521
  build=_build_directive, submit=_submit_directive,
518
522
  owns=("directive", "directive_pending_text")),
523
+ # 역할 화면과 directive 뒤 — 이 run 이 띄울 워커가 정해진 다음에 묻는다.
524
+ Step(S_REASONING_EFFORT,
525
+ applies=lambda s: (S_REASONING_EFFORT not in s.answered
526
+ and not s.directive_pending_text
527
+ and _reasoning_effort_required(s)),
528
+ build=_build_reasoning_effort, submit=_submit_reasoning_effort,
529
+ owns=("reasoning_effort",)),
519
530
  Step(S_RELATED_TASKS_PICK,
520
531
  applies=lambda s: (_identity_ready(s)
521
532
  and S_RELATED_TASKS_PICK not in s.answered),
@@ -118,6 +118,7 @@ def render_args(state: WizardState) -> dict[str, Any]:
118
118
  ),
119
119
  "pr-template-path": pr_template,
120
120
  "fix-cycle": state.fix_cycle,
121
+ "reasoning-effort": state.reasoning_effort,
121
122
  "host-session-context-json": serialize_host_session_context(
122
123
  _host_session_context(state)
123
124
  ),
@@ -132,6 +132,8 @@ class WizardState:
132
132
  # confirm / edit
133
133
  # "" | "yes" | "no" — done(release-handoff) task 재진입의 fix-cycle 기록 여부
134
134
  fix_cycle: str = ""
135
+ # `REASONING_EFFORTS` 중 하나. 워커가 정해지기 전에는 비어 있다.
136
+ reasoning_effort: str = ""
135
137
  confirmed: Optional[bool] = None
136
138
  confirmation_stages: str = ""
137
139
  confirmation_prompt: str = ""
@@ -627,6 +629,7 @@ _FIELD_DEFAULTS: dict[str, Any] = {
627
629
  "pr_template_path": "", "pr_template_pending_text": False,
628
630
  "pr_template_scope": "",
629
631
  "fix_cycle": "",
632
+ "reasoning_effort": "",
630
633
  "confirmed": None, "edit_target": "", "confirmation_prompt": "", "confirmation_stages": "",
631
634
  "confirmation_scope": {}, "user_authorization": {},
632
635
  }
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  from typing import Callable, Optional
8
8
 
9
9
  from okstra_ctl.clarification_items import user_response_sidecars
10
+ from okstra_ctl.domain.worker_exec import REASONING_EFFORTS
10
11
  from okstra_ctl.manager_paths import MANAGER_CONTEXT_DIRECTIVE_PREFIX
11
12
  from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
12
13
  from okstra_ctl.report_language import is_language_tag
@@ -25,6 +26,7 @@ from .ids import (
25
26
  S_PR_TEMPLATE,
26
27
  S_PR_TEMPLATE_PICK,
27
28
  S_PR_TEMPLATE_SCOPE,
29
+ S_REASONING_EFFORT,
28
30
  S_RELATED_TASKS,
29
31
  S_RELATED_TASKS_PICK,
30
32
  S_REPORT_LANGUAGE,
@@ -33,7 +35,14 @@ from .ids import (
33
35
  _REUSE_LAST_TOKEN,
34
36
  _SIBLINGS_TOKEN,
35
37
  )
36
- from .state import Option, Prompt, WizardError, WizardState
38
+ from .state import (
39
+ Option,
40
+ Prompt,
41
+ WizardError,
42
+ WizardState,
43
+ _identity_ready,
44
+ _load_role_profile_for_state,
45
+ )
37
46
  from .prompts import _opt, _p, _pick_snippet
38
47
  from .sources import _latest_run_inputs, _require_file, _suggest_latest_final_report
39
48
 
@@ -428,3 +437,34 @@ def _submit_pr_template_scope(state: WizardState, value: str) -> Optional[str]:
428
437
  )
429
438
  state.pr_template_scope = value
430
439
  return f"pr-template-scope: {value}"
440
+
441
+
442
+ _RECOMMENDED_REASONING_EFFORT = "high"
443
+
444
+
445
+ def _reasoning_effort_required(state: WizardState) -> bool:
446
+ """워커를 띄우지 않는 프로필(release-handoff)에는 강도를 넘길 CLI 가 없다."""
447
+ return _identity_ready(state) and bool(_load_role_profile_for_state(state).roles)
448
+
449
+
450
+ def _build_reasoning_effort(state: WizardState) -> Prompt:
451
+ t = _p(state.workspace_root, "reasoning_effort")
452
+ others = [level for level in REASONING_EFFORTS if level != _RECOMMENDED_REASONING_EFFORT]
453
+ return Prompt(
454
+ step=S_REASONING_EFFORT, kind="pick", label=t["label"],
455
+ options=[
456
+ _opt(_RECOMMENDED_REASONING_EFFORT,
457
+ t["options"][_RECOMMENDED_REASONING_EFFORT], recommended=True),
458
+ *(_opt(level, t["options"][level]) for level in others),
459
+ ],
460
+ echo_template=t["echo_template"])
461
+
462
+
463
+ def _submit_reasoning_effort(state: WizardState, value: str) -> Optional[str]:
464
+ level = value.strip().lower()
465
+ if level not in REASONING_EFFORTS:
466
+ raise WizardError(
467
+ f"expected one of {', '.join(REASONING_EFFORTS)}, got: {value!r}"
468
+ )
469
+ state.reasoning_effort = level
470
+ return f"reasoning-effort: {level}"
@@ -45,10 +45,19 @@ def main(argv: Sequence[str] | None = None) -> int:
45
45
  _print_json(_backend_payload(backend_plan, dry_run=True))
46
46
  return 0
47
47
  code, lines = _dispatch_recording_checkpoints(backend_plan)
48
+ from .phases.implementation_planning.round_completion import complete_ready_plan_round
49
+ from .worker_prompt_policy import is_plan_verify_dispatch_kind
50
+
51
+ completion = complete_ready_plan_round(backend_plan) if code == 0 and any(
52
+ is_plan_verify_dispatch_kind(job.dispatch_kind) for job in backend_plan.jobs
53
+ ) else None
54
+ if completion and completion["status"] == "failed":
55
+ code = 2
48
56
  _print_json({
49
57
  **_backend_payload(backend_plan, dry_run=False),
50
58
  "exitCode": code,
51
59
  "progressLines": lines,
60
+ **({"planVerification": completion} if completion else {}),
52
61
  })
53
62
  return code
54
63
  except DispatchError as exc:
@@ -12,7 +12,11 @@ import os
12
12
  import subprocess
13
13
  from pathlib import Path
14
14
 
15
- from .domain.worker_exec import ExecutionPolicy, WorkerExecRequest
15
+ from .domain.worker_exec import (
16
+ REASONING_EFFORTS,
17
+ ExecutionPolicy,
18
+ WorkerExecRequest,
19
+ )
16
20
  from .domain.worker_role import role_spec
17
21
  from .write_policy import WriteEnforcement, WritePolicy
18
22
 
@@ -39,6 +43,7 @@ def build_request(
39
43
  session_id: str = "",
40
44
  write_policy: WritePolicy | None = None,
41
45
  write_enforcement: WriteEnforcement | None = None,
46
+ reasoning_effort: str = "",
42
47
  ) -> WorkerExecRequest:
43
48
  """One dispatch, with every value the strategies will not re-derive.
44
49
 
@@ -46,6 +51,11 @@ def build_request(
46
51
  strategies hand what they are given straight to a CLI, so an unresolved
47
52
  `..` or symlink would survive into the sandbox boundary itself.
48
53
  """
54
+ if reasoning_effort and reasoning_effort not in REASONING_EFFORTS:
55
+ raise ValueError(
56
+ f"reasoning effort must be one of {', '.join(REASONING_EFFORTS)}: "
57
+ f"{reasoning_effort!r}"
58
+ )
49
59
  root = project_root.resolve()
50
60
  worktree = worktree_path.resolve() if worktree_path is not None else None
51
61
  return WorkerExecRequest(
@@ -64,6 +74,7 @@ def build_request(
64
74
  ),
65
75
  idle_timeout_seconds=idle_timeout_seconds,
66
76
  session_id=session_id,
77
+ reasoning_effort=reasoning_effort,
67
78
  )
68
79
 
69
80
 
@@ -18,6 +18,7 @@
18
18
  "schemaVersion": {"const": "2.0"},
19
19
  "executionIdentityVersion": {"const": 2},
20
20
  "entryMode": {"enum": ["current-session", "new-session"]},
21
+ "reasoningEffort": {"enum": ["", "low", "medium", "high", "xhigh"]},
21
22
  "participantAssignments": {
22
23
  "type": "array",
23
24
  "items": {"$ref": "#/$defs/ParticipantAssignment"}
@@ -1 +1 @@
1
- Run one Bash tool call, starting with the literal token `okstra` (never wrapped in `if`/`eval`/`export`/`$(...)`/`VAR=...`/`||`/`&&`/`npx` — a non-literal leading token defeats the `Bash(okstra:*)` permission match):
1
+ Run one shell-command tool call, starting with the literal token `okstra` (never wrapped in `if`/`eval`/`export`/`$(...)`/`VAR=...`/`||`/`&&`/`npx` — literal command tokens keep execution permissions reviewable):
@@ -0,0 +1 @@
1
+ For authored questions, `prompt_user` means the current host's available question interface, not a literal tool name. Use the selected host relay when one is provided. Preserve every question, option, description, and answer; use native controls only when the live session permits them and the complete interaction fits its limits. Otherwise show complete numbered choices and accept the user's reply; collect free text through normal conversation. If a native control is refused, follow the relay's recovery mapping, or use text when no relay is provided. A delivery acknowledgement or preselected option is not an answer; wait for the user's actual response.
@@ -0,0 +1 @@
1
+ Resolve `<host-runtime>` from the launcher's `OKSTRA_RUNTIME_HOST` when present; otherwise use the registered host ID declared by the current harness. Do not infer the host from worker models, installed executables, or `PATH`. If neither source identifies the host, report that it is unknown instead of substituting another host. Keep the same resolved host when retrying against another project directory.
@@ -1 +1 @@
1
- **Bash invocation rule (permission-friendly)**: every Bash command in this skill begins with the literal token `okstra` (or another already-allowed binary) and passes literal argument values. Do not introduce shell variables (`$STATE_FILE`, `$ANSWER`, `$projectRoot`, ...), `$(...)` command substitution, or leading `VAR=...` assignments — any of those make the leading token non-literal, defeat the `Bash(okstra:*)` permission match, and force a confirmation prompt on every wizard call. When a prior tool call emitted a path or value, read it from the tool output and paste the literal string into the next command.
1
+ **Shell invocation rule (permission-friendly)**: every shell command in this skill begins with the literal token `okstra` (or another already-allowed binary) and passes literal argument values. Do not introduce shell variables (`$STATE_FILE`, `$ANSWER`, `$projectRoot`, ...), `$(...)` command substitution, or leading `VAR=...` assignments — any of those make the command harder to audit; apply the selected host adapter's execution-permission guidance. When a prior tool call emitted a path or value, read it from the tool output and paste the literal string into the next command.
@@ -6,7 +6,7 @@ Record the semantic functions that the live harness can actually perform. Always
6
6
 
7
7
  The host-native provider runs as the current lead session when its role is supported. Other selected providers run through their registered CLI wrappers. Claude remains only the model-policy default; it is not the process owner.
8
8
 
9
- Run one Bash tool call (Bash invocation rule from the top of this file applies):
9
+ Run one shell-command tool call (Shell invocation rule from the top of this file applies):
10
10
 
11
11
  ```bash
12
12
  okstra preflight --runtime <host-runtime>
@@ -29,9 +29,9 @@ okstra config set pr-template-path "<value>" --scope global
29
29
 
30
30
  If an action has an unknown `command`, `key`, or `scope`, stop and report the wizard output instead of inventing a command.
31
31
 
32
- Before rendering the next phase's bundle — and between worker rounds within a phase (reverify/critic/gapverify batches), after you have collected that round's results and token usage and before you dispatch the next round — close the panes of the dispatches that finished in the prior round so they do not accumulate: `okstra team reclaim --project-root <projectRoot> --run-manifest <RUN_MANIFEST_PATH>` closes them, records `phase-batch-cleanup panes=<n>` with the number it closed, and prints that `PROGRESS:` line last — emit it as printed and do not call `okstra lead-progress append` for it. The command reads each dispatch's recorded status, so an in-progress worker keeps its pane whichever moment you call it. It closes only the panes okstra opened and recorded — a pane the harness opened for its own teammate carries no recorded id and is not okstra's to close. `shutdown_request` alone only idles the agent and frees no pane, so it stays part of the run-end sequence for roster/token hygiene. A `cli-wrapper` run holds no pane at all and `team reclaim` refuses it, so record the checkpoint there with `okstra lead-progress append … --phase phase-batch-cleanup --field panes=0`.
32
+ Before rendering the next phase's bundle — and between worker rounds within a phase (reverify/critic/gapverify batches), after you have collected that round's results and token usage and before you dispatch the next round — close the panes of the dispatches that finished in the prior round so they do not accumulate: `okstra team reclaim --project-root <projectRoot> --run-manifest <RUN_MANIFEST_PATH>` closes them, records `phase-batch-cleanup panes=<n>` with the number it closed, and prints that `PROGRESS:` line last — emit it as printed and do not call `okstra lead-progress append` for it. The command reads each dispatch's recorded status, so an in-progress worker keeps its pane whichever moment you call it. It closes only the panes okstra opened and recorded — a pane the harness opened for its own teammate carries no recorded id and is not okstra's to close. After collecting results and token usage, apply the selected adapter's `shutdown_workers` operation only to confirmed-complete workers; preserve in-flight workers and the lead. A `cli-wrapper` run holds no pane at all and `team reclaim` refuses it, so record the checkpoint there with `okstra lead-progress append … --phase phase-batch-cleanup --field panes=0`.
33
33
 
34
- Before you ask the user for any approval, clarification, or decision after workers have been dispatched, run `okstra team reclaim … --gate` first: it closes the finished panes and prints `PROGRESS: phase-gate-cleanup panes=<n>` for you to emit, without recording a batch cleanup. Then `TaskStop` each completed worker. A `TaskStop` by itself idles the task but leaves the pane open — the `team reclaim` call is what closes it. This keeps a user gate from being shown while finished worker panes remain; in-progress dispatches keep their panes. Then follow `prompts/lead/okstra-lead-contract.md` "User confirmation before an approval blocker": read cited plan items, worker findings, and files before asking, and ask in the user's language with each option's outcome.
34
+ Before you ask the user for any approval, clarification, or decision after workers have been dispatched, run `okstra team reclaim … --gate` first: it closes the finished panes and prints `PROGRESS: phase-gate-cleanup panes=<n>` for you to emit, without recording a batch cleanup. Then apply the selected adapter's `shutdown_workers` operation to confirmed-complete workers after collecting their results and token usage; preserve in-flight workers and the lead. This keeps a user gate from being shown while finished worker panes remain; in-progress dispatches keep their panes. Then follow `prompts/lead/okstra-lead-contract.md` "User confirmation before an approval blocker": read cited plan items, worker findings, and files before asking, and ask in the user's language with each option's outcome.
35
35
 
36
36
  Build the `okstra render-bundle` invocation from `outcome.renderArgv`, passing every token verbatim and in order (including empty strings — they are intentional `use phase default` markers).
37
37
 
@@ -44,13 +44,13 @@ Step 3's empty-answer and escaping rules apply verbatim: every flag in `renderAr
44
44
  `renderArgv` already contains exactly one `--lead-runtime <host-runtime>` pair. Do not add or replace it. Do not enumerate a fixed provider list in this skill because the wizard and role model pool own the ordered tokens.
45
45
 
46
46
  ```bash
47
- okstra render-bundle \
47
+ okstra render-bundle --summary-only \
48
48
  <outcome.renderArgv token 1> \
49
49
  <outcome.renderArgv token 2> \
50
50
  <each remaining outcome.renderArgv token in order>
51
51
  ```
52
52
 
53
- `render-bundle` auto-supplies `--workspace-root` and forces `--render-only`. Stdout prints `okstra task root:`, `okstra instruction-set:`, `okstra run manifest:`, and the full rendered lead prompt. Parse the labelled lines for `TASK_ROOT`, `INSTRUCTION_SET_PATH`, and `RUN_MANIFEST_PATH`. Also watch for an optional `okstra concurrent-run stages:` label line — present only when a concurrent run is detected (see "Concurrent-run detection branch" below).
53
+ `render-bundle` auto-supplies `--workspace-root` and forces `--render-only`. Stdout prints `okstra task root:`, `okstra instruction-set:`, `okstra run manifest:`, and other preparation status. `--summary-only` omits the lead prompt body from stdout; Step 6 reads the verified saved prompt from the manifest path. Parse the labelled lines for `TASK_ROOT`, `INSTRUCTION_SET_PATH`, and `RUN_MANIFEST_PATH`. Also watch for an optional `okstra concurrent-run stages:` label line — present only when a concurrent run is detected (see "Concurrent-run detection branch" below).
54
54
 
55
55
  Before acting as the lead, read `resources.leadPromptMetadataPath` from the run
56
56
  manifest and execute the host-native specification-link gate: