okstra 0.184.0 → 0.185.1
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 +2 -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 +386 -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 +9 -7
- package/docs/cli.md +3 -2
- package/docs/for-ai/README.md +4 -2
- package/docs/for-ai/skills/okstra-chat.md +34 -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 +6 -5
- 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/plan-body-verification.md +8 -7
- 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 +4 -3
- 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/incremental_carry.py +149 -5
- package/runtime/python/okstra_ctl/incremental_scope.py +25 -3
- package/runtime/python/okstra_ctl/next_phase.py +67 -4
- package/runtime/python/okstra_ctl/plan_items.py +40 -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 +108 -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 +109 -34
|
@@ -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,108 @@
|
|
|
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:@from:to:body`. Recipient `all` stays `all`. Sender is always `@name`.
|
|
63
|
+
|
|
64
|
+
If the output is `no unread`, do not ack.
|
|
65
|
+
|
|
66
|
+
If there are unread rows, always run:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
okstra chat ack --room <room> --as <display> --through <id>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use the last unread row's id. CLI unread does not move the cursor; this ack does.
|
|
73
|
+
|
|
74
|
+
## Step 3: Next action
|
|
75
|
+
|
|
76
|
+
Ask: send, unread, inbox, log, or done.
|
|
77
|
+
|
|
78
|
+
There is no ack menu item. Choosing unread again shows the rows then acks, same as Step 2.
|
|
79
|
+
|
|
80
|
+
### Send
|
|
81
|
+
|
|
82
|
+
1. Run `okstra chat members --room <room>`.
|
|
83
|
+
2. Pick the recipient from `all` plus those names except the current display name (`--as`). Filter after `okstra chat members`. Do not pass `--as` to `members`. Recipient is required.
|
|
84
|
+
3. Ask for the body as free input.
|
|
85
|
+
4. Run `okstra chat send --room <room> --as <display> --to <all|name> --body <text>`.
|
|
86
|
+
|
|
87
|
+
### Inbox
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
okstra chat inbox --room <room> --as <display>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This is every arrival to `@you` or `all`, including messages already acked and messages you sent to `all` or to yourself. Inbox does not move the cursor.
|
|
94
|
+
|
|
95
|
+
### Log
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
okstra chat log --room <room> --as <display>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The whole room, including messages not addressed to you. Log does not move the cursor.
|
|
102
|
+
|
|
103
|
+
## Rules
|
|
104
|
+
|
|
105
|
+
- Call only `okstra chat`. Do not open files under the chat store.
|
|
106
|
+
- Do not treat chat rows as evidence for a finding, verdict, or assignment.
|
|
107
|
+
- Workers may run the same commands with `--name` and `--as`. Joining is optional.
|
|
108
|
+
- 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
|
|
|
@@ -642,6 +642,35 @@ def write_json(path: Path, payload: dict) -> None:
|
|
|
642
642
|
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n")
|
|
643
643
|
|
|
644
644
|
|
|
645
|
+
def _report_already_approved(report_data: Mapping[str, Any] | None) -> bool:
|
|
646
|
+
if not isinstance(report_data, Mapping):
|
|
647
|
+
return False
|
|
648
|
+
frontmatter = report_data.get("frontmatter")
|
|
649
|
+
return isinstance(frontmatter, Mapping) and frontmatter.get("approved") is True
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _derive_awaiting_approval(
|
|
653
|
+
*,
|
|
654
|
+
existing: bool,
|
|
655
|
+
validation_status: str,
|
|
656
|
+
current_phase: str,
|
|
657
|
+
pointer: Mapping[str, str],
|
|
658
|
+
report_data: Mapping[str, Any] | None,
|
|
659
|
+
) -> bool:
|
|
660
|
+
"""planning 이 승인 가능한 plan-ready 를 남기면 올리고, implementation
|
|
661
|
+
이 그 승인을 소비하면 내린다. 차단 게이트나 열린 Blocks=approval 은
|
|
662
|
+
포인터가 blocked 라 올리지 않는다."""
|
|
663
|
+
if validation_status == "passed" and current_phase == "implementation":
|
|
664
|
+
return False
|
|
665
|
+
if validation_status == "passed" and current_phase == "implementation-planning":
|
|
666
|
+
return (
|
|
667
|
+
pointer.get("phase") == "implementation"
|
|
668
|
+
and pointer.get("status") == next_phase.STATUS_READY
|
|
669
|
+
and not _report_already_approved(report_data)
|
|
670
|
+
)
|
|
671
|
+
return existing
|
|
672
|
+
|
|
673
|
+
|
|
645
674
|
def update_workflow_metadata(
|
|
646
675
|
run_manifest: dict,
|
|
647
676
|
task_manifest: dict,
|
|
@@ -693,7 +722,8 @@ def update_workflow_metadata(
|
|
|
693
722
|
next_recommended_phase = next_phase.make(
|
|
694
723
|
phase=projected["phase"],
|
|
695
724
|
status=projected["status"],
|
|
696
|
-
rationale=
|
|
725
|
+
rationale=projected["rationale"]
|
|
726
|
+
or (
|
|
697
727
|
"리포트 라우팅에서 투영됨. 리드가 쓴 값과 근거는 "
|
|
698
728
|
"nextRecommendedPhaseCorrection.authored 에 있다."
|
|
699
729
|
),
|
|
@@ -713,16 +743,16 @@ def update_workflow_metadata(
|
|
|
713
743
|
status=next_phase.STATUS_BLOCKED, rationale=authored["rationale"]
|
|
714
744
|
)
|
|
715
745
|
|
|
716
|
-
|
|
717
|
-
if not isinstance(
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
746
|
+
awaiting_existing = workflow.get("awaitingApproval")
|
|
747
|
+
if not isinstance(awaiting_existing, bool):
|
|
748
|
+
awaiting_existing = False
|
|
749
|
+
awaiting_approval = _derive_awaiting_approval(
|
|
750
|
+
existing=awaiting_existing,
|
|
751
|
+
validation_status=validation_status,
|
|
752
|
+
current_phase=current_phase,
|
|
753
|
+
pointer=next_recommended_phase,
|
|
754
|
+
report_data=report_data,
|
|
755
|
+
)
|
|
726
756
|
|
|
727
757
|
last_safe_checkpoint = workflow.get("lastSafeCheckpoint", {})
|
|
728
758
|
if not isinstance(last_safe_checkpoint, dict):
|
|
@@ -3562,6 +3592,7 @@ def validate_final_report_data(
|
|
|
3562
3592
|
_validate_clarification_evidence_note(data, failures)
|
|
3563
3593
|
_validate_approval_clarification_backtrace(data, failures)
|
|
3564
3594
|
_validate_rerun_guidance(data, failures)
|
|
3595
|
+
_validate_approval_guidance(data, failures)
|
|
3565
3596
|
_validate_variation_point_analysis(
|
|
3566
3597
|
(data.get("implementationPlanning") or {}).get("variationPointAnalysis"),
|
|
3567
3598
|
resolve_architecture(_project_root_from_report(report_path)),
|
|
@@ -4147,10 +4178,11 @@ def _stage_scope_bucket(item: dict, pbv: dict) -> str:
|
|
|
4147
4178
|
|
|
4148
4179
|
Returns `in-scope` (may block), `observed` (only frozen stages), or
|
|
4149
4180
|
`deferred` (only stages not yet startable). Anything unresolvable is
|
|
4150
|
-
`in-scope`: an absent ledger is no basis to narrow
|
|
4151
|
-
`
|
|
4152
|
-
|
|
4153
|
-
|
|
4181
|
+
`in-scope`: an absent ledger is no basis to narrow. Plan-wide items
|
|
4182
|
+
(`P-Opt-*`, `P-Var-*`, `P-Dep-*`, `P-Dir-1`) with no `stageScope` stay
|
|
4183
|
+
in-scope. An unscoped `P-Val-*` / `P-Req-*` / `P-Rb-*` stays in-scope only
|
|
4184
|
+
until a stage is `done`; after that it is `deferred` so a re-plan does not
|
|
4185
|
+
re-score the whole checklist.
|
|
4154
4186
|
|
|
4155
4187
|
디스패치 큐와 같은 함수를 쓴다. 검증기가 다른 통을 내면 워커가 안 본
|
|
4156
4188
|
항목이 승인을 막거나, 본 항목이 게이트에서 빠진다.
|
|
@@ -6130,16 +6162,18 @@ def _validate_approval_clarification_backtrace(
|
|
|
6130
6162
|
f"final-report data.json: clarification `{row_id}` blocks approval "
|
|
6131
6163
|
"and is linked, but the link resolves to no stage. `incremental-"
|
|
6132
6164
|
"scope` reads the stage from a `P-Step-<stage>.<step>` / `P-Prep-"
|
|
6133
|
-
"S<stage>-<kind>` plan-item id,
|
|
6134
|
-
|
|
6135
|
-
"
|
|
6136
|
-
"
|
|
6137
|
-
"
|
|
6138
|
-
"
|
|
6165
|
+
"S<stage>-<kind>` plan-item id, from `stageScope` / `stageRefs` on "
|
|
6166
|
+
"the linked plan item or coverage row, or from a `Stage N` citation "
|
|
6167
|
+
f"in the blocked coverage row's `coveredBy`. A `P-Req-*` / `P-Val-*` "
|
|
6168
|
+
"id carries no stage number, so a row linked only that way must "
|
|
6169
|
+
"carry `stageRefs` or cite the stage in `coveredBy`. A blocker "
|
|
6170
|
+
"whose blast radius resolves to no stage cannot auto-narrow the "
|
|
6171
|
+
"next re-run; this report fails rather than forcing a full re-run."
|
|
6139
6172
|
)
|
|
6140
6173
|
|
|
6141
6174
|
|
|
6142
6175
|
_RERUN_FLAG = "--answered-clarifications"
|
|
6176
|
+
_APPROVE_HINT = re.compile(r"--approve|\bapprov", re.IGNORECASE)
|
|
6143
6177
|
|
|
6144
6178
|
|
|
6145
6179
|
def _next_step_texts(steps: object) -> list[str]:
|
|
@@ -6156,6 +6190,24 @@ def _next_step_texts(steps: object) -> list[str]:
|
|
|
6156
6190
|
return texts
|
|
6157
6191
|
|
|
6158
6192
|
|
|
6193
|
+
def _has_blocks_approval_row(data: dict) -> bool:
|
|
6194
|
+
return any(
|
|
6195
|
+
isinstance(row, dict) and row.get("blocks") == "approval"
|
|
6196
|
+
for row in data.get("clarificationItems") or []
|
|
6197
|
+
)
|
|
6198
|
+
|
|
6199
|
+
|
|
6200
|
+
def _planning_gate_blocks_approval(data: dict) -> bool:
|
|
6201
|
+
planning = data.get("implementationPlanning")
|
|
6202
|
+
if not isinstance(planning, dict):
|
|
6203
|
+
return False
|
|
6204
|
+
verification = planning.get("planBodyVerification")
|
|
6205
|
+
if not isinstance(verification, dict):
|
|
6206
|
+
return False
|
|
6207
|
+
gate = str(verification.get("gateResult") or "").strip().lower()
|
|
6208
|
+
return gate in {"blocked-by-disagreement", "aborted-non-result"}
|
|
6209
|
+
|
|
6210
|
+
|
|
6159
6211
|
def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
|
|
6160
6212
|
"""A report that withholds approval must say how to come back from it.
|
|
6161
6213
|
|
|
@@ -6168,10 +6220,7 @@ def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
|
|
|
6168
6220
|
"""
|
|
6169
6221
|
if (data.get("header") or {}).get("taskType") != "implementation-planning":
|
|
6170
6222
|
return
|
|
6171
|
-
has_blocker =
|
|
6172
|
-
isinstance(row, dict) and row.get("blocks") == "approval"
|
|
6173
|
-
for row in data.get("clarificationItems") or []
|
|
6174
|
-
)
|
|
6223
|
+
has_blocker = _has_blocks_approval_row(data)
|
|
6175
6224
|
if not has_blocker:
|
|
6176
6225
|
return
|
|
6177
6226
|
if any(_RERUN_FLAG in text for text in _next_step_texts(
|
|
@@ -6187,6 +6236,34 @@ def _validate_rerun_guidance(data: dict, failures: list[str]) -> None:
|
|
|
6187
6236
|
)
|
|
6188
6237
|
|
|
6189
6238
|
|
|
6239
|
+
def _validate_approval_guidance(data: dict, failures: list[str]) -> None:
|
|
6240
|
+
"""승인 가능한 plan-ready 는 사용자에게 승인하라고 말해야 한다.
|
|
6241
|
+
|
|
6242
|
+
포인터가 implementation/ready 여도 승인은 사용자만 뒤집는다. 다음 단계
|
|
6243
|
+
안내가 계획 재실행이면 승인 칸을 건너뛰고 같은 단계를 다시 돈다.
|
|
6244
|
+
"""
|
|
6245
|
+
if (data.get("header") or {}).get("taskType") != "implementation-planning":
|
|
6246
|
+
return
|
|
6247
|
+
planning = data.get("implementationPlanning")
|
|
6248
|
+
if not isinstance(planning, dict) or planning.get("outcome") != "plan-ready":
|
|
6249
|
+
return
|
|
6250
|
+
if _has_blocks_approval_row(data) or _planning_gate_blocks_approval(data):
|
|
6251
|
+
return
|
|
6252
|
+
if _report_already_approved(data):
|
|
6253
|
+
return
|
|
6254
|
+
if any(_APPROVE_HINT.search(text) for text in _next_step_texts(
|
|
6255
|
+
data.get("recommendedNextSteps")
|
|
6256
|
+
)):
|
|
6257
|
+
return
|
|
6258
|
+
failures.append(
|
|
6259
|
+
"final-report data.json: this plan is ready for the user to approve, "
|
|
6260
|
+
"but no `recommendedNextSteps` entry tells the reader to approve — "
|
|
6261
|
+
"name `--approve` or the in-session wizard in a step's `text` or "
|
|
6262
|
+
"one of its `commands`. Do not recommend another "
|
|
6263
|
+
"implementation-planning run."
|
|
6264
|
+
)
|
|
6265
|
+
|
|
6266
|
+
|
|
6190
6267
|
def _validate_self_fix_grouping(data: dict, failures: list[str]) -> None:
|
|
6191
6268
|
"""A self-fix round must be instructed by cause, not as a flat item list.
|
|
6192
6269
|
|
|
@@ -8163,11 +8240,11 @@ def _validate_plan_body_clarification_matching(
|
|
|
8163
8240
|
failures: list[str],
|
|
8164
8241
|
accepted_item_ids: set[str] | None = None,
|
|
8165
8242
|
) -> None:
|
|
8166
|
-
"""H5 — every plan item
|
|
8167
|
-
must point
|
|
8168
|
-
clarification row.
|
|
8169
|
-
|
|
8170
|
-
|
|
8243
|
+
"""H5 — every plan item whose *gate class after stage scope* is
|
|
8244
|
+
`majority-disagree` must point at an existing `blocks: approval`
|
|
8245
|
+
clarification row. Observed / deferred / record items stay in `setAside`
|
|
8246
|
+
and must not become a new C row — that is what grew the clarification
|
|
8247
|
+
list while the next stage was already executable.
|
|
8171
8248
|
"""
|
|
8172
8249
|
ip = data.get("implementationPlanning")
|
|
8173
8250
|
if not isinstance(ip, dict):
|
|
@@ -8189,9 +8266,7 @@ def _validate_plan_body_clarification_matching(
|
|
|
8189
8266
|
for item in pbv.get("planItems") or []:
|
|
8190
8267
|
if not isinstance(item, dict):
|
|
8191
8268
|
continue
|
|
8192
|
-
if
|
|
8193
|
-
continue
|
|
8194
|
-
if _is_dissent_downgraded(item, pbv, accepted):
|
|
8269
|
+
if _plan_item_gate_class(item, pbv, accepted) != "majority-disagree":
|
|
8195
8270
|
continue
|
|
8196
8271
|
item_id = item.get("id") or "<unknown>"
|
|
8197
8272
|
cids = _plan_item_clarification_ids(item)
|
|
@@ -8244,7 +8319,7 @@ def _validate_self_fix_before_clarification(data: dict, failures: list[str]) ->
|
|
|
8244
8319
|
for item in pbv.get("planItems") or []:
|
|
8245
8320
|
if not isinstance(item, dict):
|
|
8246
8321
|
continue
|
|
8247
|
-
if
|
|
8322
|
+
if _plan_item_gate_class(item, pbv, set()) != "majority-disagree":
|
|
8248
8323
|
continue
|
|
8249
8324
|
if _has_planner_fixable_majority(item):
|
|
8250
8325
|
allowed = " / ".join(sorted(_SELF_FIX_EXHAUSTED_REASONS))
|