okstra 0.124.0 → 0.125.1

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/storage-model.md +2 -0
  2. package/docs/architecture.md +6 -2
  3. package/docs/cli.md +2 -2
  4. package/docs/for-ai/skills/okstra-inspect.md +1 -0
  5. package/docs/for-ai/skills/okstra-pr-gen.md +2 -2
  6. package/docs/performance-improvement-plan-v2.md +2 -2
  7. package/docs/project-structure-overview.md +7 -3
  8. package/package.json +1 -1
  9. package/runtime/BUILD.json +2 -2
  10. package/runtime/bin/lib/okstra/usage.sh +1 -1
  11. package/runtime/bin/okstra-antigravity-exec.sh +10 -0
  12. package/runtime/bin/okstra-codex-exec.sh +12 -0
  13. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  14. package/runtime/prompts/lead/report-writer.md +16 -13
  15. package/runtime/prompts/profiles/_implementation-verifier.md +26 -0
  16. package/runtime/prompts/profiles/implementation-planning.md +4 -4
  17. package/runtime/prompts/profiles/implementation.md +1 -0
  18. package/runtime/prompts/profiles/requirements-discovery.md +1 -2
  19. package/runtime/python/okstra_ctl/models.py +4 -2
  20. package/runtime/python/okstra_ctl/run.py +8 -2
  21. package/runtime/python/okstra_ctl/stage_fix_carry.py +112 -0
  22. package/runtime/schemas/final-report-v1.0.schema.json +6 -6
  23. package/runtime/skills/okstra-inspect/SKILL.md +23 -737
  24. package/runtime/skills/okstra-inspect/facets/cost.md +84 -0
  25. package/runtime/skills/okstra-inspect/facets/error-zip.md +41 -0
  26. package/runtime/skills/okstra-inspect/facets/errors.md +69 -0
  27. package/runtime/skills/okstra-inspect/facets/history.md +90 -0
  28. package/runtime/skills/okstra-inspect/facets/logs.md +89 -0
  29. package/runtime/skills/okstra-inspect/facets/recap.md +96 -0
  30. package/runtime/skills/okstra-inspect/facets/report.md +61 -0
  31. package/runtime/skills/okstra-inspect/facets/status.md +145 -0
  32. package/runtime/skills/okstra-inspect/facets/time.md +56 -0
  33. package/runtime/skills/okstra-pr-gen/SKILL.md +3 -3
  34. package/runtime/templates/project-docs/task-index.template.md +1 -1
  35. package/runtime/templates/worker-prompt-preamble.md +17 -3
@@ -231,6 +231,8 @@ Main contents:
231
231
  This document is only a quick summary and is not the source of truth.
232
232
  Always use `task-manifest.json` when checking canonical metadata.
233
233
 
234
+ The rendered `task-index.md` is an English-literal artifact. The headings, field labels, and `## Notes` footnote of the template (`templates/project-docs/task-index.template.md`) ship as English literals regardless of the `reportLanguage` setting, matching the okstra-schedule-gen schedule contract. The docs-publication pipeline's Hangul check (`npm run docs:validate`) enforces this contract.
235
+
234
236
  ## Run manifest contract
235
237
 
236
238
  Each execution records the current run contract in the resolved `<run-dir>/manifests/run-manifest-<task-type>-<seq>.json`. The non-stage or stage-qualified rule above determines `<run-dir>`.
@@ -225,6 +225,10 @@ implementation-planning Phase 6
225
225
 
226
226
  Resolution is stage-scoped: only items whose `stageRefs` include the selected stage participate, so an unsafe open decision blocks that stage rather than unrelated ready stages. `no-design-inputs` resolves to `proceed`. Plans created before the `implementation-design-prep-v1` report contract also proceed with a `legacy-unassessed` warning and are not rewritten.
227
227
 
228
+ ### Fix-run incremental reverification
229
+
230
+ When an implementation run is prepared on a stage whose latest final-report data.json carries one or more verifier `FAIL` verdicts, prep derives a fix-run carry (`okstra_ctl.stage_fix_carry`) — previous report path, previous run HEAD, failed verifiers, carried blocking findings, routing recommendation — and injects it into the rendered analysis profile as a "Fix-Run Carry" block via the `{{FIX_RUN_CONTEXT}}` token. Verifiers then keep the full validation-command re-run but narrow the static design/test-quality sweep to `git diff <prev-head>..HEAD` plus a mandatory re-check of each carried finding, and the report writer authors the new data.json incrementally by copying the previous one and updating only the changed blocks (`prompts/profiles/_implementation-verifier.md` § Fix-run incremental scope, `prompts/lead/report-writer.md` § Fix-run incremental authoring). A first run on a stage, or a rerun after a PASS, renders the token empty and behaves as before.
231
+
228
232
  ### Clarification & decision-capture discipline
229
233
 
230
234
  okstra runs a single cross-phase discipline for turning open questions into resolved decisions and durable memory. It is not one module — it is enforced at three sites that share one vocabulary, deliberately split across an interactive layer and a headless layer:
@@ -294,7 +298,7 @@ The standard `okstra` workflow applies the following team contract consistently
294
298
  - The main Claude is always the `Claude lead` and operates in synthesis-only mode.
295
299
  - The default required worker roles are `Claude worker`, `Codex worker`, and `Report writer worker`. `Antigravity worker` is optional and is included as required only when explicitly named by `--workers` or the profile's `- Workers:` section.
296
300
  - `Report writer worker` focuses on report structure and evidence organization, but `Claude lead` remains the final synthesis owner.
297
- - The default model contract is computed from central defaults. Fallbacks are `Claude lead`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, and `Antigravity worker`=`auto` (when opted in). Without a separate override, `Report writer worker` follows the `Claude lead` model (therefore `opus` by default).
301
+ - The default model contract is computed from central defaults. Fallbacks are `Claude lead`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, `Report writer worker`=`sonnet`, and `Antigravity worker`=`auto` (when opted in). `Report writer worker` no longer inherits the `Claude lead` model it resolves from its own catalog `ROLE_DEFAULTS` entry because it does not vote in convergence; `--report-writer-model opus` or `OKSTRA_DEFAULT_REPORT_WRITER_MODEL` restores the previous behavior.
298
302
  - Because `Antigravity worker` is optional, it is attempted only in runs where it is explicitly included.
299
303
  - Before the final judgment, each required role in the current run's worker roster must have either a result or an explicit terminal status (`completed`, `timeout`, `error`, `not-run`).
300
304
  - Every attempted worker (`completed`, `timeout`, `error`) must have an assigned worker prompt history file under the current run's `prompts/` directory.
@@ -782,7 +786,7 @@ Each validator blocks the phase with a `contract-violated` exit code when a cont
782
786
  - The run directory is organized into typed subdirectories such as `manifests/`, `state/`, `prompts/`, `reports/`, `status/`, `sessions/`, and `worker-results/`; prompt snapshots are prepared under `prompts/` first.
783
787
  - Claude creates workers and collects results.
784
788
  - The standard workflow uses a `Claude lead` with default workers `Claude worker`, `Codex worker`, and `Report writer worker`; `Antigravity worker` is optional and included only when explicitly requested.
