okstra 0.34.1 → 0.36.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 (101) hide show
  1. package/README.kr.md +26 -16
  2. package/README.md +26 -16
  3. package/docs/kr/architecture.md +59 -45
  4. package/docs/kr/cli.md +61 -18
  5. package/docs/pr-template-usage.md +65 -0
  6. package/docs/project-structure-overview.md +358 -354
  7. package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
  8. package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
  9. package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
  10. package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
  11. package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
  12. package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
  13. package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
  14. package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
  15. package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
  16. package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
  17. package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
  18. package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
  19. package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
  20. package/docs/task-process/README.md +74 -0
  21. package/docs/task-process/common-flow.md +166 -0
  22. package/docs/task-process/error-analysis.md +101 -0
  23. package/docs/task-process/final-verification.md +167 -0
  24. package/docs/task-process/implementation-planning.md +128 -0
  25. package/docs/task-process/implementation.md +149 -0
  26. package/docs/task-process/release-handoff.md +206 -0
  27. package/docs/task-process/requirements-discovery.md +115 -0
  28. package/package.json +1 -1
  29. package/runtime/BUILD.json +2 -2
  30. package/runtime/agents/SKILL.md +12 -2
  31. package/runtime/agents/workers/claude-worker.md +26 -0
  32. package/runtime/agents/workers/codex-worker.md +27 -1
  33. package/runtime/agents/workers/gemini-worker.md +27 -1
  34. package/runtime/agents/workers/report-writer-worker.md +8 -1
  35. package/runtime/bin/okstra-central.sh +6 -6
  36. package/runtime/bin/okstra-codex-exec.sh +49 -28
  37. package/runtime/bin/okstra-gemini-exec.sh +39 -21
  38. package/runtime/bin/okstra-render-final-report.py +13 -2
  39. package/runtime/bin/okstra-wrapper-status.py +155 -0
  40. package/runtime/bin/okstra.sh +2 -2
  41. package/runtime/prompts/profiles/_common-contract.md +11 -6
  42. package/runtime/prompts/profiles/error-analysis.md +3 -7
  43. package/runtime/prompts/profiles/implementation-planning.md +22 -21
  44. package/runtime/prompts/profiles/implementation.md +28 -11
  45. package/runtime/prompts/profiles/improvement-discovery.md +42 -0
  46. package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
  47. package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
  48. package/runtime/prompts/profiles/kr/final-verification.md +48 -0
  49. package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
  50. package/runtime/prompts/profiles/kr/implementation.md +144 -0
  51. package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
  52. package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
  53. package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
  54. package/runtime/prompts/profiles/release-handoff.md +1 -1
  55. package/runtime/prompts/profiles/requirements-discovery.md +8 -12
  56. package/runtime/prompts/wizard/prompts.ko.json +230 -0
  57. package/runtime/python/lib/okstra/cli.sh +2 -49
  58. package/runtime/python/lib/okstra/globals.sh +21 -21
  59. package/runtime/python/lib/okstra/interactive.sh +7 -7
  60. package/runtime/python/okstra_ctl/clarification_items.py +3 -9
  61. package/runtime/python/okstra_ctl/consumers.py +53 -0
  62. package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
  63. package/runtime/python/okstra_ctl/i18n.py +73 -0
  64. package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
  65. package/runtime/python/okstra_ctl/index.py +1 -1
  66. package/runtime/python/okstra_ctl/paths.py +23 -20
  67. package/runtime/python/okstra_ctl/render.py +147 -202
  68. package/runtime/python/okstra_ctl/render_final_report.py +53 -10
  69. package/runtime/python/okstra_ctl/run.py +292 -107
  70. package/runtime/python/okstra_ctl/run_context.py +22 -0
  71. package/runtime/python/okstra_ctl/seeding.py +186 -0
  72. package/runtime/python/okstra_ctl/wizard.py +348 -127
  73. package/runtime/python/okstra_ctl/workflow.py +21 -2
  74. package/runtime/python/okstra_ctl/worktree.py +54 -1
  75. package/runtime/python/okstra_project/resolver.py +4 -3
  76. package/runtime/python/okstra_token_usage/report.py +2 -2
  77. package/runtime/schemas/final-report-v1.0.schema.json +22 -16
  78. package/runtime/skills/okstra-brief/SKILL.md +124 -31
  79. package/runtime/skills/okstra-convergence/SKILL.md +2 -3
  80. package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
  81. package/runtime/skills/okstra-run/SKILL.md +5 -4
  82. package/runtime/skills/okstra-schedule/SKILL.md +4 -4
  83. package/runtime/skills/okstra-setup/SKILL.md +27 -0
  84. package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
  85. package/runtime/templates/okstra.CLAUDE.md +104 -0
  86. package/runtime/templates/reports/final-report.template.md +93 -98
  87. package/runtime/templates/reports/i18n/en.json +135 -0
  88. package/runtime/templates/reports/i18n/ko.json +135 -0
  89. package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
  90. package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
  91. package/runtime/templates/reports/task-brief.template.md +2 -2
  92. package/runtime/validators/lib/fixtures.sh +30 -0
  93. package/runtime/validators/lib/runners.sh +1 -1
  94. package/runtime/validators/validate-implementation-plan-stages.py +211 -0
  95. package/runtime/validators/validate-run.py +121 -26
  96. package/runtime/validators/validate-workflow.sh +2 -2
  97. package/runtime/validators/validate_improvement_report.py +275 -0
  98. package/src/config.mjs +18 -0
  99. package/src/install.mjs +41 -14
  100. package/src/setup.mjs +133 -1
  101. package/src/uninstall.mjs +21 -1
