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.
- package/README.kr.md +26 -16
- package/README.md +26 -16
- package/docs/kr/architecture.md +59 -45
- package/docs/kr/cli.md +61 -18
- package/docs/pr-template-usage.md +65 -0
- package/docs/project-structure-overview.md +358 -354
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +1 -1
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +1 -1
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +1 -1
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +1501 -0
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +1267 -0
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +1007 -0
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +720 -0
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +681 -0
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +1691 -0
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +383 -0
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +320 -0
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +299 -0
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +335 -0
- package/docs/task-process/README.md +74 -0
- package/docs/task-process/common-flow.md +166 -0
- package/docs/task-process/error-analysis.md +101 -0
- package/docs/task-process/final-verification.md +167 -0
- package/docs/task-process/implementation-planning.md +128 -0
- package/docs/task-process/implementation.md +149 -0
- package/docs/task-process/release-handoff.md +206 -0
- package/docs/task-process/requirements-discovery.md +115 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/SKILL.md +12 -2
- package/runtime/agents/workers/claude-worker.md +26 -0
- package/runtime/agents/workers/codex-worker.md +27 -1
- package/runtime/agents/workers/gemini-worker.md +27 -1
- package/runtime/agents/workers/report-writer-worker.md +8 -1
- package/runtime/bin/okstra-central.sh +6 -6
- package/runtime/bin/okstra-codex-exec.sh +49 -28
- package/runtime/bin/okstra-gemini-exec.sh +39 -21
- package/runtime/bin/okstra-render-final-report.py +13 -2
- package/runtime/bin/okstra-wrapper-status.py +155 -0
- package/runtime/bin/okstra.sh +2 -2
- package/runtime/prompts/profiles/_common-contract.md +11 -6
- package/runtime/prompts/profiles/error-analysis.md +3 -7
- package/runtime/prompts/profiles/implementation-planning.md +22 -21
- package/runtime/prompts/profiles/implementation.md +28 -11
- package/runtime/prompts/profiles/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/_common-contract.md +92 -0
- package/runtime/prompts/profiles/kr/error-analysis.md +36 -0
- package/runtime/prompts/profiles/kr/final-verification.md +48 -0
- package/runtime/prompts/profiles/kr/implementation-planning.md +90 -0
- package/runtime/prompts/profiles/kr/implementation.md +144 -0
- package/runtime/prompts/profiles/kr/improvement-discovery.md +42 -0
- package/runtime/prompts/profiles/kr/release-handoff.md +104 -0
- package/runtime/prompts/profiles/kr/requirements-discovery.md +42 -0
- package/runtime/prompts/profiles/release-handoff.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +8 -12
- package/runtime/prompts/wizard/prompts.ko.json +230 -0
- package/runtime/python/lib/okstra/cli.sh +2 -49
- package/runtime/python/lib/okstra/globals.sh +21 -21
- package/runtime/python/lib/okstra/interactive.sh +7 -7
- package/runtime/python/okstra_ctl/clarification_items.py +3 -9
- package/runtime/python/okstra_ctl/consumers.py +53 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +0 -7
- package/runtime/python/okstra_ctl/i18n.py +73 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +44 -0
- package/runtime/python/okstra_ctl/index.py +1 -1
- package/runtime/python/okstra_ctl/paths.py +23 -20
- package/runtime/python/okstra_ctl/render.py +147 -202
- package/runtime/python/okstra_ctl/render_final_report.py +53 -10
- package/runtime/python/okstra_ctl/run.py +292 -107
- package/runtime/python/okstra_ctl/run_context.py +22 -0
- package/runtime/python/okstra_ctl/seeding.py +186 -0
- package/runtime/python/okstra_ctl/wizard.py +348 -127
- package/runtime/python/okstra_ctl/workflow.py +21 -2
- package/runtime/python/okstra_ctl/worktree.py +54 -1
- package/runtime/python/okstra_project/resolver.py +4 -3
- package/runtime/python/okstra_token_usage/report.py +2 -2
- package/runtime/schemas/final-report-v1.0.schema.json +22 -16
- package/runtime/skills/okstra-brief/SKILL.md +124 -31
- package/runtime/skills/okstra-convergence/SKILL.md +2 -3
- package/runtime/skills/okstra-report-writer/SKILL.md +35 -15
- package/runtime/skills/okstra-run/SKILL.md +5 -4
- package/runtime/skills/okstra-schedule/SKILL.md +4 -4
- package/runtime/skills/okstra-setup/SKILL.md +27 -0
- package/runtime/skills/okstra-team-contract/SKILL.md +1 -1
- package/runtime/templates/okstra.CLAUDE.md +104 -0
- package/runtime/templates/reports/final-report.template.md +93 -98
- package/runtime/templates/reports/i18n/en.json +135 -0
- package/runtime/templates/reports/i18n/ko.json +135 -0
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +78 -0
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/validators/lib/fixtures.sh +30 -0
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +211 -0
- package/runtime/validators/validate-run.py +121 -26
- package/runtime/validators/validate-workflow.sh +2 -2
- package/runtime/validators/validate_improvement_report.py +275 -0
- package/src/config.mjs +18 -0
- package/src/install.mjs +41 -14
- package/src/setup.mjs +133 -1
- 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
|
|
192
|
-
# dispatches of the same role
|
|
193
|
-
#
|
|
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 "$
|
|
202
|
-
original_caller_title=$(tmux display-message -p -t "$
|
|
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 "$
|
|
214
|
-
tmux select-pane -t "$
|
|
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 "$
|
|
222
|
-
tmux select-pane -t "$
|
|
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`
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
# `
|
|
237
|
-
#
|
|
238
|
-
# the same
|
|
239
|
-
#
|
|
246
|
+
# new pane carries the title `codex-<role>-<pid>-trace[from=<caller-pane>]`
|
|
247
|
+
# so the operator can map trace ↔ worker 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
|
|
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
|
-
|
|
245
|
-
|
|
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
|
|
253
|
-
#
|
|
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 "$
|
|
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="${
|
|
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
|
|
141
|
-
# dispatches of the same role
|
|
142
|
-
#
|
|
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 "$
|
|
151
|
-
original_caller_title=$(tmux display-message -p -t "$
|
|
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 "$
|
|
163
|
-
tmux select-pane -t "$
|
|
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 "$
|
|
171
|
-
tmux select-pane -t "$
|
|
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`
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
189
|
+
# implementation-specific. Title `gemini-<role>-<pid>-trace[from=<caller-pane>]`
|
|
190
|
+
# so the operator can map trace ↔ worker 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
|
-
|
|
186
|
-
|
|
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 "$
|
|
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="${
|
|
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
|
-
|
|
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
|
|
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))
|
package/runtime/bin/okstra.sh
CHANGED
|
@@ -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: ${
|
|
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 "$
|
|
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
|
-
-
|
|
21
|
-
-
|
|
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 (shared — artifact-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
|
|
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
|
|
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
|
|
82
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|