785
- - Worker models can be overridden with `--lead-model`, `--claude-model`, `--codex-model`, `--antigravity-model`, and `--report-writer-model`; defaults are centrally managed through `OKSTRA_DEFAULT_*` environment variables. Fallback defaults are `Claude lead`/`Report writer worker`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, and `Antigravity worker`=`auto`.
789
+ - Worker models can be overridden with `--lead-model`, `--claude-model`, `--codex-model`, `--antigravity-model`, and `--report-writer-model`; defaults are centrally managed through `OKSTRA_DEFAULT_*` environment variables. Fallback defaults are `Claude lead`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, `Report writer worker`=`sonnet`, and `Antigravity worker`=`auto`.
786
790
  - For `--task-type implementation`, select the provider that takes the Executor role with `--executor <claude|codex|antigravity>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`). Only the Executor may mutate project files. The other two providers and the Executor's own provider are each dispatched as verifiers in separate CLI sessions (session isolation preserves the self-review safeguard). The Executor's model reuses the selected provider's worker-model flag (`--claude-model` / `--codex-model` / `--antigravity-model`). Provider / displayName / workerAgent / model are recorded in the run-manifest `teamContract.executor` block.
787
791
  - Worktree cwd injection by Executor: Codex / Antigravity executors pin cwd to the worktree at the CLI layer through wrappers (`okstra-codex-exec.sh -C` / `okstra-antigravity-exec.sh --include-directories`). Because the Bash tool has no per-call cwd argument, the Claude executor prefixes cwd-sensitive toolchain invocations (`cargo`/`npm`/`pnpm`/`bun`/`pytest`/`make`/`go`) with `cd {{EXECUTOR_WORKTREE_PATH}} && <cmd>` in the same Bash invocation. Wrapping in `bash -lc`/`bash -c` is prohibited because it hides the leading `cd` token and defeats permission auto-allow. Prefer working-directory flags such as `git -C` or `cargo --manifest-path` when available. See the *Executor Worktree* block in `prompts/profiles/implementation.md` and the Executor exception in `agents/workers/claude-worker.md` for details.
788
792
  - The project-level current-task convenience pointer is `.okstra/discovery/latest-task.json`.
package/docs/cli.md CHANGED
@@ -369,7 +369,7 @@ When omitted, it uses the central default `OKSTRA_DEFAULT_ANTIGRAVITY_MODEL`, fa
369
369
  ### `--report-writer-model`
370
370
 
371
371
  Selects the model used by the `Report writer worker`.
372
- When omitted, it uses the central default `OKSTRA_DEFAULT_REPORT_WRITER_MODEL`, falling back to the lead default.
372
+ When omitted, it uses the central default `OKSTRA_DEFAULT_REPORT_WRITER_MODEL`, falling back to `sonnet`. The report writer does not vote in convergence, so its default sits below the lead tier to cut report-generation wall-clock; pass `--report-writer-model opus` to restore the previous behavior.
373
373
 
374
374
  The central-default environment variables are:
375
375
 
@@ -383,7 +383,7 @@ The central-default environment variables are:
383
383
  Fallback defaults are:
384
384
 
385
385
  - `Claude lead`: `opus`
386
- - `Report writer worker`: `opus`
386
+ - `Report writer worker`: `sonnet`
387
387
  - `Claude worker`: `opus`
388
388
  - `Codex worker`: `gpt-5.6-sol`
389
389
  - `Antigravity worker`: `auto`
@@ -3,6 +3,7 @@
3
3
  ## Source
4
4
 
5
5
  - Skill source: [`skills/okstra-inspect/SKILL.md`](../../../skills/okstra-inspect/SKILL.md)
6
+ - Facet bodies: `skills/okstra-inspect/facets/<sub-command>.md` — the skill is a thin core (preflight + dispatch + shared rules); each sub-command's full procedure is a lazily loaded facet file, guarded by `tests/contract/test_okstra_inspect_facets.py`
6
7
  - CLI registry: [`src/cli-registry.mjs`](../../../src/cli-registry.mjs)
7
8
  - context-cost CLI: `src/commands/inspect/context-cost.mjs`
8
9
  - time-report CLI: `src/commands/inspect/time-report.mjs`
@@ -27,12 +27,12 @@ A 3-option picker via `AskUserQuestion`:
27
27
 
28
28
  1. `Generate PR` — generate a PR body from a branch diff
29
29
  2. `Register template` — save a new PR body template
30
- 3. `Enter manually` — always last (okstra picker convention)
30
+ 3. `Enter directly` — always last (okstra picker convention)
31
31
 
32
32
  ## Mode A — Generate PR
33
33
 
34
34
  1. Pick a template: `okstra pr template list --json`. If empty, use the bundled default. Carry the chosen name as `<template>` (`default` for the bundled one).
35
- 2. Pick the base branch: `okstra pr branches --json`. 3-option from the top `recommended` entries plus `Enter manually`. Carry the choice as `<base>`.
35
+ 2. Pick the base branch: `okstra pr branches --json`. 3-option from the top `recommended` entries plus `Enter directly`. Carry the choice as `<base>`.
36
36
  3. Generation bundle: `okstra pr gen --base <base> --template <template> --json` → `{base, currentBranch, templateName, template, commits, diffStat}`. Then **read the real diff honestly** (SSOT): `git diff <base>...HEAD` (large diffs section by section). Fill the placeholders from the diff and commits, describing **only actual changes**. Mark a checklist box `[x]` only when the diff supports it (tests touched → tests box, docs touched → docs box). If `commits`/`diffStat` are empty, say there is nothing to describe and stop. **Never append AI trailers/footers.**
37
37
  4. Output and offer to create the PR: print the filled PR body as a single fenced markdown block. Ask whether to open a PR. **Only on an explicit yes**: write the body to a temp file and run `gh pr create --base <base> --title "<title>" --body-file <path>`. If `gh` is missing or unauthenticated (`gh auth status` fails), leave the text in chat and give manual-creation guidance. **No push/PR creation without the user's confirmation.**
38
38
 
@@ -105,7 +105,7 @@ The four default worker definitions are under `agents/workers/`.
105
105
  | B4 | Multiple renders/writes run serially during prepare | Low–medium | `scripts/okstra_ctl/run.py` render block | Lower priority |
106
106
  | B5 | The token usage collector scans session JSONL linearly | Low–medium | `scripts/okstra_token_usage/` | **Resolved** — P6 incremental cache implemented |
107
107
  | B6 | Simple work uses the same full workflow | Medium | task-type lifecycle / requirements routing | Fast-track design needed — largest remaining lever |
108
- | B7 | The report writer's final-report/data.json **output volume itself** is large (90–140 KB) | High | Measurements in §4.1 item 3 — 8–19 minutes of generation per run | Slim the report contract or select a report-writer model **user decision required** |
108
+ | B7 | The report writer's final-report/data.json **output volume itself** is large (90–140 KB) | High | Measurements in §4.1 item 3 — 8–19 minutes of generation per run | **Resolved** default report-writer model lowered to `sonnet` (catalog `ROLE_DEFAULTS`) and a prose-budget dedup contract landed on the narrative blocks (`tests/contract/test_report_prose_budget.py`); fix runs additionally author reports incrementally (see the fix-run incremental reverification section in `docs/architecture.md`) |
109
109
 
110
110
  Established assumptions:
111
111
 
@@ -363,7 +363,7 @@ The current plan correctly makes P1 the top priority, but the earlier phrases "7
363
363
 
364
364
  **2026-06-11 update** — Measurements in §4.1 confirm that P0/P1/P6, critic parallelization, and phaseTimeline instrumentation are complete, while B3 (report-writer input compression) was rejected. Only two priorities remain, and both require a user decision first:
365
365
 
