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.
- package/README.md +1 -1
- package/dist/cli-registry.mjs +9 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/chat/chat.d.mts +1 -0
- package/dist/commands/chat/chat.mjs +385 -0
- package/dist/commands/chat/chat.mjs.map +1 -0
- package/dist/lib/skill-catalog.mjs +1 -0
- package/dist/lib/skill-catalog.mjs.map +1 -1
- package/docs/architecture.md +8 -6
- package/docs/cli.md +2 -1
- package/docs/for-ai/README.md +4 -2
- package/docs/for-ai/skills/okstra-chat.md +28 -0
- package/docs/for-ai/skills/okstra-inspect.md +1 -1
- package/docs/for-ai/skills/okstra-run.md +2 -2
- package/docs/for-ai/skills/okstra-user-response.md +10 -8
- package/docs/project-structure-overview.md +5 -4
- package/docs/task-process/README.md +1 -1
- package/docs/task-process/implementation-planning.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +2 -1
- package/runtime/prompts/wizard/prompts.ko.json +2 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
- package/runtime/python/okstra_ctl/next_phase.py +67 -4
- package/runtime/python/okstra_ctl/user_response.py +147 -37
- package/runtime/python/okstra_ctl/wizard.py +13 -0
- package/runtime/skills/okstra-chat/SKILL.md +104 -0
- package/runtime/skills/okstra-inspect/facets/status.md +6 -5
- package/runtime/skills/okstra-run/SKILL.md +2 -2
- package/runtime/skills/okstra-user-response/SKILL.md +50 -16
- 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
|
-
|
|
2029
|
-
row
|
|
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. **
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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.
|
|
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
|
|
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 `
|
|
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,
|
|
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`.
|
|
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
|
-
|
|
88
|
+
## Step 2b: Investigate cited context before asking
|
|
74
89
|
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
130
|
+
1. `Record as shown` (Recommended)
|
|
131
|
+
2. `Change an answer`
|
|
98
132
|
|
|
99
|
-
Do not start a transaction until the user
|
|
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
|
|