okstra 0.184.0 → 0.185.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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.mjs +9 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/chat/chat.d.mts +1 -0
  5. package/dist/commands/chat/chat.mjs +385 -0
  6. package/dist/commands/chat/chat.mjs.map +1 -0
  7. package/dist/lib/skill-catalog.mjs +1 -0
  8. package/dist/lib/skill-catalog.mjs.map +1 -1
  9. package/docs/architecture.md +8 -6
  10. package/docs/cli.md +2 -1
  11. package/docs/for-ai/README.md +4 -2
  12. package/docs/for-ai/skills/okstra-chat.md +28 -0
  13. package/docs/for-ai/skills/okstra-inspect.md +1 -1
  14. package/docs/for-ai/skills/okstra-run.md +2 -2
  15. package/docs/for-ai/skills/okstra-user-response.md +10 -8
  16. package/docs/project-structure-overview.md +5 -4
  17. package/docs/task-process/README.md +1 -1
  18. package/docs/task-process/implementation-planning.md +1 -1
  19. package/package.json +1 -1
  20. package/runtime/BUILD.json +2 -2
  21. package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
  22. package/runtime/prompts/lead/report-writer.md +1 -1
  23. package/runtime/prompts/profiles/_clarification-recommendation.md +2 -2
  24. package/runtime/prompts/profiles/implementation-planning.md +2 -1
  25. package/runtime/prompts/wizard/prompts.ko.json +2 -0
  26. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +2 -2
  27. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
  28. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
  29. package/runtime/python/okstra_ctl/next_phase.py +67 -4
  30. package/runtime/python/okstra_ctl/user_response.py +147 -37
  31. package/runtime/python/okstra_ctl/wizard.py +13 -0
  32. package/runtime/skills/okstra-chat/SKILL.md +104 -0
  33. package/runtime/skills/okstra-inspect/facets/status.md +6 -5
  34. package/runtime/skills/okstra-run/SKILL.md +2 -2
  35. package/runtime/skills/okstra-user-response/SKILL.md +50 -16
  36. package/runtime/validators/validate-run.py +90 -15
@@ -954,12 +954,15 @@ def list_awaiting_tasks(home: Path, project_id: str, limit: int) -> list[dict]:
954
954
  # `§x.y` is a full-reading-copy heading and is not a record coordinate.
955
955
  # `path.ext:line` is a source pointer the record does not define.
956
956
  _SECTION_REF_RE = re.compile(r"§[\d.]+|[A-Z]{1,4}-\d+|[\w./-]+\.\w+:\d+")
957
+ _PATH_LINE_RE = re.compile(r"[\w./-]+\.\w+:\d+")
957
958
  _ID_TOKEN_RE = re.compile(r"^[A-Z]{1,4}-\d+$")
959
+ _PLAN_ITEM_ID_RE = re.compile(r"^P-")
958
960
  _ROW_DEFINITION_KEYS = (
959
961
  "statement",
960
962
  "summary",
961
963
  "item",
962
964
  "title",
965
+ "subject",
963
966
  "check",
964
967
  "action",
965
968
  "evidence",
@@ -1992,6 +1995,138 @@ def _option_view(option: Mapping[str, Any], index: int) -> list[str]:
1992
1995
  ]
1993
1996
 
1994
1997
 