366
- 1. **B7 — reduce report-writer output-side cost**: slim the report contract (reduce the template/schema and change validator contracts) or lower the default report-writer model. Estimated saving: 5–12 minutes per run.
366
+ 1. **B7 — reduce report-writer output-side cost**: **done** the default report-writer model is now `sonnet`, and the prose-budget contract deduplicates the narrative blocks. Remaining follow-up if further reduction is needed: evidence pointer-ization (option B of the slimming decision).
367
367
  2. **B6/P3 — fast-track routing**: shorten the lifecycle for simple work. Requires an approval-gate policy decision.
368
368
 
369
369
  ### Implementation Plan Links
@@ -54,13 +54,14 @@ okstra/
54
54
  │ ├── okstra_vendor/ vendored Jinja2 / MarkupSafe + graphify / networkx (okstra-graphify skill)
55
55
  │ ├── lib/okstra/ Bash helpers for okstra.sh
56
56
  │ └── lib/okstra-ctl/ Bash control-center subcommands
57
- ├── skills/ Claude Code skills (11)
57
+ ├── skills/ Claude Code skills (13); `_fragments/` holds shared marker blocks
58
58
  ├── agents/ lead SKILL.md + worker agent specs
59
59
  ├── prompts/ launch template, phase profiles, wizard prompt JSON
60
60
  ├── schemas/ JSON schema for final-report data.json
61
61
  ├── templates/ report, setup, PR, project-doc templates/assets
62
62
  ├── validators/ run / brief / schedule / view validators
63
63
  ├── tools/build.mjs source → runtime sync
