Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions apps/board/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ resolves run to MR in the background; the request path never waits on it).
(review first round, review re-review, respond plan, respond post), each
rebuilt from a real gate's shape with invented data
(`gate-stories.fixtures.ts`). Keep it to gates a real run asks; a variant
belongs in a DOM test, not a new story. Invented names only (the repo is
public).
belongs in a DOM test, not a new story. The one exception is the
`Review question: …` stories, one per state of asking the review pane about
a finding (the ask link, the box open, sending, asked, a 409, the pane
gone, a parked gate, the revised strip), kept so that UI can be worked on
in Storybook; their play steps click into each state. Invented names only
(the repo is public).

## Respond rounds

Expand Down
13 changes: 7 additions & 6 deletions apps/board/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ board is not peering at all.

## Launching agents (local-only)

| endpoint | body |
| --------------- | ----------------------------------------------------------------------------------- |
| `POST /review` | `{ mrUrl, iid }`. `400` when `reviewCwd` is unset |
| `POST /respond` | same shape, for your own MR |
| `POST /doctor` | same shape, to repair mechanical breakage |
| `POST /drafts` | approve or dismiss a held doctor draft. The only path from a draft to a GitLab note |
| endpoint | body |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /review` | `{ mrUrl, iid }`. `400` when `reviewCwd` is unset |
| `POST /respond` | same shape, for your own MR |
| `POST /doctor` | same shape, to repair mechanical breakage |
| `POST /drafts` | approve or dismiss a held doctor draft. The only path from a draft to a GitLab note |
| `POST /review/finding-question` | `{ mrUrl, iid, gateId, findingId, question, tabId? }`. Types `/board:review-finding-question <gateId> <findingId> "<question>"` into the pane running the review's Claude session (a pane at its gate form gets one Escape, then the line again) and focuses it. On a parked gate with no pane running the session, resumes the review with the line as its prompt instead. `400` for a finding id outside `[A-Za-z0-9._:-]` or an empty question or one over 2000 characters; `409` when the gate is not the MR's open or parked review gate, the gate does not list the finding, no pane runs the session of an open gate, the resume tab is already open, or the pane will not take the line |

A launched agent's lifecycle status never arrives over HTTP. The status CLIs
publish it on the rt daemon's event bus (`board/agent-status/<kind>`) and the
Expand Down
140 changes: 140 additions & 0 deletions apps/board/skills-src/review-finding-question/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
name: board:review-finding-question
description: >-
Use when the human asks about one finding of the review this session wrote,
invoked in a board review pane as "/board:review-finding-question <gate-id>
<finding-id> "<question>"" while the session holds at its review-post gate.
Not for any other session.
disable-model-invocation: true
---

# Review finding question

The human is deciding on your review gate and has a question about one
finding: is it real, is it overstated, what does the data say. You answer
with evidence, then they choose to revise the finding or leave it. They
finish the review on the board, never in this pane.

## Arguments

`<gate-id> <finding-id> "<question>"`, commas between them optional. The
gate is the review-post gate this session opened; the finding is an `id` in
your report json (the `.json` sibling of your `--report`). Your `--state`
and `--status-bin` are the ones this session was launched with.

This skill runs only inside the board review session that wrote that
report. If this session has no `/board:review` launch, or the finding id is
not in the report json, say so in one line and stop.

The line can also arrive as the first message of a resume: a gate the board
parked after a long wait closed this pane, and a question about one of its
findings resumes the session with it. The gate is then `parked`, not `open`,
and no wait loop is running; everything below holds the same.

## The rule for the whole conversation

Until the human says to revise, you change nothing: no edit to the report,
no gate opened or answered, no post to the MR, no commit, no database
write. Databases only through rt's sdm verbs (the `rt:sdm-connect` skill),
only `*-read-only` resources, QA unless the human names prod in this
conversation. Posting and the verdict are the human's, on the board.

## 1. Restate, then explore through a helper

Restate in two lines: the finding (tier, title, `file:line`) and the
question. Then dispatch one explorer with the Agent tool
(`subagent_type: Explore`, which has no Edit or Write, so read-only is
enforced; `model: sonnet`, `effort: medium`) and wait for it. Its brief is:

- the finding, verbatim from the report json, and the MR url;
- the human's question, verbatim;
- what it may use: the code (`mr_view`, `gitlab_get`, or a worktree
through the `rt:worktree` skill, disposed of before it returns), logs,
New Relic, and QA databases through rt's sdm verbs on `*-read-only`
resources; prod only when the human named it;
- that it changes nothing anywhere and returns: what it checked (each query
or search, verbatim), what it found, what it could not check, and its
verdict.

Each follow-up question that needs new digging gets its own explorer with
the same brief and the new question. A question you can answer from what
you already hold, answer directly.

## 2. Report in this shape, then ask for the outcome as a form

```
Verdict: holds | narrower than written | broader than written | does not hold
Evidence:
- <what was checked> → <what it showed> (one line each, queries verbatim)
Not checked: <what the explorer could not reach, or "nothing">
Proposed change: <new tier / anchor / comment, or "drop f2", or "none">
```

**REQUIRED SUB-SKILL:** Use mattstack:wrap-up to ask for the outcome. The
form has one Decisions question, "What should happen to <finding-id>?",
with exactly these options, the one matching your verdict first and
labelled `(Recommended)`:

- **Revise and re-raise the gate**: apply the proposed change.
- **Leave it as is**: the finding stands.
- **Keep asking**: the human has more to ask.

Recommend "Revise" when the proposed change is not "none", otherwise
"Leave it as is". Act on the answer: §3, §4, or wait for the next
question. A typed reply instead of a pick is a follow-up question: answer
it, report again in the same shape, and ask again.

## 3. Revise (only on the human's go-ahead)

1. Show the revised finding next to the original: tier, anchor, body,
comment. Keep its `id` and every field you were not asked to change.
For "drop", say so. Then ask with mattstack:wrap-up: one Decisions
question, "Apply this revision to <finding-id>?", options **Apply it
(Recommended)**, **Change the wording**, **Cancel**. Apply only on
"Apply it"; "Change the wording" takes their edit and asks again;
"Cancel" goes to §4.
2. Edit the finding in the report json and the report markdown, keeping
them in step (tier counts and summary lines that name it included).
3. Rebuild the gate's open file exactly as this session built the first
one (the same scripts, extras and arguments). The old gate and its wait
loop stay as they are until this succeeds: if it fails, say what
failed, restore nothing by hand, and keep holding at the old gate.
4. Check the old gate is still waiting on the human:
`<status-bin> gate wait <state> --max-ms 1000`. Only `{"status":
"pending"}` goes on. An `answers` line means the human already decided
on the board: re-raise nothing, tell them the review was answered
before the revision landed and that it is in the report only, then
carry on with that answer as the wrapper's gate step does (a running
wait loop delivers the same answer: act on it once). A failure (the
gate closed) is the same: re-raise nothing and say so.
5. If a background wait loop is running on this gate, stop it with
TaskStop (load it with ToolSearch when it is deferred).
6. Run the same `open-gate.sh <status-bin> <state> review-post
<open-file>`. The new gate supersedes the old one, a parked one too.
7. Start one background `<status-bin> gate wait <state> --max-ms 90000`
loop on the new gate and end the turn holding at it. Do not show a pane
form: the human finishes on the board.
8. Tell the human: the finding is revised, the gate is re-raised, finish
the review on the board.

A `closed (superseded)` result for the old gate id later is your own
re-raise: ignore it and keep holding at the new gate. If the open fails,
say what failed, restore nothing by hand, and leave the old gate's state as
the wrapper's gate step would.

## 4. Leave it as is

Change nothing. Tell the human the finding stands and to finish on the
board. If this gate's wait loop is no longer running, start one, then end
the turn holding at the gate.

## Red flags

| Thought | Do instead |
|---|---|
| "I'll just grep and query myself" | Dispatch the explorer; your context holds the review. |
| "Prod read-only would settle it" | Offer it; run it only when the human names prod. |
| "They want it gone, I'll answer the gate as comment" | Revise, re-raise, send them to the board. |
| "The old gate closed, the run is over" | That close is your re-raise. Hold at the new gate. |
| "The revision is ready, re-raise it now" | Check the old gate is still pending first; a late re-raise reopens a posted review. |
| "I'll list the options in text, it's quicker" | The outcome is a mattstack:wrap-up form; the pick is the record. |
143 changes: 143 additions & 0 deletions apps/board/skills/review-finding-question/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
name: board:review-finding-question
description: >-
Use when the human asks about one finding of the review this session wrote,
invoked in a board review pane as "/board:review-finding-question <gate-id>
<finding-id> "<question>"" while the session holds at its review-post gate.
Not for any other session.
disable-model-invocation: true
---

<!-- expanded by rt skills expand from the sources below; edits here are drift (edit the source dir and re-run) -->

<!-- part: step source=review-finding-question/SKILL.md path=review-finding-question/SKILL.md lines=11-140 -->
# Review finding question

The human is deciding on your review gate and has a question about one
finding: is it real, is it overstated, what does the data say. You answer
with evidence, then they choose to revise the finding or leave it. They
finish the review on the board, never in this pane.

## Arguments

`<gate-id> <finding-id> "<question>"`, commas between them optional. The
gate is the review-post gate this session opened; the finding is an `id` in
your report json (the `.json` sibling of your `--report`). Your `--state`
and `--status-bin` are the ones this session was launched with.

This skill runs only inside the board review session that wrote that
report. If this session has no `/board:review` launch, or the finding id is
not in the report json, say so in one line and stop.

The line can also arrive as the first message of a resume: a gate the board
parked after a long wait closed this pane, and a question about one of its
findings resumes the session with it. The gate is then `parked`, not `open`,
and no wait loop is running; everything below holds the same.

## The rule for the whole conversation

Until the human says to revise, you change nothing: no edit to the report,
no gate opened or answered, no post to the MR, no commit, no database
write. Databases only through rt's sdm verbs (the `rt:sdm-connect` skill),
only `*-read-only` resources, QA unless the human names prod in this
conversation. Posting and the verdict are the human's, on the board.

## 1. Restate, then explore through a helper

Restate in two lines: the finding (tier, title, `file:line`) and the
question. Then dispatch one explorer with the Agent tool
(`subagent_type: Explore`, which has no Edit or Write, so read-only is
enforced; `model: sonnet`, `effort: medium`) and wait for it. Its brief is:

- the finding, verbatim from the report json, and the MR url;
- the human's question, verbatim;
- what it may use: the code (`mr_view`, `gitlab_get`, or a worktree
through the `rt:worktree` skill, disposed of before it returns), logs,
New Relic, and QA databases through rt's sdm verbs on `*-read-only`
resources; prod only when the human named it;
- that it changes nothing anywhere and returns: what it checked (each query
or search, verbatim), what it found, what it could not check, and its
verdict.

Each follow-up question that needs new digging gets its own explorer with
the same brief and the new question. A question you can answer from what
you already hold, answer directly.

## 2. Report in this shape, then ask for the outcome as a form

```
Verdict: holds | narrower than written | broader than written | does not hold
Evidence:
- <what was checked> → <what it showed> (one line each, queries verbatim)
Not checked: <what the explorer could not reach, or "nothing">
Proposed change: <new tier / anchor / comment, or "drop f2", or "none">
```

**REQUIRED SUB-SKILL:** Use mattstack:wrap-up to ask for the outcome. The
form has one Decisions question, "What should happen to <finding-id>?",
with exactly these options, the one matching your verdict first and
labelled `(Recommended)`:

- **Revise and re-raise the gate**: apply the proposed change.
- **Leave it as is**: the finding stands.
- **Keep asking**: the human has more to ask.

Recommend "Revise" when the proposed change is not "none", otherwise
"Leave it as is". Act on the answer: §3, §4, or wait for the next
question. A typed reply instead of a pick is a follow-up question: answer
it, report again in the same shape, and ask again.

## 3. Revise (only on the human's go-ahead)

1. Show the revised finding next to the original: tier, anchor, body,
comment. Keep its `id` and every field you were not asked to change.
For "drop", say so. Then ask with mattstack:wrap-up: one Decisions
question, "Apply this revision to <finding-id>?", options **Apply it
(Recommended)**, **Change the wording**, **Cancel**. Apply only on
"Apply it"; "Change the wording" takes their edit and asks again;
"Cancel" goes to §4.
2. Edit the finding in the report json and the report markdown, keeping
them in step (tier counts and summary lines that name it included).
3. Rebuild the gate's open file exactly as this session built the first
one (the same scripts, extras and arguments). The old gate and its wait
loop stay as they are until this succeeds: if it fails, say what
failed, restore nothing by hand, and keep holding at the old gate.
4. Check the old gate is still waiting on the human:
`<status-bin> gate wait <state> --max-ms 1000`. Only `{"status":
"pending"}` goes on. An `answers` line means the human already decided
on the board: re-raise nothing, tell them the review was answered
before the revision landed and that it is in the report only, then
carry on with that answer as the wrapper's gate step does (a running
wait loop delivers the same answer: act on it once). A failure (the
gate closed) is the same: re-raise nothing and say so.
5. If a background wait loop is running on this gate, stop it with
TaskStop (load it with ToolSearch when it is deferred).
6. Run the same `open-gate.sh <status-bin> <state> review-post
<open-file>`. The new gate supersedes the old one, a parked one too.
7. Start one background `<status-bin> gate wait <state> --max-ms 90000`
loop on the new gate and end the turn holding at it. Do not show a pane
form: the human finishes on the board.
8. Tell the human: the finding is revised, the gate is re-raised, finish
the review on the board.

A `closed (superseded)` result for the old gate id later is your own
re-raise: ignore it and keep holding at the new gate. If the open fails,
say what failed, restore nothing by hand, and leave the old gate's state as
the wrapper's gate step would.

## 4. Leave it as is

Change nothing. Tell the human the finding stands and to finish on the
board. If this gate's wait loop is no longer running, start one, then end
the turn holding at the gate.

## Red flags

| Thought | Do instead |
|---|---|
| "I'll just grep and query myself" | Dispatch the explorer; your context holds the review. |
| "Prod read-only would settle it" | Offer it; run it only when the human names prod. |
| "They want it gone, I'll answer the gate as comment" | Revise, re-raise, send them to the board. |
| "The old gate closed, the run is over" | That close is your re-raise. Hold at the new gate. |
| "The revision is ready, re-raise it now" | Check the old gate is still pending first; a late re-raise reopens a posted review. |
| "I'll list the options in text, it's quicker" | The outcome is a mattstack:wrap-up form; the pick is the record. |
8 changes: 8 additions & 0 deletions apps/board/skills/review-finding-question/compiled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "skills:board:review-finding-question",
"dir": "skills/review-finding-question",
"covers": [
"skills:board:review-finding-question"
],
"keys": {}
}
Loading
Loading