1998
+ def _option_probe_texts(options: list[Any]) -> list[str]:
1999
+ texts: list[str] = []
2000
+ for option in options:
2001
+ if not isinstance(option, Mapping):
2002
+ continue
2003
+ texts.extend(
2004
+ str(option.get(key) or "")
2005
+ for key in ("answer", "rationale", "addedWork", "directionChange")
2006
+ )
2007
+ return texts
2008
+
2009
+
2010
+ def _row_probe_texts(row: Mapping[str, Any]) -> list[str]:
2011
+ return [
2012
+ str(row.get("statement") or ""),
2013
+ str(row.get("expected_form") or ""),
2014
+ *_option_probe_texts(list(row.get("options") or [])),
2015
+ ]
2016
+
2017
+
2018
+ def _path_line_refs(*texts: str) -> list[str]:
2019
+ found: list[str] = []
2020
+ seen: set[str] = set()
2021
+ for text in texts:
2022
+ for match in _PATH_LINE_RE.findall(text or ""):
2023
+ if match not in seen:
2024
+ seen.add(match)
2025
+ found.append(match)
2026
+ return found
2027
+
2028
+
2029
+ def _why_asked(row: Mapping[str, Any]) -> str:
2030
+ approval = row.get("approval_context") or {}
2031
+ if not isinstance(approval, Mapping):
2032
+ return "not stated in the report"
2033
+ unblock = str(approval.get("unblockCondition") or "").strip()
2034
+ if unblock:
2035
+ return unblock
2036
+ classification = str(approval.get("classification") or "").strip()
2037
+ return classification or "not stated in the report"
2038
+
2039
+
2040
+ def _linked_plan_items(
2041
+ record: dict[str, Any] | None, clarification_id: str
2042
+ ) -> list[dict[str, str]]:
2043
+ if record is None:
2044
+ return []
2045
+ linked: list[dict[str, str]] = []
2046
+ seen: set[str] = set()
2047
+
2048
+ def walk(node: object) -> None:
2049
+ if isinstance(node, dict):
2050
+ row_id = node.get("id")
2051
+ refs = node.get("clarificationRefs") or []
2052
+ if (
2053
+ isinstance(row_id, str)
2054
+ and _PLAN_ITEM_ID_RE.match(row_id)
2055
+ and isinstance(refs, list)
2056
+ and clarification_id in refs
2057
+ and row_id not in seen
2058
+ ):
2059
+ seen.add(row_id)
2060
+ linked.append({
2061
+ "id": row_id,
2062
+ "definition": _row_definition(node) or "not stated in the report",
2063
+ })
2064
+ for value in node.values():
2065
+ walk(value)
2066
+ elif isinstance(node, list):
2067
+ for item in node:
2068
+ walk(item)
2069
+
2070
+ walk(record)
2071
+ return linked
2072
+
2073
+
2074
+ def _format_ref_list(label: str, items: list[str]) -> list[str]:
2075
+ if not items:
2076
+ return [f"{label}: none"]
2077
+ return [f"{label}:", *(f"- {item}" for item in items)]
2078
+
2079
+
2080
+ def _format_open_row_view(
2081
+ row: Mapping[str, Any],
2082
+ record: dict[str, Any] | None,
2083
+ markdown_text: str,
2084
+ response: UserResponseEntry | None,
2085
+ ) -> list[str]:
2086
+ item = row["item"]
2087
+ probe = _row_probe_texts(row)
2088
+ refs = sorted(set(_SECTION_REF_RE.findall(" ".join(probe))))
2089
+ resolved = (
2090
+ resolve_refs_from_record(record, refs)
2091
+ if record is not None
2092
+ else resolve_refs(markdown_text, refs)
2093
+ )
2094
+ lines = [
2095
+ "",
2096
+ f"[{item.row_id}]",
2097
+ f"Kind: {item.kind}",
2098
+ f"Blocks: {item.blocks}",
2099
+ f"Report status: {item.status}",
2100
+ f"Question: {row['statement']}",
2101
+ f"Expected form: {row['expected_form']}",
2102
+ f"Current response: {response.value if response else 'none'}",
2103
+ f"Current disposition: {response.disposition if response else 'none'}",
2104
+ f"Why asked: {_why_asked(row)}",
2105
+ "Options:",
2106
+ ]
2107
+ approval = row.get("approval_context") or {}
2108
+ if isinstance(approval, Mapping) and approval:
2109
+ lines.extend([
2110
+ f"Approval classification: {approval.get('classification', '')}",
2111
+ f"Approval unblock condition: {approval.get('unblockCondition', '')}",
2112
+ f"Approval recommended disposition: {approval.get('recommendedDisposition', '')}",
2113
+ ])
2114
+ for index, option in enumerate(row["options"], start=1):
2115
+ lines.extend(_option_view(option, index))
2116
+ linked = _linked_plan_items(record, item.row_id)
2117
+ lines.extend(_format_ref_list(
2118
+ "Linked plan items",
2119
+ [f"{plan['id']}: {plan['definition']}" for plan in linked],
2120
+ ))
2121
+ lines.extend(_format_ref_list("Cited artifacts", _path_line_refs(*probe)))
2122
+ lines.append("Context:")
2123
+ lines.extend(
2124
+ f"- {ref['ref']}: {ref['definition'] or 'not stated in the report'}"
2125
+ for ref in resolved
2126
+ )
2127
+ return lines
2128
+
2129
+
1995
2130
  def format_show_view(report_path: Path, project_root: Path) -> str:
