okstra 0.191.1 → 0.192.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 (35) hide show
  1. package/docs/architecture.md +2 -2
  2. package/docs/cli.md +2 -1
  3. package/docs/project-structure-overview.md +3 -1
  4. package/docs/task-process/README.md +2 -2
  5. package/docs/task-process/common-flow.md +4 -5
  6. package/package.json +1 -1
  7. package/runtime/BUILD.json +2 -2
  8. package/runtime/agents/workers/translator-worker.md +1 -1
  9. package/runtime/prompts/launch.template.md +1 -1
  10. package/runtime/prompts/lead/convergence.md +7 -3
  11. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  12. package/runtime/prompts/lead/report-writer.md +12 -9
  13. package/runtime/prompts/wizard/prompts.ko.json +52 -23
  14. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +2 -0
  15. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +4 -0
  16. package/runtime/python/okstra_ctl/convergence.py +63 -1
  17. package/runtime/python/okstra_ctl/convergence_reverify_prompt.py +231 -0
  18. package/runtime/python/okstra_ctl/dispatch_core.py +66 -22
  19. package/runtime/python/okstra_ctl/dispatch_state.py +24 -0
  20. package/runtime/python/okstra_ctl/next_phase.py +18 -8
  21. package/runtime/python/okstra_ctl/plan_items.py +6 -4
  22. package/runtime/python/okstra_ctl/report_finalize.py +57 -10
  23. package/runtime/python/okstra_ctl/report_narrative.py +33 -0
  24. package/runtime/python/okstra_ctl/report_synthesis_packet.py +3 -0
  25. package/runtime/python/okstra_ctl/report_translation_dispatch.py +300 -0
  26. package/runtime/python/okstra_ctl/verdict_blocks.py +37 -7
  27. package/runtime/python/okstra_ctl/wizard/confirmation.py +13 -1
  28. package/runtime/python/okstra_ctl/wizard/engine.py +16 -2
  29. package/runtime/python/okstra_ctl/wizard/registry.py +11 -2
  30. package/runtime/python/okstra_ctl/wizard/roles.py +364 -361
  31. package/runtime/python/okstra_ctl/wizard/state.py +39 -27
  32. package/runtime/python/okstra_ctl/wizard/steps_identity.py +50 -8
  33. package/runtime/python/okstra_ctl/wizard/steps_roles.py +1 -0
  34. package/runtime/skills/okstra-run/SKILL.md +5 -9
  35. package/runtime/validators/validate-run.py +2 -2
@@ -452,7 +452,7 @@ The fourth column is the `workflow.nextRecommendedPhase` pointer Phase 7 leaves
452
452
  |---|---|---|---|---|
453
453
  | `requirements-discovery` | Classify the request as bugfix, feature, refactor, ops, or improvement, then route it to a safe next phase | work category, routing decision, missing-input list, clarification requests | from `requirementsDiscovery.routing.nextTaskType`: `ready` at `error-analysis` or `implementation-option-selection`; `pending` when the run settles on neither | No |
454
454
  | `error-analysis` | Analyze the symptoms, causes, and reproduction gaps of a reported error/incident based on evidence | symptom/trigger summary, root-cause hypotheses, reproduction gap, validation path | from `errorAnalysis.routing.nextTaskType`: `ready` at `implementation-option-selection` after a credible cause, or at `error-analysis` for continued investigation | No |
