okstra 0.134.0 → 0.136.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/docs/architecture/storage-model.md +1 -1
- package/docs/cli.md +1 -1
- package/docs/for-ai/skills/okstra-pr-gen.md +2 -1
- package/docs/for-ai/skills/okstra-user-response.md +8 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/bin/lib/okstra/interactive.sh +24 -67
- package/runtime/bin/okstra-antigravity-exec.sh +22 -10
- package/runtime/bin/okstra-claude-exec.sh +20 -9
- package/runtime/bin/okstra-codex-exec.sh +22 -10
- package/runtime/prompts/lead/convergence.md +1 -1
- package/runtime/prompts/lead/team-contract.md +2 -2
- package/runtime/prompts/profiles/_common-contract.md +0 -1
- package/runtime/prompts/profiles/_implementation-executor.md +20 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -0
- package/runtime/python/okstra_ctl/backfill.py +2 -1
- package/runtime/python/okstra_ctl/context_cost.py +3 -2
- package/runtime/python/okstra_ctl/convergence_engine.py +11 -2
- package/runtime/python/okstra_ctl/error_log_core.py +3 -1
- package/runtime/python/okstra_ctl/handoff.py +2 -1
- package/runtime/python/okstra_ctl/implementation_outcome.py +6 -5
- package/runtime/python/okstra_ctl/models.py +2 -0
- package/runtime/python/okstra_ctl/path_hints.py +3 -1
- package/runtime/python/okstra_ctl/paths.py +261 -2
- package/runtime/python/okstra_ctl/plan_run_root.py +4 -4
- package/runtime/python/okstra_ctl/render_final_report.py +1 -1
- package/runtime/python/okstra_ctl/run.py +12 -1
- package/runtime/python/okstra_ctl/wizard.py +5 -3
- package/runtime/python/okstra_ctl/worker_artifact_paths.py +5 -1
- package/runtime/python/okstra_token_usage/collect.py +25 -15
- package/runtime/python/okstra_token_usage/pricing.py +4 -1
- package/runtime/skills/okstra-pr-gen/SKILL.md +24 -0
- package/runtime/skills/okstra-user-response/SKILL.md +65 -17
- package/runtime/validators/forbidden_actions.py +1 -1
- package/runtime/validators/validate-run.py +20 -22
- package/runtime/validators/validate_session_conformance.py +1 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-user-response
|
|
3
3
|
description: >-
|
|
4
|
-
Use this to answer an okstra task's open clarification questions in-session, without hand-editing any file. The tell is a request to respond to an okstra run's clarification items or its approval gate — "answer okstra", "I'll answer the questions", "clarification response", "user response", "approve and move on". This skill lists tasks whose latest report still has open clarification blockers,
|
|
4
|
+
Use this to answer an okstra task's open clarification questions in-session, without hand-editing any file. The tell is a request to respond to an okstra run's clarification items or its approval gate — "answer okstra", "I'll answer the questions", "clarification response", "user response", "approve and move on". This skill lists tasks whose latest report still has open clarification blockers, then walks the open C-ids one at a time — each rewritten as a self-contained question with background, three concrete answers (recommendation first) plus "Enter directly" — echoes the collected answers back for an explicit "confirmed" acknowledgement, optionally records approval, and writes the `user-responses/` sidecar via `okstra user-response write`. NOT for starting a run (okstra-run), inspecting a finished task (okstra-inspect), or generating a brief (okstra-brief-gen). The skill never picks an answer — it builds the option board and transcribes what the user decides.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# OKSTRA User Response
|
|
8
8
|
|
|
9
9
|
Single entry point for answering the clarification questions an okstra run left behind (the open `C-*` rows under the final report's `## 1. Clarification Items`) **in-session**, and recording those answers as a `runs/<type>/user-responses/` sidecar. The next `/okstra-run` auto-attaches this sidecar via `--clarification-response`.
|
|
10
10
|
|
|
11
|
-
**Core principle — the skill never picks an answer for the user.**
|
|
11
|
+
**Core principle — the skill never picks an answer for the user.** It builds the option board — background, a self-contained question, three concrete answers, `Enter directly` — and the user alone picks from it; every `value` is what the user chose or typed. It never calls `write` until the user has explicitly confirmed.
|
|
12
|
+
|
|
13
|
+
**Second principle — one question at a time.** Never batch two clarification items into one question, and never dump the whole open list at the user. Ask item 1, transcribe the answer, then ask item 2.
|
|
12
14
|
|
|
13
15
|
| Sub-command | What it does |
|
|
14
16
|
|---|---|
|
|
@@ -63,7 +65,7 @@ Returns a JSON array (latest report mtime first); each entry:
|
|
|
63
65
|
|
|
64
66
|
Carry the chosen entry's `reportPath` and `taskKey` forward.
|
|
65
67
|
|
|
66
|
-
## Step 2:
|
|
68
|
+
## Step 2: Fetch the open rows (data only — ask nothing yet)
|
|
67
69
|
|
|
68
70
|
```bash
|
|
69
71
|
okstra user-response show --report <reportPath>
|
|
@@ -71,33 +73,79 @@ okstra user-response show --report <reportPath>
|
|
|
71
73
|
|
|
72
74
|
Returns `{reportPath, rows: [{id, kind, blocks, status, statement, recommended, alternatives, contextRefs, resolvedRefs}]}`. `resolvedRefs` is `[{ref, definition}]` — the CLI has already looked up what each internal token (`RB-002`, `FU-001`, `§4.7`, …) means in the report body; `definition` is `null` only when the report text alone could not resolve it (e.g. a `path:line` pointer).
|
|
73
75
|
|
|
74
|
-
**Do
|
|
76
|
+
This call is a **data fetch, not a presentation step**. Do not print `rows` at the user, and do not paste a raw `statement` as a question — a `statement` like `Rewrite RB-002 rollback (see §4.7)` is meaningless on its own, which is the whole reason this skill exists. Announce only the count and the plan:
|
|
77
|
+
|
|
78
|
+
> `<N>` open items — I'll go through them one at a time.
|
|
79
|
+
|
|
80
|
+
Step 3 then turns exactly one row at a time into a question.
|
|
81
|
+
|
|
82
|
+
## Step 3: Ask one item at a time (loop)
|
|
83
|
+
|
|
84
|
+
Walk the open rows in report order. **One item per `AskUserQuestion` call** — never batch two items into one call, and never pre-announce the items still to come. Ask, transcribe, move on. Head each item with its position and what it is holding up:
|
|
85
|
+
|
|
86
|
+
> **[2/5] C-014** — blocks: approval gate
|
|
87
|
+
|
|
88
|
+
### 3a. Background first (plain text, above the picker)
|
|
89
|
+
|
|
90
|
+
`AskUserQuestion` carries one sentence of question, so the background goes in the message text right before the call. Write these three blocks, **3–6 lines total** — enough that someone who never read the report can answer:
|
|
91
|
+
|
|
92
|
+
1. **Situation** — what the run was doing when it stopped at this item, in the user's own domain terms.
|
|
93
|
+
2. **What is undecided** — the actual fork, with every internal token expanded inline from `resolvedRefs[].definition`; the user must never need to know what `RB-002` is to answer. Say what is stuck right now in plain words: `blocks: approval` → the approval gate stays shut and `implementation` cannot start; `blocks: next-phase` → the next phase cannot begin.
|
|
94
|
+
3. **What changes with your answer** — what each direction actually causes downstream.
|
|
95
|
+
|
|
96
|
+
Where the background comes from — in this order:
|
|
97
|
+
|
|
98
|
+
- `resolvedRefs[].definition`, which is already resolved and needs no file read;
|
|
99
|
+
- when a `definition` is `null`, or the three blocks would otherwise be empty, **Read** the report `§`/`path:line` that this item's `contextRefs[]` points at and take it from there;
|
|
100
|
+
- **never invent it.** If the report genuinely does not say why, write `The report states nothing beyond the statement itself.` and go straight to the question. Fabricated background corrupts the answer it collects.
|
|
101
|
+
|
|
102
|
+
Close the background with the raw source on one line, so the mapping back to the report stays visible:
|
|
103
|
+
|
|
104
|
+
> Source: `C-014` — "<raw statement>"
|
|
105
|
+
|
|
106
|
+
### 3b. The picker — three answers plus `Enter directly`
|
|
107
|
+
|
|
108
|
+
One `AskUserQuestion` (single-select), with exactly four options in this order:
|
|
109
|
+
|
|
110
|
+
1. **The recommendation** — the answer from `recommended` restated in plain language, label suffixed `(Recommended)`; its description carries the rationale (the part of the `recommended` cell after `—`).
|
|
111
|
+
2. **Alternative** — `alternatives[0]`.
|
|
112
|
+
3. **Alternative** — `alternatives[1]`.
|
|
113
|
+
4. **`Enter directly`** — always last, always present.
|
|
114
|
+
|
|
115
|
+
Slots 2–3 fill from `alternatives[]` first. When `alternatives[]` is short, fill each remaining slot with the first of these that applies:
|
|
116
|
+
|
|
117
|
+
- **a concrete candidate the report itself names** — another option row, an approach §2 rejected, a value already in use elsewhere. Its description must open with `Not an okstra proposal — from <where>` and cite that source.
|
|
118
|
+
- **`Reframe this question`** — the item is held rather than answered, and the next run re-asks it.
|
|
119
|
+
|
|
120
|
+
That last entry is what guarantees the picker always reaches three answers plus `Enter directly`. Never pad a slot with an option no source supports, and never mark anything but `recommended` as recommended.
|
|
121
|
+
|
|
122
|
+
Keep each `label` to the answer itself (1–5 words); the description carries the consequence.
|
|
123
|
+
|
|
124
|
+
### 3c. Transcribe the decision, then move on
|
|
75
125
|
|
|
76
|
-
|
|
77
|
-
- **`recommended`** — the answer okstra proposed (+ rationale), also stated in plain language. It is only a recommendation.
|
|
78
|
-
- **`alternatives[]`** — list them if present, each in plain language.
|
|
79
|
-
- **Raw source** (secondary, for traceability): the raw `id` + `statement` so the mapping back to the report stays visible.
|
|
126
|
+
Record one entry — `{id, kind, value, rationale?, disposition}`, `kind` copied from the row `show` returned:
|
|
80
127
|
|
|
81
|
-
|
|
128
|
+
| The user picks | `value` | `disposition` |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| Option 1–3 | that option's full answer text, not its short label | `answer` |
|
|
131
|
+
| `Enter directly` → their own answer | the user's utterance verbatim (rationale into `rationale`) | `answer` |
|
|
132
|
+
| `Reframe this question`, or free text asking for it to be re-asked | what the user wants re-asked, verbatim (empty → the raw statement) | `reframe` |
|
|
82
133
|
|
|
83
|
-
|
|
134
|
+
A reframe is not an answer, so it does not satisfy the approval gate.
|
|
84
135
|
|
|
85
|
-
|
|
86
|
-
2. **Ask-to-explain (fallback)** — Step 2 already expanded every internal token, so the question should already be self-contained. If the user still asks "what does this mean?" or a `resolvedRefs` `definition` was `null`, open the report `§`/`path:line` that this item's `contextRefs[]` points at **using Read** and explain it in plain language. Only explain — **do not pick the answer for the user**; after explaining, hand the decision back to the user.
|
|
87
|
-
3. **Free-form** — the user gives a narrative answer that matches neither a recommendation nor an alternative. `value` = that narrative verbatim, with the rationale in `rationale` if needed. `disposition: "answer"`.
|
|
88
|
-
4. **Hold + reframe** — if the user asks to "re-frame this question itself", record it as `disposition: "reframe"`. Put what the user wants re-asked into `value` verbatim. A reframe is not an answer, so it does not satisfy the approval gate — the next run re-frames that question.
|
|
136
|
+
If the user replies with a question instead of an answer ("what does this mean?"), record nothing: **Read** the `§`/`path:line` from `contextRefs[]`, explain it in plain language, and re-ask the same item with the same four options. Explain only — **do not resolve it for them**; the decision goes back to the user.
|
|
89
137
|
|
|
90
|
-
|
|
138
|
+
Echo one line per finished item (`[2/5] C-014 → answer: 60s`), then ask the next one. Do not summarize the whole set until Step 4.
|
|
91
139
|
|
|
92
140
|
## Step 4: Echo the full sidecar back → explicit "confirmed" gate
|
|
93
141
|
|
|
94
|
-
Once every open item
|
|
142
|
+
Once the loop has covered every open item, **before** calling `write`, **echo back** everything collected (each `id`, `disposition`, `value`, `rationale` if present, and the approval decision from Step 5 below) to the user as-is. Then ask for explicit confirmation:
|
|
95
143
|
|
|
96
144
|
> Record it as shown above? (Reply `confirmed` to write the sidecar.)
|
|
97
145
|
|
|
98
146
|
- **Never call `write`** until the user says `confirmed` (or gives clear approval).
|
|
99
147
|
- If the user changes any item, show the echo-back again with the changed value and re-confirm.
|
|
100
|
-
- Every `value` must be the user
|
|
148
|
+
- Every `value` must be what the user picked or typed, never a wording you settled on for them — this gate guarantees that.
|
|
101
149
|
|
|
102
150
|
## Step 5: (Optional) Record approval
|
|
103
151
|
|
|
@@ -166,7 +166,7 @@ def scan_forbidden_actions(
|
|
|
166
166
|
resolve_team_needles_with_source,
|
|
167
167
|
)
|
|
168
168
|
|
|
169
|
-
since, until = resolve_run_window(team_state_path, team_state)
|
|
169
|
+
since, until = resolve_run_window(team_state_path, team_state, relax_start=False)
|
|
170
170
|
lead_sid = (team_state.get("lead") or {}).get("sessionId") or ""
|
|
171
171
|
team_needles, _needle_source = resolve_team_needles_with_source(
|
|
172
172
|
team_state, project_root, since, until, projects_root=claude_projects_dir
|
|
@@ -43,6 +43,7 @@ from okstra_ctl.conformance import ( # noqa: E402
|
|
|
43
43
|
qa_result_from_dict,
|
|
44
44
|
validate_conformance_manifest,
|
|
45
45
|
)
|
|
46
|
+
from okstra_ctl.paths import RunRef # noqa: E402
|
|
46
47
|
from okstra_ctl.md_table import ( # noqa: E402
|
|
47
48
|
is_separator_row as _is_markdown_separator,
|
|
48
49
|
split_pipe_row as _split_pipe_row,
|
|
@@ -1143,17 +1144,15 @@ def _scope_manifest_entries(manifest: dict, stage_name: str | None) -> dict:
|
|
|
1143
1144
|
|
|
1144
1145
|
|
|
1145
1146
|
def _task_root_from_run_dir(run_dir: Path) -> Path:
|
|
1146
|
-
"""run_dir 에서
|
|
1147
|
+
"""run_dir 에서 task_root 를 복원한다.
|
|
1147
1148
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
→ 같은 task_root. `runs` 를 위로 탐색하므로 stage-<N> 레벨이 추가돼도
|
|
1151
|
-
안전하다. `runs` 가 조상에 없으면 기존 동작(두 단계 위)로 폴백한다.
|
|
1149
|
+
레이아웃 해석은 `RunRef.from_run_dir` 이 소유한다. run 디렉터리가 아닌
|
|
1150
|
+
입력에는 기존 동작(두 단계 위)으로 폴백해 검증기가 죽지 않게 한다.
|
|
1152
1151
|
"""
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1152
|
+
try:
|
|
1153
|
+
return RunRef.from_run_dir(run_dir).task_root
|
|
1154
|
+
except ValueError:
|
|
1155
|
+
return run_dir.parent.parent
|
|
1157
1156
|
|
|
1158
1157
|
|
|
1159
1158
|
def _read_run_inputs_payload(
|
|
@@ -3129,19 +3128,15 @@ _PASSING_VERDICT_TOKENS = frozenset({"accepted", "conditional-accept"})
|
|
|
3129
3128
|
def _consumers_rows(report_path: Path) -> list[dict] | None:
|
|
3130
3129
|
"""`runs/implementation-planning/consumers.jsonl` rows for this task.
|
|
3131
3130
|
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
a missing artifact into a false accusation.
|
|
3131
|
+
``None`` when the report path is unreadable as a run reference or the file
|
|
3132
|
+
is absent, so the caller does not turn a missing artifact into a false
|
|
3133
|
+
accusation.
|
|
3136
3134
|
"""
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
runs_dir = parent
|
|
3141
|
-
break
|
|
3142
|
-
if runs_dir is None:
|
|
3135
|
+
try:
|
|
3136
|
+
ref = RunRef.from_report_path(report_path)
|
|
3137
|
+
except ValueError:
|
|
3143
3138
|
return None
|
|
3144
|
-
path =
|
|
3139
|
+
path = ref.sibling("implementation-planning").run_dir / "consumers.jsonl"
|
|
3145
3140
|
if not path.is_file():
|
|
3146
3141
|
return None
|
|
3147
3142
|
rows = []
|
|
@@ -3497,8 +3492,11 @@ def _validate_stage_carry_sidecar_exists(
|
|
|
3497
3492
|
stage = evidence.get("stageNumber")
|
|
3498
3493
|
if not isinstance(stage, int):
|
|
3499
3494
|
return
|
|
3500
|
-
#
|
|
3501
|
-
|
|
3495
|
+
# Carry sidecars are stage-SHARED: the next stage's carry-in and
|
|
3496
|
+
# `consumers.backfill_done_from_carry` glob them without knowing the
|
|
3497
|
+
# producing run's layout. `RunRef.carry()` owns that flat-vs-staged rule;
|
|
3498
|
+
# resolving it under the stage run dir forced the lead to write it twice.
|
|
3499
|
+
carry_path = RunRef.from_report_path(report_path).carry(stage)
|
|
3502
3500
|
if not carry_path.exists():
|
|
3503
3501
|
failures.append(
|
|
3504
3502
|
f"implementation run declares stage-{stage} sidecar evidence but "
|
|
@@ -194,7 +194,7 @@ def _collect_lead_evidence(
|
|
|
194
194
|
)
|
|
195
195
|
from okstra_token_usage.paths import claude_project_dir
|
|
196
196
|
|
|
197
|
-
since, until = resolve_run_window(team_state_path, team_state)
|
|
197
|
+
since, until = resolve_run_window(team_state_path, team_state, relax_start=False)
|
|
198
198
|
lead_sid = (team_state.get("lead") or {}).get("sessionId") or ""
|
|
199
199
|
team_needles, _needle_source = resolve_team_needles_with_source(
|
|
200
200
|
team_state, project_root, since, until, projects_root=projects_root
|