Skip to content
Merged
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
59 changes: 47 additions & 12 deletions crates/voro/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <task-id> [--yes] review | waiting → done; then offers to
remove the task's dispatch worktree (--yes
skips the confirmation)
Expand Down Expand Up @@ -1704,9 +1708,17 @@ fn done_verb(store: &mut Store, args: DoneArgs) -> Result<String, String> {
.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();
Expand Down Expand Up @@ -1776,7 +1788,7 @@ fn show_verb(store: &mut Store, id: i64) -> Result<String, String> {
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();
}
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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::<Vec<_>>().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();
Expand Down
46 changes: 36 additions & 10 deletions crates/voro/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}

---

Expand All @@ -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
Expand Down Expand Up @@ -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}"
Expand All @@ -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::<Vec<_>>().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]
Expand All @@ -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}");
}

Expand Down
18 changes: 10 additions & 8 deletions docs/agent-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<what changed and why, then how you verified it — a PR description>"
voro propose <project> "Follow-up title" --from "$VORO_TASK_ID" --body-file plan.md

- `ask` when you are blocked on a human decision and cannot proceed.
Expand All @@ -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).
Expand Down
8 changes: 7 additions & 1 deletion plugins/voro/skills/voro-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,17 @@ when its last blocker closes.
voro start <id> # ready → running (claim the task)
voro ask <id> --question "A or B?" # running → needs-input (blocked on human)
voro answer <id> TEXT # needs-input → running
voro done <id> [--branch NAME] # running → review; --branch records the
voro done <id> [--summary TEXT] [--branch NAME]
# running → review; --branch records the
# git branch your work landed on
voro abort <id> # 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 <id>` 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
Expand Down