455
- | `implementation-option-selection` | Compare or validate implementation directions before detailed planning | up to three ranked directions, per-direction `coveragePercent` and `scopePrecisionPercent`, rejected-candidate audit, separate `DIRECTION SELECTION` response | from the `implementationOptionSelection.routing` string enum: `ready` at `implementation-planning` once a direction is confirmed, `pending` on `pending-direction-selection`, `blocked` on `blocked` | No (strictly read-only; source edits, builds, tests, migrations, and deploys are prohibited) |
455
+ | `implementation-option-selection` | Compare or validate implementation directions before detailed planning | up to three ranked directions, per-direction `coveragePercent` and `scopePrecisionPercent`, rejected-candidate audit, separate `DIRECTION SELECTION` response | from the `implementationOptionSelection.routing` string enum: `ready` at `implementation-planning` both for a confirmed direction and for `pending-direction-selection` with ranked candidates (the planning wizard's `selected_direction_pick` takes the choice; the rationale names the candidates), `pending` when no candidate was ranked, `blocked` on `blocked` | No (strictly read-only; source edits, builds, tests, migrations, and deploys are prohibited) |
456
456
  | `implementation-planning` | Expand one selected direction into an executable plan without changing its mechanism or architecture boundary | selected-direction snapshot/reference, direction realization, affected-file list, Stage Map, validation/rollback, exact plan coverage, YAML frontmatter `approved: false`, **§5.5.9 Plan Body Verification**. Existing plans without `planningContract: selected-direction` retain the legacy option-candidate and `implementation-option:` contract | from `implementationPlanning.outcome`: `ready` at `implementation` on `plan-ready` or on a candidate-comparison plan with no `outcome` (the plan still needs its separate approval before that run starts), `ready` at `implementation-option-selection` on `direction-invalidated` | No |
457
457
  | `implementation` | Modify source code according to the approved `implementation-planning` final report. **One run executes exactly one stage** (selected with `--stage <auto\|N>`) | commit list, diff summary, out-of-plan edits block, validation/TDD evidence, rollback verification, verifier results (Antigravity/Codex/Claude), `carry/stage-<N>.json` evidence sidecar | from `implementation.routingRecommendation.target`: `ready` at that phase — `final-verification` on a clean stage, otherwise `error-analysis`, `implementation-planning`, or `implementation` | Yes (limited to the approved plan's file list; `git push`/publish/deploy/real migration prohibited) |
458
458
  | `final-verification` | Check completed work for residual defects and regression risk, then make a release judgment | acceptance verdict, residual risk, follow-up routing (`error-analysis`/`implementation-option-selection`/`implementation-planning`/`release-handoff`) | from `finalVerification.routingRecommendation.target`: `ready` at that value — `release-handoff` only on an `accepted` verdict, otherwise the phase owning the defect (cause, selected direction, or detailed plan). `release-handoff(stage-group)` is a scope qualifier on the same phase, so it projects to `release-handoff`. `done` becomes `terminal` | No (read-only tests only) |
@@ -863,7 +863,7 @@ The manifest-provided `lead-events-*.jsonl` file is the canonical record for str
863
863
 
864
864
  `activityContractVersion: 1` is an interpretation version for new `implementation-planning` artifacts. A run without that field remains a historical run and does not require activity events or an `agentActivity[]` projection.
865
865
 
866
- The shared `okstra report-finalize` entrypoint reads canonical activity before translation source checking. Under contract v3 its in-process `project-activity` step assembles the report, filtering events by run identity and validating activity ID order before `agentActivity[]` is published. A schema-invalid composed record is still published so `validate-run` can scan it; the step itself fails. A historical v2 manifest retains the in-place projection path. A failed Phase 7 step does not skip later checks: `validate-run` still runs, and only `record-group-memory` and `teardown-stages` are skipped. For a non-English report, the lead runs `token-usage`, `project-activity`, and `check-source` before translator dispatch, then resumes at `render-views` after the translation sidecar exists. Conformance compares the resulting `agentActivity[]` IDs, order, and core fields with the canonical events for every lead host.
866
+ The shared `okstra report-finalize` entrypoint reads canonical activity before translation source checking. Under contract v3 its in-process `project-activity` step assembles the report, filtering events by run identity and validating activity ID order before `agentActivity[]` is published. A schema-invalid composed record is still published so `validate-run` can scan it; the step itself fails. A historical v2 manifest retains the in-place projection path. A failed Phase 7 step does not skip later checks: `validate-run` still runs, and only `record-group-memory` and `teardown-stages` are skipped. For a non-English report, the `translate` step between `check-source` and `render-views` materializes and dispatches the translator worker itself (`okstra_ctl.report_translation_dispatch`) and requires the `*.i18n.<lang>.json` sidecar; the lead no longer splits the sequence around a manual dispatch. Conformance compares the resulting `agentActivity[]` IDs, order, and core fields with the canonical events for every lead host.
867
867
 
868
868
  Approval blockers use `open`, `answered`, `resolved`, and `obsolete`. `open` blocks until the user judges. `answered` with `accept-risk` / `select` / `answer` does not block approval or the next phase; the DISAGREE votes stay on the plan item as evidence. A response sidecar with a proceeding disposition unblocks the same way. `request-revision` and `reject` still withhold the next phase until this report's `supersessionLedger` records that the answer was incorporated (`superseded` or `no-dependent-statement`).
869
869
 
package/docs/cli.md CHANGED
@@ -790,6 +790,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
790
790
  | `okstra convergence collect-results --plan <round-plan.json> --mode <adversarial\|collaborative> --result <worker>=<path>… --run-manifest <run-manifest.json> --output <round-results.json>` | Read one round's worker responses into the `apply-round --results` shape. `--mode` picks the verdict vocabulary — the adversarial prompt answers `REFUTED` / `SURVIVES` / `SURVIVES-WITH-CAVEAT` / `UNVERIFIABLE`, which this maps to `disagree` / `agree` / `supplement` / `unverifiable`, and copies `**Basis**` into `disagreeBasis`. `--dispatch` supplies the terminal status and duration, which live in the dispatch rather than the response; a worker that never returned gets a `--dispatch` and no `--result`. Exits 2 on a dispatched finding with no verdict, a verdict for a finding the plan did not dispatch to that worker, a planned worker with no recorded outcome, or a vote with no explanation |
791
791
  | `okstra convergence apply-round --work-state <path> --plan <path> --results <path>` | Validate one complete structured result set and atomically reduce it into working state |
792
792
  | `okstra convergence critic-prompt --run-manifest <path>` | Render the coverage-critic task instructions to stdout; the lead writes the output verbatim into the file the prompt materializer's `--instruction` takes. The body carries the run's Round 0 consolidated findings, one line per Phase 4 analyser (worker id, result path, its finding ids), the two mandates plus the `duplicateOf` declaration rule, and — on an implementation-planning re-run — an already-covered index of the prior report's requirement-coverage row ids, clarification row ids, and stage titles (ids and titles only, never body text). Emits the `**Prompt Delivery Mode:**` header and the `Primary analysis packet` line the critic dispatch contract requires, so no part of the critic body is hand-written. Exits 2 when the run has published no grouping yet, or when the manifest carries no `analysisPacketPath` |
793
+ | `okstra convergence reverify-prompt --run-manifest <path> --plan <round-plan.json> --worker <worker-id>` | Render one worker's reverify task instructions to stdout; the lead writes the output verbatim into the file the prompt materializer's `--instruction` takes. The body carries the round's mandate (adversarial or collaborative, from the grouping's `config`), the plan row's findings in plan order — each with its summary, origin worker, cited-evidence line, the origin worker's result file and `### <item-id>` section, and the origin audit sidecar the verifier is told it may open — and the response format the collector parses. Exits 2 when the plan dispatches nothing to that worker, names a finding the grouping lacks, or is not a `dispatch` plan |
793
794
  | `okstra convergence apply-critic-gaps --work-state <path> --results <path>` | Apply one verified coverage-critic batch after the main queue reaches a terminal state |
794
795
  | `okstra convergence finalize --work-state <path> --output <path>` | Materialize the terminal schema v1.3 convergence state |
795
796
  | `okstra convergence validate --state <path> --kind <working\|final>` | Validate replayable working state or a terminal final state |
@@ -846,7 +847,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
846
847
  | `okstra approval-decision <open\|resolve\|carry> --ledger <approval-decisions.json> …` | Write the lead-owned clarification and approval ledger. `open` validates classification-specific dispositions and complete option fields, `resolve` requires real `A-NNN` check references, and `carry` keeps prior resolved decisions outside the active clarification list. `carry --from-responses <instruction-set/clarification-response.md>` is the source of truth for an answer given in an earlier run: the bundle is task-level and cumulative, each response section names the report that posed the question, and `--clarification-id` repeats to carry several ids in one call. `carry --source-ledger` remains for a prior run's ledger that is still on disk and needs `--source-run-ref`. Prepare seeds `carriedDecisions[]` itself when it creates a run's ledger from a `--clarification-response` that names a report record — every row that record answered or resolved, plus rows its user-responses sidecars answered (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`) — so `carry` is for ids that record does not answer. |
847
848
  | `okstra design-snapshot --narrative <report-narrative.md> --output <design-preparation.json>` | Detect implementation-planning design surfaces and write the detector-owned snapshot consumed by final report assembly. |
848
849
  | `okstra plan-verify --narrative <report-narrative.md> --state <plan-body-verification.json>` | Recompute the plan-body gate from the convergence-owned state before `data.json` publication. `--report <historical-data.json>` remains the v2 reader. |
849
- | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run Phase 7 in the manifest's contract order. Contract v3 collects usage into team state, assembles all single-owner inputs into `data.json` once, then checks, renders, spawns follow-ups, validates, records the run's conclusion and the group's start order into the task-group's `group-context.md` (`record-group-memory`, creating the file when absent), and tears down eligible stage worktrees. Contract v2 retains its historical in-place projection sequence as a read-only compatibility path. A failed step still runs every later check through `validate-run`; `record-group-memory` and `teardown-stages` are skipped so a failed run neither hands an unvalidated conclusion to sibling tasks nor reclaims worktrees. The result carries `nextInGroup` (the first task in start order not yet started) and, for a terminal pointer, `nextCommand` closes on starting it from its brief. Reports each step and prints the ordered `--only` recovery tail from the earliest failure. This is the shared path for every lead adapter. |
850
+ | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run Phase 7 in the manifest's contract order. Contract v3 collects usage into team state, assembles all single-owner inputs into `data.json` once, checks the English source, translates (`translate`: for a non-English `reportLanguage` it materializes and dispatches the translator worker unless the `*.i18n.<lang>.json` sidecar already exists, and fails when the worker leaves none), then renders with that sidecar overlaid, spawns follow-ups, validates, records the run's conclusion and the group's start order into the task-group's `group-context.md` (`record-group-memory`, creating the file when absent), and tears down eligible stage worktrees. Contract v2 retains its historical in-place projection sequence as a read-only compatibility path. A failed step still runs every later check through `validate-run`; `record-group-memory` and `teardown-stages` are skipped so a failed run neither hands an unvalidated conclusion to sibling tasks nor reclaims worktrees. The result carries `nextInGroup` (the first task in start order not yet started) and, for a terminal pointer, `nextCommand` closes on starting it from its brief. Reports each step and prints the ordered `--only` recovery tail from the earliest failure. This is the shared path for every lead adapter. |
850
851
  | `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly, and schema v3 data uses the same always-generated, task-specific human HTML path. The full reading copy uses `templates/reports/final-report-v2.template.md` and is rendered on demand with `okstra render-final-report`. Passing the Markdown sibling locates the same data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity, Response ID parity, and that every in-page `href="#…"` lands on an element of the page. For a non-English report the command prints two counts: `translated N string(s) into <lang> (M left in English, K unresolved)` from the sidecar overlay, and `rendered R line(s) still in English on the <lang> page` from the written page itself — the second sees fields the extractor does not offer, so `M = 0` with `R > 0` means a reader-facing key is missing from `PROSE_KEYS`. |
851
852
  | `okstra design-prep <list\|show\|write>` | Review AI-prepared implementation design requests, inspect their effective confirmed response, or append a confirmed user/wizard response without editing the planning report |
852
853
  | `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. A `pick` with more choices than the host picker can display keeps `kind: "pick"` but adds `presentation: "numbered-text"`; render every option as a numbered Markdown list and submit the user's 1-based number, exact value, or exact label. Invalid, out-of-range, and ambiguous answers re-prompt without dropping choices. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
@@ -352,13 +352,15 @@ Important modules:
352
352
  | `convergence_engine.py` | pure `ConvergenceEngine` reducer — seeds Round 0 working state, plans roster-aware rounds, applies structured outcomes and one critic-gap batch, finalizes schema v1.3, and validates replayable state without dispatch or filesystem ownership |
353
353
  | `convergence_store.py`, `convergence_migration.py` | atomic JSON persistence plus legacy/new-engine seed decisions; valid terminal finals are reused, while invalid state requires byte-preserving archival before restart |
354
354
  | `convergence.py` | `okstra convergence` internal CLI orchestration for `seed`, `plan-round`, `apply-round`, `critic-prompt`, `apply-critic-gaps`, `finalize`, `validate`, and `example`; it composes the reducer, store, and migration policy without duplicating their decisions |
355
+ | `convergence_reverify_prompt.py` | renders one worker's reverify instruction body `okstra convergence reverify-prompt` prints — the round mandate, the plan row's findings with each origin worker's result file, item id, and audit sidecar (declared openable), and the collector's response format. Replaces the hand-written instruction whose abbreviated evidence line and `- Verdict:` format cost a round |
355
356
  | `convergence_critic_prompt.py` | renders the coverage-critic seed body `okstra convergence critic-prompt` prints — the Round 0 consolidated findings, one line per Phase 4 analyser (worker id, result path, its finding ids), the two mandates plus the `duplicateOf` rule, and, on an implementation-planning re-run, an already-covered index of the prior report's requirement-coverage row ids, clarification row ids, and stage titles. Ids and titles only; the prior report's body is never copied |
356
357
  | `plan_items.py`, `plan_items_cli.py` | deterministic extraction of the report-writer narrative `P-*` plan-item queue plus the `okstra plan-items extract` / `validate` / `seed` / `collect-verdicts` / `apply-verdicts` / `derivations` adapter; v2 data.json remains a read input |
357
358
  | `claim_reproduction.py` | reproduces a plan-body single-vote `fact` claim before it can block on one vote — runs the declared probe (`path-exists` / `path-absent` / `literal-present` / `literal-absent` / `citations-differ`) inside the resolved project root and returns `reproduced` / `not-reproduced` / `not-runnable`, which `plan-items apply-verdicts --run-manifest` writes into `reproductionResult` (always overwriting the worker-sent value so a verifier cannot score its own claim). A `judgement` claim, or a `fact` that does not reproduce, takes the quorum route |
358
359
  | `plan_derivations.py` | the supersession sweep `_common-contract.md` requires an author to do by hand — extracts the symbols, paths, and ids an answered clarification names and reports every plan string that mentions one. Advisory: it locates candidates and never judges which are now false |
359
360
  | `scope_provenance.py` | single source of truth for the scope-provenance grammar every phase-emitted requirement must declare, shared by `validators/validate-run.py` and `validators/validate_fanout.py` so the planning report and fan-out packets cannot drift |
360
361
  | `worker_artifact_paths.py` | canonical worker artifact path derivation (e.g. `audit_sidecar_rel` inserts `-audit-` after the first `-worker-` token), so dispatch and validation agree on non-canonical-path rejection |
361
- | `report_finalize.py` | Phase 7 post-report sequence **SSOT** runs `check-source` → `token-usage` `render-views` `spawn-followups` `validate-run` `record-group-memory` `teardown-stages` in that load-bearing order. A non-zero exit still runs every later check through `validate-run` and names the earliest failure; `record-group-memory` (this run's conclusion into the task-group's `group-context.md`, plus `nextInGroup` for the closeout) and `teardown-stages` are skipped when any earlier step failed. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
362
+ | `report_translation_dispatch.py` | Phase 7 `translate` stepfor a non-English `reportLanguage` and no `*.i18n.<lang>.json` sidecar, reuses or materializes this run's translator reservation (`agent-prompt materialize --audience translator` in-process, instruction file under `state/`), runs the CLI-wrapper dispatch, and succeeds only when the sidecar exists afterwards. Replaces the manual lead sequence that was skipped in practice |
363
+ | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `check-source` → `translate` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` → `record-group-memory` → `teardown-stages` in that load-bearing order. A non-zero exit still runs every later check through `validate-run` and names the earliest failure; `record-group-memory` (this run's conclusion into the task-group's `group-context.md`, plus `nextInGroup` for the closeout) and `teardown-stages` are skipped when any earlier step failed. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
362
364
  | `wrapper_status.py` | worker wrapper status sidecar reader — the host-side reader of the sidecar `worker_runner.py` writes. `is_terminal` is the one question it answers for the dispatch record and the pane reclaim: does `stage` read `exited` |
363
365
  | `worker_runner.py` | runs one worker CLI and records what happened — shared by every provider entrypoint. Owns the `selectors` pump over the child's streams, the stream-arrival idle watchdog (`killpg` on breach), the run-wide progress cap on the log copy, and the status sidecar's whole life. A run that dies after launch still closes its sidecar, so `worker_liveness` never reads a dead worker as running |
364
366
  | `session_transcript.py` | worker session transcript — one line per event (time, speaker, body) with a run-wide progress-line cap (`LOG_LINE_CAP`, elision notice) so a single-file dispatch's tool echo cannot dominate the project's `.okstra/` bytes; the fixed shape lets a later lead write share the same file |
@@ -34,7 +34,7 @@ flowchart TD
34
34
 
35
35
  `okstra-run` does not call `scripts/okstra.sh`. Instead it goes through `okstra wizard` and `okstra render-bundle` and converges on the same single Python entrypoint, `prepare_task_bundle()`.
36
36
 
37
- Launch selection is role slots and model refs, not a provider roster. The wizard asks role counts (`min..max`, default **recommended**), then `--role-model <role>=<provider>/<model>` per slot. current-session lead is this session and is listed on the confirmation summary. Roles with `min = 0` stay closed unless the user adds them. There is no provider multi-pick and no `Use defaults / Customize` fork for worker selection. `--workers` is compatibility-only. `lead` is a compatibility alias for `leader`. `executor` is a compatibility alias for `implementer`. New records write `leader` and `implementer`.
37
+ Launch selection is role slots and model refs, not a provider roster. The wizard asks one screen per static role: a checkbox of candidate models for a role that runs several instances (the number checked is the instance count, rendered as `--role-count <role>=<N>` plus one `--role-model <role>=<provider>/<model>` per checked model; the label states the profile range and recommended count), a single pick for a fixed single-instance role. current-session lead is this session and is listed on the confirmation summary. Roles with `min = 0` stay closed unless the user adds them. There is no provider multi-pick and no `Use defaults / Customize` fork for worker selection. `--workers` is compatibility-only. `lead` is a compatibility alias for `leader`. `executor` is a compatibility alias for `implementer`. New records write `leader` and `implementer`.
38
38
 
39
39
  ## 3. task-type documents
40
40
 
@@ -75,7 +75,7 @@ The last column is the `workflow.nextRecommendedPhase` pointer Phase 7 leaves be
75
75
  |---|---|---|---|---|
76
76
  | `requirements-discovery` | common questions only | profile/brief/base-ref exist | multi-worker analysis, convergence 1 round default | `ready` at `error-analysis` or `implementation-option-selection`; `pending` when neither is settled |
77
77
  | `error-analysis` | common questions only | profile/brief/base-ref exist | multi-worker analysis, convergence 2 rounds default | `ready` at `implementation-option-selection`, or at `error-analysis` while the investigation continues |
78
- | `implementation-option-selection` | comparison or preselected-validation context | stable brief IDs and at least three analysers | read-only candidate validation, exact coverage, separate direction confirmation | `ready` at `implementation-planning`, `pending` on `pending-direction-selection`, or `blocked` |
78
+ | `implementation-option-selection` | comparison or preselected-validation context | stable brief IDs and at least three analysers | read-only candidate validation, exact coverage, separate direction confirmation | `ready` at `implementation-planning` (a confirmed direction, or ranked candidates the planning wizard lets the user pick from), `pending` when no candidate was ranked, or `blocked` |
79
79
  | `implementation-planning` | selected-direction report for a new plan | selection report/sidecar/digest or same-task planning rerun | one-direction realization + Phase 6 plan-body verification | `ready` at `implementation` on approvable `plan-ready` (`awaitingApproval` until the user flips `approved`); `blocked` when the gate is blocking or a `Blocks=approval` row is open; `ready` at `implementation-option-selection` on `direction-invalidated` |
80
80
  | `implementation` | approved plan, stage multi-pick, executor | approved marker, Stage Lifecycle Snapshot, stage-key reservation, QA command deny-list | one run = one stage; executor writes in isolated stage worktree, verifiers read-only | `ready` at the stage report's `routingRecommendation.target` — `final-verification` on a clean stage |
81
81
  | `final-verification` | approved plan, stage pick (whole-task or single-stage) | `VERIFICATION_TARGET` resolved; whole-task auto integration/teardown or single-stage worktree reuse | whole-task may integrate stages first; analyser verification itself is read-only | `ready` at `release-handoff` on an `accepted` verdict, otherwise at the phase owning the defect; `terminal` on `done` |
@@ -58,7 +58,7 @@ stateDiagram-v2
58
58
  BaseRef --> ImplementationExtras: implementation only
59
59
  BaseRef --> LeaderSession: non-implementation
60
60
  ImplementationExtras --> LeaderSession
61
- LeaderSession --> RoleSlots: role-count then role-model
61
+ LeaderSession --> RoleSlots: one model screen per role
62
62
  RoleSlots --> OptionalInputs: directive / related / clarification
63
63
  OptionalInputs --> Confirm
64
64
  Confirm --> EditTarget: Edit
@@ -151,9 +151,8 @@ flowchart TD
151
151
  T[task-type selected] --> W{active worktree in registry?}
152
152
  W -->|yes| Reuse[reuse existing worktree<br/>base-ref prompt skipped]
153
153
  W -->|no| Base[ask base-ref<br/>validate with git rev-parse]
154
- Base --> C[role-count min..max<br/>omit uses recommended]
155
- Reuse --> C
156
- C --> M[role-model provider/model per slot]
154
+ Base --> M[one screen per static role<br/>checkbox: models checked = instances<br/>single pick: fixed single role]
155
+ Reuse --> M
157
156
  M --> O[directive / related / clarification]
158
157
  O --> Special{release-handoff?}
159
158
  Special -->|yes| PR[PR template override/scope]
@@ -161,7 +160,7 @@ flowchart TD
161
160
  PR --> Confirm
162
161
  ```
163
162
 
164
- Launch selection is role slots and model refs. The wizard does not show a provider roster multi-pick and does not fork on `Use defaults / Customize` for workers. Omitting `--role-count` keeps each static role at its profile **recommended** count within `min..max`. Duplicate model refs in the same role are rejected. `--workers` remains a CLI compatibility input only.
163
+ Launch selection is role slots and model refs. The wizard does not show a provider roster multi-pick and does not fork on `Use defaults / Customize` for workers. A multi-instance role is one checkbox screen whose checked models become the instances (the wizard renders `--role-count` from that number); on the CLI, omitting `--role-count` keeps each static role at its profile **recommended** count within `min..max`. Duplicate model refs in the same role are rejected. `--workers` remains a CLI compatibility input only.
165
164
 
166
165
  Worktree rules differ per phase. From `requirements-discovery` through `implementation-planning`, the task-key worktree is reused. `implementation` uses the task-key worktree as an anchor but does the actual execution isolated one stage at a time in a stage-key (`stage-<N>`) worktree and the `runs/implementation/stage-<N>/` deliverables. `final-verification --stage N` reuses that implementation stage worktree as a read target, and whole-task mode auto-integrates the stage commits into the task-key worktree and then builds the verification target. The Stage Lifecycle Snapshot is a read-side view that does not change this storage structure.
167
166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.191.1",
3
+ "version": "0.192.0",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.191.1",
3
- "builtAt": "2026-09-08T22:08:39.785Z",
2
+ "package": "0.192.0",
3
+ "builtAt": "2026-09-09T09:02:57.157Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -7,7 +7,7 @@ description: |
7
7
  Context: okstra finished Phase 6 with `meta.reportLanguage: "ko"` and is entering Phase 7.
8
8
  user: "okstra this task bundle"
9
9
  assistant: "Phase 7 — dispatching translator-worker to write the ko translation sidecar."
10
- <commentary>The okstra skill dispatches this agent before `okstra report-finalize` so `render-views` has a sidecar to overlay.</commentary>
10
+ <commentary>`okstra report-finalize` dispatches this agent in its `translate` step so `render-views` has a sidecar to overlay.</commentary>
11
11
  </example>
12
12
  color: cyan
13
13
  model: inherit
@@ -56,7 +56,7 @@ For every other task type:
56
56
  - Pointer `status: ready` → `/okstra-run` for that `phase`. Quote the pointer's `rationale` — that sentence is this run's report saying why that phase comes next, and it is the analysis the user asked for.
57
57
  - Pointer `status: terminal` → the lifecycle ends here. Say the task is finished and quote the pointer's `rationale`. Do not propose a run and do not send the user to `/okstra-inspect`: the decision is already made, so there is nothing to inspect. If this run registered follow-up tasks, name them and the command that starts one.
58
58
  - Pointer `status: blocked` → the pointer's `rationale` names what is in the way and what to run. Quote it and issue that command — `/okstra-user-response` for the `C-NNN` ids it lists, or `/okstra-run` for the phase it names.
59
- - Pointer `status: pending` with a `rationale` → the comparison finished and the user's own choice is what comes next. Quote the `rationale` and issue the command it names. Do not re-run the phase that just completed, and do not send the user to `/okstra-inspect`: a finished phase has nothing to inspect, and re-running it discards the result the user is being asked to choose from.
59
+ - Pointer `status: pending` with a `rationale` → the user's own input is what comes next. Quote the `rationale` and issue the command it names. Do not re-run the phase that just completed, and do not send the user to `/okstra-inspect`: a finished phase has nothing to inspect, and re-running it discards the result the user is being asked to act on.
60
60
  - Phase 7 `validate-run` failed → one line naming the blocking cause, then `/okstra-run` to re-run this phase. Only a failure matching the blocking allowlist (`okstra_ctl.blocking_checks`) reaches this row; every other finding was demoted to an advisory, printed as `validate-run: advisory — <finding>`, and the run passed. Name those advisories in one line and take the command from the matching pointer row above — an advisory is not a reason to re-run.
61
61
  - Otherwise → `/okstra-inspect status` for this task.
62
62
 
@@ -240,7 +240,9 @@ Every finding re-verification, coverage critic, acceptance critic, and critic-ve
240
240
 
241
241
  For every finding reverify row and critic-gap verification row, first write a
242
242
  call-specific task-instructions file under the current run's `state/`
243
- directory. For a v2 run, take `participantRef` and
243
+ directory for a finding reverify row, the verbatim output of `okstra
244
+ convergence reverify-prompt --run-manifest <run-manifest> --plan
245
+ <round-plan.json> --worker <workerId>`. For a v2 run, take `participantRef` and
244
246
  `sourceRoleExecutionRef` from the canonical round-plan dispatch row or the
245
247
  matching canonical working-state worker row. That stored reference is the
246
248
  selected source `RoleExecution` row's `roleExecutionRef`, not that row's
@@ -295,7 +297,7 @@ Call `await_workers(handles)` through the same adapter and apply the shared term
295
297
 
296
298
  **Enforced:** `verify_agent_invocation` in `scripts/okstra_ctl/agent/invocation.py` and `validate_reverify_prompt` in `scripts/okstra_ctl/worker_prompt_contract.py` validate generated delivery before dispatch.
297
299
 
298
- Use `okstra agent-prompt materialize` for every reverify prompt. Write only the assigned claims, evidence, questions, and the response format in the instruction file. The materializer generates path headers through `worker_prompt_headers`, the source Worktree from the active run, and the execution identity from the run manifest. It also generates the model, task type, and exact `workflow.forbiddenActions` before the instructions. These values are checked by `verify_agent_invocation` and `validate_reverify_prompt` before publication and dispatch.
300
+ Use `okstra agent-prompt materialize` for every reverify prompt. Render the instruction file with `okstra convergence reverify-prompt --run-manifest <run-manifest> --plan <round-plan.json> --worker <worker-id>` and write its output verbatim; it carries the round's mandate, every planned finding with the origin worker's result file, item id, and audit sidecar (which the verifier is told it may open), and the response format the collector parses. Do not hand-copy evidence or the response format: a hand-copied `**Cited evidence**` line carried part of the origin's citation, so the verifier judged the lead's transcription and refuted five claims as `burden-not-met`, and a hand-written `- Verdict:` format cost the same round (2026-09-09). **Enforced (rendering):** `okstra_ctl.convergence_reverify_prompt`. The materializer generates path headers through `worker_prompt_headers`, the source Worktree from the active run, and the execution identity from the run manifest. It also generates the model, task type, and exact `workflow.forbiddenActions` before the instructions. These values are checked by `verify_agent_invocation` and `validate_reverify_prompt` before publication and dispatch.
299
301
 
300
302
  The generated `**Project Root:**` owns .okstra artifacts; `**Worktree:**` names the source checkout. Use the prompt's `**Invocation metadata path:**` for its invocation metadata. The result passed as `--result` is the worker's own result and carries the canonical `-worker-` token used by `audit_sidecar_rel`. Reverify has one result path, so omit `--audit-source`. The materializer supplies the audit, errors, read scope, and provider-specific plain-file write instructions.
301
303
 
@@ -334,6 +336,8 @@ This is the single largest avoidable cost in `requirements-discovery`, `error-an
334
336
 
335
337
  ### Lightweight Re-verification Prompt
336
338
 
339
+ Rendered by `okstra convergence reverify-prompt` when `config.adversarial` is false; the block below is the reference shape, and the rendered body additionally carries each finding's `**Origin item**` and `**Origin audit sidecar**` lines.
340
+
337
341
  ```
338
342
  ## Instructions
339
343
 
@@ -374,7 +378,7 @@ For each finding, respond as:
374
378
 
375
379
  ### Adversarial Re-verification Prompt
376
380
 
377
- Used instead of the lightweight/full-reanalysis prompt when `config.adversarial == true`. The required anchor headers (§"Required reverify-prompt anchor headers") are identical. The `[Required reading]` clause is suppressed; only the cited-evidence paths of the items under attack are injected (see §"Adversarial Verification Mode" → Scoped full-reanalysis).
381
+ Used instead of the lightweight/full-reanalysis prompt when `config.adversarial == true`. Rendered by `okstra convergence reverify-prompt`; the block below is the reference shape, and the rendered body additionally carries each finding's `**Origin item**` and `**Origin audit sidecar**` lines. The required anchor headers (§"Required reverify-prompt anchor headers") are identical. The `[Required reading]` clause is suppressed; only the cited-evidence paths of the items under attack are injected (see §"Adversarial Verification Mode" → Scoped full-reanalysis).
378
382
 
379
383
  ```
380
384
  ## Instructions
@@ -465,7 +465,7 @@ The detailed persistence sequence lives in [report-writer](./report-writer.md).
465
465
  Order of operations:
466
466
 
467
467
  1. Run `okstra report-finalize ...`. Contract v3 collects usage, assembles the role-owned inputs into `data.json` once, checks the source, renders views, persists follow-ups, validates the run, records this run's conclusion into the task-group's `group-context.md` for the group's sibling tasks, and performs eligible teardown in order. When the task's pointer is terminal and the group has a task not yet started, the result's `nextInGroup` names it and `nextCommand` closes on starting it from its brief — the group's start order is the brief ordinal; the briefs' Related Task Graph edges are quoted as `waits for` and do not reorder it.
468
- 2. When `meta.reportLanguage` is not `en`, first run only `token-usage`, `project-activity`, and `check-source`. Dispatch the translator against that assembled record, then resume with only `render-views`, `spawn-followups`, `validate-run`, `record-group-memory`, and `teardown-stages` so assembly is not repeated.
468
+ 2. When `meta.reportLanguage` is not `en`, the same call dispatches the translator: its `translate` step (after `check-source`, before `render-views`) materializes the translator prompt when this run has no undispatched reservation, runs the CLI-wrapper dispatch, and requires the `*.i18n.<lang>.json` sidecar. Do not dispatch the translator by hand or split the sequence around it; if the step fails, clear the cause and resume with the `--only translate --only render-views …` hint the result prints (**Enforced:** `okstra_ctl.report_translation_dispatch`).
469
469
 
470
470
  Keep the assigned worker prompt history paths stable in `team-state`, `run-manifest`, and `task-manifest`. Do not rewrite prompt artifacts to `/tmp` or omit prompt metadata for attempted workers.
471
471
 
@@ -485,7 +485,7 @@ For every other task type:
485
485
  - Pointer `status: terminal` → the lifecycle ends here. Say the task is finished and quote the pointer's `rationale`. Do not propose a run and do not send the user to `/okstra-inspect`: the decision is already made, so there is nothing to inspect. If this run registered follow-up tasks, name them and the command that starts one.
486
486
  - Pointer `status: terminal` and the result carries `nextInGroup` → the task is finished and the task-group has a task not yet started: say this task is finished, then close on `/okstra-run` for `nextInGroup.briefId` from its `brief` path — the group's start order is the brief ordinal, and `nextCommand.note` already names the task and its brief.
487
487
  - Pointer `status: blocked` → the pointer's `rationale` names what is in the way and what to run. Quote it and issue that command — `/okstra-user-response` for the `C-NNN` ids it lists, or `/okstra-run` for the phase it names.
488
- - Pointer `status: pending` with a `rationale` → the comparison finished and the user's own choice is what comes next. Quote the `rationale` and issue the command it names. Do not re-run the phase that just completed, and do not send the user to `/okstra-inspect`: a finished phase has nothing to inspect, and re-running it discards the result the user is being asked to choose from.
488
+ - Pointer `status: pending` with a `rationale` → the user's own input is what comes next. Quote the `rationale` and issue the command it names. Do not re-run the phase that just completed, and do not send the user to `/okstra-inspect`: a finished phase has nothing to inspect, and re-running it discards the result the user is being asked to act on.
489
489
  - Phase 7 `validate-run` failed → one line naming the blocking cause, then `/okstra-run` to re-run this phase with the recorded sidecar. Only a failure matching the blocking allowlist (`okstra_ctl.blocking_checks`) reaches this row; every other finding was demoted to an advisory, printed as `validate-run: advisory — <finding>`, and the run passed. Name those advisories in one line and take the command from the matching pointer row above — an advisory is not a reason to re-run.
490
490
  - Otherwise → `/okstra-inspect status` for this task.
491
491
 
@@ -89,7 +89,7 @@ This section adds report-specific checks to [okstra-lead-contract](./okstra-lead
89
89
  4. When the check reports `mechanical: true` (every entry is a `replace` or `remove`), run `okstra agent-prompt apply-corrections` with the same arguments: okstra writes the corrected narrative to `reportNarrativePath` and records a `lead-correction-applied` activity row naming the ledger and its correction ids. No writer dispatch, `record-dispatch`, or `link-result` follows; the roster row's result already exists.
90
90
  5. Otherwise materialize the writer prompt with the same `--corrections <ledger>` under a new invocation id and prompt path (retire the first attempt's link with `reject-result` as [plan-body-verification](./plan-body-verification.md) describes). okstra renders `## Corrections` (each entry with its label path, current value, replacement or rule, schema constraint, and reason) and `## Output`; the instruction body carries only context.
91
91
 
92
- A report-writer materialization without `--corrections` whose narrative already exists and parses is refused before any prompt is written — free-form corrections cannot be checked before the writer runs, and four of six re-runs in the 2026-09-03 measurement were lead instructions that contradicted the authoring contract. Only a narrative whose structure does not parse (line grammar, an unknown top-level field) is re-authored, not corrected: that dispatch needs no ledger, and its body quotes the parser's message. Value defects — an id outside its pattern, a value outside its enum, a missing required field — leave the structure readable and are exactly what the ledger fixes; the a3 attempt of the 2026-09-03 run carried twenty `SC-` ids that assembly refused and was still a corrective base.
92
+ A report-writer materialization without `--corrections` whose narrative already exists and parses is refused before any prompt is written — free-form corrections cannot be checked before the writer runs, and four of six re-runs in the 2026-09-03 measurement were lead instructions that contradicted the authoring contract. Only a narrative whose structure does not parse (line grammar, an unknown top-level field) is re-authored, not corrected: that dispatch needs no ledger, and its body quotes the parser's message. A narrative that breaks the line grammar is not a produced artifact: the dispatcher settles that attempt as `required worker artifact is unusable: narrative does not parse: …` and retries it inside the same batch, so you see the parser's message at collection, not at Phase 7 assembly (**Enforced:** `okstra_ctl.dispatch_state.unusable_result_defect`, read by `missing_completion_paths` and the `team await` record path). The synthesis packet's Authoring Contract carries the line grammar itself (`report_narrative.NARRATIVE_GRAMMAR_INSTRUCTIONS`), so a writer that reads only the packet still sees it. Value defects — an id outside its pattern, a value outside its enum, a missing required field — leave the structure readable and are exactly what the ledger fixes; the a3 attempt of the 2026-09-03 run carried twenty `SC-` ids that assembly refused and was still a corrective base.
93
93
 
94
94
  **Enforced:** `_with_report_writer_sections` / `_refuse_free_form_correction` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`, `report_corrections.check_corrections`, `agent/prompt_cli/corrections.run_corrections_apply`; `tests/run/test_agent_prompt_corrections.py` and `tests/contract/test_report_writer_v3_contract.py` keep this procedure in the lead contract.
95
95
 
@@ -123,22 +123,25 @@ For historical schema-v1 Markdown only, the following heading table remains a re
123
123
 
124
124
  **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` is the order — `report-finalize` runs the steps from that tuple, so the sequence cannot be reordered by a caller. Running the steps by hand is what this rule forbids, and that path is not reachable through the CLI.
125
125
 
126
- Do not run the eight steps below manually. Invoke `okstra report-finalize`; contract 3.0 runs them in this order:
126
+ Do not run the nine steps below manually. Invoke `okstra report-finalize`; contract 3.0 runs them in this order:
127
127
 
128
128
  1. **`token-usage`** — collect usage into team state without touching the final record.
129
129
  2. **`project-activity`** — report assembly validates every owner input and publishes the final record once.
130
130
  3. **`check-source`** — verify the assembled English source.
131
- 4. **`render-views`** — render the Markdown reading copy and human HTML.
132
- 5. **`spawn-followups`** — materialize registered follow-up tasks.
133
- 6. **`validate-run`** — validate the record, views, run manifest, and team state.
134
- 7. **`record-group-memory`** — write this run's conclusion (headline, decisions, watch-outs, open follow-ups, record path, next phase) and the group's start order into the task-group's `group-context.md` okstra region, creating the file when the group has none; skipped, like teardown, when an earlier step failed. Sibling tasks read it as `## Task-Group Memory`.
135
- 8. **`teardown-stages`** — remove eligible stage worktrees after successful validation.
131
+ 4. **`translate`** — for a non-English `reportLanguage`, materialize and dispatch the translator worker and require its `*.i18n.<lang>.json` sidecar; a no-op for English or when the sidecar already exists.
132
+ 5. **`render-views`** — render the Markdown reading copy and human HTML, with the translation sidecar overlaid.
133
+ 6. **`spawn-followups`** — materialize registered follow-up tasks.
134
+ 7. **`validate-run`** — validate the record, views, run manifest, and team state.
135
+ 8. **`record-group-memory`** — write this run's conclusion (headline, decisions, watch-outs, open follow-ups, record path, next phase) and the group's start order into the task-group's `group-context.md` okstra region, creating the file when the group has none; skipped, like teardown, when an earlier step failed. Sibling tasks read it as `## Task-Group Memory`.
136
+ 9. **`teardown-stages`** — remove eligible stage worktrees after successful validation.
136
137
 
137
138
  After `report-finalize` returns, the lead — not the report writer — closes the run with the launch prompt's User closeout: one command the user can run now.
138
139
 
139
- ### Before `report-finalize`: the translation sidecar
140
+ ### The translation sidecar: the `translate` step
140
141
 
141
- Never dispatch the translator before report assembly and `check-source`. For a non-English human report, first run `report-finalize --only token-usage --only project-activity --only check-source --only render-views`; the extraction command refuses to build a work list from a non-English source. `render-views` belongs in that first call: the HTML view renders from the report body and never reads the translation sidecar, so rendering it early costs one rerun and means a translator that never starts does not also cost the run its only human-readable artifact. Then dispatch the translator worker with `okstra agent-prompt materialize --audience translator --worker-id translator --dispatch-kind translator --assignment-ref translator --result <run>/worker-results/translator-translations-<task-type>-<seq>.md --audit-source <run>/worker-results/translator-worker-<task-type>-<seq>.md`, `okstra agent-prompt record-dispatch`, `okstra worker-dispatch --workers translator`, and `okstra agent-prompt link-result`. `--result` is the translator's own report and `--audit-source` the worker result the audit sidecar derives from; the two must differ, and the materializer refuses a translator prompt without a distinct `--audit-source` because `worker-dispatch` reads both from the prompt's `**Result Path:**` and `**Worker Result Path:**` anchors and refuses a prompt missing the second. A translator prompt that failed a pre-dispatch gate is re-materialized under the **same** invocation id with `--replace-undispatched`; a second invocation id leaves two undispatched reservations for one run, there is no command that withdraws a reservation, and `worker-dispatch` refuses to choose between them. **Enforced:** `_materialize_run` in `scripts/okstra_ctl/agent/prompt_cli/materialize.py`; `_translator_job_from_reservation` in `scripts/okstra_ctl/dispatch_core.py`. Resume with `report-finalize --only render-views --only spawn-followups --only validate-run --only record-group-memory --only teardown-stages`; do not assemble the record a second time. `render-views` is idempotent and overwrites the view it already wrote, this time with the translation overlaid. When the translator cannot run at all — a host approval gate, an unavailable provider — say so and finish the sequence anyway: the run keeps an English view rather than no view, and `report-finalize` inserts the missing `render-views` itself when a `--only validate-run` call would otherwise validate a report that has none (**Enforced:** `okstra_ctl.report_finalize._with_view_the_validator_reads`).
142
+ `report-finalize` dispatches the translator itself. Its `translate` step runs after `check-source` (the extractor refuses a work list from a non-English source) and before `render-views` (which overlays the sidecar), and it is a no-op when `reportLanguage` is `en` or the `*.i18n.<lang>.json` sidecar already exists. When the sidecar is missing, the step reuses this run's undispatched translator reservation if the lead already materialized one, otherwise it materializes the prompt itself instruction file `state/translator-instructions-<task-type>-<seq>.md` (kept when the lead wrote one), prompt `prompts/translator-worker-prompt-<task-type>-<seq>.md`, `--result worker-results/translator-translations-<task-type>-<seq>.md`, `--audit-source worker-results/translator-worker-<task-type>-<seq>.md`, invocation id `<task-type>-<seq>-translator` (`-r2`, `-r3` after a failed attempt) then runs the CLI-wrapper dispatch, which records the dispatch and links the result. Do not run `agent-prompt materialize --audience translator` or `worker-dispatch --workers translator` by hand before `report-finalize`; the sequence used to be a manual lead step and was skipped in practice (2026-09-09, fontsninja-v3-site dev-10628-3: a `ko` run finalized in one call, zero translator reservations, English HTML). **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` places the step; `okstra_ctl.report_translation_dispatch.translate_report` owns it; `_translator_job_from_reservation` in `scripts/okstra_ctl/dispatch_core.py` still refuses two undispatched reservations for one run, which only a hand-made second reservation produces.
143
+
144
+ The step succeeds only when the sidecar exists afterwards; a translator that exits 0 without publishing it fails the step. A failed `translate` does not stop the sequence: `render-views` still writes the view from the English body, `validate-run` records the missing sidecar as an advisory, and the result's resume hint starts at `--only translate --only render-views …` — run that once the cause (a host approval gate, an unavailable provider) is cleared rather than closing the run on an English view.
142
145
 
143
146
  ## Routing pointer
144
147
 
@@ -3,7 +3,7 @@
3
3
  "locale": "ko",
4
4
  "steps": {
5
5
  "task_pick": {
6
- "label": "어느 task? (남은 작업 최신순 추천)",
6
+ "label": "어느 task? (1번이 추천, 나머지는 남은 작업 최신순)",
7
7
  "echo_template": "task: {value}",
8
8
  "options": {
9
9
  "__new__": "직접 입력 (새 작업 또는 목록에 없는 task)",
@@ -97,7 +97,7 @@
97
97
  "echo_template": "task-type: {value}"
98
98
  },
99
99
  "selected_direction_pick": {
100
- "label": "상세 계획의 입력으로 사용할 확정 구현 방향 보고서를 선택하세요 (같은 task의 최신 3개)",
100
+ "label": "상세 계획의 입력으로 사용할 확정 구현 방향 보고서를 선택하세요 (같은 task의 최신 3개). 고른 보고서의 user-responses/ 답변(방향 선택·C-NNN 답)이 함께 전달됩니다 — clarification-response 단계는 필요 없습니다",
101
101
  "echo_template": "selected-direction: {value}",
102
102
  "errors": {
103
103
  "none": "같은 task에서 선택할 implementation-option-selection 최종 보고서를 찾을 수 없습니다.",
@@ -503,28 +503,8 @@
503
503
  "no": "아니오 — 단계별로 다시 입력"
504
504
  }
505
505
  },
506
- "role_count": {
507
- "label": "{role} 역할 인스턴스 수를 선택하세요 ({minimum}..{maximum}, 적정 {default})",
508
- "echo_template": "role-count: {value}",
509
- "options": {
510
- "count": "{count}개{default_suffix}",
511
- "default_suffix": " (적정)"
512
- }
513
- },
514
- "role_add": {
515
- "label": "선택 역할 {role} 을(를) 이번 run 에 추가할까요? (최대 {maximum}개)",
516
- "echo_template": "role-add: {value}",
517
- "options": {
518
- "skip": "추가 안 함{skip_warning}{default_suffix}",
519
- "add": "{count}개 추가{default_suffix}",
520
- "default_suffix": " (기본)",
521
- "skip_warnings": {
522
- "critic": " — 차단 kind 에서 분석자 표가 1대1 동수면 가를 주체가 없어, 그 항목마다 승인 결정과 Blocks=approval clarification 행이 열립니다 (자동 tie-break 대신 사용자 질문. phase 는 계속 진행됩니다)"
523
- }
524
- }
525
- },
526
506
  "role_model": {
527
- "label": "{role} 역할 {ordinal}/{count} 인스턴스의 모델을 선택하세요",
507
+ "label": "{role} 역할의 모델을 선택하세요 (인스턴스 1개)",
528
508
  "echo_template": "role-model: {value}",
529
509
  "options": {
530
510
  "model": "{model_ref} — {display}{default_suffix}",
@@ -634,6 +614,55 @@
634
614
  "edit_target": {
635
615
  "label": "어느 step 으로 돌아갈까요?",
636
616
  "echo_template": "edit-target: {value}"
617
+ },
618
+ "role_models": {
619
+ "label": "{role} 역할의 모델을 고르세요 — 고른 만큼 인스턴스를 띄웁니다 ({range}). 앞줄 추천은 프로젝트 modelDefaults(없으면 카탈로그 기본값) 순서입니다",
620
+ "echo_template": "role-models: {value}",
621
+ "options": {
622
+ "model": "{model_ref} — {display}",
623
+ "skip": "추가 안 함{skip_warning}",
624
+ "__free_input__": "직접 선택 (실행 가능한 전체 후보 {total}개에서 고르기)",
625
+ "skip_warnings": {
626
+ "critic": " — 차단 kind 에서 분석자 표가 1대1 동수면 가를 주체가 없어, 그 항목마다 승인 결정과 Blocks=approval clarification 행이 열립니다 (자동 tie-break 대신 사용자 질문. phase 는 계속 진행됩니다)"
627
+ }
628
+ },
629
+ "labels": {
630
+ "range": "허용 {minimum}..{maximum}개, 권장 {recommended}개",
631
+ "exact": "정확히 {count}개"
632
+ },
633
+ "errors": {
634
+ "count_out_of_range": "{range} 골라야 합니다 — 고른 수 {count}개",
635
+ "min_one_required": "모델을 {range} 고르세요 (이 역할을 빼려면 '추가 안 함')",
636
+ "unknown_option": "목록에 없는 항목입니다: {values}"
637
+ },
638
+ "echo_variants": {
639
+ "custom": "role-models: (전체 후보에서 직접 선택)",
640
+ "skipped": "role-models: (추가 안 함)"
641
+ }
642
+ },
643
+ "role_models_custom": {
644
+ "label": "{role} 역할의 모델을 전체 후보에서 고르세요 — 고른 만큼 인스턴스를 띄웁니다 ({range})",
645
+ "echo_template": "role-models: {value}",
646
+ "options": {
647
+ "model": "{model_ref} — {display}",
648
+ "skip": "추가 안 함{skip_warning}",
649
+ "skip_warnings": {
650
+ "critic": " — 차단 kind 에서 분석자 표가 1대1 동수면 가를 주체가 없어, 그 항목마다 승인 결정과 Blocks=approval clarification 행이 열립니다 (자동 tie-break 대신 사용자 질문. phase 는 계속 진행됩니다)"
651
+ }
652
+ },
653
+ "labels": {
654
+ "range": "허용 {minimum}..{maximum}개, 권장 {recommended}개",
655
+ "exact": "정확히 {count}개"
656
+ },
657
+ "errors": {
658
+ "count_out_of_range": "{range} 골라야 합니다 — 고른 수 {count}개",
659
+ "min_one_required": "모델을 {range} 고르세요 (이 역할을 빼려면 '추가 안 함')",
660
+ "unknown_option": "목록에 없는 항목입니다: {values}"
661
+ },
662
+ "echo_variants": {
663
+ "custom": "role-models: (전체 후보에서 직접 선택)",
664
+ "skipped": "role-models: (추가 안 함)"
665
+ }
637
666
  }
638
667
  },
639
668
  "confirmation": {
@@ -133,6 +133,8 @@ For `AskUserQuestion`, map each wizard option to the tool's `{label, description
133
133
 
134
134
  For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer. Do not translate numbers, CSV members, labels, or values before `okstra wizard step`. A sequential group wraps each raw reply in one compact JSON object keyed by `questions[].step`; the wizard owns normalization.
135
135
 
136
+ The `confirm` prompt's `label` is the selection summary (one line per resolved input, then the question). Pass it verbatim as the `AskUserQuestion` question text — every line, including `(none)` values — with its three options. Do not replace the summary with a table or a prose digest of your own: a line you drop is a setting the user never saw.
137
+
136
138
 
137
139
  ## Semantic operation mapping
138
140
 
@@ -115,6 +115,10 @@ For `request_user_input`, send one to three questions. Each question carries `id
115
115
  For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer: do not translate a number such as `1`, a CSV reply such as `1, 3`, an option label, or an option value before `okstra wizard step`. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`; the wizard owns all normalization.
116
116
 
117
117
 
118
+ ### Confirm step
119
+
120
+ The `confirm` prompt's `label` is the selection summary (one line per resolved input, then the question). Send it verbatim as that question's text — every line, including `(none)` values — and offer its three options. Do not replace the summary with a table or a prose digest of your own; the user is confirming exactly what the wizard resolved, and a line you drop is a setting the user never saw.
121
+
118
122
  ### Runtime-generated selectable screens
119
123
 
120
124
  `wizard/engine.py` adapts choice screens before returning `next` when the session declares `native_single_select`. `wizard/picker_navigation.py` preserves all original choices while paging long lists, collecting multi-selection through toggle/complete choices, and disambiguating duplicate labels. Oversized or unsupported groups are presented one member at a time. These paths are exercised by `tests/domain/wizard/test_picker_navigation.py` and `test_role_model_selection.py`.
@@ -22,6 +22,11 @@ from .convergence_engine import (
22
22
  validate_final_state,
23
23
  validate_working_state,
24
24
  )
25
+ from .convergence_reverify_prompt import (
26
+ ReverifyPromptError,
27
+ reverify_findings,
28
+ reverify_prompt_body,
29
+ )
25
30
  from .convergence_critic_prompt import (
26
31
  analyser_results,
27
32
  covered_index,
@@ -285,6 +290,8 @@ _CLI_EPILOG = r"""Usage:
285
290
  okstra convergence apply-round --work-state <path> --plan <path> \
286
291
  --results <path>
287
292
  okstra convergence critic-prompt --run-manifest <path>
293
+ okstra convergence reverify-prompt --run-manifest <path> --plan <path> \
294
+ --worker <worker-id>
288
295
  okstra convergence apply-critic-gaps --work-state <path> --results <path>
289
296
  okstra convergence finalize --work-state <path> --output <path>
290
297
  okstra convergence validate --state <path> --kind <working|final>
@@ -388,6 +395,25 @@ def _parser() -> argparse.ArgumentParser:
388
395
  )
389
396
  critic_prompt.add_argument("--run-manifest", type=Path, required=True)
390
397
 
398
+ reverify_prompt = subparsers.add_parser(
399
+ "reverify-prompt",
400
+ help="render one worker's reverify task instructions to stdout",
401
+ description=(
402
+ "Print the reverify instruction body for one `dispatches[]` row of "
403
+ "a round plan. The lead writes it verbatim to the file the prompt "
404
+ "materializer's `--instruction` takes. It carries the round's "
405
+ "mandate (adversarial or collaborative, from the grouping's "
406
+ "config), every planned finding with its summary, origin worker, "
407
+ "cited-evidence line, the origin worker's result file and item id, "
408
+ "and the origin audit sidecar the verifier may open, plus the "
409
+ "response format the collector parses. Nothing here is hand-written."
410
+ ),
411
+ formatter_class=argparse.RawDescriptionHelpFormatter,
412
+ )
413
+ reverify_prompt.add_argument("--run-manifest", type=Path, required=True)
414
+ reverify_prompt.add_argument("--plan", type=Path, required=True)
415
+ reverify_prompt.add_argument("--worker", required=True)
416
+
391
417
  apply_critic = subparsers.add_parser(
392
418
  "apply-critic-gaps",
393
419
  help="apply one coverage-critic verification batch",
@@ -1403,6 +1429,39 @@ def _prepare_groups(args: argparse.Namespace) -> tuple[str, Path]:
1403
1429
  return "prepared", output
1404
1430
 
1405
1431
 
1432
+ def _reverify_prompt(args: argparse.Namespace) -> str:
1433
+ """한 워커의 reverify 지시문 본문. 입력은 매니페스트·라운드 계획·워커 id."""
1434
+ authority = validated_run_authority(args.run_manifest)
1435
+ groups_path = authority.run_dir / "state" / _canonical_run_artifact_name(
1436
+ "convergence-groups", authority.task_type, authority.state_sequence
1437
+ )
1438
+ if not groups_path.is_file():
1439
+ raise ConvergenceContractError(
1440
+ "the reverify prompt needs the Round 0 grouping; run "
1441
+ f"`okstra convergence prepare-groups` first: {groups_path}"
1442
+ )
1443
+ groups = load_owned_json_object(groups_path)
1444
+ plan = load_owned_json_object(args.plan)
1445
+ config = groups.get("config") if isinstance(groups.get("config"), Mapping) else {}
1446
+ round_number = plan.get("round")
1447
+ if not isinstance(round_number, int) or round_number < 1:
1448
+ raise ConvergenceContractError("round plan has no positive `round`")
1449
+ if plan.get("action") != "dispatch":
1450
+ raise ConvergenceContractError(
1451
+ f"round plan action is {plan.get('action')!r}, not `dispatch`; "
1452
+ "there is nothing to verify this round"
1453
+ )
1454
+ return reverify_prompt_body(
1455
+ task_key=_manifest_authority_string(authority.payload, "taskKey"),
1456
+ round_number=round_number,
1457
+ adversarial=bool(config.get("adversarial", False)),
1458
+ findings=reverify_findings(
1459
+ groups, plan, args.worker,
1460
+ project_root=authority.project_root, run_dir=authority.run_dir,
1461
+ ),
1462
+ )
1463
+
1464
+
1406
1465
  def _execute(args: argparse.Namespace) -> tuple[str, Path]:
1407
1466
  operations: dict[str, Any] = {
1408
1467
  "prepare-groups": _prepare_groups,
@@ -1433,8 +1492,11 @@ def main(argv: list[str] | None = None) -> int:
1433
1492
  if args.operation == "critic-prompt":
1434
1493
  print(_critic_prompt(args), end="")
1435
1494
  return 0
1495
+ if args.operation == "reverify-prompt":
1496
+ print(_reverify_prompt(args), end="")
1497
+ return 0
1436
1498
  action, path = _execute(args)
1437
- except (ConvergenceContractError, VerdictBlockError,
1499
+ except (ConvergenceContractError, VerdictBlockError, ReverifyPromptError,
1438
1500
  json.JSONDecodeError, ValueError) as exc:
1439
1501
  print(f"error: {exc}", file=sys.stderr)
1440
1502
  return 2