@@ -187,19 +187,35 @@ python3 "$script_dir/okstra-wrapper-status.py" \
187
187
  init "$status_path" "$(basename "$0")" "$role" "$$" "$started_ts" "$log_path" \
188
188
  >>"$log_path" 2>&1 || true
189
189
 
190
+ # Resolve caller pane id robustly. tmux normally exports both `$TMUX` and
191
+ # `$TMUX_PANE` to processes started inside a pane, but Claude Code's Bash
192
+ # tool can drop `$TMUX_PANE` while preserving `$TMUX` — which would
193
+ # silently skip the caller-pane rename below AND let `tmux split-window`
194
+ # attach the trace pane to whatever tmux currently considers active
195
+ # (not necessarily Claude's pane). When the wrapper is launched from
196
+ # Claude Code, the Claude session's pane IS the active pane at this
197
+ # moment, so falling back to `display-message -p '#{pane_id}'` recovers
198
+ # the correct id.
199
+ caller_pane="${TMUX_PANE:-}"
200
+ if [[ -z "$caller_pane" && -n "${TMUX:-}" ]]; then
201
+ caller_pane=$(tmux display-message -p '#{pane_id}' 2>/dev/null || true)
202
+ fi
203
+
190
204
  # Pane titles: worker (caller) pane gets `codex-<role>-<pid>`; the sibling
191
- # trace pane appends `-trace`. The wrapper PID disambiguates concurrent
192
- # dispatches of the same role (e.g. two `codex-worker` panes spawned in
193
- # parallel) so the operator can match worker trace at a glance.
205
+ # trace pane appends `-trace[from=<caller-pane-id>]`. The wrapper PID
206
+ # disambiguates concurrent dispatches of the same role; the embedded
207
+ # caller pane id keeps the trace worker mapping visible even if the
208
+ # worker pane's title is later overwritten by the parent process (e.g.
209
+ # Claude Code's TUI emitting OSC 2 escape sequences on its own pane).
194
210
  pane_label="codex-${role}-$$"
195
- trace_label="${pane_label}-trace"
211
+ trace_label="${pane_label}-trace[from=${caller_pane:-?}]"
196
212
 
197
213
  # Capture the caller pane's current title so the EXIT trap can restore it
198
214
  # once the wrapper returns. Empty when not in tmux or capture fails — the
199
215
  # restore step degrades to a no-op in that case.
200
216
  original_caller_title=""
201
- if [[ -n "${TMUX_PANE:-}" ]]; then
202
- original_caller_title=$(tmux display-message -p -t "$TMUX_PANE" '#{pane_title}' 2>/dev/null || true)
217
+ if [[ -n "$caller_pane" ]]; then
218
+ original_caller_title=$(tmux display-message -p -t "$caller_pane" '#{pane_title}' 2>/dev/null || true)
203
219
  fi
204
220
 
