From c8cd31feb0dd5dba9c62a9a771213807ce14fae9 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Fri, 14 Aug 2026 17:12:26 +0100 Subject: [PATCH] Say a completion summary is the PR description in every prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DESIGN.md §8 already settles that a `done` summary "is written to read as a PR description" — `voro pr` opens the pull request with that text as the body — but the agent-facing prompts only gestured at it ("a PR-ready `--summary`"), and the skill did not mention `--summary` at all. Agents returned one-liners like "Implemented X, tests pass", which is the whole PR body the operator then reviews against. Every place an agent is told what a summary is now says the shape in a phrase: the dispatch preamble's closing paragraph, the shared `REWORK_SUMMARY_SENTENCE` (still demanding the point-by-point answer, still one constant so the message and the preamble cannot drift), the `done` and `set --summary` help blocks, the `done` warning and the `[incomplete report]` nudge when they name the missing summary, the `voro-cli` skill's transitions block, and the copy-paste preamble in docs/agent-integration.md, whose example was modelling the bad shape. Nothing about when a summary is recorded moves, and summaries stay optional — the wording describes the shape of one that is written. --- crates/voro/src/cli.rs | 59 +++++++++++++++++++++------ crates/voro/src/dispatch.rs | 46 ++++++++++++++++----- docs/agent-integration.md | 18 ++++---- plugins/voro/skills/voro-cli/SKILL.md | 8 +++- 4 files changed, 100 insertions(+), 31 deletions(-) diff --git a/crates/voro/src/cli.rs b/crates/voro/src/cli.rs index 6f52e25..6e669b3 100644 --- a/crates/voro/src/cli.rs +++ b/crates/voro/src/cli.rs @@ -129,8 +129,9 @@ tasks the git branch dispatch injects into the prompt; --no-branch clears it. --summary sets or replaces a running/review task's - completion summary (the PR body `pr` opens - from) without a reject/done round trip. + completion summary — the PR description `pr` + opens the pull request from, so write it as + one — without a reject/done round trip. --repo re-points the task at another of its project's repos; --no-repo returns it to the project default @@ -234,11 +235,14 @@ transitions running | stalled → review (from stalled: reporting a dead session's finished work on its behalf); the summary is the agent's - completion note (kept as a summary event, and - the PR body `pr` opens from) — write it as a - PR description; --branch records the git branch - the work landed on (agent return path). Warns - but succeeds when branch or summary is absent + completion report, kept as a summary event + and used as the body `pr` opens the pull + request with — so write it as a PR + description, what changed and why then how + you verified it, not a status line; --branch + records the git branch the work landed on + (agent return path). Warns but succeeds when + branch or summary is absent accept [--yes] review | waiting → done; then offers to remove the task's dispatch worktree (--yes skips the confirmation) @@ -1704,9 +1708,17 @@ fn done_verb(store: &mut Store, args: DoneArgs) -> Result { .filter_map(|(what, present)| (!present).then_some(what)) .collect(); if !missing.is_empty() { + // Naming the shape of the half that is missing, where that half is + // the summary: an agent told to supply one is told it is the PR + // description (DESIGN.md §8). + let shape = if has_summary { + "" + } else { + "; the summary is the PR description `pr` opens the pull request from" + }; write!( out, - "\nnote: no {} recorded — complete the report with `voro set {id}` if this task produced code", + "\nnote: no {} recorded — complete the report with `voro set {id}` if this task produced code{shape}", missing.join(" or ") ) .unwrap(); @@ -1776,7 +1788,7 @@ fn show_verb(store: &mut Store, id: i64) -> Result { if incomplete { writeln!( out, - "incomplete report: a branch is recorded but no summary — complete it with `voro set {id} --summary ...`" + "incomplete report: a branch is recorded but no summary — complete it with `voro set {id} --summary ...`, written as the PR description `pr` opens the pull request from" ) .unwrap(); } @@ -4761,13 +4773,17 @@ mod tests { ok(&mut s, &["start", "1"]); let out = ok(&mut s, &["done", "1", "--summary", "did it"]); assert!(out.contains("note: no branch recorded"), "{out}"); + // Only the summary half carries the shape note. + assert!(!out.contains("PR description"), "{out}"); - // branch given, no summary: the note names summary only + // branch given, no summary: the note names summary only, and says what + // shape the half it is asking for takes (DESIGN.md §8). ok(&mut s, &["add", "demo", "T2", "--state", "ready"]); ok(&mut s, &["set", "2", "--branch", "feat/x"]); ok(&mut s, &["start", "2"]); let out = ok(&mut s, &["done", "2"]); assert!(out.contains("note: no summary recorded"), "{out}"); + assert!(out.contains("the summary is the PR description"), "{out}"); } #[test] @@ -4794,10 +4810,13 @@ mod tests { ok(&mut s, &["done", "1", "--branch", "feat/x"]); assert!(ok(&mut s, &["list"]).contains("[incomplete report]")); + let shown = ok(&mut s, &["show", "1"]); assert!( - ok(&mut s, &["show", "1"]) - .contains("incomplete report: a branch is recorded but no summary") + shown.contains("incomplete report: a branch is recorded but no summary"), + "{shown}" ); + // The nudge names the shape of the half it is asking for. + assert!(shown.contains("the PR description"), "{shown}"); } #[test] @@ -4916,6 +4935,22 @@ mod tests { ); } + /// Both verbs that record one say what a summary is: the description `pr` + /// opens the pull request from (DESIGN.md §8). + #[test] + fn help_describes_a_summary_as_the_pr_description() { + let mut s = store(); + // The help is hand-wrapped into its column, so match on the prose + // rather than on where the lines happen to break. + let out = ok(&mut s, &["help"]); + let flowed = out.split_whitespace().collect::>().join(" "); + assert!(flowed.contains("write it as a PR description"), "{out}"); + assert!( + flowed.contains("the PR description `pr` opens the pull request from"), + "{out}" + ); + } + #[test] fn help_documents_import() { let mut s = store(); diff --git a/crates/voro/src/dispatch.rs b/crates/voro/src/dispatch.rs index 01fc628..e0b7736 100644 --- a/crates/voro/src/dispatch.rs +++ b/crates/voro/src/dispatch.rs @@ -48,11 +48,12 @@ the operator answers right here in this session — so when your question has be answered, run `voro resume {task_id}` before continuing, to move the task back to running (Voro records no answer text; the exchange is already in this transcript). The `--from {task_id}` on `propose` links each follow-up discovered-from this -task; drop it for a proposal that stands on its own. Finish -with your work committed on a branch and a PR-ready `--summary` on `done` — what -changed, why, and how you verified it — since `voro pr` opens the pull request -straight from that summary. Never modify the database with raw SQL, which would -bypass the state machine and event log.{branch}{docs}{rework} +task; drop it for a proposal that stands on its own. Finish with your work +committed on a branch and a `--summary` on `done`: that summary is the pull +request's description — `voro pr` opens the PR with it as the body — so write it +as one, a short account of what changed and why followed by how you verified it, +not a status line. Never modify the database with raw SQL, which would bypass +the state machine and event log.{branch}{docs}{rework} --- @@ -63,11 +64,12 @@ bypass the state machine and event log.{branch}{docs}{rework} /// narrowed to the diff since the rejected revision, so the summary is what /// carries the operator from each of their points to the change that answers /// it; without it they are back to rediscovering the rework from the diff. -const REWORK_SUMMARY_SENTENCE: &str = "Report with `voro done {task_id}{db} --summary \"...\"` whose summary answers that \ - feedback point by point — one item per point, saying what you changed or \ - why you did not. The operator re-reviews only the diff since the revision \ - they rejected, so that summary is what connects your changes to their \ - points."; +const REWORK_SUMMARY_SENTENCE: &str = "Report with `voro done {task_id}{db} --summary \"...\"`. That summary is the \ + pull request's description, so write it as one — a PR body that answers \ + that feedback point by point, one item per point, saying what you changed \ + or why you did not. The operator re-reviews only the diff since the \ + revision they rejected, so that summary is what connects your changes to \ + their points."; /// The `{rework}` block for a task that has already been through review and was /// sent back (DESIGN.md §8). It rides the preamble because a redispatch is the @@ -2229,6 +2231,10 @@ mod tests { "{reworking}" ); assert!(reworking.contains("point by point"), "{reworking}"); + assert!( + reworking.contains("pull request's description"), + "a rework summary is a PR body too: {reworking}" + ); assert!( reworking.contains("voro done 62 --summary"), "the instruction must be copy-pasteable: {reworking}" @@ -2238,6 +2244,22 @@ mod tests { assert!(!first.contains("point by point"), "{first}"); } + /// Every dispatched prompt says what shape a completion summary takes: it + /// is the pull request's description, since `voro pr` opens the PR with it + /// as the body (DESIGN.md §8). + #[test] + fn preamble_says_the_summary_is_the_pr_description() { + let plain = render_preamble(62, &Store::production_db_path(), None, &[], false); + // The template is hand-wrapped, so match the prose rather than where + // the lines happen to break. + let flowed = plain.split_whitespace().collect::>().join(" "); + assert!( + flowed.contains("that summary is the pull request's description"), + "{plain}" + ); + assert!(flowed.contains("not a status line"), "{plain}"); + } + /// The rejection as the live session hears it: the operator's points /// verbatim, plus the same instruction the preamble carries. #[test] @@ -2256,6 +2278,10 @@ mod tests { "{message}" ); assert!(message.contains("point by point"), "{message}"); + assert!( + message.contains("pull request's description"), + "the rework summary is the PR body: {message}" + ); assert!(message.contains("voro done 62 --summary"), "{message}"); } diff --git a/docs/agent-integration.md b/docs/agent-integration.md index c707ecc..e126df7 100644 --- a/docs/agent-integration.md +++ b/docs/agent-integration.md @@ -31,7 +31,8 @@ points, run the matching command — Voro surfaces it in the operator's queue: voro ask "$VORO_TASK_ID" --question "Schema A or B? Trade-offs: ..." voro resume "$VORO_TASK_ID" - voro done "$VORO_TASK_ID" --branch "$(git rev-parse --abbrev-ref HEAD)" --summary "Implemented X, tests pass" + voro done "$VORO_TASK_ID" --branch "$(git rev-parse --abbrev-ref HEAD)" \ + --summary "" voro propose "Follow-up title" --from "$VORO_TASK_ID" --body-file plan.md - `ask` when you are blocked on a human decision and cannot proceed. @@ -40,13 +41,14 @@ points, run the matching command — Voro surfaces it in the operator's queue: already in this transcript. - `done` when the work is complete and ready for review. Record **both** flags on the one call: `--branch` is the git branch your work landed on and `--summary` - is a PR-ready account of what changed, why, and how you verified it — on a - GitHub-reviewed project `voro pr` opens the pull request straight from them - and needs both, on any project the summary is the review context, and a `done` - that supplies only one leaves the task flagged `[incomplete report]`. Omit both only - for a task that produced no code (planning, triage). If the task named an - intended branch, you were told which one in the dispatch preamble — create or - check it out yourself. + is the pull request's description — what changed and why, then how you + verified it, written as a PR body rather than a status line. On a + GitHub-reviewed project `voro pr` opens the pull request with that summary as + its body and needs both, on any project the summary is the review context, and + a `done` that supplies only one leaves the task flagged `[incomplete report]`. + Omit both only for a task that produced no code (planning, triage). If the + task named an intended branch, you were told which one in the dispatch + preamble — create or check it out yourself. - `propose` to record follow-up work you noticed; `--from "$VORO_TASK_ID"` links it back to this task (`voro` reads no environment on its own — pass the id, as every verb here does). diff --git a/plugins/voro/skills/voro-cli/SKILL.md b/plugins/voro/skills/voro-cli/SKILL.md index f842dbb..77a6be6 100644 --- a/plugins/voro/skills/voro-cli/SKILL.md +++ b/plugins/voro/skills/voro-cli/SKILL.md @@ -138,11 +138,17 @@ when its last blocker closes. voro start # ready → running (claim the task) voro ask --question "A or B?" # running → needs-input (blocked on human) voro answer TEXT # needs-input → running -voro done [--branch NAME] # running → review; --branch records the +voro done [--summary TEXT] [--branch NAME] + # running → review; --branch records the # git branch your work landed on voro abort # running → ready (backing out) ``` +`done`'s `--summary` (or `--summary-file PATH`, for a multi-line one) is your +account of the work, and it is the pull request's description: `voro pr` opens +the PR with it as the body, so write it as one — what changed and why, then how +you verified it — rather than a status line. + **When you are asked to work on a task, run `voro start ` before you do anything else.** This claims the task and moves it to `running`, so the queue reflects that it is being worked rather than still waiting. The task id is in