1996
2131
  context = _validate_owned_report_context(
1997
2132
  report_path, expected_project_root=project_root
@@ -1999,6 +2134,10 @@ def format_show_view(report_path: Path, project_root: Path) -> str:
1999
2134
  rows, record = _all_report_rows(context.report_path)
2000
2135
  state = _existing_sidecar_state(context.sidecar_path)
2001
2136
  current = {entry.response_id: entry for entry in state.entries}
2137
+ markdown_text = (
2138
+ "" if record is not None
2139
+ else context.markdown_path.read_text(encoding="utf-8")
2140
+ )
2002
2141
  lines = [
2003
2142
  "USER RESPONSE REPORT",
2004
2143
  f"Report: {context.report_path}",
@@ -2015,51 +2154,22 @@ def format_show_view(report_path: Path, project_root: Path) -> str:
2015
2154
  if candidates:
2016
2155
  lines.append("Plan option candidates:")
2017
2156
  for index, candidate in enumerate(candidates, start=1):
2157
+ current_pick = (
2158
+ state.plan_decision is not None
2159
+ and state.plan_decision.implementation_option == candidate
2160
+ )
2018
2161
  lines.extend([
2019
2162
  f"Plan option {index}: {candidate}",
2020
2163
  f" Recommended: {'yes' if candidate == recommended_name else 'no'}",
2021
- " Current decision: "
2022
- f"{'yes' if state.plan_decision and state.plan_decision.implementation_option == candidate else 'no'}",
2164
+ f" Current decision: {'yes' if current_pick else 'no'}",
2023
2165
  ])
2024
2166
  for row in rows:
2025
2167
  item = row["item"]
2026
2168
  if item.status not in {"open", "answered"} or item.row_id in current:
2027
2169
  continue
2028
- refs = sorted(set(_SECTION_REF_RE.findall(
2029
- row["statement"] + " " + row["expected_form"]
2030
- )))
2031
- resolved = (
2032
- resolve_refs_from_record(record, refs)
2033
- if record is not None
2034
- else resolve_refs(context.markdown_path.read_text(encoding="utf-8"), refs)
2035
- )
2036
- response = current.get(item.row_id)
2037
- lines.extend([
2038
- "",
2039
- f"[{item.row_id}]",
2040
- f"Kind: {item.kind}",
2041
- f"Blocks: {item.blocks}",
2042
- f"Report status: {item.status}",
2043
- f"Question: {row['statement']}",
2044
- f"Expected form: {row['expected_form']}",
2045
- f"Current response: {response.value if response else 'none'}",
2046
- f"Current disposition: {response.disposition if response else 'none'}",
2047
- "Options:",
2048
- ])
2049
- approval = row.get("approval_context") or {}
2050
- if approval:
2051
- lines.extend([
2052
- f"Approval classification: {approval.get('classification', '')}",
2053
- f"Approval unblock condition: {approval.get('unblockCondition', '')}",
2054
- f"Approval recommended disposition: {approval.get('recommendedDisposition', '')}",
2055
- ])
2056
- for index, option in enumerate(row["options"], start=1):
2057
- lines.extend(_option_view(option, index))
2058
- lines.append("Context:")
2059
- lines.extend(
2060
- f"- {ref['ref']}: {ref['definition'] or 'not stated in the report'}"
2061
- for ref in resolved
2062
- )
2170
+ lines.extend(_format_open_row_view(
2171
+ row, record, markdown_text, current.get(item.row_id),
2172
+ ))
2063
2173
  return "\n".join(lines) + "\n"
2064
2174
 
2065
2175
 
@@ -2733,6 +2733,8 @@ def _build_task_type(state: WizardState) -> Prompt:
2733
2733
  recommended_suffix = t["options"].get("_RECOMMENDED_SUFFIX", "")
2734
2734
  rerun_suffix = t["options"].get("_RERUN_SUFFIX", "")
2735
2735
  next_suffix = t["options"].get("_NEXT_SUFFIX", "")
2736
+ approve_suffix = t["options"].get("_APPROVE_SUFFIX", recommended_suffix)
2737
+ blocked_rerun_suffix = t["options"].get("_BLOCKED_RERUN_SUFFIX", rerun_suffix)
2736
2738
  description_by_type = dict(TASK_TYPES)
2737
2739
  options: list[Option] = []
2738
2740
 
@@ -2758,10 +2760,21 @@ def _build_task_type(state: WizardState) -> Prompt:
2758
2760
  # `ready` 가 아니면 추천은 비고, 아래 `currentPhase` 재실행 옵션이 남는다.
2759
2761
  # 실패한 run 의 포인터가 `{"phase": "", "status": "blocked"}` 라는 점에서
2760
2762
  # 그것이 맞는 제안이다 — 그 옵션은 포인터가 아니라 `currentPhase` 에서 온다.
2763
+ # 계획 승인 대기는 `awaitingApproval` 로 표시한다. 구현이 추천이지만 먼저
2764
+ # 승인을 받아야 하므로 접미사로 구분한다. 열린 C-NNN 때문에 blocked 면
2765
+ # 재실행은 답이 기록된 뒤에만 고르라고 접미사로 말한다.
2761
2766
  recommended = (revision_requested or state.task_type
2762
2767
  or next_phase.autofill_task_type({"workflow": workflow}))
2763
2768
  if not recommended and not workflow:
2764
2769
  recommended = TASK_TYPE_VALUES[0]
2770
+ pointer = next_phase.promote(workflow.get("nextRecommendedPhase"))
2771
+ if workflow.get("awaitingApproval") is True and recommended == "implementation":
2772
+ recommended_suffix = approve_suffix
2773
+ if (
2774
+ pointer["status"] == next_phase.STATUS_BLOCKED
2775
+ and (workflow.get("currentPhase") or "") == "implementation-planning"
2776
+ ):
2777
+ rerun_suffix = blocked_rerun_suffix
2765
2778
  add(recommended, recommended_suffix)
2766
2779
  add(workflow.get("currentPhase") or "", rerun_suffix)
2767
2780
  add(_phase_after(recommended), next_suffix)
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: okstra-chat
3
+ description: Use when the user wants to create or join a global okstra chat room, send a message to everyone or to one participant, read unread arrivals, or reopen the inbox. Trigger words include "okstra chat", "okstra-chat", "chat room", "join the room", "send a chat message".
4
+ ---
5
+
6
+ # okstra-chat
7
+
8
+ Cross-session rooms in the global okstra home. Not a project task artifact.
9
+ Do not write JSON. Call `okstra chat` and read its fixed text.
10
+
11
+ Rooms are independent of tasks and runs. A participant is this host session.
12
+ The display name is typed at join. Do not invent a default name.
13
+
14
+ ## When to use
15
+
16
+ - The user wants a room that a Claude lead and a Grok lead can both join.
17
+ - The user wants to send a message, see unread arrivals, or reopen the inbox.
18
+
19
+ ## Step 0: CLI
20
+
21
+ Run as a separate Bash tool call with literal leading token:
22
+
23
+ ```bash
24
+ okstra chat --help
25
+ ```
26
+
27
+ If `okstra` is not on PATH, tell the user:
28
+
29
+ `okstra not installed — run npx okstra@latest install once, then retry this skill.`
30
+
31
+ Do not use `npx` from this skill.
32
+
33
+ ## Step 1: Create or join
34
+
35
+ Ask one question: create a room, or join an existing room.
36
+
37
+ If the host has a native picker, use it. Otherwise print a numbered list.
38
+
39
+ ### Create
40
+
41
+ 1. Ask for the room name as free input.
42
+ 2. Run `okstra chat create --room <room>`.
43
+ 3. Ask for the display name as free input. Do not suggest a generated name.
44
+ 4. Run `okstra chat join --room <room> --name <display>`.
45
+
46
+ ### Join
47
+
48
+ 1. Run `okstra chat rooms`.
49
+ 2. If the output is `no rooms`, say so and offer create.
50
+ 3. Otherwise pick a room from that list (picker, or numbered list if the picker limit is exceeded).
51
+ 4. Ask for the display name as free input.
52
+ 5. Run `okstra chat join --room <room> --name <display>`.
53
+
54
+ `--name` is required. An empty name, `all`, or a name already in the room fails.
55
+
56
+ ## Step 2: Unread
57
+
58
+ ```bash
59
+ okstra chat unread --room <room> --as <display>
60
+ ```
61
+
62
+ Show the rows. Each row is `id:time:recipient:body`.
63
+
64
+ ## Step 3: Next action
65
+
66
+ Ask: send, inbox, log, ack, or done.
67
+
68
+ ### Send
69
+
70
+ 1. Run `okstra chat members --room <room>`.
71
+ 2. Pick the recipient from `all` plus those names. Recipient is required.
72
+ 3. Ask for the body as free input.
73
+ 4. Run `okstra chat send --room <room> --as <display> --to <all|name> --body <text>`.
74
+
75
+ ### Inbox
76
+
77
+ ```bash
78
+ okstra chat inbox --room <room> --as <display>
79
+ ```
80
+
81
+ This is every arrival to `@you` or `all`, including messages already acked.
82
+
83
+ ### Log
84
+
85
+ ```bash
86
+ okstra chat log --room <room> --as <display>
87
+ ```
88
+
89
+ The whole room, including messages not addressed to you.
90
+
91
+ ### Ack
92
+
93
+ After unread, mark the last id the user has read:
94
+
95
+ ```bash
96
+ okstra chat ack --room <room> --as <display> --through <id>
97
+ ```
98
+
99
+ ## Rules
100
+
101
+ - Call only `okstra chat`. Do not open files under the chat store.
102
+ - Do not treat chat rows as evidence for a finding, verdict, or assignment.
103
+ - Workers may run the same commands with `--name` and `--as`. Joining is optional.
104
+ - Do not generate a display name from the provider, model, or execution label.
@@ -95,12 +95,13 @@ It has already promoted legacy values.
95
95
  The status response always includes one of:
96
96
 
97
97
  1. **Resume current run** — if `latestResumeCommandPath` exists, display that path.
98
- 2. **Restart current phase** — task can be re-run with the same `task-key` and current `taskType`.
99
- Branches 3–5 are decided by `workflow.nextRecommendedPhase.status` alone — one status, one branch, no other field consulted:
98
+ 2. **Ask the user to approve** — if `workflow.awaitingApproval` is true. Tell the user to approve the plan (`okstra-run` with `--task-type implementation`, which asks `approve_plan_confirm`, or `--approve`). Quote `nextRecommendedPhase.rationale`. A `ready` pointer to `implementation` here means implementation is next after approval, not that it may launch as if already approved. Do not re-run `implementation-planning`.
99
+ 3. **Restart current phase** — only when `awaitingApproval` is false and the pointer is not `ready`. The task can be re-run with the same `task-key` and current `taskType`.
100
+ Branches 4–6 are decided by `workflow.nextRecommendedPhase.status` when `awaitingApproval` is false — one status, one branch:
100
101
 
101
- 3. **Start next phase** — `status` is `ready`. Propose `nextRecommendedPhase.phase` as the next run's `--task-type` and quote its `rationale` as the reason. This is the only status under which a named phase may be launched, so it is the only branch that proposes a run. A `ready` pointer to `release-handoff` already implies an `accepted` final-verification verdict (the report validator refuses that routing target otherwise), so do not re-gate it here.
102
- 4. **Need more information** — `status` is `pending` (the last run did not settle where this task goes next) or `blocked` (it did settle, and the answer is that something outside the run has to change first). Neither proposes a run, and a leftover `phase` name does not change that — `prepare` keeps the name when it lowers a pointer to `pending`, so read `status`, not the emptiness of `phase`. Show the `rationale`, and for `blocked` state what it names as the obstacle. The way forward is a fresh brief, a clarification response, or a re-run of the current phase — not a next-phase launch.
103
- 5. **Task complete (terminal)** — `status` is `terminal`: the task lifecycle ends here. This is **not** a "next phase" — do not propose a new okstra run. Surface the latest report and ask the user whether any follow-up task should be opened separately.
102
+ 4. **Start next phase** — `status` is `ready` and `awaitingApproval` is false. Propose `nextRecommendedPhase.phase` as the next run's `--task-type` and quote its `rationale` as the reason. This is the only status under which a named phase may be launched without a prior approval ask, so it is the only branch that proposes a run. A `ready` pointer to `release-handoff` already implies an `accepted` final-verification verdict (the report validator refuses that routing target otherwise), so do not re-gate it here.
103
+ 5. **Need more information** — `status` is `pending` (the last run did not settle where this task goes next) or `blocked` (it did settle, and the answer is that something outside the run has to change first). Neither proposes a run, and a leftover `phase` name does not change that — `prepare` keeps the name when it lowers a pointer to `pending`, so read `status`, not the emptiness of `phase`. Show the `rationale`, and for `blocked` state what it names as the obstacle. After `implementation-planning`, the first action is `okstra-user-response` on the named `C-NNN` ids; do not re-run planning until those answers exist, and do not start implementation.
104
+ 6. **Task complete (terminal)** — `status` is `terminal`: the task lifecycle ends here. This is **not** a "next phase" — do not propose a new okstra run. Surface the latest report and ask the user whether any follow-up task should be opened separately.
104
105
 
105
106
  ### status.4 — Update workStatus (write)
106
107
 
@@ -241,7 +241,7 @@ If an action has an unknown `command`, `key`, or `scope`, stop and report the wi
241
241
 
242
242
  Before rendering the next phase's bundle — and between worker rounds within a phase (reverify/critic/gapverify batches), after you have collected that round's results and token usage and before you dispatch the next round — close the panes of the dispatches that finished in the prior round so they do not accumulate, in two passes. First count: `okstra team reclaim --project-root <projectRoot> --run-manifest <RUN_MANIFEST_PATH> --dry-run` closes nothing and prints one `<paneId>\t<kind>` line per pane it would close — count those lines as `<n>`. Then run the same command **without** `--dry-run` to close them, and emit `PROGRESS: phase-batch-cleanup panes=<n>` with that count at the batch boundary. The command reads each dispatch's recorded status, so an in-progress worker keeps its pane whichever moment you call it. It closes only the panes okstra opened and recorded — a pane the harness opened for its own teammate carries no recorded id and is not okstra's to close. `shutdown_request` alone only idles the agent and frees no pane, so it stays part of the run-end sequence for roster/token hygiene. A `cli-wrapper` run holds no pane at all, so `<n>` is `0` — still emit the checkpoint.
243
243
 
244
- Before you ask the user for any approval, clarification, or decision after workers have been dispatched, run the same two passes first: `okstra team reclaim … --dry-run` to count the panes, then the same command without `--dry-run` to close them, emit `PROGRESS: phase-gate-cleanup panes=<n>`, and `TaskStop` each completed worker. A `TaskStop` by itself idles the task but leaves the pane open — the `team reclaim` call is what closes it. This keeps a user gate from being shown while finished worker panes remain; in-progress dispatches keep their panes.
244
+ Before you ask the user for any approval, clarification, or decision after workers have been dispatched, run the same two passes first: `okstra team reclaim … --dry-run` to count the panes, then the same command without `--dry-run` to close them, emit `PROGRESS: phase-gate-cleanup panes=<n>`, and `TaskStop` each completed worker. A `TaskStop` by itself idles the task but leaves the pane open — the `team reclaim` call is what closes it. This keeps a user gate from being shown while finished worker panes remain; in-progress dispatches keep their panes. Then follow `prompts/lead/okstra-lead-contract.md` "User confirmation before an approval blocker": read cited plan items, worker findings, and files before asking, and ask in the user's language with each option's outcome.
245
245
 
246
246
  Build the `okstra render-bundle` invocation from `outcome.renderArgv`, passing every token verbatim and in order (including empty strings — they are intentional `use phase default` markers).
247
247
 
@@ -390,4 +390,4 @@ Do not read the wizard state file directly. `okstra wizard outcome` exposes any
390
390
 
391
391
  - Echo each captured answer (`result.echo`) on one short line so the user sees what was registered.
392
392
  - Never invent identity; if a `text` prompt returns an empty answer where the wizard rejects it, the user must retry.
393
- - After Step 6, begin the lead workflow without re-summarizing the skill itself. For a single run, the end of Step 6 is the end of the run — but in an unattended chain where `orchestration.chainStages` has 2+ elements, repeat Step 6 per stage until Step 7's queue is empty (or it stops at a "not ready" / exception gate), then finish.
393
+ - After Step 6, begin the lead workflow without re-summarizing the skill itself. For a single run, the end of Step 6 is the end of the run — but in an unattended chain where `orchestration.chainStages` has 2+ elements, repeat Step 6 per stage until Step 7's queue is empty (or it stops at a "not ready" / exception gate), then finish. After an `implementation-planning` run, read `workflow.awaitingApproval` and the next-phase pointer from the task manifest. If awaiting approval, the next sentence to the user is to approve the plan (`okstra-run` → `implementation`, or `--approve`). Do not start another planning run. If the pointer is `blocked`, name the rationale and send the user to `okstra-user-response`; do not re-run planning until those answers exist.
@@ -1,7 +1,7 @@
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 a report or sidecar. It projects the available tasks and one report as fixed text, asks one question at a time, confirms the user's exact answers, and publishes only the user-owned user-responses sidecar through a typed transaction. NOT for starting a run, inspecting a finished task, or generating a brief.
4
+ Use this to answer an okstra task's open clarification questions in-session without hand-editing a report or sidecar. It projects the available tasks and one report as fixed text, reads cited context before asking, asks one question at a time in the user's language with each option's outcome, confirms the user's exact answers, and publishes only the user-owned user-responses sidecar through a typed transaction. NOT for starting a run, inspecting a finished task, or generating a brief.
5
5
  ---
6
6
 
7
7
  # OKSTRA User Response
@@ -13,26 +13,28 @@ The model-facing commands are fixed text reads and typed transaction writes:
13
13
  | Command | Purpose |
14
14
  |---|---|
15
15
  | `user-response list-view` | Show tasks that still await user input. |
16
- | `user-response show-view` | Show questions, choices, resolved context, and current response state. |
16
+ | `user-response show-view` | Show questions, choices, why asked, linked plan items, cited artifacts, resolved context, and current response state. |
17
17
  | `user-response begin` | Open a sidecar transaction for one report identity. |
18
18
  | `user-response answer` | Add or replace one validated clarification answer. |
19
19
  | `user-response plan-decision` | Record an explicit plan decision in the transaction. |
20
20
  | `user-response legacy-report-authoring` | Record legacy report-authoring permission for report contract 2.0 only. |
21
21
  | `user-response finalize` | Atomically merge and publish the user-owned sidecar. |
22
22
 
23
- Do not use the automation-oriented `list` or `show` commands. Do not open a report record to select fields. The fixed views provide every value this skill may use.
23
+ Do not use the automation-oriented `list` or `show` commands. Do not open a report record to select fields. Question text and `options[]` come only from the fixed views. Cited files listed in `show-view` are read only to explain those options.
24
24
 
25
25
  ## Step 0: Preflight
26
26
 
27
+ Use the registered host ID that the current harness declares for this session. Do not infer it from an executable or `PATH`. Do not substitute `claude-code`.
28
+
27
29
  <!-- BEGIN FRAGMENT: bash-invocation-rule -->
28
30
  Run one Bash tool call, starting with the literal token `okstra` (never wrapped in `if`/`eval`/`export`/`$(...)`/`VAR=...`/`||`/`&&`/`npx` — a non-literal leading token defeats the `Bash(okstra:*)` permission match):
29
31
  <!-- END FRAGMENT: bash-invocation-rule -->
30
32
 
31
33
  ```bash
32
- okstra preflight --runtime claude-code
34
+ okstra preflight --runtime <host-runtime>
33
35
  ```
34
36
 
35
- On `Okstra preflight: failed`, show `Reason` and `Recovery`, then stop. On `Okstra preflight: ready`, carry the fixed `Project root` and `Project ID` lines as literal values.
37
+ On `Okstra preflight: failed`, show `Reason` and `Recovery`, then stop. On `Okstra preflight: ready`, carry the fixed `Project root`, `Project ID`, `Runtime`, and `Relay contract` lines as literal values.
36
38
 
37
39
  <!-- BEGIN FRAGMENT: preflight-outdated-cli -->
38
40
  If the call fails with `unknown command: preflight`, the `okstra` binary on PATH predates this skill — tell the user to update it (`npm i -g okstra@latest`), then stop (`/okstra-setup` does not update the binary).
@@ -48,6 +50,17 @@ okstra paths --field home
48
50
  Every subsequent `okstra <subcmd>` call self-bootstraps its Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
49
51
  <!-- END FRAGMENT: python-bootstrap-note -->
50
52
 
53
+ ## Host picker
54
+
55
+ Every choice this skill asks — the task pick, each clarification, an explicit plan decision, and the final record confirmation — uses the same host picker as `okstra-run`.
56
+
57
+ Read the absolute path in the fixed `Relay contract` line. In that file, take the `Wizard interaction relay` JSON. Intersect its `semanticFunctions` with the functions this session can actually call, using each `interactions` kind's `function` field. The live harness does not expose tools named `native_single_select`. Keep `native-single` only when `native_single_select` is in that intersection. Keep `nativeLimits`. If `Relay contract` is `-`, native-single is unavailable.
58
+
59
+ - When `native-single` is available and the option count fits `nativeLimits` (unique labels, within min/max): call `interactions.native-single.function` once with one question and every option as `{label, description}` in original order. Do not print a numbered list in chat while the native tool is available. Claude Code's function is `AskUserQuestion`, Grok's is `ask_user_question`, Codex's is `request_user_input` — copy the relay field; do not substitute one name for another.
60
+ - Otherwise render a 1-based numbered Markdown list and wait for the next message. Do not drop options to force the native tool.
61
+
62
+ Never invent a picker function. Never ask the user to type a number when the native tool is available.
63
+
51
64
  ## Step 1: Select a task from the fixed list view
52
65
 
53
66
  ```bash
@@ -56,7 +69,7 @@ okstra user-response list-view --home <resolved-home> --project <projectId> --li
56
69
 
57
70
  The view gives `Task key`, `Task type`, `Report`, open-item counts, and readability status. If the count is zero, answer `No task has open clarification items.` and stop. Do not continue with an unreadable entry.
58
71
 
59
- Present up to three task choices. The final picker option is `Enter directly`, where the user may provide a report path or task key.
72
+ Present up to three task choices through the host picker. The final picker option is `Enter directly`, where the user may provide a report path or task key.
60
73
 
61
74
  ## Step 2: Read the fixed report view
62
75
 
@@ -64,21 +77,41 @@ Present up to three task choices. The final picker option is `Enter directly`, w
64
77
  okstra user-response show-view --report <reportPath> --project-root <projectRoot>
65
78
  ```
66
79
 
67
- The view contains the report identity, contract version, every open clarification question, its expected form, its current response and disposition, its options, approval context, plan option candidates, current plan decision, and resolved context. It is the only report-information source for this skill.
80
+ The view contains the report identity, contract version, every open clarification question, its expected form, its current response and disposition, its options, approval context, plan option candidates, current plan decision, resolved context, why the row is asked, linked plan items, and cited artifacts. Question text and `options[]` come only from this view. Do not open a report record to select fields.
68
81
 
69
82
  Each entry in `options[]` corresponds to `{role, answer, rationale, scopeImpact, addedWork, directionChange, disposition}`. Put the `recommended` option first and suffix its label with `(Recommended)`. Then put the alternatives in view order and finish with `Enter directly`.
70
83
 
71
- Contract 3.0 options also expose `reach` and `scopeEffects`. Contract 3.0 approval-blocking rows expose `approvalContext`. Present those values exactly as the view prints them.
84
+ Contract 3.0 options also expose `reach` and `scopeEffects`. Contract 3.0 approval-blocking rows expose `approvalContext`.
85
+
86
+ When an axis says `not stated in the report`, repeat that text. Do not infer missing report-owned impact. The skill must **never invent it**.
72
87
 
73
- Each option description uses all three impact axes in this order:
88
+ ## Step 2b: Investigate cited context before asking
74
89
 
75
- > `<rationale>` — Scope: `<scopeImpact>` · Added work: `<addedWork>` · Direction: `<directionChange>`
90
+ Do not present a picker from the raw field dump. For each still-open item, read the investigation list the view printed:
76
91
 
77
- When an axis says `not stated in the report`, repeat that text. Do not infer missing impact. The skill must **never invent it**.
92
+ 1. Every `Cited artifacts:` `path:line` — open that file under the project root from preflight. The line number is the starting point, not a license to skip the surrounding function or section.
93
+ 2. Every `Linked plan items:` definition and every `Context:` definition.
94
+
95
+ Stop at that list. Do not search the rest of the repository for extra files. If a cited path is missing or unreadable, say so in the question; do not guess its contents.
96
+
97
+ Investigation explains. It never adds an option, drops an option, or changes the answer that `--option-number` will record.
78
98
 
79
99
  ## Step 3: Ask one clarification at a time
80
100
 
81
- For each item that still needs an answer, show its position, ID, blocking effect, question, expected form, and resolved context. Use one single-select question per clarification.
101
+ Ask in the user's language. Do not lead with `Kind`, `Blocks`, `Expected form`, or `C-NNN`. The question body is:
102
+
103
+ 1. Why this is being asked (`Why asked`, restated so a non-author of the report can follow it).
104
+ 2. What is already decided (`Context` and linked plan items, in one or two sentences).
105
+ 3. The fork (`Question`, restated as a choice the user can act on).
106
+ 4. What stays blocked if they do not answer (`Blocks=approval` → the plan cannot be approved; `Blocks=next-phase` → the next phase cannot start cleanly).
107
+
108
+ Keep the row id at the end of the question, in parentheses, so the later transaction can name it.
109
+
110
+ Use one single-select question per clarification, through the host picker. Each option description uses this order:
111
+
112
+ > If you pick this: `<addedWork>`. What it reverses: `<directionChange>`. Scope: `<reach or scopeImpact>`. Why it is on the board: `<rationale>`.
113
+
114
+ When investigation quoted a cited file, add one more sentence that names the path. That sentence does not replace a `not stated in the report` axis.
82
115
 
83
116
  Use the displayed values to confirm the user's choice. Do not copy a predefined option's answer, disposition, reach, or scope effects into command arguments. The typed command resolves those report-owned fields from its option number.
84
117
 
@@ -88,15 +121,16 @@ Use the displayed values to confirm the user's choice. Do not copy a predefined
88
121
  | Enters an answer | the user's text verbatim | `answer` |
89
122
  | Asks for the item to be presented again | the user's request verbatim | `reframe` |
90
123
 
91
- Copy `kind` from the view. A `reframe` does not satisfy the gate. If the user asks what an item means, explain only from the view and ask the same item again.
124
+ Copy `kind` from the view. A `reframe` does not satisfy the gate. If the user asks what an item means, explain from the view plus the cited files already read, then ask the same item again.
92
125
 
93
126
  ## Step 4: Confirm the complete response
94
127
 
95
- Echo each clarification ID, kind, disposition, value, and rationale. Include any explicit plan decision or legacy report-authoring decision. Ask:
128
+ Echo each clarification ID, kind, disposition, value, and rationale. Include any explicit plan decision or legacy report-authoring decision. Ask through the host picker, two options:
96
129
 
97
- > Record it as shown above? Reply `confirmed` to publish the sidecar.
130
+ 1. `Record as shown` (Recommended)
131
+ 2. `Change an answer`
98
132
 
99
- Do not start a transaction until the user clearly confirms. If the user changes an item, show the complete response again and reconfirm.
133
+ Do not start a transaction until the user picks `Record as shown`. If they pick `Change an answer`, show the complete response again and reconfirm with the same picker. Do not ask them to type `confirmed`.
100
134
 
101
135
  ## Step 5: Begin the typed transaction
102
136