205
221
  _okstra_status_finish() {
@@ -210,16 +226,16 @@ _okstra_status_finish() {
210
226
  python3 "$script_dir/okstra-wrapper-status.py" \
211
227
  finish "$status_path" "$exit_code" "$ended_ts" "$duration_ms" \
212
228
  >>"$log_path" 2>&1 || true
213
- if [[ -n "${TMUX_PANE:-}" && -n "$original_caller_title" ]]; then
214
- tmux select-pane -t "$TMUX_PANE" -T "$original_caller_title" 2>/dev/null || true
229
+ if [[ -n "$caller_pane" && -n "$original_caller_title" ]]; then
230
+ tmux select-pane -t "$caller_pane" -T "$original_caller_title" 2>/dev/null || true
215
231
  fi
216
232
  }
217
233
  trap _okstra_status_finish EXIT
218
234
 
219
235
  # Label the caller (worker) pane now that the restore trap is armed. Any
220
236
  # failure after this point still rewinds the title to its prior value.
221
- if [[ -n "${TMUX_PANE:-}" ]]; then
222
- tmux select-pane -t "$TMUX_PANE" -T "$pane_label" 2>/dev/null || true
237
+ if [[ -n "$caller_pane" ]]; then
238
+ tmux select-pane -t "$caller_pane" -T "$pane_label" 2>/dev/null || true
223
239
  fi
224
240
 
225
241
  # When a tmux session is reachable, split a sibling pane that tails the live
@@ -227,35 +243,40 @@ fi
227
243
  # for the wrapper to exit. This fires in every phase the wrapper is invoked
228
244
  # from (analysis, error-analysis, implementation-planning, implementation,
229
245
  # …) — long-running codex dispatches are not implementation-specific. The
230
- # new pane carries the title `codex-<role>-<pid>-trace` (matching the
231
- # caller pane's `codex-<role>-<pid>` label so workertrace pairs are
232
- # greppable); `role` is the optional 5th positional arg (defaults to
233
- # `worker`); callers that dispatch a different role (e.g. `executor`) must
234
- # pass it explicitly. The `<pid>` suffix is the wrapper's PID and
235
- # disambiguates concurrent dispatches of the same role. The pane uses
236
- # `tail -F`
237
- # (follow-by-name) so it survives any truncation a re-dispatch performs on
238
- # the same log path. Failures are tolerated silently: missing $TMUX, a tmux
239
- # that refuses to split (size constraints, locked client), or a stale socket
246
+ # new pane carries the title `codex-<role>-<pid>-trace[from=<caller-pane>]`
247
+ # so the operator can map traceworker by pane id even when the worker
248
+ # pane title is later overwritten by Claude Code. The split is explicitly
249
+ # anchored to the caller pane (`-t "$caller_pane"`) to avoid attaching to
250
+ # tmux's idle active pane when `$TMUX_PANE` was missing. `role` is the
251
+ # optional 5th positional arg (defaults to `worker`); callers that
252
+ # dispatch a different role (e.g. `executor`) must pass it explicitly.
253
+ # The `<pid>` suffix is the wrapper's PID and disambiguates concurrent
254
+ # dispatches of the same role. The pane uses `tail -F` (follow-by-name)
255
+ # so it survives any truncation a re-dispatch performs on the same log
256
+ # path. Failures are tolerated silently: missing $TMUX, a tmux that
257
+ # refuses to split (size constraints, locked client), or a stale socket
240
258
  # all degrade to "log file is still on disk; the operator can tail it
241
- # manually from any terminal." The wrapper does NOT switch focus to the new
242
- # pane — control returns to the caller's pane via `tmux last-pane`.
259
+ # manually from any terminal." The wrapper does NOT switch focus to the
260
+ # new pane — control returns to the caller's pane via `tmux last-pane`.
243
261
  if [[ -n "${TMUX:-}" ]]; then
244
- trace_pane=$(tmux split-window -h -P -F '#{pane_id}' \
245
- -c "$(dirname "$log_path")" \
262
+ split_args=(-h -P -F '#{pane_id}' -c "$(dirname "$log_path")")
263
+ if [[ -n "$caller_pane" ]]; then
264
+ split_args+=(-t "$caller_pane")
265
+ fi
266
+ trace_pane=$(tmux split-window "${split_args[@]}" \
246
267
  "tail -F $(printf '%q' "$log_path")" 2>/dev/null || true)
247
268
  if [[ -n "$trace_pane" ]]; then
248
269
  tmux select-pane -t "$trace_pane" -T "$trace_label" 2>/dev/null || true
249
270
  tmux last-pane 2>/dev/null || true
250
271
  # Register the spawned pane so the `SessionEnd` hook (see
251
272
  # `okstra-trace-cleanup.sh`) can kill it when the caller's Claude
252
- # session exits. Scope by caller `$TMUX_PANE` — the pane Claude itself
253
- # is attached to — so concurrent Claude instances in the same tmux
273
+ # session exits. Scope by `$caller_pane` — the pane Claude itself is
274
+ # attached to — so concurrent Claude instances in the same tmux
254
275
  # session do not stomp each other's trace panes.
255
- if [[ -n "${TMUX_PANE:-}" ]]; then
276
+ if [[ -n "$caller_pane" ]]; then
256
277
  registry_dir="${TMPDIR:-/tmp}/okstra-trace-panes"
257
278
  mkdir -p "$registry_dir" 2>/dev/null || true
258
- safe_pane="${TMUX_PANE//[^A-Za-z0-9]/_}"
279
+ safe_pane="${caller_pane//[^A-Za-z0-9]/_}"
259
280
  printf '%s\n' "$trace_pane" >> "$registry_dir/${safe_pane}.list" 2>/dev/null || true
260
281
  fi
261
282
  fi
@@ -136,19 +136,31 @@ python3 "$script_dir/okstra-wrapper-status.py" \
136
136
  init "$status_path" "$(basename "$0")" "$role" "$$" "$started_ts" "$log_path" \
137
137
  >>"$log_path" 2>&1 || true
138
138
 
139
+ # Resolve caller pane id robustly. See `okstra-codex-exec.sh` for the full
140
+ # rationale — kept in lock-step: tmux normally exports both `$TMUX` and
141
+ # `$TMUX_PANE`, but Claude Code's Bash tool can drop `$TMUX_PANE` while
142
+ # preserving `$TMUX`, which silently skips the caller-pane rename and
143
+ # lets `tmux split-window` attach to whatever tmux considers active.
144
+ caller_pane="${TMUX_PANE:-}"
145
+ if [[ -z "$caller_pane" && -n "${TMUX:-}" ]]; then
146
+ caller_pane=$(tmux display-message -p '#{pane_id}' 2>/dev/null || true)
147
+ fi
148
+
139
149
  # Pane titles: worker (caller) pane gets `gemini-<role>-<pid>`; the sibling
140
- # trace pane appends `-trace`. The wrapper PID disambiguates concurrent
141
- # dispatches of the same role (e.g. two `gemini-worker` panes spawned in
142
- # parallel) so the operator can match worker trace at a glance.
150
+ # trace pane appends `-trace[from=<caller-pane-id>]`. The wrapper PID
151
+ # disambiguates concurrent dispatches of the same role; the embedded
152
+ # caller pane id keeps the trace worker mapping visible even if the
153
+ # worker pane's title is later overwritten by the parent process (e.g.
154
+ # Claude Code's TUI emitting OSC 2 escape sequences on its own pane).
143
155
  pane_label="gemini-${role}-$$"
144
- trace_label="${pane_label}-trace"
156
+ trace_label="${pane_label}-trace[from=${caller_pane:-?}]"
145
157
 
146
158
  # Capture the caller pane's current title so the EXIT trap can restore it
147
159
  # once the wrapper returns. Empty when not in tmux or capture fails — the
148
160
  # restore step degrades to a no-op in that case.
149
161
  original_caller_title=""
150
- if [[ -n "${TMUX_PANE:-}" ]]; then
151
- original_caller_title=$(tmux display-message -p -t "$TMUX_PANE" '#{pane_title}' 2>/dev/null || true)
162
+ if [[ -n "$caller_pane" ]]; then
163
+ original_caller_title=$(tmux display-message -p -t "$caller_pane" '#{pane_title}' 2>/dev/null || true)
152
164
  fi
153
165
 
154
166
  _okstra_status_finish() {
@@ -159,40 +171,46 @@ _okstra_status_finish() {
159
171
  python3 "$script_dir/okstra-wrapper-status.py" \
160
172
  finish "$status_path" "$exit_code" "$ended_ts" "$duration_ms" \
161
173
  >>"$log_path" 2>&1 || true
162
- if [[ -n "${TMUX_PANE:-}" && -n "$original_caller_title" ]]; then
163
- tmux select-pane -t "$TMUX_PANE" -T "$original_caller_title" 2>/dev/null || true
174
+ if [[ -n "$caller_pane" && -n "$original_caller_title" ]]; then
175
+ tmux select-pane -t "$caller_pane" -T "$original_caller_title" 2>/dev/null || true
164
176
  fi
165
177
  }
166
178
  trap _okstra_status_finish EXIT
167
179
 
168
180
  # Label the caller (worker) pane now that the restore trap is armed. Any
169
181
  # failure after this point still rewinds the title to its prior value.
170
- if [[ -n "${TMUX_PANE:-}" ]]; then
171
- tmux select-pane -t "$TMUX_PANE" -T "$pane_label" 2>/dev/null || true
182
+ if [[ -n "$caller_pane" ]]; then
183
+ tmux select-pane -t "$caller_pane" -T "$pane_label" 2>/dev/null || true
172
184
  fi
173
185
 
174
186
  # When a tmux session is reachable, split a sibling pane tailing the log so
175
187
  # the operator can watch progress live. This fires in every phase the
176
188
  # wrapper is invoked from — long-running gemini dispatches are not
177
- # implementation-specific. Title `gemini-<role>-<pid>-trace` (matching the
178
- # caller pane's `gemini-<role>-<pid>` label so workertrace pairs are
179
- # greppable). `role` is the optional 5th positional arg (defaults to
180
- # `worker`); callers that dispatch a different role must pass it
181
- # explicitly. The `<pid>` suffix is the wrapper's PID and disambiguates
182
- # concurrent dispatches of the same role. See the codex wrapper for the
183
- # full design rationale and the silent-degrade failure model.
189
+ # implementation-specific. Title `gemini-<role>-<pid>-trace[from=<caller-pane>]`
190
+ # so the operator can map traceworker by pane id even when the worker
191
+ # pane title is later overwritten by Claude Code. The split is explicitly
192
+ # anchored to the caller pane to avoid attaching to tmux's idle active
193
+ # pane when `$TMUX_PANE` was missing. `role` is the optional 5th
194
+ # positional arg (defaults to `worker`); callers that dispatch a
195
+ # different role must pass it explicitly. The `<pid>` suffix is the
196
+ # wrapper's PID and disambiguates concurrent dispatches of the same role.
197
+ # See the codex wrapper for the full design rationale and the
198
+ # silent-degrade failure model.
184
199
  if [[ -n "${TMUX:-}" ]]; then
185
- trace_pane=$(tmux split-window -h -P -F '#{pane_id}' \
186
- -c "$(dirname "$log_path")" \
200
+ split_args=(-h -P -F '#{pane_id}' -c "$(dirname "$log_path")")
201
+ if [[ -n "$caller_pane" ]]; then
202
+ split_args+=(-t "$caller_pane")
203
+ fi
204
+ trace_pane=$(tmux split-window "${split_args[@]}" \
187
205
  "tail -F $(printf '%q' "$log_path")" 2>/dev/null || true)
188
206
  if [[ -n "$trace_pane" ]]; then
189
207
  tmux select-pane -t "$trace_pane" -T "$trace_label" 2>/dev/null || true
190
208
  tmux last-pane 2>/dev/null || true
191
209
  # See `okstra-codex-exec.sh` for the registry rationale — kept in lock-step.
192
- if [[ -n "${TMUX_PANE:-}" ]]; then
210
+ if [[ -n "$caller_pane" ]]; then
193
211
  registry_dir="${TMPDIR:-/tmp}/okstra-trace-panes"
194
212
  mkdir -p "$registry_dir" 2>/dev/null || true
195
- safe_pane="${TMUX_PANE//[^A-Za-z0-9]/_}"
213
+ safe_pane="${caller_pane//[^A-Za-z0-9]/_}"
196
214
  printf '%s\n' "$trace_pane" >> "$registry_dir/${safe_pane}.list" 2>/dev/null || true
197
215
  fi
198
216
  fi
@@ -26,8 +26,9 @@ _HERE = Path(__file__).resolve().parent
26
26
  # scripts; for in-repo invocation we add ``scripts/`` explicitly.
27
27
  sys.path.insert(0, str(_HERE))
28
28
 
29
+ from okstra_ctl.i18n import SUPPORTED_LANGS # noqa: E402
29
30
  from okstra_ctl.render_final_report import ( # noqa: E402
30
- RenderError,
31
+ FinalReportRenderError,
31
32
  render_to_file,
32
33
  )
33
34
 
@@ -68,6 +69,15 @@ def main(argv: list[str]) -> int:
68
69
  "the repo-local copy."
69
70
  ),
70
71
  )
72
+ parser.add_argument(
73
+ "--report-language",
74
+ choices=list(SUPPORTED_LANGS),
75
+ default=None,
76
+ help=(
77
+ "Override the language passed into the renderer. When omitted, "
78
+ "the renderer reads data.json.meta.reportLanguage (fallback 'en')."
79
+ ),
80
+ )
71
81
  parser.add_argument(
72
82
  "--force",
73
83
  action="store_true",
@@ -88,8 +98,9 @@ def main(argv: list[str]) -> int:
88
98
  args.data,
89
99
  output,
90
100
  template_path=args.template,
101
+ report_language=args.report_language,
91
102
  )
92
- except RenderError as exc:
103
+ except FinalReportRenderError as exc:
93
104
  print(f"error: {exc}", file=sys.stderr)
94
105
  return 1
95
106
 
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ """okstra-wrapper-status.py — heartbeat sidecar writer for codex/gemini wrappers.
3
+
4
+ The codex/gemini wrappers (`okstra-codex-exec.sh`, `okstra-gemini-exec.sh`)
5
+ dispatch a long-running CLI under `Bash(run_in_background: true)` and rely on
6
+ `BashOutput` polling for liveness. That polling stream only carries stdout
7
+ plus a binary `running`/`completed` state. Several recovery decisions need
8
+ more — specifically, "did this wrapper start at all, when, and how did it
9
+ finish?" — so the wrappers write a small JSON sidecar at
10
+ `<prompt-path>.status.json` that survives independent of the polling channel.
11
+
12
+ Consumers:
13
+
14
+ * `codex-worker` / `gemini-worker` step 8c: read `log_path` to capture a
15
+ diagnostic tail when `exit_code == 0` but the canonical Result file is
16
+ absent.
17
+ * Lead: cross-check `started_ts` / `ended_ts` to distinguish "wrapper hung
18
+ before CLI launched" from "CLI finished but never wrote artifact" when
19
+ applying the redispatch policy (see okstra-team-contract "Lead Redispatch
20
+ Policy on Result-Missing").
21
+
22
+ Failures are deliberately non-fatal for the caller — the wrapper's main
23
+ job is to run the underlying CLI; a missing sidecar must not break that.
24
+ On any error the script prints a one-line diagnostic to stderr and exits 0.
25
+
26
+ Schema (schemaVersion 1):
27
+
28
+ {
29
+ "schemaVersion": 1,
30
+ "wrapper": "<basename of caller>",
31
+ "role": "<worker|executor|verifier|...>",
32
+ "pid": <int — wrapper process pid at init time>,
33
+ "started_ts": <epoch seconds>,
34
+ "log_path": "<absolute path to the wrapper live log>",
35
+ "stage": "started" | "exited",
36
+ "exit_code": <int, only when stage=exited>,
37
+ "ended_ts": <epoch seconds, only when stage=exited>,
38
+ "duration_ms": <int, only when stage=exited>,
39
+ "timeout": <bool, only when killed by idle-watchdog>,
40
+ "idle_at_ts": <epoch seconds, only when timeout>,
41
+ "idle_seconds": <int, only when timeout>,
42
+ "terminated_by": "idle-watchdog" (only when timeout)
43
+ }
44
+
45
+ CLI:
46
+
47
+ okstra-wrapper-status.py init <status-path> <wrapper> <role> <pid> <started-ts> <log-path>
48
+ okstra-wrapper-status.py finish <status-path> <exit-code> <ended-ts> <duration-ms>
49
+ okstra-wrapper-status.py timeout <status-path> <idle-at-ts> <idle-seconds>
50
+ """
51
+ from __future__ import annotations
52
+
53
+ import json
54
+ import os
55
+ import sys
56
+
57
+
58
+ def warn(msg: str) -> None:
59
+ print(f"okstra-wrapper-status: {msg}", file=sys.stderr)
60
+
61
+
62
+ def atomic_write(path: str, doc: dict) -> None:
63
+ tmp = path + ".tmp"
64
+ with open(tmp, "w", encoding="utf-8") as f:
65
+ json.dump(doc, f, ensure_ascii=False, indent=2)
66
+ f.write("\n")
67
+ os.replace(tmp, path)
68
+
69
+
70
+ def cmd_init(argv: list[str]) -> None:
71
+ if len(argv) != 6:
72
+ warn("init expects: <status-path> <wrapper> <role> <pid> <started-ts> <log-path>")
73
+ return
74
+ status_path, wrapper, role, pid, started_ts, log_path = argv
75
+ doc = {
76
+ "schemaVersion": 1,
77
+ "wrapper": wrapper,
78
+ "role": role,
79
+ "pid": int(pid),
80
+ "started_ts": int(started_ts),
81
+ "log_path": log_path,
82
+ "stage": "started",
83
+ }
84
+ try:
85
+ atomic_write(status_path, doc)
86
+ except OSError as exc:
87
+ warn(f"init: failed to write {status_path}: {exc}")
88
+
89
+
90
+ def cmd_finish(argv: list[str]) -> None:
91
+ if len(argv) != 4:
92
+ warn("finish expects: <status-path> <exit-code> <ended-ts> <duration-ms>")
93
+ return
94
+ status_path, exit_code, ended_ts, duration_ms = argv
95
+ try:
96
+ with open(status_path, "r", encoding="utf-8") as f:
97
+ doc = json.load(f)
98
+ except FileNotFoundError:
99
+ warn(f"finish: sidecar absent at {status_path}; skipping")
100
+ return
101
+ except (OSError, json.JSONDecodeError) as exc:
102
+ warn(f"finish: failed to read {status_path}: {exc}")
103
+ return
104
+ doc["stage"] = "exited"
105
+ doc["exit_code"] = int(exit_code)
106
+ doc["ended_ts"] = int(ended_ts)
107
+ doc["duration_ms"] = int(duration_ms)
108
+ try:
109
+ atomic_write(status_path, doc)
110
+ except OSError as exc:
111
+ warn(f"finish: failed to write {status_path}: {exc}")
112
+
113
+
114
+ def cmd_timeout(argv: list[str]) -> None:
115
+ if len(argv) != 3:
116
+ warn("timeout expects: <status-path> <idle-at-ts> <idle-seconds>")
117
+ return
118
+ status_path, idle_at, idle_seconds = argv
119
+ try:
120
+ with open(status_path, "r", encoding="utf-8") as f:
121
+ doc = json.load(f)
122
+ except FileNotFoundError:
123
+ warn(f"timeout: sidecar absent at {status_path}; skipping")
124
+ return
125
+ except (OSError, json.JSONDecodeError) as exc:
126
+ warn(f"timeout: failed to read {status_path}: {exc}")
127
+ return
128
+ doc["timeout"] = True
129
+ doc["idle_at_ts"] = int(idle_at)
130
+ doc["idle_seconds"] = int(idle_seconds)
131
+ doc["terminated_by"] = "idle-watchdog"
132
+ try:
133
+ atomic_write(status_path, doc)
134
+ except OSError as exc:
135
+ warn(f"timeout: failed to write {status_path}: {exc}")
136
+
137
+
138
+ def main(argv: list[str]) -> int:
139
+ if len(argv) < 2:
140
+ warn("missing subcommand (init|finish|timeout)")
141
+ return 0
142
+ sub = argv[1]
143
+ if sub == "init":
144
+ cmd_init(argv[2:])
145
+ elif sub == "finish":
146
+ cmd_finish(argv[2:])
147
+ elif sub == "timeout":
148
+ cmd_timeout(argv[2:])
149
+ else:
150
+ warn(f"unknown subcommand: {sub}")
151
+ return 0
152
+
153
+
154
+ if __name__ == "__main__":
155
+ sys.exit(main(sys.argv))
@@ -68,7 +68,7 @@ if [[ "$ASSUME_YES" != "true" ]] && [[ -t 0 ]] && [[ -t 1 ]]; then
68
68
  cat >&2 <<CONFIRM_EOF
69
69
  okstra execution summary:
70
70
  render only: ${RENDER_ONLY}
71
- task type: ${ANALYSIS_TYPE}
71
+ task type: ${TASK_TYPE}
72
72
  project id: ${PROJECT_ID}
73
73
  project root: ${PROJECT_ROOT}
74
74
  task group: ${TASK_GROUP}
@@ -103,7 +103,7 @@ PY_ARGS=(
103
103
  --project-id "$PROJECT_ID"
104
104
  --task-group "$TASK_GROUP"
105
105
  --task-id "$TASK_ID"
106
- --task-type "$ANALYSIS_TYPE"
106
+ --task-type "$TASK_TYPE"
107
107
  --task-brief "$BRIEF_PATH"
108
108
  )
109
109
  [[ -n "${DIRECTIVE-}" ]] && PY_ARGS+=(--directive "$DIRECTIVE")
@@ -17,8 +17,12 @@ profile document.
17
17
  - **Phase 5.5 (convergence — peer review by workers)**: the lead replays each analyser's findings to the *other* analysers and collects `AGREE` / `DISAGREE` / `SUPPLEMENT` verdicts across up to `effectiveMaxRounds` rounds. Workers act as peer reviewers of each other's findings in this phase; the lead mediates but does not vote. See `skills/okstra-convergence/SKILL.md` for the round protocol, queue invariants, and final classification (`full-consensus` / `partial-consensus` / `contested` / `worker-unique`).
18
18
  - Do NOT conclude "no peer review happens" from the roster alone — every profile that lists ≥2 analyser workers runs convergence by default (`convergence.enabled=true` in `task-manifest.json`).
19
19
  - Tooling — read-only MCP availability (shared):
20
- - the read-only MCP servers declared in the task brief's `## Available MCP Servers` section may be queried as a read-only cross-check; that section is the canonical source of which servers and tools exist for this run, and any MCP-derived finding MUST cite server, table, and the SELECT used. MCP MUST NEVER be used as a write path — schema/data mutations go through repository migration files reviewed by humans.
21
- - Authority & permissions assumption (HARD RULE applies to every okstra task-type):
20
+ - MCP is not implicit okstra context. Query an MCP server only when the task brief explicitly lists it as source material for this run. Any MCP-derived finding MUST cite server, table, and the SELECT used. MCP MUST NEVER be used as a write path — schema/data mutations go through repository migration files reviewed by humans.
21
+ - Resource boundary (sharedartifact-home rule):
22
+ - Okstra-owned project artifacts live only under `<PROJECT_ROOT>/.project-docs/okstra/**`. Treat `project.json`, task bundles, run artifacts, `glossary.md`, and `decisions/` under that subtree as the canonical okstra memory.
23
+ - Treat paths outside `<PROJECT_ROOT>/.project-docs/okstra/**` as source material only when the task brief's `Source Material` or `Reporter Confirmations` explicitly cites them; they never become okstra memory.
24
+ - Create, modify, or delete only inside `<PROJECT_ROOT>/.project-docs/okstra/**` unless the brief verbatim requests a specific non-okstra edit. The phase performing that edit must quote the user instruction in its report. Implementation source edits also require the approved implementation plan.
25
+ - Authority & permissions assumption (applies to every okstra task-type):
22
26
  - **Assume the user (and their team) holds full authority and every permission required for the anticipated, in-flight, or follow-up work.** Treat external approvals, third-party access grants, role/IAM permissions, organisational sign-off, legal/compliance review, vendor coordination, and "verify access exists" steps as already satisfied unless the task brief explicitly states otherwise.
23
27
  - Do NOT add such items to routing decisions, missing-materials lists, clarification questions, option trade-offs, dependency/migration risk, validation checklists, rollout plans, acceptance blockers, residual risks, release recommendations, the `## 5. Clarification Items` table, or any day/effort estimate. They are not legitimate sources of schedule extension.
24
28
  - Internal okstra phase handoffs (e.g. the `User Approval Request` block in `implementation-planning`) are unaffected — those are the user themselves approving and proceed without external coordination.
@@ -54,7 +58,7 @@ profile document.
54
58
  - `intent-check:` → `Kind=decision`, recommended answer = reporter confirmation. NEVER silently resolve an `intent-check:` by inference at this layer.
55
59
  - `terminology:` → `Kind=decision`, recommended answer = canonical term from `<PROJECT_ROOT>/.project-docs/okstra/glossary.md` (or "extend okstra glossary via brief Step 4.5").
56
60
  - `conversion-block:` → `Kind=decision`, recommended answer = "보고자에게 직접 확인". The brief is explicitly signalling that translation failed; further inference is forbidden until the reporter clarifies.
57
- - `adr-candidate:` → handled by `implementation-planning`; carry forward without modification. Approved decision files land at `<PROJECT_ROOT>/.project-docs/okstra/decisions/<NNNN>-<slug>.md` (okstra-internal), never at external `<PROJECT_ROOT>/docs/adr/`.
61
+ - `adr-candidate:` → handled by `implementation-planning`; carry forward without modification. Approved decision files land only at `<PROJECT_ROOT>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`.
58
62
  - `general:` → free-form; classify per the standard `Clarification Items` rules.
59
63
  - Any decision in this run that contradicts the brief's `Source Material` must be raised back to the reporter via a `Clarification Items` row; it must NOT be silently overridden. Disagreement with the reporter is allowed only after the row is resolved.
60
64
  - This contract is the single authority on brief consumption. Phase-specific addenda may *tighten* these rules but may not relax them.
@@ -65,7 +69,8 @@ profile document.
65
69
  - section 5 is a **single unified table** per `final-report-template.md`. Every clarification item — whether the user must attach a file, choose between options, or supply a single number/path — is one row of that table. Do not split it into sub-sections (`5.1 추가 자료 요청` / `5.2 사용자 확인 질문` / `4.5.9 Open Questions` are removed and the validator fails reports that reintroduce them), do not create a parallel table elsewhere in the report, and do not duplicate the same item into the top-of-report `User Approval Request (사용자 승인 게이트)` block or any other section.
66
70
  - each row's `Kind` column picks one of `{material, decision, data-point}`: `material` for files / snapshots / logs / screenshots the user must attach (the `User input` cell will hold a path or URL); `decision` for choices and yes/no confirmations only the user can make; `data-point` for a single number, ID, date, or short string the user can answer inline. Items that mix "yes/no + file path if yes" are one row of `Kind=material` with the combined expectation written into `Expected form`.
67
71
  - each row's `Blocks` column picks one of `{approval, next-phase, none}`. `approval` is reserved for items that gate an approval action, especially the `implementation-planning` User Approval Request; outside `implementation-planning`, unresolved brief reporter-confirmation rows use `next-phase` instead. `next-phase` blocks the next run from starting cleanly. `none` is informational/audit-only.
68
- - write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the shape of the answer (예/아니오, 보기 중 하나, 숫자/날짜, 파일 경로, 짧은 서술 등); supply concrete option choices when applicable.
72
+ - write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the answer shape (예/아니오, 보기 중 하나, 숫자/날짜, 파일 경로, 짧은 서술 등); supply concrete option choices when applicable.
73
+ - if a phase requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 8-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: <answer> — <rationale>; Alternatives: <options>`. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns.
69
74
  - the same `final-report.md` file is the canonical artifact carried into the next run; the user appends answers inline before rerunning. The preferred turn-around is `scripts/okstra.sh --resume-clarification --task-key <project-id>:<task-group>:<task-id>` (opens the latest report in `$EDITOR`, then auto-reruns the same phase with `--clarification-response` carry-in). The lower-level form `--clarification-response <path>` remains available for scripted runs.
70
75
  - if a clarification response was carried in for this run, render the conditional `## 0. Clarification Response Carried In From Previous Run` section (the template's `RENDER_IF` guard activates it), walk every `C-*` row of the prior report's `## 5. Clarification Items` table, reconcile each one against new evidence, and update its `Status` to `resolved` or `obsolete` before issuing the next decision/verdict. When no carry-in path was provided, omit the `## 0.` heading entirely — the validator fails reports that emit an empty Section 0 stub (e.g. "No prior clarification response was provided for this run.").
71
76
  - Verdict Card (shared — applies to every final-report regardless of profile):
@@ -78,8 +83,8 @@ profile document.
78
83
  - Reading Confirmation lines (one short line per input file confirming end-to-end reading) live in the **worker audit sidecar** at `runs/<task-type>/worker-results/<worker>-audit-<task-type>-<seq>.md`, NOT in the worker's main worker-results file. The worker-results body starts at section 1 (Findings). The validator fails worker-results files that contain a `## 0. Reading Confirmation` heading.
79
84
  - The audit sidecar carries any other meta the worker wants to log (tool-call counts, MCP query summaries, timing notes). The lead's final-report does NOT duplicate this content — it is consumed by the validator and by post-run audit tooling, not by end-user readers.
80
85
 
81
- - Markdown authoring (shared — applies to every markdown document produced by the lead or any worker, including final-reports, worker-results, briefs, and ad-hoc notes):
82
- - every document must begin with an `Index` section.
86
+ - Markdown authoring (shared — applies to markdown documents not already governed by an okstra template/schema):
87
+ - ad-hoc markdown documents should begin with an `Index` section. Template-governed artifacts such as final-reports, worker-results, and briefs follow their own schema first.
83
88
  - include only information necessary to fulfill the user's stated purpose and directly related requirements.
84
89
  - follow only the sections, format, tone, and scope specified by the user, plus the required `Index` section.
85
90
  - when writing task instructions or work orders, define the scope of work clearly and specifically, including deliverables, acceptance criteria, and verification steps when relevant.
@@ -9,11 +9,7 @@
9
9
  - gemini — when added to the roster it joins the analyser set; omitted by default
10
10
  {{INCLUDE:_common-contract.md}}
11
11
  - Brief consumption (phase-specific addendum — shared rules live in `_common-contract.md` under "Brief handoff contract"):
12
- - **Precondition check (BLOCKING — runs before any analysis)**: read the brief's frontmatter `reporter-confirmations:` field and inspect every `Open Questions` row prefixed `intent-check:` / `conversion-block:` for the `[CONFIRMED …]` marker.
13
- - `reporter-confirmations: complete` → proceed normally.
14
- - `reporter-confirmations: partial` → proceed; treat still-unmarked `intent-check:` / `conversion-block:` rows per the `skipped` branch below.
15
- - `reporter-confirmations: skipped` (or `partial` with remainder) → do NOT silently infer the missing answers. Promote each unmarked `intent-check:` / `conversion-block:` row into this run's `## 5. Clarification Items` as `Kind=decision, Blocks=next-phase`, with the recommended answer drawn from the brief's matching `intent-inference` / `conversion-block:` text and clearly labelled `보고자 직접 확인 권장`. Then proceed with the root-cause analysis using the inference as a *hypothesis* only.
16
- - `reporter-confirmations: pending` (or field missing) → ABORT analysis. Write only `## 0. Reporter Confirmation Required` summarising which rows are pending and stop. The final report carries `Blocks=next-phase`.
12
+ - Apply the shared reporter-confirmation precondition exactly as written. In this phase, unresolved `intent-check:` / `conversion-block:` rows use `Blocks=next-phase`; any unconfirmed inference may be used as a labelled hypothesis only.
17
13
  - the reporter's symptom description in `Source Material` is the ground truth for what to reproduce. Do not paraphrase it when stating the symptom in the report; quote it.
18
14
  - any `intent-inference` augmentation that re-characterises the symptom (e.g. classifying "가끔 안 됨" as "intermittent failure on a specific code path") is a **hypothesis**, not a confirmed symptom. If `[CONFIRMED …]` appears on the matching `intent-check:` row, treat the confirmation as the symptom; otherwise, follow the precondition's `skipped` branch above and keep the inference labelled as hypothesis in the root-cause analysis.
19
15
  - `conversion-block:` rows mean the brief could not map a reporter statement to project vocabulary; never attempt to invent the missing mapping in this phase — the precondition above already handled them.
@@ -31,9 +27,9 @@
31
27
  - Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
32
28
  - if any blocking uncertainty remains at the time of writing the final report, populate `## 5. Clarification Items` in `final-report-template.md` (a single unified table; `Blocks=next-phase` for items the next run cannot start without)
33
29
  - prefer plain Korean over abbreviations (e.g. write "초당 평균 요청 수" instead of "QPS", "재현 절차" instead of "repro")
34
- - every clarification row carries a `Recommended` answer + one-line rationale; rows that lack a recommendation are rejected as half-formed.
30
+ - every clarification row carries a recommended answer + one-line rationale inside the `Expected form` cell; rows that lack a recommendation are rejected as half-formed.
35
31
  - **Codebase-first ambiguity resolution (defect rule)**: any ambiguity about repro, file behavior, or symbol semantics that can be answered by `Read` / `Grep` / log inspection MUST be resolved that way and recorded with file:line (or log-line) evidence. Writing a clarification row for something the codebase or shipped logs already answer is a defect of this phase.
36
- - **`evidence-checked:` cell required**: every clarification row carries an `evidence-checked: <path:line> | none` cell. `evidence-checked: <path:line>` means the codebase / log / reproducer was inspected and the row records what was found. `evidence-checked: none` is allowed ONLY when the row's nature is "only the reporter can answer this" (reporter-side data, business priority, environment they observed); the row body must state which one in one line. A row with `evidence-checked: none` that *could* have been answered by code or logs is a defect.
32
+ - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none <reporter-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only the reporter can answer this" (reporter-side data, business priority, environment they observed). A row with `none` that *could* have been answered by code or logs is a defect.
37
33
  - Non-goals:
38
34
  - implementation details unless they are necessary to validate the cause
39
35
  - **source code edits, builds, migrations, or deployments** — this run produces evidence and cause analysis only; the fix belongs to a later `implementation-planning` run followed by an `implementation` run