64
+ ├── tools/sync-skill-fragments.mjs expand shared fragment blocks into skills/*/SKILL.md
64
65
  ├── tools/docs_publication/ public-manual publication pipeline (build-time only; not shipped)
65
66
  ├── config/docs-publication.json publication inventory: every tracked Markdown path + classification
66
67
  ├── runtime/ generated install payload; do not edit directly
@@ -220,6 +221,7 @@ Important modules:
220
221
  | `run.py` | `prepare_task_bundle()` single authority and CLI parser |
221
222
  | `implementation_stage.py` | `implementation` single-stage run orchestration — read the Stage Lifecycle Snapshot → pick an available Stage Map entry → provision an isolated stage worktree → publish the selected stage as run context (extracted from `run.py`) |
222
223
  | `stage_targets.py` | Stage readiness/verification policy SSOT — from the Stage Lifecycle Snapshot (`consumers.jsonl` ledger + carry sidecar backfill + active registry reservation) it decides which stage is runnable, which commit it branches from, and what final-verification checks. `order_stage_closure` topologically sorts (Kahn) the dependency closure of the wizard's multi-selected stage set to produce the unattended `chain-stages` chaining order |
224
+ | `stage_fix_carry.py` | fix-run carry derivation for a re-run on an `implementation` stage whose latest final-report data.json carries verifier `FAIL` verdicts — collects the previous report path, previous run HEAD, failed verifiers, carried blocking findings, and a routing recommendation, which `run.py` renders into the analysis profile through the `{{FIX_RUN_CONTEXT}}` token. A first run, or a re-run after `PASS`, yields no carry and renders the token empty |
223
225
  | `stage_reconcile.py` | best-effort git reconciliation shared by the stage prepare flow (delegates to `git_reconcile.auto_reconcile`; advisory — failures are only reported to stderr, the dependency gate stays authoritative) |
224
226
  | `design_surfaces.py` | deterministic detection of an `implementation-planning` stage's design surface — matches the stage's file-path tokens/suffixes/patterns and action wording via `SurfaceRule` to derive which design input the stage needs among domain contract, DB/table schema, external interface, transaction/consistency, transformation mapping, lifecycle, rollout/observability, and manual user test, plus its evidence (`TriggerEvidence`). An unmappable structure raises `DesignSurfaceError` |
225
227
  | `design_prep.py` | fingerprint / materialize / resolve backend for design-preparation requests (CLI: `okstra design-prep <list\|show\|write>`) — computes an assessment fingerprint from the approved planning snapshot's `ASSESSMENT_FIELDS`, idempotently writes an Okstra-owned request under `design-prep-requests/`, and resolves the highest-revision append-only user response under `design-prep-inputs/` whose fingerprint matches as the effective response. Keeps the three authorities (report snapshot / Okstra request / user input) separate and never modifies the report or existing revisions. Sidecar I/O is protected by a directory-fd anchor + flock |
@@ -342,14 +344,16 @@ Optional (v1.0 backward-compatible) top-level keys:
342
344
 
343
345
  ### 4.10 `skills/`
344
346
 
345
- 13 user-facing skills (the only skills `okstra install` copies). The list SSOT is `USER_SKILL_NAMES` in `src/lib/skill-catalog.mjs`; the Claude plugin manifest and the installer both derive from it:
347
+ 13 user-facing skills (the only skills `okstra install` copies). The list SSOT is `USER_SKILL_NAMES` in `src/lib/skill-catalog.mjs`; the Claude plugin manifest and the installer both derive from it.
348
+
349
+ Boilerplate shared by several skills (bash invocation rule, outdated-CLI preflight, python bootstrap note) is kept canonical in `skills/_fragments/*.md` and expanded in place inside each `SKILL.md` between `<!-- BEGIN FRAGMENT: <name> -->` / `<!-- END FRAGMENT: <name> -->` markers by `tools/sync-skill-fragments.mjs` (`--check` fails on drift). Sources stay fully expanded, so `runtime/` and installed copies remain self-contained; the guards are `tests-js/skill-fragments.test.mjs` and `tests/contract/test_prompt_fragment_ownership.py`.
346
350
 
347
351
  | Skill | User-invocable | Role |
348
352
  |---|---:|---|
349
353
  | `okstra-brief-gen` | yes | Produce task brief from ticket/doc/link/conversation |
350
354
  | `okstra-run` | yes | Start/resume okstra task in current Claude Code session |
351
355
  | `okstra-memory` | yes | Store/search/archive global conversation memory under `~/.okstra/memory-book` |
352
- | `okstra-inspect` | yes | Unified read-side — sub-commands `status` (lifecycle + workStatus), `history` (past runs / re-run / resume), `report` (find final-report), `time` (elapsed-time breakdown), `logs` (wrapper log inventory + cleanup), `cost` (task bundle context/read cost) |
356
+ | `okstra-inspect` | yes | Unified read-side — sub-commands `status` (lifecycle + workStatus), `history` (past runs / re-run / resume), `report` (find final-report), `time` (elapsed-time breakdown), `logs` (wrapper log inventory + cleanup), `cost` (task bundle context/read cost), `errors` (error-log aggregation), `error-zip` (anonymized cross-project error bundle), `recap` (cross-run phase recap). `SKILL.md` is a thin core (preflight + dispatch table + shared rules) and each sub-command body lives in `skills/okstra-inspect/facets/<sub-command>.md`, lazily read only after dispatch resolves; the 1:1 match between dispatch rows and facet files is enforced by `tests/contract/test_okstra_inspect_facets.py` |
353
357
  | `okstra-rollup` | yes | Cross-task roll-up — aggregate runs/time/errors across a task-group (or whole project) and synthesize a digest from the report files |
354
358
  | `okstra-usage` | yes | Read-only project usage snapshot — aggregate recent run coverage, tokens, known cost, CPU, and wall-clock time by task type (default: 30 days) |
355
359
  | `okstra-schedule-gen` | yes | Generate task-group schedule |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.124.0",
3
+ "version": "0.125.1",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.124.0",
3
- "builtAt": "2026-07-19T03:50:18.974Z",
2
+ "package": "0.125.1",
3
+ "builtAt": "2026-07-20T08:47:14.936Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -93,7 +93,7 @@ options:
93
93
  --codex-model Model for Codex worker. Default: OKSTRA_DEFAULT_CODEX_MODEL or gpt-5.6-sol
94
94
  --antigravity-model Model for Antigravity worker. Default: OKSTRA_DEFAULT_ANTIGRAVITY_MODEL or gemini-3.1-pro
95
95
  --report-writer-model
96
- Model for report writer worker. Default: OKSTRA_DEFAULT_REPORT_WRITER_MODEL or lead model default
96
+ Model for report writer worker. Default: OKSTRA_DEFAULT_REPORT_WRITER_MODEL or sonnet
97
97
  --lead-runtime Lead runtime adapter. Default: claude-code.
98
98
  codex is currently render-only and records Codex adapter
99
99
  metadata in prepared artifacts without dispatching workers.
@@ -254,6 +254,16 @@ mkfifo "$stdout_fifo"
254
254
  tee -a "$log_path" < "$stdout_fifo" &
255
255
  stdout_tee_pid=$!
256
256
 
257
+ # Disable git's fsmonitor for every git command agy runs in this process tree —
258
+ # the main worktree's fsmonitor daemon leaks its IPC socket into task worktrees,
259
+ # so git status/commit here intermittently fail with `fsmonitor_ipc__send_query`
260
+ # errors. GIT_CONFIG_* scopes the override to this process tree without touching
261
+ # the user's repo config. See `okstra-codex-exec.sh` for the full rationale.
262
+ _gc_idx="${GIT_CONFIG_COUNT:-0}"
263
+ export "GIT_CONFIG_KEY_${_gc_idx}=core.fsmonitor"
264
+ export "GIT_CONFIG_VALUE_${_gc_idx}=false"
265
+ export GIT_CONFIG_COUNT="$(( _gc_idx + 1 ))"
266
+
257
267
  agy --print "$(cat "$prompt_path")" --model "$model" "${add_dir_args[@]}" \
258
268
  --print-timeout "$PRINT_TIMEOUT" --dangerously-skip-permissions \
259
269
  2>> "$log_path" \
@@ -328,6 +328,18 @@ mkfifo "$stdout_fifo"
328
328
  tee -a "$log_path" < "$stdout_fifo" &
329
329
  stdout_tee_pid=$!
330
330
 
331
+ # Disable git's fsmonitor for every git command codex runs in this process
332
+ # tree. The main worktree's fsmonitor daemon leaks its IPC socket into task
333
+ # worktrees, so git status/commit here intermittently fail with
334
+ # `fsmonitor_ipc__send_query` errors (workers otherwise recover by hand with
335
+ # `git -c core.fsmonitor=false`). Injecting it via GIT_CONFIG_* scopes the
336
+ # override to this process tree without touching the user's repo config, and
337
+ # appends after any GIT_CONFIG_* the caller already set.
338
+ _gc_idx="${GIT_CONFIG_COUNT:-0}"
339
+ export "GIT_CONFIG_KEY_${_gc_idx}=core.fsmonitor"
340
+ export "GIT_CONFIG_VALUE_${_gc_idx}=false"
341
+ export GIT_CONFIG_COUNT="$(( _gc_idx + 1 ))"
342
+
331
343
  codex exec -C "$project_root" ${extra_args[@]+"${extra_args[@]}"} --model "$model" --sandbox workspace-write -c approval_policy=never - \
332
344
  < "$prompt_path" \
333
345
  2>> "$log_path" \
@@ -124,7 +124,7 @@ The table below documents those prep-time seed values **for reference only** —
124
124
  | Role | Seed model | Worker assignment | Source definition |
125
125
  |------|-----------|---------------|-------------------|
126
126
  | Lead role | opus | -- | runtime-specific role label; orchestration + convergence supervision + final-report review/approval |
127
- | Report writer worker | opus | report-writer-worker | `agents/workers/report-writer-worker.md` |
127
+ | Report writer worker | sonnet | report-writer-worker | `agents/workers/report-writer-worker.md` |
128
128
  | Claude worker | opus | claude-worker | `agents/workers/claude-worker.md` |
129
129
  | Codex worker | gpt-5.6-sol | codex-worker | generated from `agents/workers/_cli-wrapper-template.md` + `codex-worker.params.json` |
130
130
  | Antigravity worker | auto | antigravity-worker | generated from `agents/workers/_cli-wrapper-template.md` + `antigravity-worker.params.json` |
@@ -53,6 +53,9 @@ The prompt MUST include, in this order at the top:
53
53
  into `data.json.meta.reportLanguage`.
54
54
  12. For implementation-planning runs: a literal block listing the 10 required English section headings (`Option Candidates`, `Trade-off`, `Recommended Option`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `User Approval Request`, `Plan Body Verification`, `Implementation Design Preparation`). The writer uses these exact substrings as section headings (Korean translation in parentheses is allowed), and the `Plan Body Verification` section carries its required `Gate result:` line.
55
55
  13. An explicit instruction: `You are the author of TWO files: (a) the final-report data.json at <Result Path>, (b) the worker-results audit file at <Worker Result Path>. After writing the data.json, invoke "okstra render-final-report <Result Path>" through the available execution interface so the markdown sibling is rendered before you return. Do not return the report inline. The validator fails the run when (a)'s schema validation fails, when the rendered markdown is absent, or when (b) is missing.`
56
+ 14. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT — at most 3 sentences. `rationale.*` fields stay within 2 sentences each and reference the verdict card / row IDs instead of restating their prose; `readerSummary` fields are one line each; `summary` stays at 3-5 rows unless the run covers multiple tickets. The schema field descriptions carry the same budgets (`tests/contract/test_report_prose_budget.py` guards both surfaces). Generation time scales with output volume, so exceeding the budget is a cost bug, not extra diligence.
57
+
58
+ **Fix-run incremental authoring (applies when the run's profile carries a "Fix-Run Carry" block).** Do not author the data.json from scratch. Start by copying the previous run's data.json (the `Previous report` path in the Fix-Run Carry block) to this run's Result Path, then update ONLY the blocks the fix run changed: `meta`/`header` (run seq, dates), `executionStatus`, `implementation.verifierResults`, `implementation.validationEvidence`, `implementation.commitList` / `diffSummary`, `crossVerification`, `verdictCard`, `finalVerdict`, and any `evidence` rows the fix touched. Deliverable prose for unchanged sections is carried forward verbatim — do not re-generate it. Then invoke the renderer exactly as in a full run. The schema validation and renderer contract are unchanged, so an incrementally-authored data.json passes the same post-hoc gates. The lead's dispatch prompt MUST include the previous data.json path when the carry block is present.
56
59
 
57
60
  **Completion detection after dispatch (BLOCKING).** A dispatch acknowledgement is NOT completion — detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection", with a one-entry pending set covering the data.json (Result Path) and the worker-results file (Worker Result Path). Do NOT end the turn with a prose "waiting for the report" statement.
58
61
 
@@ -104,7 +107,7 @@ The four Phase 7 steps below MUST execute in this exact order. Reordering them i
104
107
  - When the report has **no** `C-*` clarification rows and is **not** a Plan Approval widget target, the html carries no interactive forms (it would only duplicate the MD), so the renderer prints `html: skipped (...)` and writes nothing. This is the expected state for such runs — `validators/validate-report-views.py` treats "no C-* rows + no approval target + no html" as a pass, not a missing artifact.
105
108
 
106
109
  This step runs after usage collection so token placeholders are substituted in any rendered html and before routing persistence so the html artifact, when generated, exists for the validator step that checks it.
107
- 3. **Complete routing and follow-up persistence.** Run the follow-up task spawner when Section 4 is non-empty. It turns the report's `## 4. Follow-up Tasks (Follow-up Tasks)` rows into `tasks/<task-group>/<new-task-id>/` stubs.
110
+ 3. **Complete routing and follow-up persistence.** Run the follow-up task spawner when Section 4 is non-empty. It turns the report's `## 4. Follow-up Tasks` rows into `tasks/<task-group>/<new-task-id>/` stubs.
108
111
 
109
112
  ```bash
110
113
  okstra spawn-followups \
@@ -194,9 +197,9 @@ Token Summary Generation Rules:
194
197
  CLI `--report-language` flag is given.
195
198
  - All values come from `usageSummary` (populated by `scripts/okstra-token-usage.py` at the start of Phase 7). Do not estimate or invent.
196
199
  - **Lead** row: `usageSummary.leadTotalTokens` / `usageSummary.leadBillableEquivalentTokens` / `usageSummary.estimatedCostUsd.lead`.
197
- - **Worker subtotal** (ko: `Worker subtotal`) row: `usageSummary.workerTotalTokens` / `usageSummary.workerBillableEquivalentTokens` / `usageSummary.estimatedCostUsd.claudeWorkers`.
198
- - **Grand total** (ko: `Grand total`) row: `usageSummary.grandTotalTokens` / `usageSummary.grandBillableEquivalentTokens` / sum of `lead + claudeWorkers`.
199
- - **Codex/Antigravity CLI add-on** (ko: `Codex/Antigravity CLI add-on cost`) row: `usageSummary.estimatedCostUsd.cliWorkers`. If 0, still show the row so the reader sees that no CLI work was billed under those providers (or that CLI fallback occurred).
200
+ - **Worker subtotal** row: `usageSummary.workerTotalTokens` / `usageSummary.workerBillableEquivalentTokens` / `usageSummary.estimatedCostUsd.claudeWorkers`.
201
+ - **Grand total** row: `usageSummary.grandTotalTokens` / `usageSummary.grandBillableEquivalentTokens` / sum of `lead + claudeWorkers`.
202
+ - **Codex/Antigravity CLI add-on** row: `usageSummary.estimatedCostUsd.cliWorkers`. If 0, still show the row so the reader sees that no CLI work was billed under those providers (or that CLI fallback occurred).
200
203
  - Format tokens with comma separators (e.g., `32,500`); format USD with two decimals (e.g., `$1.43`).
201
204
  - If `lead` or any `worker.usage` records unavailable evidence, show `--` for that row and append a one-line note (`reason: <note>`).
202
205
  - If pricing for a model is unknown, the script omits `estimatedCostUsd` for that block — show `N/A` in that column and add a note like `pricing missing for model <model>`.
@@ -207,16 +210,16 @@ When the run's `task-type` is `implementation-planning`, the final report MUST c
207
210
 
208
211
  | # | Required substring | Recommended heading form |
209
212
  |---|--------------------|--------------------------|
210
- | 1 | `Option Candidates` | `### Option Candidates (Option Candidates)` |
211
- | 2 | `Trade-off` | `### Trade-off Matrix (Trade-off Matrix)` |
212
- | 3 | `Recommended Option` | `### Recommended Option (Recommended Option)` |
213
- | 4 | `Stepwise Execution Order` | `### Stepwise Execution Order (Stepwise Execution Order)` |
214
- | 5 | `Dependency` | `### Dependency / Migration Risk (Dependency / Migration Risk)` |
215
- | 6 | `Validation Checklist` | `### Validation Checklist (Validation Checklist)` |
216
- | 7 | `Rollback` | `### Rollback Strategy (Rollback Strategy)` |
213
+ | 1 | `Option Candidates` | `### Option Candidates (Korean gloss)` |
214
+ | 2 | `Trade-off` | `### Trade-off Matrix (Korean gloss)` |
215
+ | 3 | `Recommended Option` | `### Recommended Option (Korean gloss)` |
216
+ | 4 | `Stepwise Execution Order` | `### Stepwise Execution Order (Korean gloss)` |
217
+ | 5 | `Dependency` | `### Dependency / Migration Risk (Korean gloss)` |
218
+ | 6 | `Validation Checklist` | `### Validation Checklist (Korean gloss)` |
219
+ | 7 | `Rollback` | `### Rollback Strategy (Korean gloss)` |
217
220
  | 8 | `User Approval Request` | Satisfied by the top-of-report `## User Approval Request (User Approval Gate)` block. Do NOT recreate a `### 5.5.8 User Approval Request` body stub — the validator now fails reports that contain one. |
218
- | 9 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (Plan Body Verification)` containing a `Gate result:` line — copy `templates/reports/final-report.template.md §5.5.9` verbatim. Validator checks both substrings. |
219
- | 10 | `Implementation Design Preparation` | `### Implementation Design Preparation (Implementation Design Preparation)` — rendered from `implementationPlanning.designPreparation` by `templates/reports/final-report.template.md §5.5.10`. |
221
+ | 9 | `Plan Body Verification` + `Gate result:` | `### Plan Body Verification (Korean gloss)` containing a `Gate result:` line — copy `templates/reports/final-report.template.md §5.5.9` verbatim. Validator checks both substrings. |
222
+ | 10 | `Implementation Design Preparation` | `### Implementation Design Preparation (Korean gloss)` — rendered from `implementationPlanning.designPreparation` by `templates/reports/final-report.template.md §5.5.10`. |
220
223
 
221
224
  The Korean translation in parentheses is optional but the English keyword is mandatory. The body of each section is written in the Report Language per the writing rules below. For non-`implementation-planning` runs, omit this entire block — these headings are NOT validator-checked for other task-types.
222
225
 
@@ -102,6 +102,32 @@ Re-running commands proves the diff *builds and passes*; it does NOT prove the d
102
102
  - **Advisory findings (recorded as recommendations; verdict MAY still PASS):** function >50 effective lines, a single body mixing read+write stages, weak readability, a missing-but-non-critical outcome assertion, newly orphaned private/public code that is safe to remove but not on a critical path, or weak-but-not-misleading names. These land in the verifier result as `should-fix` / `nit` recommendations, not as a `FAIL`.
103
103
  - **Output.** Every finding — blocking or advisory — is a structured item in the verifier's worker result (`path:line`, rule, severity, suggested fix) so it carries into Phase 5.5 convergence and the final report. A blocking hit sets the verifier verdict to `FAIL` with the rule cited, using the same verdict machinery as the Discrepancy rule above. `Claude lead` MUST NOT silently downgrade a cited blocking finding to advisory during synthesis; an override requires a concrete cited reason, exactly as for the Discrepancy rule.
104
104
 
105
+ ### Fix-run incremental scope (applies when the profile carries a "Fix-Run Carry" block)
106
+
107
+ A fix run re-verifies a stage whose previous run already passed a full static
108
+ sweep and failed only on cited blocking findings. Re-sweeping the whole stage
109
+ diff re-buys wall-clock without new information, so the static scope narrows —
110
+ the command re-run does not:
111
+
112
+ - **Command re-run stays full.** Every Tier 1/2/3 command from the plan's
113
+ validation set runs end-to-end exactly as in a first run. QA-RESULT gating
114
+ (`validate-run.py` Tier 3) is unchanged.
115
+ - **Static design & test-quality sweep narrows to the fix diff.** Enumerate
116
+ `git diff <prev-head>..HEAD` (the `Previous run HEAD` line of the Fix-Run
117
+ Carry block) instead of the whole stage diff, and re-check each carried
118
+ blocking finding against the current tree. The blocking/advisory taxonomy
119
+ above applies unchanged to that narrowed file set.
120
+ - **Carried findings drive the verdict.** Every carried blocking finding MUST
121
+ be re-checked and cited in the verifier result as `resolved` (with the fix
122
+ commit) or `still-failing` (verdict `FAIL`). A new blocking defect inside
123
+ the fix diff also forces `FAIL`. Files untouched since `<prev-head>` are
124
+ out of static-sweep scope — they already passed the previous full sweep.
125
+
126
+ Enforcement: the carried-finding re-check lands in the verifier result file
127
+ (Phase 5.5 convergence consumes it); a fix-run verifier result that cites no
128
+ carried findings is a contract violation the lead records via
129
+ `okstra error-log append-observed --error-type contract-violation`.
130
+
105
131
  ### DB / IO / SQL change — real-execution gate (mock-only acceptance forbidden)
106
132
 
107
133
  A mocked unit test cannot observe the SQL a query builder actually emits — `count({ col: 'FontFamily.fontFamily' })` passes a mocked suite yet throws `Unknown column` on a real database. For this class of change a green mock-only suite is therefore NOT evidence; only a run against a real (or faithful-replica) datastore is. This gate is the verifier's enforcement of that rule.
@@ -104,13 +104,13 @@
104
104
  - **Different repos** — a single okstra task **cannot** span repos: every stage worktree is a `git worktree add` in one repo's main checkout (one `<project-id>`), the run-index / manifests / registry are keyed to that one project, and the edit allowlist only covers that project's tree plus `~/.okstra/worktrees/**`. This is a structural limit, not a style preference. Therefore cross-repo work MUST be split into **a separate okstra run per repo** — never modelled as stages of one task. State, in the Stage Map `Cross-project parallelism:` line, whether those per-repo runs can proceed in parallel. To avoid re-deriving shared analysis, the second repo's run should consume the first run's relevant plan/decision artifacts as brief Source Material (see the cross-project dependency rows below).
105
105
  - **Parallel-feasibility check (mandatory for every multi-project plan):** disjoint files (S9 below) is necessary but NOT sufficient for parallelism — a cross-project API/contract/schema/deploy-order dependency forces sequencing even when no file overlaps. For each pair of projects, explicitly determine and record (in the `Cross-project parallelism:` line) whether they are independent (run in parallel) or sequenced (and the exact dependency that forces the order).
106
106
  - **Parallel-safety invariant (BLOCKING):** any two stages that are both `depends-on (none)` MUST predict disjoint file sets in their `Stage Exit Contract`. Two parallel `implementation` runs would otherwise edit the same file concurrently. Work touching a shared file must either go in one stage or be ordered with `depends-on`. Enforced by `validators/validate-implementation-plan-stages.py` check S9.
107
- - **Cross-project dependency rows (`crossProjectDependencies` array — replaces the old `## Cross-Repo Carry` appendix):** a dependency on another project (a different repo / a different top-level independently-deployable module / a published package) is recorded not as the freeform `## Cross-Repo Carry` appendix but as an **`XP-NNN` row of the structured field `crossProjectDependencies`**. Put one XP row per project dependency and fill in whichever `direction` applies (rendered as `### Cross-Project Dependencies` §5.4; a single-project plan uses an empty array). The row fields replace the old three subsections with — `requiredWork` (the concrete work the other side must build) / `verificationSignal` (the signal this run will observe) / `linkedWork` (the stage·step of this plan that is blocked until the signal is met) / `howToStart` (the exact handoff in the other repo).
108
- - `direction: upstream-precondition` — this run **waits on the other side's prerequisite work (precondition required)**: `requiredWork` = the concrete work the other side must implement first, `verificationSignal` = the signal this run must observe before proceeding (PR merged / endpoint live / version published), `linkedWork` = the stage·step of this plan that is blocked until the signal is met, `howToStart` = in the other repo, `okstra-brief-gen` (**cite this report's absolute path as Source Material** — the only permitted cross-`<PROJECT_ROOT>` read) → `okstra-run`.
109
- - `direction: downstream-carry` — this run **produces what the other side will consume (the existing carry)**: `requiredWork` = the self-contained B-portion the other side will implement next (from the other side's perspective, a new `R-NNNproposed stages·affected files), `verificationSignal` = the (this-run-delivered) signal the other side confirms before proceeding, `linkedWork` = the stage·step of this plan that delivers that signal, `howToStart` is the same as upstream. Do not hand over this run's already-`done` stages as if they were the other side's stages — include only the portion the other side still has to build.
107
+ - **Cross-project dependency rows (`crossProjectDependencies` array — replaces the old `## Cross-Repo Carry` appendix):** a dependency on another project (a different repo / a different top-level independently-deployable module / a published package) is recorded not as the freeform `## Cross-Repo Carry` appendix but as an **`XP-NNN` row of the structured field `crossProjectDependencies`**. Put one XP row per project dependency and fill in whichever `direction` applies (rendered as `### Cross-Project Dependencies` §5.4; a single-project plan uses an empty array). The row fields replace the old three subsections with — `requiredWork` (the concrete work the other side must build) / `verificationSignal` (the signal this run will observe) / `linkedWork` (the stage/step of this plan that is blocked until the signal is met) / `howToStart` (the exact handoff in the other repo).
108
+ - `direction: upstream-precondition` — this run **waits on the other side's prerequisite work (precondition required)**: `requiredWork` = the concrete work the other side must implement first, `verificationSignal` = the signal this run must observe before proceeding (PR merged / endpoint live / version published), `linkedWork` = the stage/step of this plan that is blocked until the signal is met, `howToStart` = in the other repo, `okstra-brief-gen` (**cite this report's absolute path as Source Material** — the only permitted cross-`<PROJECT_ROOT>` read) → `okstra-run`.
109
+ - `direction: downstream-carry` — this run **produces what the other side will consume (the existing carry)**: `requiredWork` = the self-contained B-portion the other side will implement next (from the other side's perspective, a new `R-NNN`, proposed stages, and affected files), `verificationSignal` = the (this-run-delivered) signal the other side confirms before proceeding, `linkedWork` = the stage/step of this plan that delivers that signal, `howToStart` is the same as upstream. Do not hand over this run's already-`done` stages as if they were the other side's stages — include only the portion the other side still has to build.
110
110
  - **A cross-repo dependency cannot be expressed with `depends-on`:** that gate only resolves commits inside a single repo's git graph (`scripts/okstra_ctl/run.py` `_resolve_stage_base_commit`), so it cannot point at a commit in another repo. Cross-repo work is split into a separate okstra run + XP row rather than a stage (the Different-repos rule). The other run is structurally independent, so it does not automatically recognise this run's completed stages as `done` (nor should it — that is this repo's work); the XP row is only a narrative input that seeds the other side's planning. This run does not write to the other repo's tree or its `.okstra/` (it emits only this report's XP rows). The old "Recognition caveat" wording has been moved into a render note (i18n `crossProjectRecognitionNote`) and is emitted automatically in the `### Cross-Project Dependencies` section.
111
111
  - **Stage exit contract is the carry surface:** keep it as narrow as possible. Wider surface = more downstream coupling.
112
112
  - dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, repo-internal sequencing)
113
- - **Cross-Project Dependencies (conditionally required):** when the plan depends on work in another project / repo / published package, add (a) a `kind: cross-project` DM row to `dependencyMigrationRisk`, and (b) a matching `XP-NNN` row to `crossProjectDependencies`. An upstream-precondition row must have concrete `requiredWork`·`verificationSignal`·`howToStart` — `validators/validate-run.py` enforces that a DM `cross-project` ⇒ at least one `direction: upstream-precondition` XP row, and the schema enforces non-empty row fields. A cross-project dependency is recorded as this structured precondition, not as a soft Recommended Next Step. A single-project plan uses an empty array.
113
+ - **Cross-Project Dependencies (conditionally required):** when the plan depends on work in another project / repo / published package, add (a) a `kind: cross-project` DM row to `dependencyMigrationRisk`, and (b) a matching `XP-NNN` row to `crossProjectDependencies`. An upstream-precondition row must have concrete `requiredWork` / `verificationSignal` / `howToStart` — `validators/validate-run.py` enforces that a DM `cross-project` ⇒ at least one `direction: upstream-precondition` XP row, and the schema enforces non-empty row fields. A cross-project dependency is recorded as this structured precondition, not as a soft Recommended Next Step. A single-project plan uses an empty array.
114
114
  - **recommendedNextSteps policy:** keep the substance of cross-project preconditions/carries in `crossProjectDependencies`, and put in `§3 Recommended Next Steps` only a pointer to that section (`§5.4 Cross-Project Dependencies`) — no double recording.
115
115
  - validation checklist (pre / mid / post) — each item is an exact command or observable outcome
116
116
  - rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
@@ -2,6 +2,7 @@
2
2
 
3
3
  - Purpose: realise the approved `implementation-planning` deliverable as actual source changes, with cross-model verification, while keeping the run reversible
4
4
  - **Run-level fixed cost:** the verifier set, Phase 5.5 convergence, and the Phase 6 report-writer run exactly once per implementation run, over this run's single stage diff — never once per step.
5
+ - **Fix run (profile carries a "Fix-Run Carry" block):** the executor's scope is the carried blocking findings plus the previous routing recommendation — it MUST NOT re-execute plan steps the previous run completed. Verifiers apply the "Fix-run incremental scope" section of `_implementation-verifier.md`; the report writer applies "Fix-run incremental authoring" in `report-writer.md`. The full validation-command re-run is NOT reduced.
5
6
  - Required workers:
6
7
  - claude
7
8
  - codex
@@ -63,5 +63,4 @@
63
63
  - full implementation design unless it is required to decide the next phase
64
64
  - **source code edits, plan authoring, builds, or deployments** — this run only classifies the work and routes it; deeper analysis and planning belong to subsequent phases
65
65
  - **writes outside `<PROJECT_ROOT>/.okstra/`** — this phase only uses okstra's artifact root. Glossary additions land in `<PROJECT_ROOT>/.okstra/glossary.md` (via `okstra-brief-gen` Step 4.5); decision drafts land in `<PROJECT_ROOT>/.okstra/decisions/` (via `implementation-planning`).
66
- - work-unit decomposition (fan-out) is in-scope for this phase — but each unit's *solution designsource
67
- edits·plan authoring remain non-goals and are handled by downstream phases
66
+ - work-unit decomposition (fan-out) is in-scope for this phase — but each unit's *solution design*, source edits, and plan authoring remain non-goals and are handled by downstream phases
@@ -61,10 +61,12 @@ CODEX = {
61
61
  PROVIDER_MAPPINGS = {"claude": CLAUDE, "antigravity": ANTIGRAVITY, "codex": CODEX}
62
62
 
63
63
  # ROLE-keyed (not provider): 1:1 with run.py recommended_role_models. "lead" uses
64
- # the claude provider; "report-writer" is role-only (None caller uses lead's).
64
+ # the claude provider; "report-writer" resolves via the claude provider too.
65
+ # report-writer defaults below the lead tier on purpose: it does not vote in
66
+ # convergence, and its wall-clock is dominated by sheer report generation.
65
67
  ROLE_DEFAULTS = {
66
68
  "lead": "opus", "claude": "opus", "codex": "gpt-5.6-sol",
67
- "antigravity": "gemini-3.1-pro", "report-writer": None,
69
+ "antigravity": "gemini-3.1-pro", "report-writer": "sonnet",
68
70
  }
69
71
 
70
72
 
@@ -30,6 +30,7 @@ from okstra_project import project_json_path, upsert_project_json
30
30
  from okstra_project.state import slugify
31
31
  from . import fix_cycles
32
32
  from .analysis_packet import build_analysis_packet
33
+ from .stage_fix_carry import derive_stage_fix_carry
33
34
  from .clarification_items import (
34
35
  attached_user_responses_section,
35
36
  clarification_response_with_sidecars,
@@ -1346,7 +1347,7 @@ def recommended_role_models() -> dict[str, str]:
1346
1347
  "claude": _default("OKSTRA_DEFAULT_CLAUDE_MODEL", default_model("claude")),
1347
1348
  "codex": _default("OKSTRA_DEFAULT_CODEX_MODEL", default_model("codex")),
1348
1349
  "antigravity": _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", default_model("antigravity")),
1349
- "report-writer": _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", lead_default),
1350
+ "report-writer": _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", default_model("report-writer")),
1350
1351
  }
1351
1352
 
1352
1353
 
@@ -1600,7 +1601,7 @@ def _write_instruction_set_sources(
1600
1601
  instruction_set.mkdir(parents=True, exist_ok=True)
1601
1602
  profile_rendered = profile_content
1602
1603
  if inp.task_type == "implementation":
1603
- profile_rendered += "\n\n{{DESIGN_PREP_CONTEXT}}"
1604
+ profile_rendered += "\n\n{{DESIGN_PREP_CONTEXT}}\n\n{{FIX_RUN_CONTEXT}}"
1604
1605
  profile_tokens = (
1605
1606
  "EXECUTOR_PROVIDER",
1606
1607
  "EXECUTOR_DISPLAY_NAME",
@@ -1613,6 +1614,7 @@ def _write_instruction_set_sources(
1613
1614
  "EXECUTOR_WORKTREE_STATUS",
1614
1615
  "EXECUTOR_WORKTREE_NOTE",
1615
1616
  "DESIGN_PREP_CONTEXT",
1617
+ "FIX_RUN_CONTEXT",
1616
1618
  "PHASE_FORBIDDEN_ACTIONS",
1617
1619
  )
1618
1620
  for key in profile_tokens:
@@ -2038,6 +2040,10 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
2038
2040
  _publish_stage_run_claim(
2039
2041
  inp, ctx, ctx_stage_map, stage_run_claim,
2040
2042
  )
2043
+ carry = derive_stage_fix_carry(
2044
+ Path(ctx["RUN_DIR"]), ctx.get("EXECUTOR_WORKTREE_PATH", ""),
2045
+ )
2046
+ ctx["FIX_RUN_CONTEXT"] = carry.as_prompt_markdown() if carry else ""
2041
2047
 
2042
2048
  if inp.render_only:
2043
2049
  # render-only entry path (e.g. okstra-run skill, in-session takeover):
@@ -0,0 +1,112 @@
1
+ """Derive the fix-run carry for an implementation stage whose previous run
2
+ ended with one or more verifier FAIL verdicts.
3
+
4
+ Pure read-side: scans the stage run directory's final-report data.json files
5
+ (the JSON SSOT written by the report writer) and the stage worktree HEAD.
6
+ Returns None when the upcoming run is not a fix run, so callers can treat
7
+ "no carry" and "first run on this stage" identically.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import re
13
+ import subprocess
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+
17
+ _REPORT_RE = re.compile(r"final-report-implementation-(\d+)\.data\.json$")
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class StageFixCarry:
22
+ prev_report_path: str
23
+ prev_report_seq: str
24
+ prev_head_commit: str
25
+ failed_verifiers: list[str]
26
+ blocking_findings: list[str]
27
+ routing_recommendation: str
28
+
29
+ def as_prompt_markdown(self) -> str:
30
+ lines = [
31
+ "## Fix-Run Carry (this run fixes a prior FAIL)",
32
+ "",
33
+ f"- Previous report (data.json SSOT): `{self.prev_report_path}`",
34
+ f"- Previous run HEAD (fix-diff base): `{self.prev_head_commit}`",
35
+ f"- Failed verifiers: {', '.join(self.failed_verifiers)}",
36
+ "- Carried blocking findings:",
37
+ ]
38
+ lines.extend(f" - {finding}" for finding in self.blocking_findings)
39
+ if self.routing_recommendation:
40
+ lines.append(
41
+ f"- Previous routing recommendation: {self.routing_recommendation}"
42
+ )
43
+ lines.extend([
44
+ "",
45
+ "Scope rules for this fix run live in the implementation sidecars "
46
+ "(`_implementation-verifier.md` § Fix-run incremental scope, "
47
+ "`report-writer.md` § Fix-run incremental authoring).",
48
+ "Lead duties: transcribe this block verbatim into every CLI "
49
+ "executor/verifier prompt (CLI processes cannot read this file), "
50
+ "and pass the previous data.json path above to the report-writer "
51
+ "dispatch so it authors incrementally.",
52
+ ])
53
+ return "\n".join(lines)
54
+
55
+
56
+ def derive_stage_fix_carry(
57
+ stage_run_dir: Path, worktree_path: str,
58
+ ) -> StageFixCarry | None:
59
+ report = _latest_report(Path(stage_run_dir))
60
+ if report is None:
61
+ return None
62
+ try:
63
+ data = json.loads(report.read_text(encoding="utf-8"))
64
+ except (OSError, json.JSONDecodeError):
65
+ return None
66
+ impl = data.get("implementation")
67
+ if not isinstance(impl, dict):
68
+ return None
69
+ failed = [
70
+ row for row in impl.get("verifierResults") or []
71
+ if isinstance(row, dict) and row.get("verdict") == "FAIL"
72
+ ]
73
+ if not failed:
74
+ return None
75
+ head = _git_head(worktree_path)
76
+ if not head:
77
+ return None
78
+ return StageFixCarry(
79
+ prev_report_path=str(report),
80
+ prev_report_seq=_REPORT_RE.search(report.name).group(1),
81
+ prev_head_commit=head,
82
+ failed_verifiers=[str(row.get("verifier", "")) for row in failed],
83
+ blocking_findings=[
84
+ str(row.get("discrepancy") or row.get("verifier") or "")
85
+ for row in failed
86
+ ],
87
+ routing_recommendation=str(impl.get("routingRecommendation") or ""),
88
+ )
89
+
90
+
91
+ def _latest_report(stage_run_dir: Path) -> Path | None:
92
+ reports_dir = stage_run_dir / "reports"
93
+ if not reports_dir.is_dir():
94
+ return None
95
+ candidates = sorted(
96
+ (p for p in reports_dir.iterdir() if _REPORT_RE.search(p.name)),
97
+ key=lambda p: _REPORT_RE.search(p.name).group(1),
98
+ )
99
+ return candidates[-1] if candidates else None
100
+
101
+
102
+ def _git_head(worktree_path: str) -> str:
103
+ if not worktree_path:
104
+ return ""
105
+ try:
106
+ out = subprocess.run(
107
+ ["git", "-C", worktree_path, "rev-parse", "HEAD"],
108
+ capture_output=True, text=True, check=False,
109
+ )
110
+ except OSError:
111
+ return ""
112
+ return out.stdout.strip() if out.returncode == 0 else ""
@@ -146,7 +146,7 @@
146
146
  ],
147
147
  "additionalProperties": false,
148
148
  "properties": {
149
- "finalConclusion": { "type": "string", "minLength": 1 },
149
+ "finalConclusion": { "type": "string", "minLength": 1, "description": "Conclusion SSOT for the whole report: at most 3 sentences. Other prose blocks reference this instead of restating it." },
150
150
  "verdictToken": { "$ref": "#/$defs/VerdictToken" },
151
151
  "direction": { "$ref": "#/$defs/Direction" },
152
152
  "approvalRequired": { "type": "boolean" },
@@ -176,14 +176,14 @@
176
176
 
177
177
  "rationale": {
178
178
  "type": "object",
179
- "description": "## 작업 배경과 근거 — reviewer-facing narrative answering the four questions in order. Each field is prose (not a table) and MUST carry at least one evidence reference (path:line, a report ID such as C-001/F-013/§5.4, or another cited source) OR an explicit insufficiency marker (e.g. '근거 불충분'). validate-run.py fails a field with neither. Required for every task-type.",
179
+ "description": "## 작업 배경과 근거 — reviewer-facing narrative answering the four questions in order. Each field is prose (not a table) and MUST carry at least one evidence reference (path:line, a report ID such as C-001/F-013/§5.4, or another cited source) OR an explicit insufficiency marker (e.g. '근거 불충분'). validate-run.py fails a field with neither. Required for every task-type. Prose budget: at most 2 sentences per field; reference verdictCard.finalConclusion and row IDs instead of restating them.",
180
180
  "required": ["motivation", "problem", "approach", "justification"],
181
181
  "additionalProperties": false,
182
182
  "properties": {
183
- "motivation": { "type": "string", "minLength": 1, "description": "왜 이 작업을 하는가 — 목표·맥락." },
184
- "problem": { "type": "string", "minLength": 1, "description": "왜 이게 문제인가 — 현재 상태의 결함을 증거로." },
185
- "approach": { "type": "string", "minLength": 1, "description": "그래서 어떤 작업이 필요한가 — 택한 방향." },
186
- "justification": { "type": "string", "minLength": 1, "description": "왜 이게 합리적 선택인가 — 대안 대비 근거." }
183
+ "motivation": { "type": "string", "minLength": 1, "description": "왜 이 작업을 하는가 — 목표·맥락. ≤2 sentences." },
184
+ "problem": { "type": "string", "minLength": 1, "description": "왜 이게 문제인가 — 현재 상태의 결함을 증거로. ≤2 sentences." },
185
+ "approach": { "type": "string", "minLength": 1, "description": "그래서 어떤 작업이 필요한가 — 택한 방향. ≤2 sentences." },
186
+ "justification": { "type": "string", "minLength": 1, "description": "왜 이게 합리적 선택인가 — 대안 대비 근거. ≤2 sentences." }
187
187
  }
188
188
  },
189
189