diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf44297..632d5baa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to the claude-plugins project will be documented in this fil The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Entries are listed newest-first; each plugin section is treated as released when merged to `main`. +### vibe v1.0.39 + +#### Changed +- Plain words ask for a pull request (operator ruling, 2026-10-15). "Raise a PR", "open a pull request", "I'll get the PR merged", "I'll get it merged", "I'll ship it myself" and "I want to take it through review" are an explicit request and take handoff's requested pull request path. `guardrails.md` ("Tests") states it once; the vibe and handoff skills, the change worker and `quality-loop.md` point to it. Tests are still written only on that request, and the person is never offered the branch to merge. +- A requested pull request runs the whole path: the same writer's `pull-request` turn writes the tests, step 7 commits, pushes and publishes through `vibe-environment-worker` in redeploy mode, and from the request on every handoff brief carries the `pull-request` phase. No brief may say both that the person takes the work to a pull request and that no tests are authored. +- Handoff now opens the requested pull request as its last step. A new `pull-request` mode in `vibe-environment-worker` (its own launcher record action, with no record writes, no push and no commit) checks the branch is at the verified published commit, fills symphony-alpha's current PR template from the session's own record (Summary, an honest Breaking changes answer, the UI Feature Flag attestation only from the flag record, a Test plan of 20 lines or fewer, and a note for the reviewer where the record cannot support an answer), titles it with the ticket slug within 72 characters, and runs `gh pr create`. It never enqueues, merges, requests reviewers, triggers CI or adds the `warning-acknowledged` label. The ticket stays with the person, In Progress, its Next line says they take the pull request through review and merge, and the closing message gives them the pull request link. The old "handoff opens none" wording is gone. +- "Who should pick this up next?" is replaced by one plain two-way question, asked only when the person's words do not already answer it: "Do you want me to open a pull request so you can take it through review and merge, or pass it to someone, usually design and then engineering, to finish?" Handoff settles it before the checks (new step 0, "Settle how it ends") because a pull request changes what they run. Passing it on without a name asks "Who should I pass it to? A name or email is fine." right away, then uses the existing lookup. A confused reply ("oh i thought you were going to code it") gets the two endings explained plainly, never a name prompt. The vibe skill's section 8 asks the same question, adding the existing keep and throw away options. +- A new session's first reply carries one line, as a notice that never waits: "When you're done, I can either open a pull request for you, or pass it to design or engineering to finish." A resumed session does not get it. +- Person-facing text no longer uses "handoff" as an unexplained term. The stale-session line, the already-finished replies and the closing reminder say what happens in plain words. The `$handoff` skill name and its triggers are unchanged. The vibe skill and `guardrails.md` no longer assume every session goes to design and engineering, or that the person is always an executive. +- Both harnesses get the same behavior: the prose is shared, and the new `pull-request` record action is declared once in the launcher's record modes, which the Claude launcher (`claude-worker.mjs`) and the Codex native record grant (`writer-state.mjs acquire-record`) both read. +- Coverage: a new contract test file (`test_vibe_session_endings_contract.py`) whose ten tests all fail on 1.0.38; the two existing tests that pinned the old question and "handoff opens none" now pin the new wording; a Claude launcher test admits the environment worker's `pull-request` action with no record writes, and a Codex native record test grants it to that worker only. A new `evals/` suite of 15 cases for `claude plugin eval` (Claude Code only) checks each ending for both an executive and a product manager, including the real exchange that prompted this change (the README says how to run it). + ### vibe v1.0.38 #### Changed diff --git a/plugins/vibe/.claude-plugin/plugin.json b/plugins/vibe/.claude-plugin/plugin.json index 0b887301..4ad967bf 100644 --- a/plugins/vibe/.claude-plugin/plugin.json +++ b/plugins/vibe/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "vibe", "description": "Vibe sessions, handoff, seed refresh, and ClosedLoop ticket workflows", - "version": "1.0.38", + "version": "1.0.39", "dependencies": ["closedloop-core"], "author": {"name": "ClosedLoop", "email": "support@closedloop.ai"} } diff --git a/plugins/vibe/.codex-plugin/plugin.json b/plugins/vibe/.codex-plugin/plugin.json index 1f91db96..a380850c 100644 --- a/plugins/vibe/.codex-plugin/plugin.json +++ b/plugins/vibe/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "vibe", - "version": "1.0.38", + "version": "1.0.39", "description": "Vibe sessions, handoff, seed refresh, and ClosedLoop ticket workflows", "author": {"name": "ClosedLoop", "email": "support@closedloop.ai"}, "homepage": "https://github.com/closedloop-ai/claude-plugins", @@ -11,7 +11,7 @@ "interface": { "displayName": "ClosedLoop Vibe", "shortDescription": "Vibe sessions and ClosedLoop ticket workflows.", - "longDescription": "Vibe sessions let a non-engineer build product UI in the in-app browser on their own Vercel copy of symphony-alpha, kept on a live ClosedLoop ticket, and hand the work to design and then engineering.", + "longDescription": "Vibe sessions let a non-engineer build product UI in the in-app browser on their own Vercel copy of symphony-alpha, kept on a live ClosedLoop ticket, and finish it as a pull request they take through review or pass it to design and engineering.", "developerName": "ClosedLoop", "category": "Development", "capabilities": ["Write", "Analyze"], diff --git a/plugins/vibe/README.md b/plugins/vibe/README.md index 68de0f6f..daed1ef5 100644 --- a/plugins/vibe/README.md +++ b/plugins/vibe/README.md @@ -170,10 +170,15 @@ checklist. ### `handoff` Checks and reviews the session's work, verifies its current app environment or -canonical prototype publication, and completes the same live ticket. Resolves -the next owner named by the person and assigns the ticket with status In -Progress. Canonical prototype review and metadata transitions are preserved; -handoff ends at the branch without opening a pull request. +canonical prototype publication, and completes the same live ticket. A session +ends one of two ways, settled from the person's words or one plain question: +a pull request they take through review and merge, which handoff opens as its +last step with the ticket left with them, or passing it on, where handoff +resolves the next owner they name and assigns the ticket. The status stays In +Progress either way. Plain words such as "I'll get the PR merged" or "I'll +ship it myself" count as asking for a pull request, and the person is never +offered a branch to merge. Canonical prototype review and metadata +transitions are preserved. Nobody authors tests in vibe or handoff. The same implementation writer only repairs existing tests and fixtures the session's change broke (imports, renamed or retired symbols, setup, type errors) without changing any @@ -245,7 +250,7 @@ The plugin bundles the ClosedLoop ticket automation skill pack: `cl-policy`, `cl | `vibe-setup-worker` | sonnet | Machine preflight, worktree bootstrap, owned local processes, and confirmed session discard | | `vibe-requirements-worker` | sonnet | Read-only ticket, requirements, overlap, and owning-route discovery | | `vibe-ticket-worker` | sonnet | Live ticket creation, next-owner lookup, handoff grading, assignment, and cancellation | -| `vibe-environment-worker` | sonnet | Vercel environment creation, redeploy, flag snapshots, Desktop setup, and deployment records | +| `vibe-environment-worker` | sonnet | Vercel environment creation, redeploy, flag snapshots, Desktop setup, deployment records, and opening a pull request the person asked for at handoff | | `vibe-change-worker` | sonnet | Sole persistent implementation writer for local planning, full-stack changes, stories, fixes, the handoff missing tests list and serialized recording | | `vibe-backend-worker` | opus | Read-only decision-table and backend guidance for that writer | | `vibe-primitive-worker` | sonnet | Read-only reusable-component and steward-specification guidance for that writer | @@ -259,6 +264,25 @@ The plugin bundles the ClosedLoop ticket automation skill pack: `cl-policy`, `cl | `vibe-seed-fix-worker` | sonnet | Seed drift fixes, validation, and follow-up PR-check and review-comment fixes | | `vibe-seed-pr-worker` | sonnet | Seed PR push, status, main sync, merge queue, review replies, and owned worktree cleanup | +## Evals + +`evals/` holds a `claude plugin eval` suite for how a session ends: plain +words asking for a pull request, passing the work on, an unclear "I'm done", +the endings line in a new session's first reply, and the closing pull request +link. Each case is a one-turn rehearsal with the session state in the prompt, +so it needs no symphony-alpha checkout, and it is named by persona (`exec-` +for someone who describes the work and passes it on, `pm-` for someone who +iterates from reference screenshots and may take it to a pull request +themselves). The cases load `closedloop-core` too, which sits outside this +plugin, so run the suite from the repository's `plugins/` folder: + +```bash +cd plugins +claude plugin eval . --eval-dir vibe/evals --model sonnet -j 2 --max-cost-usd 5 --no-publish +``` + +Add `--ablation none` to skip the no-plugin baseline arm. CI does not run it. + ## Runtime Files Version `1.0.14` contains 10 skills and 16 agents. It has no standalone diff --git a/plugins/vibe/agents/vibe-change-worker.md b/plugins/vibe/agents/vibe-change-worker.md index 837c8a38..e92b9fb2 100644 --- a/plugins/vibe/agents/vibe-change-worker.md +++ b/plugins/vibe/agents/vibe-change-worker.md @@ -1,6 +1,6 @@ --- name: vibe-change-worker -description: The one persistent implementation writer for a vibe session. Keeps the same context, worktree and branch while planning, implementing all frontend/backend/prototype/component work, fixing reviewed issues, repairing existing tests its change broke and listing the missing tests for engineering at handoff; it writes tests only when the person asks for a pull request. Uses graph-first owner and product-decision research, local canonical plans, Storybook and existing checks. Never commits or opens a PR. +description: The one persistent implementation writer for a vibe session. Keeps the same context, worktree and branch while planning, implementing all frontend/backend/prototype/component work, fixing reviewed issues, repairing existing tests its change broke and listing the missing tests for engineering at handoff; it writes tests only when the person asks for a pull request, in any plain words. Uses graph-first owner and product-decision research, local canonical plans, Storybook and existing checks. Never commits or opens a PR. model: sonnet tools: Read, Write, Edit, Grep, Glob, Bash, Skill --- @@ -272,7 +272,14 @@ Obtain independent whole-table verification. Never claim final core alignment with source gaps or unresolved rows; they block final handoff. Only a `pull-request` continuation, which the orchestrator sends after the -person explicitly asks for a pull request, authorizes you to write tests: full +person explicitly asks for a pull request, authorizes you to write tests. Plain +words that say they will take the work through review and merge themselves +("raise a PR", "I'll get the PR merged", "I'll ship it myself", "I want to take +it through review") are that request (`guardrails.md`, "Tests"), but only the +orchestrator's `pull-request` turn carries the authority; when such words reach +you in another turn, quote them in your result so the orchestrator routes +them, and author nothing. Never +offer the person the branch to merge. The turn requires full tests for every change in the session, covering acceptance criteria, every unsuperseded row, production wiring and meaningful failure paths, and every missing tests entry. Verify helpers/reviewers never author or fix tests. diff --git a/plugins/vibe/agents/vibe-environment-worker.md b/plugins/vibe/agents/vibe-environment-worker.md index 79898474..c1805e08 100644 --- a/plugins/vibe/agents/vibe-environment-worker.md +++ b/plugins/vibe/agents/vibe-environment-worker.md @@ -1,6 +1,6 @@ --- name: vibe-environment-worker -description: Runs a vibe session's Vercel environment in symphony-alpha for the vibe and handoff orchestrators. Create mode takes the production flag snapshot in PostHog for the person's real account, pushes the vibe/ branch, starts the environment through the repo's request workflow, follows it to the end, and records the app, API, and Storybook URLs and deployed commit it reports on the session and the live ticket. Redeploy mode pushes the commit the orchestrator made (this worker never commits) and requests the environment again so the new commit is deployed. Flags mode takes a new snapshot when the person asked; desktop mode requests the environment again so it signs in the session's local Desktop profile. Returns a short status. +description: Runs a vibe session's Vercel environment in symphony-alpha for the vibe and handoff orchestrators. Create mode takes the production flag snapshot in PostHog for the person's real account, pushes the vibe/ branch, starts the environment through the repo's request workflow, follows it to the end, and records the app, API, and Storybook URLs and deployed commit it reports on the session and the live ticket. Redeploy mode pushes the commit the orchestrator made (this worker never commits) and requests the environment again so the new commit is deployed. Flags mode takes a new snapshot when the person asked; desktop mode requests the environment again so it signs in the session's local Desktop profile. Pull-request mode opens the pull request the person asked for at handoff, on the already pushed branch, and never merges it. Returns a short status. model: sonnet tools: Read, Write, Grep, Glob, Bash --- @@ -37,7 +37,7 @@ Never self-declare continuation intent or borrow the publisher's lease. Publication readiness is not completed handoff E2E coverage: preserve reported incomplete scenarios and never assign/claim completion from a preview alone. -The mode (`create`, `redeploy`, `flags`, or `desktop`), the worktree path, the live +The mode (`create`, `redeploy`, `flags`, `desktop`, or `pull-request`), the worktree path, the live ticket slug, and for redeploy the session summary, the session's `localFixes` paths (managed local workarounds for a symphony-alpha bug, authored historically by setup or currently by the sole implementation writer; they @@ -332,6 +332,49 @@ and `baseBranchSource: "mcp_input"`. Then update the ticket per flag snapshot, and Sessions (it shows the URLs only once they are verified), and add your Progress line. +## Pull-request mode + +Handoff sends this as its last step, only when the person asked for a pull +request (`../skills/handoff/SKILL.md`, "A requested pull request"). Your +grant is the `pull-request` record action: no ticket or session writes, no +push, no commit. Inputs: the worktree, the live ticket slug, the confirmed +summary, the final verified published commit, the tests the `pull-request` +turn wrote and the check results, and the ticket's Production impact and Flag +changes and gates fields. + +1. Confirm the branch on `origin` is at the verified published commit + (`git -C "" ls-remote origin `). If not, return `BLOCKED`; + never push to make it so. +2. If an open pull request already exists for the branch + (`gh pr list --repo closedloop-ai/symphony-alpha --head --state open --json url`), + return `DONE` with its URL and create nothing. +3. Fetch the current PR template from main + (`gh api repos/closedloop-ai/symphony-alpha/contents/.github/pull_request_template.md --jq .content | base64 -d`) + and fill every section from the session's own record, never from + invention: + - Summary: the confirmed summary, with the live ticket's slug. + - Breaking changes: an honest answer from Production impact; write the + root `AGENTS.md`'s "none" answer only when Production impact supports it + (no API response field, database column or cross-process wire shape + changed). + - UI Feature Flag: the heading alone, unless the change adds a net-new + surface, screen or navigation item or a new flag primitive; then attest + `[ui-flag:gated] ` or `[ui-flag:N/A] ` only from the Flag + changes and gates field. + - Test plan: at most 20 lines: the tests written and the checks run, with + their results. + When the record cannot support a required answer, say so in that section + for the reviewer instead of inventing one. +4. Title: `: `, at most 72 + characters including the ` (#NNNN)` GitHub appends, with no mention of AI + tools. Create it with + `gh pr create --repo closedloop-ai/symphony-alpha --base main --head --title "" --body-file <file>`, + the body file in the session's private git directory. +5. Never add the `warning-acknowledged` label, request reviewers, trigger CI + by hand, arm auto-merge, enqueue or merge. + +Return `DONE` with the pull request URL, or `BLOCKED` with why. + ## Return (under 100 words) `DONE` with the verified app, API, and Storybook URLs exactly as diff --git a/plugins/vibe/agents/vibe-ticket-worker.md b/plugins/vibe/agents/vibe-ticket-worker.md index e468c8c1..61c38b20 100644 --- a/plugins/vibe/agents/vibe-ticket-worker.md +++ b/plugins/vibe/agents/vibe-ticket-worker.md @@ -194,7 +194,9 @@ historical inspections accurately. Preserve the existing grading block. table file stays in this worktree. 6. Fill Handoff and Design Review from the verified final packet/evidence you were given, per the template; its - Next line names the next owner you were given. The Session decision table + Next line names the next owner you were given; when the person asked for a + pull request, that owner is the session's operator and the line takes the + template's pull request form. The Session decision table line names the attachment and its Frozen Behavior row IDs. The Feature docs line lists the edited pages from the writer's record and never claims a page that is not in the diff (`git -C "<wt>" diff --stat <inventory baseCommit>`). diff --git a/plugins/vibe/evals/.gitignore b/plugins/vibe/evals/.gitignore new file mode 100644 index 00000000..fbca2253 --- /dev/null +++ b/plugins/vibe/evals/.gitignore @@ -0,0 +1 @@ +results/ diff --git a/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/explains-both-endings.md b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/explains-both-endings.md new file mode 100644 index 00000000..d3b780c9 --- /dev/null +++ b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/explains-both-endings.md @@ -0,0 +1,8 @@ +--- +type: llm +criteria: | + Grade only the REPLY line. Pass when it explains, in plain words, that the code is already built and that the + session can end one of two ways (a pull request the person takes through review and merge, or passing it to + someone, usually design and then engineering, to finish), and then lets the person choose. Fail when it asks for a + name or email before they chose, or explains only one of the two options. +--- diff --git a/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-branch-to-merge.md b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-handoff-word.md b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-handoff-word.md new file mode 100644 index 00000000..558d3dc2 --- /dev/null +++ b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-handoff-word.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'REPLY:[^\n]*\bhand ?-?off' +--- diff --git a/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-name-prompt.md b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-name-prompt.md new file mode 100644 index 00000000..c93f13bc --- /dev/null +++ b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/no-name-prompt.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'name or (an )?email|who should (i|we) (pass|send|give)' +--- diff --git a/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/route.md b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/route.md new file mode 100644 index 00000000..d9974d77 --- /dev/null +++ b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(ask-two-way|explain-and-ask-two-way)\b' +--- diff --git a/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/two-way-choice.md b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/two-way-choice.md new file mode 100644 index 00000000..994fde51 --- /dev/null +++ b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/graders/two-way-choice.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'open a pull request[\s\S]{0,200}pass it (on|to)' +--- diff --git a/plugins/vibe/evals/exec-confused-thought-you-would-code-it/prompt.md b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/prompt.md new file mode 100644 index 00000000..b30061d1 --- /dev/null +++ b/plugins/vibe/evals/exec-confused-thought-you-would-code-it/prompt.md @@ -0,0 +1,27 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [exec, negative] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "add a due date column to the projects board", "make the overdue ones stand out". +- The person said they were done, and you replied with the question your instructions give for how the session ends. + +The person just said: "oh i thought you were going to code it" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/exec-done-asks-two-way/graders/no-handoff-word.md b/plugins/vibe/evals/exec-done-asks-two-way/graders/no-handoff-word.md new file mode 100644 index 00000000..558d3dc2 --- /dev/null +++ b/plugins/vibe/evals/exec-done-asks-two-way/graders/no-handoff-word.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'REPLY:[^\n]*\bhand ?-?off' +--- diff --git a/plugins/vibe/evals/exec-done-asks-two-way/graders/no-name-prompt.md b/plugins/vibe/evals/exec-done-asks-two-way/graders/no-name-prompt.md new file mode 100644 index 00000000..c93f13bc --- /dev/null +++ b/plugins/vibe/evals/exec-done-asks-two-way/graders/no-name-prompt.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'name or (an )?email|who should (i|we) (pass|send|give)' +--- diff --git a/plugins/vibe/evals/exec-done-asks-two-way/graders/plain-language.md b/plugins/vibe/evals/exec-done-asks-two-way/graders/plain-language.md new file mode 100644 index 00000000..e2be0158 --- /dev/null +++ b/plugins/vibe/evals/exec-done-asks-two-way/graders/plain-language.md @@ -0,0 +1,9 @@ +--- +type: llm +criteria: | + Grade only the text after "REPLY:". It is a message to a person who is not an engineer. Pass unless it does one + of these: uses one of these engineering terms without explaining it (worktree, branch, ledger, commit, merge + queue, MCP, seeded, "handoff" or "hand off"); or asks the person a technical or engineering question. The words + "pull request", "review", "merge", "design" and "engineering" are the approved plain names of the endings and + never count against it. Placeholders in angle brackets are fine. +--- diff --git a/plugins/vibe/evals/exec-done-asks-two-way/graders/route.md b/plugins/vibe/evals/exec-done-asks-two-way/graders/route.md new file mode 100644 index 00000000..d9974d77 --- /dev/null +++ b/plugins/vibe/evals/exec-done-asks-two-way/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(ask-two-way|explain-and-ask-two-way)\b' +--- diff --git a/plugins/vibe/evals/exec-done-asks-two-way/graders/two-way-choice.md b/plugins/vibe/evals/exec-done-asks-two-way/graders/two-way-choice.md new file mode 100644 index 00000000..994fde51 --- /dev/null +++ b/plugins/vibe/evals/exec-done-asks-two-way/graders/two-way-choice.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'open a pull request[\s\S]{0,200}pass it (on|to)' +--- diff --git a/plugins/vibe/evals/exec-done-asks-two-way/prompt.md b/plugins/vibe/evals/exec-done-asks-two-way/prompt.md new file mode 100644 index 00000000..f92b6269 --- /dev/null +++ b/plugins/vibe/evals/exec-done-asks-two-way/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [exec, unclear-end] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "add a due date column to the projects board", "make the overdue ones stand out". + +The person just said: "I'm done" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/exec-new-session-first-reply/graders/endings-line-is-not-a-question.md b/plugins/vibe/evals/exec-new-session-first-reply/graders/endings-line-is-not-a-question.md new file mode 100644 index 00000000..f024c2ee --- /dev/null +++ b/plugins/vibe/evals/exec-new-session-first-reply/graders/endings-line-is-not-a-question.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'pass it to design or engineering to finish\?' +--- diff --git a/plugins/vibe/evals/exec-new-session-first-reply/graders/endings-line.md b/plugins/vibe/evals/exec-new-session-first-reply/graders/endings-line.md new file mode 100644 index 00000000..f5786b49 --- /dev/null +++ b/plugins/vibe/evals/exec-new-session-first-reply/graders/endings-line.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'When you.re done, I can either open a pull request for you, or pass it to design or engineering to finish\.' +--- diff --git a/plugins/vibe/evals/exec-new-session-first-reply/graders/notice-does-not-wait.md b/plugins/vibe/evals/exec-new-session-first-reply/graders/notice-does-not-wait.md new file mode 100644 index 00000000..a85498da --- /dev/null +++ b/plugins/vibe/evals/exec-new-session-first-reply/graders/notice-does-not-wait.md @@ -0,0 +1,9 @@ +--- +type: llm +criteria: | + Grade only the text after "REPLY:". Pass when the line about how the session can end ("When you're done, I can + either open a pull request for you, or pass it to design or engineering to finish.") is a statement, the reply + does not ask the person to choose or confirm an ending now, and any question it asks is about starting the + session (for example whether their copy of the app starts with sample data or empty). Placeholders in angle + brackets, such as <email>, are fine. Fail otherwise. +--- diff --git a/plugins/vibe/evals/exec-new-session-first-reply/graders/route.md b/plugins/vibe/evals/exec-new-session-first-reply/graders/route.md new file mode 100644 index 00000000..de6fb7a2 --- /dev/null +++ b/plugins/vibe/evals/exec-new-session-first-reply/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(start-new)\b' +--- diff --git a/plugins/vibe/evals/exec-new-session-first-reply/prompt.md b/plugins/vibe/evals/exec-new-session-first-reply/prompt.md new file mode 100644 index 00000000..1b784023 --- /dev/null +++ b/plugins/vibe/evals/exec-new-session-first-reply/prompt.md @@ -0,0 +1,25 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [exec, notice] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- No session exists yet. Preflight passed, and both connectors answer. +- The person typed the request below with `$vibe`. + +The person just said: "$vibe I want to add a due date column to the projects board" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/exec-pass-on-named-person/graders/no-branch-to-merge.md b/plugins/vibe/evals/exec-pass-on-named-person/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-named-person/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/exec-pass-on-named-person/graders/no-two-way.md b/plugins/vibe/evals/exec-pass-on-named-person/graders/no-two-way.md new file mode 100644 index 00000000..1bd58ab8 --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-named-person/graders/no-two-way.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'open a pull request' +--- diff --git a/plugins/vibe/evals/exec-pass-on-named-person/graders/route.md b/plugins/vibe/evals/exec-pass-on-named-person/graders/route.md new file mode 100644 index 00000000..9035ca0c --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-named-person/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(pass-on-lookup)\b' +--- diff --git a/plugins/vibe/evals/exec-pass-on-named-person/prompt.md b/plugins/vibe/evals/exec-pass-on-named-person/prompt.md new file mode 100644 index 00000000..0e6e72e6 --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-named-person/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [exec, pass-on] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "add a due date column to the projects board", "make the overdue ones stand out". + +The person just said: "I'm done, give it to Sam Rivera" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/exec-pass-on-send-to-design/graders/asks-name-or-email.md b/plugins/vibe/evals/exec-pass-on-send-to-design/graders/asks-name-or-email.md new file mode 100644 index 00000000..0343a4d1 --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-send-to-design/graders/asks-name-or-email.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'name or (an )?email' +--- diff --git a/plugins/vibe/evals/exec-pass-on-send-to-design/graders/no-branch-to-merge.md b/plugins/vibe/evals/exec-pass-on-send-to-design/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-send-to-design/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/exec-pass-on-send-to-design/graders/route.md b/plugins/vibe/evals/exec-pass-on-send-to-design/graders/route.md new file mode 100644 index 00000000..cb57bb08 --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-send-to-design/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(ask-name)\b' +--- diff --git a/plugins/vibe/evals/exec-pass-on-send-to-design/prompt.md b/plugins/vibe/evals/exec-pass-on-send-to-design/prompt.md new file mode 100644 index 00000000..5b9fa57c --- /dev/null +++ b/plugins/vibe/evals/exec-pass-on-send-to-design/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [exec, pass-on] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "add a due date column to the projects board", "make the overdue ones stand out". + +The person just said: "Great, I'm done, send it to design" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/no-branch-to-merge.md b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/no-name-prompt.md b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/no-name-prompt.md new file mode 100644 index 00000000..c93f13bc --- /dev/null +++ b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/no-name-prompt.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'name or (an )?email|who should (i|we) (pass|send|give)' +--- diff --git a/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/route.md b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/route.md new file mode 100644 index 00000000..6520bc85 --- /dev/null +++ b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(requested-pull-request)\b' +--- diff --git a/plugins/vibe/evals/exec-pr-intent-take-it-through-review/prompt.md b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/prompt.md new file mode 100644 index 00000000..ad921a05 --- /dev/null +++ b/plugins/vibe/evals/exec-pr-intent-take-it-through-review/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [exec, pr-intent] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "add a due date column to the projects board", "make the overdue ones stand out". + +The person just said: "Good, I'm finished. I want to take it through review" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-done-asks-two-way/graders/no-handoff-word.md b/plugins/vibe/evals/pm-done-asks-two-way/graders/no-handoff-word.md new file mode 100644 index 00000000..558d3dc2 --- /dev/null +++ b/plugins/vibe/evals/pm-done-asks-two-way/graders/no-handoff-word.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'REPLY:[^\n]*\bhand ?-?off' +--- diff --git a/plugins/vibe/evals/pm-done-asks-two-way/graders/no-name-prompt.md b/plugins/vibe/evals/pm-done-asks-two-way/graders/no-name-prompt.md new file mode 100644 index 00000000..c93f13bc --- /dev/null +++ b/plugins/vibe/evals/pm-done-asks-two-way/graders/no-name-prompt.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'name or (an )?email|who should (i|we) (pass|send|give)' +--- diff --git a/plugins/vibe/evals/pm-done-asks-two-way/graders/plain-language.md b/plugins/vibe/evals/pm-done-asks-two-way/graders/plain-language.md new file mode 100644 index 00000000..e2be0158 --- /dev/null +++ b/plugins/vibe/evals/pm-done-asks-two-way/graders/plain-language.md @@ -0,0 +1,9 @@ +--- +type: llm +criteria: | + Grade only the text after "REPLY:". It is a message to a person who is not an engineer. Pass unless it does one + of these: uses one of these engineering terms without explaining it (worktree, branch, ledger, commit, merge + queue, MCP, seeded, "handoff" or "hand off"); or asks the person a technical or engineering question. The words + "pull request", "review", "merge", "design" and "engineering" are the approved plain names of the endings and + never count against it. Placeholders in angle brackets are fine. +--- diff --git a/plugins/vibe/evals/pm-done-asks-two-way/graders/route.md b/plugins/vibe/evals/pm-done-asks-two-way/graders/route.md new file mode 100644 index 00000000..d9974d77 --- /dev/null +++ b/plugins/vibe/evals/pm-done-asks-two-way/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(ask-two-way|explain-and-ask-two-way)\b' +--- diff --git a/plugins/vibe/evals/pm-done-asks-two-way/graders/two-way-choice.md b/plugins/vibe/evals/pm-done-asks-two-way/graders/two-way-choice.md new file mode 100644 index 00000000..994fde51 --- /dev/null +++ b/plugins/vibe/evals/pm-done-asks-two-way/graders/two-way-choice.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'open a pull request[\s\S]{0,200}pass it (on|to)' +--- diff --git a/plugins/vibe/evals/pm-done-asks-two-way/prompt.md b/plugins/vibe/evals/pm-done-asks-two-way/prompt.md new file mode 100644 index 00000000..58b61c3b --- /dev/null +++ b/plugins/vibe/evals/pm-done-asks-two-way/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, unclear-end] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "make the filter chips look like this Linear screenshot" (screenshot attached), "tighten the chip spacing to match it", "the selected chip needs the darker fill". + +The person just said: "I'm done" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-new-session-first-reply/graders/endings-line-is-not-a-question.md b/plugins/vibe/evals/pm-new-session-first-reply/graders/endings-line-is-not-a-question.md new file mode 100644 index 00000000..f024c2ee --- /dev/null +++ b/plugins/vibe/evals/pm-new-session-first-reply/graders/endings-line-is-not-a-question.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'pass it to design or engineering to finish\?' +--- diff --git a/plugins/vibe/evals/pm-new-session-first-reply/graders/endings-line.md b/plugins/vibe/evals/pm-new-session-first-reply/graders/endings-line.md new file mode 100644 index 00000000..f5786b49 --- /dev/null +++ b/plugins/vibe/evals/pm-new-session-first-reply/graders/endings-line.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'When you.re done, I can either open a pull request for you, or pass it to design or engineering to finish\.' +--- diff --git a/plugins/vibe/evals/pm-new-session-first-reply/graders/route.md b/plugins/vibe/evals/pm-new-session-first-reply/graders/route.md new file mode 100644 index 00000000..de6fb7a2 --- /dev/null +++ b/plugins/vibe/evals/pm-new-session-first-reply/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(start-new)\b' +--- diff --git a/plugins/vibe/evals/pm-new-session-first-reply/prompt.md b/plugins/vibe/evals/pm-new-session-first-reply/prompt.md new file mode 100644 index 00000000..941ac19b --- /dev/null +++ b/plugins/vibe/evals/pm-new-session-first-reply/prompt.md @@ -0,0 +1,25 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, notice] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- No session exists yet. Preflight passed, and both connectors answer. +- The person typed the request below with `$vibe`. + +The person just said: "$vibe make the projects board filter chips look like this Linear screenshot (screenshot attached)" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/asks-name-or-email.md b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/asks-name-or-email.md new file mode 100644 index 00000000..0343a4d1 --- /dev/null +++ b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/asks-name-or-email.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'name or (an )?email' +--- diff --git a/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/no-branch-to-merge.md b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/route.md b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/route.md new file mode 100644 index 00000000..cb57bb08 --- /dev/null +++ b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(ask-name)\b' +--- diff --git a/plugins/vibe/evals/pm-pass-on-someone-else-finishes/prompt.md b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/prompt.md new file mode 100644 index 00000000..366848fb --- /dev/null +++ b/plugins/vibe/evals/pm-pass-on-someone-else-finishes/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, pass-on] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "make the filter chips look like this Linear screenshot" (screenshot attached), "tighten the chip spacing to match it", "the selected chip needs the darker fill". + +The person just said: "I'm done, someone else can finish" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/no-branch-to-merge.md b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/no-name-prompt.md b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/no-name-prompt.md new file mode 100644 index 00000000..c93f13bc --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/no-name-prompt.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'name or (an )?email|who should (i|we) (pass|send|give)' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/route.md b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/route.md new file mode 100644 index 00000000..6520bc85 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(requested-pull-request)\b' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/prompt.md b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/prompt.md new file mode 100644 index 00000000..c9c72b46 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-get-the-pr-merged/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, pr-intent] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "make the filter chips look like this Linear screenshot" (screenshot attached), "tighten the chip spacing to match it", "the selected chip needs the darker fill". + +The person just said: "ok i'm done. i will get the pr merged" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/no-branch-to-merge.md b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/no-name-prompt.md b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/no-name-prompt.md new file mode 100644 index 00000000..c93f13bc --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/no-name-prompt.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'name or (an )?email|who should (i|we) (pass|send|give)' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/route.md b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/route.md new file mode 100644 index 00000000..6520bc85 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(requested-pull-request)\b' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-raise-a-pr/prompt.md b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/prompt.md new file mode 100644 index 00000000..82b65f6c --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-raise-a-pr/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, pr-intent] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "make the filter chips look like this Linear screenshot" (screenshot attached), "tighten the chip spacing to match it", "the selected chip needs the darker fill". + +The person just said: "that's everything, can you raise a PR" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/no-branch-to-merge.md b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/no-name-prompt.md b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/no-name-prompt.md new file mode 100644 index 00000000..c93f13bc --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/no-name-prompt.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'name or (an )?email|who should (i|we) (pass|send|give)' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/route.md b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/route.md new file mode 100644 index 00000000..6520bc85 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(requested-pull-request)\b' +--- diff --git a/plugins/vibe/evals/pm-pr-intent-ship-it-myself/prompt.md b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/prompt.md new file mode 100644 index 00000000..85854e1b --- /dev/null +++ b/plugins/vibe/evals/pm-pr-intent-ship-it-myself/prompt.md @@ -0,0 +1,26 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, pr-intent] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, seeded data, live ticket ISS-4321, branch `vibe/projects-board-chips`. +- Every request is built, deployed to the person's preview, and shown to them. Nothing is queued. +- Earlier requests, in the person's words: "make the filter chips look like this Linear screenshot" (screenshot attached), "tighten the chip spacing to match it", "the selected chip needs the darker fill". + +The person just said: "Looks right now. I'm done, I'll ship it myself" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/no-branch-to-merge.md b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/no-reassignment.md b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/no-reassignment.md new file mode 100644 index 00000000..a11c84cc --- /dev/null +++ b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/no-reassignment.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: '(assigned|passed|handed) (it )?to (design|engineering|someone)' +--- diff --git a/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/pr-link.md b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/pr-link.md new file mode 100644 index 00000000..2c5f7781 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/pr-link.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'github\.com/closedloop-ai/symphony-alpha/pull/9999' +--- diff --git a/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/route.md b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/route.md new file mode 100644 index 00000000..abc5a39a --- /dev/null +++ b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(final-message|requested-pull-request)\b' +--- diff --git a/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/prompt.md b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/prompt.md new file mode 100644 index 00000000..3fad6cd5 --- /dev/null +++ b/plugins/vibe/evals/pm-pr-path-ends-with-the-pr-link/prompt.md @@ -0,0 +1,28 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, pr-path] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:handoff`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `projects-board-chips`, live ticket ISS-4321 (https://app.closedloop.ai/acme/issues/ISS-4321), branch `vibe/projects-board-chips`. +- The person asked for a pull request. The finishing skill (`vibe:handoff`) ran every step: the tests were written, every check and review passed, the final commit was published (app https://vibe-projects-board-chips.example.vercel.app, Storybook https://vibe-projects-board-chips-storybook.example.vercel.app), and the ticket is complete. +- Its last step just returned: DONE, pull request https://github.com/closedloop-ai/symphony-alpha/pull/9999 opened on that branch. +- Earlier requests, in the person's words: "make the filter chips look like this Linear screenshot" (screenshot attached), "tighten the chip spacing to match it", "the selected chip needs the darker fill". +- This turn is the message to the person that ends the session. + +The person just said: "(nothing new; write the closing message)" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-branch-to-merge.md b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-branch-to-merge.md new file mode 100644 index 00000000..069678d1 --- /dev/null +++ b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-branch-to-merge.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'branch\b[^.\n]{0,40}\bmerge|merge\b[^.\n]{0,20}\bbranch' +--- diff --git a/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-handoff-word.md b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-handoff-word.md new file mode 100644 index 00000000..558d3dc2 --- /dev/null +++ b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-handoff-word.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'REPLY:[^\n]*\bhand ?-?off' +--- diff --git a/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-reassignment-lookup.md b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-reassignment-lookup.md new file mode 100644 index 00000000..60c9a7d8 --- /dev/null +++ b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/no-reassignment-lookup.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'ROUTE:\s*pass-on-lookup' +--- diff --git a/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/route.md b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/route.md new file mode 100644 index 00000000..6520bc85 --- /dev/null +++ b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/route.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'ROUTE:\s*(requested-pull-request)\b' +--- diff --git a/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/starts-the-pull-request-path.md b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/starts-the-pull-request-path.md new file mode 100644 index 00000000..46b2b2b1 --- /dev/null +++ b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/graders/starts-the-pull-request-path.md @@ -0,0 +1,9 @@ +--- +type: llm +criteria: | + Grade only the REPLY line. Pass when it treats the message as a request for a pull request: it says the work will + go to a pull request that the person takes through review and merge, and it does not treat their name as someone + to pass the work to or ask who should pick it up. Saying it will send the pull request link together with the + ticket and app links is fine. Fail when it offers a branch, or the ticket, for the person to merge instead of a + pull request. +--- diff --git a/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/prompt.md b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/prompt.md new file mode 100644 index 00000000..831d9140 --- /dev/null +++ b/plugins/vibe/evals/pm-real-exchange-get-the-pr-merged-with-name/prompt.md @@ -0,0 +1,31 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, pr-intent, real-exchange] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:handoff`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- App session `sign-in-page-match`, live ticket ISS-4322, branch `vibe/sign-in-page-match`. The person redesigned + the Desktop sign-in page from two reference screenshots of another product. +- The person said they were done, and the finishing skill (`vibe:handoff`) is running. The conversation since then: + - You: "Who should pick this up next? A name or email is fine." + - Person: "oh i thought you were going to code it" + - You: "Handing it off is how it gets into the real Closedloop app. I don't merge changes into the product myself, + so the ticket goes to whoever will do that." +- Earlier requests, in the person's words: "make the filter chips look like this Linear screenshot" (screenshot attached), "tighten the chip spacing to match it", "the selected chip needs the darker fill". + +The person just said: "i will get the pr merged. my name is Jordan Lee" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/evals/pm-resume-has-no-endings-line/graders/no-endings-line.md b/plugins/vibe/evals/pm-resume-has-no-endings-line/graders/no-endings-line.md new file mode 100644 index 00000000..0aad94af --- /dev/null +++ b/plugins/vibe/evals/pm-resume-has-no-endings-line/graders/no-endings-line.md @@ -0,0 +1,6 @@ +--- +type: regex +match: not_contains +flags: i +pattern: 'open a pull request for you' +--- diff --git a/plugins/vibe/evals/pm-resume-has-no-endings-line/graders/offers-the-session.md b/plugins/vibe/evals/pm-resume-has-no-endings-line/graders/offers-the-session.md new file mode 100644 index 00000000..88d46cbf --- /dev/null +++ b/plugins/vibe/evals/pm-resume-has-no-endings-line/graders/offers-the-session.md @@ -0,0 +1,6 @@ +--- +type: regex +match: contains +flags: i +pattern: 'REPLY:[\s\S]*Projects board filter chips' +--- diff --git a/plugins/vibe/evals/pm-resume-has-no-endings-line/prompt.md b/plugins/vibe/evals/pm-resume-has-no-endings-line/prompt.md new file mode 100644 index 00000000..cc45ed1c --- /dev/null +++ b/plugins/vibe/evals/pm-resume-has-no-endings-line/prompt.md @@ -0,0 +1,25 @@ +--- +runs: 1 +plugins: ["../..", "../../../closedloop-core"] +max_turns: 8 +timeout_seconds: 300 +allowed_tools: [Skill, Read, Grep, Glob] +tags: [pm, notice] +--- + +This is a rehearsal of one turn of a vibe session, with no real environment. +Load the instructions with the Skill tool (`vibe:vibe`) and decide your next +message to the person exactly as those instructions say. You may Read the +plugin's own reference files. Do not run scripts, dispatch workers or call any +other tool, and do not ask me about the environment: the session state below +is everything that has already happened. + +Session state: +- One earlier session is active: `projects-board-chips` ("Projects board filter chips, last worked Tuesday, 6 files changed"). +- The person typed the request below with `$vibe`. + +The person just said: "$vibe pick up where I left off" + +Answer in exactly this form, and nothing else: +REPLY: <the message you would send the person now, word for word> +ROUTE: <one of: requested-pull-request | pass-on-lookup | ask-name | ask-two-way | explain-and-ask-two-way | start-new | resume | final-message> diff --git a/plugins/vibe/skills/handoff/SKILL.md b/plugins/vibe/skills/handoff/SKILL.md index d72bfd99..c0a7d78a 100644 --- a/plugins/vibe/skills/handoff/SKILL.md +++ b/plugins/vibe/skills/handoff/SKILL.md @@ -1,6 +1,6 @@ --- name: handoff -description: Finish a vibe session in symphony-alpha and hand it to whoever picks it up next, usually design and then engineering. Through workers, completes internal scope and Storybook checks, runs lint, typecheck and the existing tests without writing any (it lists the missing tests for engineering), drafts marked edits to existing product docs pages, reviews the integrated result and fixes confirmed findings, and verifies each touched screen in the running web app and Desktop with the repo's control skill. Asks who should pick the work up next, checks the live ticket is complete, publishes the final app environment or canonical prototype preview, and assigns the ticket with status left In Progress. It ends at the branch; no pull request is opened. Use when someone says "handoff", "hand this off", "send this to engineering", or "I'm done with this". Pairs with the vibe skill. +description: Finish a vibe session in symphony-alpha and hand it to whoever picks it up next, usually design and then engineering. Through workers, completes internal scope and Storybook checks, runs lint, typecheck and the existing tests without writing any (it lists the missing tests for engineering), drafts marked edits to existing product docs pages, reviews the integrated result and fixes confirmed findings, and verifies each touched screen in the running web app and Desktop with the repo's control skill. Settles in plain words how the session ends: a pull request the person takes through review and merge (plain-language intent such as "I'll get the PR merged" counts as asking for one), or passing it to someone, asking for a name or email only then. Checks the live ticket is complete, publishes the final app environment or canonical prototype preview, then either opens the requested pull request as its last step with the ticket left with the person, or assigns the ticket to the next owner, status left In Progress either way. Use when someone says "handoff", "hand this off", "send this to engineering", or "I'm done with this". Pairs with the vibe skill. --- # Handoff @@ -9,13 +9,17 @@ The person ran `vibe` and built something they are happy with. You orchestrate making it safe and complete for the next people, without asking them to understand any of the engineering. The session's live ClosedLoop ticket was created when the session started and the workers kept it current; -handoff checks it is complete rather than writing it. The person says who -picks the work up next, and the ticket is assigned to them. Usually that is -design, who reviews the components in the branch's Vercel Storybook and -comments on the ticket on sign-off, then engineering, who finishes the work -through analysis, a pull request, and merge; sometimes it goes straight to -engineering. The ticket stays In Progress throughout, and no pull request is -opened here. +handoff checks it is complete rather than writing it. The session ends one +of two ways (operator ruling, 2026-10-15). The person can ask for a pull +request they take through review and merge themselves ("A requested pull +request"): handoff opens it as its last step and the ticket stays with them. +Or they pass it to someone, and the ticket is assigned to whoever they name. +Usually that is design, who reviews the components in the branch's Vercel +Storybook and comments on the ticket on sign-off, then engineering, who +finishes the work through analysis, a pull request, and merge; sometimes it +goes straight to engineering. The ticket stays In Progress throughout. Never +say "handoff" to the person as if they know the word, and never offer to send +them the branch to merge. ## Your role: orchestrate, never do the work @@ -59,8 +63,9 @@ The build loop is lean (`../vibe/references/quality-loop.md`, "Lean build loop"), so handoff is where the session's reviews and heavy validation run: the guardrails and adversarial reviews, browser E2E, visual QA, logical QA, product-contract QA, the existing tests, Storybook footprint and the running app -checks. Handoff opens no pull request; "A requested pull request" below is the -one exception to the no-tests rule. +checks. Handoff opens a pull request only when the person asks for one ("A +requested pull request" below), which is also the one exception to the +no-tests rule. Harness notes match the `vibe` skill: in Codex invoke as `$handoff` and spawn plugin agents from `../../agents/<name>.md` with the file's body as the @@ -91,8 +96,9 @@ This recovery is internal; never ask the person running the session about it or Every answer the person gives during handoff (to a worker's `NEEDS_PERSON`, or as a correction to the summary in step 2) is one of two kinds, and the -worker that asked says which (`behavior` or `wording`). The one exception is -who picks the work up next, which step 8 asks and routes itself. +worker that asked says which (`behavior` or `wording`). The exceptions are +how the session ends, which step 0 settles, and who picks the work up next, +which step 8 asks and routes itself. - **Behavior**: it decides what the product does. A rule, a permission (who may do something), what happens in a case, an acceptance criterion, or a @@ -140,12 +146,22 @@ change worker has not handled; it refuses one with `NEEDS_CHANGE`. | Visual QA, logical QA and product-contract QA | the repository's own read-only skills (step 6) | | Backend: two review passes | the `workflow-code-review` skill (itself orchestrator-only) | | Last push and Vercel check | `vibe-environment-worker` (redeploy mode) | -| Next owner, ticket check, and assignment | `vibe-ticket-worker` (lookup mode, then handoff mode, then assign mode) | +| Opening a requested pull request | `vibe-environment-worker` (`pull-request` mode) | +| Next owner, ticket check, and assignment | `vibe-ticket-worker` (lookup mode, then handoff mode, then assign mode; a requested pull request skips lookup and assign) | ## A requested pull request Without the person's explicit request for a pull request, nothing in vibe or -handoff writes a test. When the person explicitly asks for a pull request to +handoff writes a test. The person asks in plain words, and plain-language +intent to take the work through review and merge themselves IS that request +(`../vibe/references/guardrails.md`, "Tests"; operator ruling, 2026-10-15): +"raise a PR", "open a pull request", "I'll get the PR merged", "I'll get it +merged", "I'll ship it myself", "I want to take it through review", or +choosing the pull request in step 0's question. Words like these take this +path wherever they come, during the session or at any step here, and are +never answered with a branch to merge. + +When the person explicitly asks for a pull request to be raised, full tests for every change in the session are required before it opens. Resume the SAME writer in `pull-request` mode, the only turn whose envelope authorizes tests, to write them: every acceptance criterion and @@ -153,25 +169,63 @@ unsuperseded row, production wiring and failure paths, on web and Desktop where their adapters differ, and every entry on the missing tests list. Run `node scripts/handoff-inventory.mjs --worktree "<wt>" --phase pull-request`, then step 3's guardrail check, step 5's checks with the new tests included and -step 6's review on the result, then step 7's commit. The pull request does not -open until those tests pass and their independent review is clean. Handoff -itself still opens none. +step 6's review on the result, then step 7's commit and its push and +publication through `vibe-environment-worker` in redeploy mode. The pull +request does not open until those tests pass and their independent review is +clean. + +From the request on, every brief in this handoff (the writer, the reviewers, +the verify and environment workers, the ticket worker) carries the +`pull-request` phase. No brief may say both that the person takes the work to +a pull request and that no tests are authored: a requested pull request is the +one case where the SAME writer authors them. + +On this path the ticket stays with the person: step 8 skips the next-owner +question and lookup and gives the ticket worker the session's operator as the +next owner, and step 9 skips assign mode. Handoff opens the pull request as +its last step (step 9) through `vibe-environment-worker` in `pull-request` +mode, and the person gets its link, never a branch to merge. ## 0. Pick the session Run `node ../vibe/scripts/vibe-sessions.mjs list` (it uses the checkout the vibe preflight remembered). Use the session whose worktree you are in. If you are not in one and more than one session is `active`, list them in plain -words and ask which one to hand off. Read its `liveTicket` and `localFixes`. +words and ask which one they want to finish. Read its `liveTicket` and `localFixes`. If the session has no `liveTicket` (it started before live tickets existed), dispatch `vibe-ticket-worker` in create mode first; it records the slug on -the session. If it is `handed-off`, tell the person it was already -handed off and stop. +the session. If it is `handed-off`, tell the person it is already +finished (a pull request was opened, or it was passed on) and stop. Then run `node ../vibe/scripts/vibe-sessions.mjs codex-sessions --worktree "<wt>"` so this conversation is recorded on the session too. +### Settle how it ends + +Settle the ending before the checks, because a requested pull request changes +what they run. Use the ending the person already chose (in the vibe skill's +section 8 or in their own words here); never re-ask it. + +- Words that say they will take it through review and merge themselves (the + list in "A requested pull request") choose the pull request: follow that + section. Ask nothing more. +- Words that say someone else finishes it ("send it to design", "give it to + <name>", "someone else can finish") choose passing it on. When they gave no + name, ask step 8's question for a name or email now, so the lookup in step 8 + has it; never ask it again there. +- Otherwise ask one plain question, in exactly these words: + +``` +Do you want me to open a pull request so you can take it through review and merge, or pass it to someone, usually design and then engineering, to finish? +``` + +Never ask for a name before they choose passing it on; once they choose it +without naming anyone, ask step 8's question right away. If they are confused +about what happens next (for example "oh i thought you were going to code +it"), explain the two endings plainly in a line or two and ask the same +question; never jump straight to asking for a name. + ### Owned prototype sessions Use the same recorded persistent writer, owned `prototype/<slug>` worktree, @@ -206,7 +260,8 @@ publication, before completing or assigning the ticket. Never give a stale preview as current. Shared story/footprint rules, next-owner lookup and ticket assignment stay intact. The final links truthfully name the immutable prototype preview, full SHA, slug and resolved next owner, not fabricated app/API fields. -Neither vibe nor handoff opens a PR. +Neither vibe nor handoff opens a PR unless the person asks for one ("A +requested pull request"). ## 1. Internal checklist @@ -432,7 +487,8 @@ that artifact or claims missing final evidence is a successful handoff. Publication readiness may precede external E2E completion for this FINAL handoff snapshot: retain exact named limitations, safely publish the committed SHA for existing CI/live QA, and keep final coverage/assignment incomplete. -No feature PR or automatic manual-CI trigger is added. ROOT routes CI permission +No automatic manual-CI trigger is added, and no feature PR opens here; a +requested one opens in step 9. ROOT routes CI permission internally; never ask the vibe coder for technical-plan approval. For actual authorized/completed evidence, ROOT binds `mainSyncCiRun: {runId, attempt}` in the SAME writer's actual source progress/handoff grant, then resumes @@ -537,14 +593,18 @@ packet and distinguish historical evidence from the actual delivered result. Unavailable evidence is a truthful gap, not a fabricated pass. This section is finished-work metadata, never a technical plan uploaded for approval. -Use a next owner the person already named for this handoff; never re-ask that -settled choice or infer an owner from a company default. If nobody was named, -have the ticket worker check the current session's prior decisions first. -Only if the necessary choice remains unresolved, ask one plain question in -exactly these words: +On a requested pull request (step 0), skip the question and the lookup: the +next owner is the session's operator, the person who ran it, and the ticket +stays with them. + +When they chose passing it on, use a next owner the person already named for +this handoff; never re-ask that settled choice or infer an owner from a +company default. If nobody was named, have the ticket worker check the +current session's prior decisions first. Only if the necessary choice remains +unresolved, ask one plain question in exactly these words: ``` -Who should pick this up next? A name or email is fine. +Who should I pass it to? A name or email is fine. ``` Dispatch `vibe-ticket-worker` in lookup mode with the worktree and their @@ -572,7 +632,8 @@ handoff"). Have it refresh that section after any behavior answer that changes the table, before the ticket worker sees the table again. Then dispatch `vibe-ticket-worker` in handoff mode with: the worktree, the live -ticket slug, the next owner (full name and email), the inventory path, the +ticket slug, the next owner (full name and email), whether the person asked +for a pull request, the inventory path, the confirmed summary and the person's corrections, the footprint, check, and review summaries, the Design blocks from build and handoff fixes, the required session decision table at `.closedloop-ai/decision-tables/<session-slug>.md` with whole-table alignment, @@ -604,12 +665,25 @@ runs Repeat until it returns `DONE`. Never go on to step 9 while a behavior answer has not been through the change worker, while its re-run checks, reviews, -or redeploy are unfinished, or without a next owner the lookup resolved to -exactly one user. +or redeploy are unfinished, or, when passing it on, without a next owner the +lookup resolved to exactly one user. ## 9. Hand it over -Dispatch `vibe-ticket-worker` in assign mode with the worktree, the live +On a requested pull request, skip assign mode: the ticket stays with the +person, In Progress. As handoff's last step, dispatch +`vibe-environment-worker` in `pull-request` mode (its own `pull-request` +record action, an exclusive record turn with no record writes) with the +worktree, the live ticket slug, the confirmed summary, the final verified +published commit from step 7, the tests the `pull-request` turn wrote and +step 5's check results, and the ticket's Production impact and Flag changes +and gates fields. It opens the pull request on the already pushed branch and +returns its URL; it never enqueues or merges it. On `BLOCKED`, tell the +person in one plain line that the pull request could not be opened and +suggest they message engineering with the session slug; never give them the +branch to merge instead. Then mark the session handed off as below. + +When they chose passing it on, dispatch `vibe-ticket-worker` in assign mode with the worktree, the live ticket slug, and the next owner's user id and email from step 8. It confirms the ticket is still assigned to the session's operator, the person who ran it (if engineering or design already took it, it returns `BLOCKED` and you stop: @@ -624,8 +698,10 @@ runs. Tell them, in a few lines: the ticket link, the app link as `<appUrl>/sign-in` (the app's root sends a signed-out visitor to account creation), the Storybook link (the full Storybook UI per -`../vibe/references/environment.md`, "Storybook links"), the -branch name, and what happens next: the next owner, by name, picks it up +`../vibe/references/environment.md`, "Storybook links"), and what happens +next. On a requested pull request: the pull request link, and that they take +it through review and merge from there; never the branch to merge. When +passing it on: the branch name, and that the next owner, by name, picks it up from the ticket (usually design reviews the components in Storybook and comments on the ticket on sign-off, then engineering finishes it). Say that the Storybook link opens only after signing in to Vercel with a team account. If you open it and land on a diff --git a/plugins/vibe/skills/vibe/SKILL.md b/plugins/vibe/skills/vibe/SKILL.md index 592ec388..742da03a 100644 --- a/plugins/vibe/skills/vibe/SKILL.md +++ b/plugins/vibe/skills/vibe/SKILL.md @@ -1,6 +1,6 @@ --- name: vibe -description: Start or resume a vibe-coding session in symphony-alpha for a non-engineer (such as a CEO) working in the Codex Desktop in-app browser. For a pure mockup or fake-data exploration, invokes the repository's canonical prototype skill itself in an owned prototype session, always shares on Vercel, and returns the immutable preview URL, full deployed commit SHA, and slug; handoff preserves the same live ticket and next-owner assignment without opening a PR. Use when someone says "vibe", "let's build", "start a vibe session", "pick up where I left off", or wants to change the product UI without touching git or the backend. Hand the finished work off with the handoff skill. +description: Start or resume a vibe-coding session in symphony-alpha for a non-engineer (such as an executive or a product manager) working in the Codex Desktop in-app browser. For a pure mockup or fake-data exploration, invokes the repository's canonical prototype skill itself in an owned prototype session, always shares on Vercel, and returns the immutable preview URL, full deployed commit SHA, and slug; handoff preserves the same live ticket and next-owner assignment, opening a PR only when the person asks for one. Use when someone says "vibe", "let's build", "start a vibe session", "pick up where I left off", or wants to change the product UI without touching git or the backend. Hand the finished work off with the handoff skill. --- # Vibe @@ -9,8 +9,9 @@ You are the orchestrator of a vibe session. You pair with a person who is not an engineer and does not use git. They describe what they want in chat or by annotating the app on their Vercel environment; workers you dispatch make the change in code, and each change deploys on its own so the person can look at it. -Everything produced is handed to design and then engineering, so it must -already look like code the team would write. +Everything produced is reviewed by design and engineering, whether the person +takes it to a pull request themselves or passes it on, so it must already look +like code the team would write. ## Your role: orchestrate, never do the work @@ -47,9 +48,10 @@ Follow `references/quality-loop.md` for the one persistent writer, serial queue, internal planning/review and communication. The person never sees a technical plan or technical question, and needs no upfront summary. Updates say only which feature is complete, which of its acceptance criteria are not done yet, and -what is being worked on next. The two notices in -section 5, "Notices that never wait" (a reference product's behavior list and -new Storybook components), are the only other messages, and neither waits. +what is being worked on next. The notices in +section 5, "Notices that never wait" (a reference product's behavior list, +new Storybook components and the endings line in a new session's first +reply), are the only other messages, and none of them waits. Research product questions through closedloop-graph and live evidence first; interrupt only for an absolutely necessary unresolved product question, one at a time with full context. Never re-ask a settled decision. @@ -162,7 +164,13 @@ No worker authors a test in vibe or handoff (`references/guardrails.md`, change broke, without changing what any test asserts, or deletes a recorded test that covered only code the request retires; handoff lists the missing tests for engineering, and tests are written only when the person -explicitly asks for a pull request. This skill opens no PR. Never ask the +explicitly asks for a pull request. They ask in plain words: "raise a PR", +"open a pull request", "I'll get the PR merged", "I'll get it merged", "I'll +ship it myself" or "I want to take it through review" is that request +(`references/guardrails.md`, "Tests"; operator ruling, 2026-10-15), and it +takes the handoff skill's requested pull request path ("A requested pull +request"), which opens the pull request for them at the end. This skill opens +no PR itself, and never offers to send the person the branch to merge. Never ask the person about tests or any other engineering choice: those rules decide it, or it goes to engineering. Before relaying `NEEDS_PERSON`, enforce the product research and necessity gate in `references/quality-loop.md`; technical choices @@ -246,18 +254,19 @@ Only after the person chose resume, run `node scripts/vibe-sessions.mjs list` final option "Start something new". If they named a session or slug, offer the matching session first. Let them pick. Never resume on a guess. - Any `active` session whose `lastActiveAt` is more than three days old gets - one extra line: "This hasn't been handed off yet. Hand it off now, keep - working on it, or throw it away?" Act on the answer (handoff skill, resume, - or section 9). -- A `handed-off` session belongs to design and engineering now (its ticket is - no longer assigned to the person who ran it) and is not offered. + one extra line: "This one isn't finished yet. Finish it now, keep working on + it, or throw it away?" Act on the answer (finish it as section 8 says, + resume, or section 9). +- A `handed-off` session is finished (a pull request was opened for the + person, or it was passed to design and engineering) and is not offered. Then follow "Resuming" below. ### Starting new -1. Use the work the person already described. Only when they have not supplied - it, ask what they want to work on: a ClosedLoop ticket (ISS-, PRD-, or a pasted +1. Your first reply to the new session carries the endings line (section 5, + "Notices that never wait"). Use the work the person already described. + Only when they have not supplied it, ask what they want to work on: a ClosedLoop ticket (ISS-, PRD-, or a pasted URL) or a plain description. A vibe session builds the real thing, backend included when a change needs it, so if they clearly want a mockup or an exploration with made-up data (nothing it shows needs to be real), start @@ -332,7 +341,7 @@ unrecorded prototype. Next-owner assignment and canonical metadata transitions remain in the handoff flow. Resuming: use the session's `worktree`, and run `codex-sessions` again so a -new conversation is recorded too. Starting new or resuming stops any other +new conversation is recorded too. A resumed session gets no endings line. Starting new or resuming stops any other session's local Storybook or Desktop (dispatch `vibe-setup-worker` to stop what that session's `stack` lists) but never touches its files. You commit only through `scripts/commit-worktree.mjs` and never push, stash, or rebase; @@ -574,8 +583,9 @@ evidence, never by replacing the writer. ### Notices that never wait -These two messages are notifications, not approval gates. Send each as one -plain message, then keep going without waiting for a reply. The person is +These messages are notifications, not approval gates. Send each as one +plain message (the endings line rides in the first reply instead), then keep +going without waiting for a reply. The person is never asked to approve them, and they never become an engineering or architecture question. If the person answers or interrupts to cut or change something, that message continues the active request in the SAME writer, which changes @@ -599,6 +609,12 @@ what it already built, like any other request about it. story (never the bare `iframe.html` canvas; `references/environment.md`, "Storybook links"), then resume the SAME writer to build the feature with them. A change that only reuses existing components skips this step. +- Endings. In the first reply to a NEW session (operator ruling, + 2026-10-15), include this one line as it is: "When you're done, I can + either open a pull request for you, or pass it to design or engineering to + finish." It is a notice, not a question: it rides along with whatever that + first reply says or asks, and nothing waits on it. Never add it when + resuming a session, and never repeat it later in the session. Pure mockups still use canonical `$prototype` guidance through this writer; the person only types `$vibe` and `$handoff`. Initial session creation chooses @@ -679,12 +695,38 @@ otherwise. ## 8. Ending a session -When they say they are done, ask once: "Hand this off now, keep it to come -back to, or throw it away?" On "hand it off", run the handoff skill. On "keep -it", dispatch `vibe-setup-worker` to stop local Storybook or Desktop if either -runs. Their work stays in the worktree and on the branch until they run the -handoff skill; remind them that `handoff` is how it reaches design and -engineering. On "throw it away", follow section 9. +A session ends one of two ways (operator ruling, 2026-10-15): a pull request +the person takes through review and merge themselves (the handoff skill's "A +requested pull request"), or passing it to someone, usually design and then +engineering, to finish. Never use the word "handoff" with the person as if +they know it; the skill name `$handoff` is the only place it appears. + +When they say they are done, settle which ending from their own words: + +- Words that say they will take it through review and merge themselves + ("raise a PR", "I'll get the PR merged", "I'll ship it myself", "I want to + take it through review"; `references/guardrails.md`, "Tests") choose the + pull request. Run the handoff skill with that choice; ask nothing more. +- Words that say someone else finishes it ("send it to design", "give it to + <name>", "someone else can finish") choose passing it on. When they gave no + name, ask at once with the handoff skill's step 8 question ("Who should I + pass it to? A name or email is fine."). Run the handoff skill with that + choice and the name they gave. +- Otherwise ask once, in exactly these words: "Do you want me to open a pull + request so you can take it through review and merge, or pass it to + someone, usually design and then engineering, to finish? You can also keep + it to come back to, or throw it away." Never ask for a name before they + choose passing it on. + +If they are confused about what happens next (for example "oh i thought you +were going to code it"), explain the two endings plainly in a line or two and +ask the same question; never jump straight to asking for a name. Never offer +to send them the branch to merge. + +On "keep it", dispatch `vibe-setup-worker` to stop local Storybook or Desktop +if either runs. Their work stays in the worktree and on the branch until they +come back and say they are done; remind them that is how it gets a pull +request or reaches design and engineering. On "throw it away", follow section 9. ## 9. Throwing a session away @@ -693,7 +735,8 @@ Whenever the person asks to throw a session away (at any point, for any 1. Run `node scripts/vibe-sessions.mjs discard --worktree "<wt>"` (without `--confirm` it changes nothing). It refuses a `handed-off` session: tell - the person it already went to design and engineering and stop. + the person it is already finished (a pull request was opened, or it went + to design and engineering) and stop. 2. Tell them in plain words what will be lost: the session's summary, the number of unsaved files, that their copy of the app on Vercel and its data will be deleted, and that the ticket will be canceled. Ask them to confirm. diff --git a/plugins/vibe/skills/vibe/references/guardrails.md b/plugins/vibe/skills/vibe/references/guardrails.md index 33f04ffd..ee016d47 100644 --- a/plugins/vibe/skills/vibe/references/guardrails.md +++ b/plugins/vibe/skills/vibe/references/guardrails.md @@ -1,7 +1,7 @@ # Guardrails -Vibe work is handed to design and then engineering, who review it and open -the pull request. It must already follow the team's patterns so they extend it +Vibe work is reviewed by design and engineering, whether the person takes it +to a pull request themselves or passes it on for them to open one. It must already follow the team's patterns so they extend it rather than rewrite it. The repo's `AGENTS.md` files are the contract; read the nearest one before editing a directory. This file adds the rules specific to vibe sessions and points at the repo rules that matter most here. @@ -42,9 +42,11 @@ rules still apply. Every session builds the real thing, frontend and backend: a screen that needs data or an action the API does not provide gets that backend built by the SAME persistent `vibe-change-worker`, using read-only backend guidance. A session that needs no new -data changes only frontend paths. It still ends on the session's branch: -design reviews it, then an engineer opens the pull request and reviews it -before it merges. +data changes only frontend paths. Unless the person asks for a pull request, +it ends on the session's branch: design reviews it, then an engineer opens +the pull request and reviews it before it merges. When the person asks for +one ("Tests"), handoff opens the pull request for them to take through review +and merge. | Frontend (the sole persistent `vibe-change-worker`) | Never | |---|---| @@ -402,6 +404,16 @@ Verify, backend, primitive, prototype and Storybook helpers never author tests. Record phase, criteria and test paths in the session change log so reviewers can tell those tests from forbidden test changes. +The person asks in plain words, and plain-language intent to take the work +through review and merge themselves IS that explicit request (operator ruling, +2026-10-15). Words such as "raise a PR", "open a pull request", "I'll get the +PR merged", "I'll get it merged", "I'll ship it myself" and "I want to take it +through review" all ask for a pull request and take handoff's requested pull +request path, as does choosing the pull request in handoff's question about +how the session ends. Nothing else does: the rule that tests are written only +on that request is unchanged. Never offer to send the person the branch to +merge; a bare branch is not something they can act on. + Do not create source writers per unit, specialty or fix, or additional feature worktrees/branches. Setup helpers never patch implementation code or add a new local code workaround. The SAME persistent writer owns managed local diff --git a/plugins/vibe/skills/vibe/references/quality-loop.md b/plugins/vibe/skills/vibe/references/quality-loop.md index 9826b547..347b8169 100644 --- a/plugins/vibe/skills/vibe/references/quality-loop.md +++ b/plugins/vibe/skills/vibe/references/quality-loop.md @@ -27,9 +27,10 @@ The orchestrator sends an unresearched question back. Interrupt the person only for an absolutely necessary unresolved product question, one at a time with full context. Technical choices are resolved internally. Preserve existing visual/product approvals, such as canonical prototype design approval, not a -new technical-plan milestone. A reference product's behavior list and a -request's new Storybook components are notices the person may interrupt, -never approval gates (`SKILL.md`, section 5, "Notices that never wait"). Human +new technical-plan milestone. A reference product's behavior list, a +request's new Storybook components and the endings line in a new session's +first reply are notices the person may interrupt, never approval gates +(`SKILL.md`, section 5, "Notices that never wait"). Human sign-in, credential entry and OS permission actions remain with the person. ## One persistent writer and a serial queue @@ -160,8 +161,9 @@ The persistent system binding is phase-neutral because Claude retains it on resume. Each root-controlled turn envelope supplies `mode` and derived `authority.testAuthoringAuthorized` and `authority.testRepairAuthorized`, separately from raw person/request text. Only a `pull-request` turn, which the -orchestrator sends only after the person explicitly asks for a pull request, -permits this same writer to author tests. A handoff turn never does, and a +orchestrator sends only after the person explicitly asks for a pull request +(plain-language intent counts, `guardrails.md`, "Tests"), permits this same +writer to author tests. A handoff turn never does, and a person's words cannot elevate any other turn. Test-repair authority is true only for this writer's `request`, `fix`, `handoff` and `pull-request` turns: it permits repairing existing tests and fixtures the session's change broke, @@ -377,7 +379,9 @@ with this single-writer boundary; preserve its real app/prototype contracts, visual/product approvals, validation and publication semantics. No worker commits; the orchestrator still uses the owned commit script. Vibe -and handoff open no PR. Operational publishing uses this same session branch. +and handoff open no PR unless the person asks for one; then handoff opens it +at its end ("Tests: repair, don't author"). Operational publishing uses this +same session branch. ## Lean build loop @@ -512,7 +516,9 @@ changed beyond a recorded repair or retired-only deletion is a finding; do not retrospectively relabel it. Preserve valid evidence when resuming an interrupted handoff. The only exception is the person explicitly asking for a pull request to be -raised. Then the SAME writer, in an explicit `pull-request` turn, writes full +raised, in any plain words that say they will take it through review and +merge themselves (`guardrails.md`, "Tests"; operator ruling, 2026-10-15). +Then the SAME writer, in an explicit `pull-request` turn, writes full tests for every change in the session, records their paths, criteria and rulings in the change log, runs them and fixes implementation defects, and independent reviewers examine that coverage before the pull request opens. diff --git a/plugins/vibe/skills/vibe/references/ticket-template.md b/plugins/vibe/skills/vibe/references/ticket-template.md index 9e4f9cdf..f2fef4c5 100644 --- a/plugins/vibe/skills/vibe/references/ticket-template.md +++ b/plugins/vibe/skills/vibe/references/ticket-template.md @@ -192,7 +192,10 @@ subagent that worked on this.> this up. Usually design reviews the components in the Storybook above (it opens after signing in to Vercel with a team account) and comments here on sign-off, then engineering finishes it through analysis, a pull request, - and merge. + and merge. On a requested pull request (the handoff skill's "A requested + pull request"), it reads instead: "Next: <the person who ran the session, by full name> takes this + through review and merge in the pull request handoff opens from this + branch." The ticket stays assigned to them. ## Design Review diff --git a/plugins/vibe/skills/vibe/scripts/dist/claude-worker.mjs b/plugins/vibe/skills/vibe/scripts/dist/claude-worker.mjs index 1e66dfdd..fc6ac52b 100644 --- a/plugins/vibe/skills/vibe/scripts/dist/claude-worker.mjs +++ b/plugins/vibe/skills/vibe/scripts/dist/claude-worker.mjs @@ -11946,11 +11946,24 @@ var recordModes = { create: ["create_document_version"], redeploy: ["create_document_version"], flags: ["create_document_version"], - desktop: ["create_document_version"] + desktop: ["create_document_version"], + "pull-request": [] }, "vibe-prototype-worker": { share: ["create_document_version"] } }; -var recordActionSchema = external_exports.enum(["progress", "create", "handoff", "assign", "cancel", "redeploy", "flags", "desktop", "share", "discard"]); +var recordActionSchema = external_exports.enum([ + "progress", + "create", + "handoff", + "assign", + "cancel", + "redeploy", + "flags", + "desktop", + "share", + "discard", + "pull-request" +]); var requestContinuationSchema = external_exports.object({ runtime: external_exports.enum(["codex", "claude"]), requestId: identifierSchema, diff --git a/plugins/vibe/skills/vibe/scripts/dist/main-sync.mjs b/plugins/vibe/skills/vibe/scripts/dist/main-sync.mjs index ecaba3c9..e037ef02 100644 --- a/plugins/vibe/skills/vibe/scripts/dist/main-sync.mjs +++ b/plugins/vibe/skills/vibe/scripts/dist/main-sync.mjs @@ -224299,7 +224299,19 @@ var storedValidationRecipeSchema = external_exports.object({ }).strict(); // src/record-context.ts -var recordActionSchema = external_exports.enum(["progress", "create", "handoff", "assign", "cancel", "redeploy", "flags", "desktop", "share", "discard"]); +var recordActionSchema = external_exports.enum([ + "progress", + "create", + "handoff", + "assign", + "cancel", + "redeploy", + "flags", + "desktop", + "share", + "discard", + "pull-request" +]); var requestContinuationSchema = external_exports.object({ runtime: external_exports.enum(["codex", "claude"]), requestId: identifierSchema, diff --git a/plugins/vibe/skills/vibe/scripts/dist/writer-state.mjs b/plugins/vibe/skills/vibe/scripts/dist/writer-state.mjs index 2980dfa0..b5bcaabe 100644 --- a/plugins/vibe/skills/vibe/scripts/dist/writer-state.mjs +++ b/plugins/vibe/skills/vibe/scripts/dist/writer-state.mjs @@ -12029,11 +12029,24 @@ var recordModes = { create: ["create_document_version"], redeploy: ["create_document_version"], flags: ["create_document_version"], - desktop: ["create_document_version"] + desktop: ["create_document_version"], + "pull-request": [] }, "vibe-prototype-worker": { share: ["create_document_version"] } }; -var recordActionSchema = external_exports.enum(["progress", "create", "handoff", "assign", "cancel", "redeploy", "flags", "desktop", "share", "discard"]); +var recordActionSchema = external_exports.enum([ + "progress", + "create", + "handoff", + "assign", + "cancel", + "redeploy", + "flags", + "desktop", + "share", + "discard", + "pull-request" +]); var requestContinuationSchema = external_exports.object({ runtime: external_exports.enum(["codex", "claude"]), requestId: identifierSchema, diff --git a/plugins/vibe/tools/python/test_handoff_skill_contract.py b/plugins/vibe/tools/python/test_handoff_skill_contract.py index 20f8b86f..522c2b06 100644 --- a/plugins/vibe/tools/python/test_handoff_skill_contract.py +++ b/plugins/vibe/tools/python/test_handoff_skill_contract.py @@ -95,9 +95,9 @@ def test_change_worker_checks_a_handoff_answer_against_the_code() -> None: def test_step_eight_reuses_a_named_owner_or_asks_without_defaulting() -> None: step_eight = section(HANDOFF_SKILL.read_text(), "## 8. Choose who picks it up, then check the ticket") - assert "Who should pick this up next? A name or email is fine." in step_eight + assert "Who should I pass it to? A name or email is fine." in step_eight assert "`vibe-ticket-worker` in lookup mode" in step_eight - assert "Use a next owner the person already named" in step_eight + assert "use a next owner the person already named" in step_eight assert "never re-ask that settled choice" in step_eight assert "Keep the assignment progress internal" in step_eight assert "report the next owner at completion" in step_eight diff --git a/plugins/vibe/tools/python/test_vibe_handoff_docs_tests_control_contract.py b/plugins/vibe/tools/python/test_vibe_handoff_docs_tests_control_contract.py index 1b1d18ea..6cc62ce9 100644 --- a/plugins/vibe/tools/python/test_vibe_handoff_docs_tests_control_contract.py +++ b/plugins/vibe/tools/python/test_vibe_handoff_docs_tests_control_contract.py @@ -166,7 +166,7 @@ def test_only_an_explicit_pull_request_request_authorizes_full_tests() -> None: for required in ("explicitly asks for a pull request to be raised", "`pull-request` mode", "the only turn whose envelope authorizes tests", "every entry on the missing tests list", "--phase pull-request", "does not open until those tests pass", - "Handoff itself still opens none"): + "Handoff opens the pull request as its last step"): assert required in requested, required writer = section(agent("vibe-change-worker"), "## Tests: repair, don't author") assert "Only a `pull-request` continuation" in writer diff --git a/plugins/vibe/tools/python/test_vibe_session_endings_contract.py b/plugins/vibe/tools/python/test_vibe_session_endings_contract.py new file mode 100644 index 00000000..d4cbfb58 --- /dev/null +++ b/plugins/vibe/tools/python/test_vibe_session_endings_contract.py @@ -0,0 +1,198 @@ +"""Contract checks for how a vibe session ends (operator ruling, 2026-10-15). + +1. Plain words that say the person will take the work through review and merge + themselves ("i will get the pr merged", "I'll ship it myself") are an + explicit request for a pull request and take handoff's requested pull + request path. The person is never offered the branch to merge. +2. Handoff no longer asks "Who should pick this up next?". It settles the + ending with one plain two-way question that never says "handoff": open a + pull request, or pass it on. Only passing it on asks for a name or email. +3. The first reply to a new session carries one endings line as a notice that + never waits; a resumed session does not get it. +4. On the pull request path the SAME writer authors the tests in its + `pull-request` turn, step 7 pushes and publishes, handoff opens the pull + request as its last step through the environment worker's `pull-request` + mode, the ticket stays with the person, and they get the pull request link. + No brief may say both that the person takes it to a pull request and that + no tests are authored. +""" + +from __future__ import annotations + +import re +from pathlib import Path + + +PLUGIN_ROOT = Path(__file__).resolve().parents[2] +AGENTS = PLUGIN_ROOT / "agents" +VIBE_SKILL = PLUGIN_ROOT / "skills" / "vibe" / "SKILL.md" +HANDOFF_SKILL = PLUGIN_ROOT / "skills" / "handoff" / "SKILL.md" +REFERENCES = PLUGIN_ROOT / "skills" / "vibe" / "references" +GUARDRAILS = REFERENCES / "guardrails.md" +QUALITY = REFERENCES / "quality-loop.md" +TICKET_TEMPLATE = REFERENCES / "ticket-template.md" +RECORD_CONTEXT = PLUGIN_ROOT.parents[1] / "tools" / "vibe-workers" / "src" / "record-context.ts" +RULING = "operator ruling, 2026-10-15" +TWO_WAY = ("Do you want me to open a pull request so you can take it through review and merge, " + "or pass it to someone, usually design and then engineering, to finish?") +ENDINGS_LINE = ("When you're done, I can either open a pull request for you, " + "or pass it to design or engineering to finish.") +PR_PHRASES = ("raise a PR", "I'll get the PR merged", "I'll ship it myself", "I want to take it through review") +BRANCH_TO_MERGE = re.compile(r"branch to merge", re.IGNORECASE) +NEGATION = re.compile(r"\b(never|not)\b", re.IGNORECASE) +HANDOFF_WORD = re.compile(r"hand ?off", re.IGNORECASE) + + +def flat(text: str) -> str: + return re.sub(r"\s+", " ", text) + + +def section(text: str, heading: str) -> str: + """The section under `heading`, up to the next heading of the same or a higher level, flattened.""" + level = len(heading.split(" ", 1)[0]) + start = text.index(heading) + rest = text[start + len(heading) :] + ends = [match.start() for match in re.finditer(r"\n(#{1,%d}) " % level, rest)] + return flat(rest[: ends[0]] if ends else rest) + + +def agent(name: str) -> str: + return (AGENTS / f"{name}.md").read_text() + + +def plugin_texts() -> dict[str, str]: + files = [*PLUGIN_ROOT.glob("skills/**/*.md"), *AGENTS.glob("*.md"), PLUGIN_ROOT / "README.md"] + return {str(path.relative_to(PLUGIN_ROOT)): path.read_text() for path in files} + + +def test_plain_words_are_an_explicit_pull_request_request() -> None: + rule = section(GUARDRAILS.read_text(), "## Tests") + assert "plain-language intent to take the work through review and merge themselves IS that explicit request" in rule + assert RULING in rule + for phrase in (*PR_PHRASES, "open a pull request", "I'll get it merged"): + assert phrase in rule, phrase + assert "take handoff's requested pull request path" in rule + assert "the rule that tests are written only on that request is unchanged" in rule + assert "That request is the only thing that authorizes test writing" in rule + + +def test_every_place_that_decides_a_pull_request_request_recognizes_plain_words() -> None: + requested = section(HANDOFF_SKILL.read_text(), "## A requested pull request") + tests_rule = flat(VIBE_SKILL.read_text()) + ending = section(VIBE_SKILL.read_text(), "## 8. Ending a session") + writer = section(agent("vibe-change-worker"), "## Tests: repair, don't author") + for phrase in PR_PHRASES: + for name, text in (("handoff", requested), ("vibe", tests_rule), ("ending", ending), ("writer", writer)): + assert phrase in text, (name, phrase) + assert "plain-language intent to take the work through review and merge themselves IS that request" in requested + assert "takes the handoff skill's requested pull request path" in tests_rule + assert "choose the pull request. Run the handoff skill with that choice; ask nothing more." in ending + assert "only the orchestrator's `pull-request` turn carries the authority" in writer + assert "plain-language intent counts" in section(QUALITY.read_text(), "## One persistent writer and a serial queue") + assert "in any plain words" in section(QUALITY.read_text(), "## Tests: repair, don't author") + + +def test_the_two_way_question_replaces_who_picks_it_up() -> None: + handoff = HANDOFF_SKILL.read_text() + settle = section(handoff, "### Settle how it ends") + assert TWO_WAY in settle + assert "Never ask for a name before they choose passing it on" in settle + assert "never jump straight to asking for a name" in settle + assert "never re-ask it" in settle + assert TWO_WAY in section(VIBE_SKILL.read_text(), "## 8. Ending a session") + assert not HANDOFF_WORD.search(TWO_WAY) + step_eight = section(handoff, "## 8. Choose who picks it up, then check the ticket") + assert "Who should I pass it to? A name or email is fine." in step_eight + assert "On a requested pull request (step 0), skip the question and the lookup" in step_eight + for name, text in plugin_texts().items(): + assert "Who should pick this up next?" not in text, name + assert "Hand this off now" not in text, name + + +def test_the_person_never_hears_handoff_as_an_unexplained_term() -> None: + ending = section(VIBE_SKILL.read_text(), "## 8. Ending a session") + assert 'Never use the word "handoff" with the person as if they know it' in ending + assert 'Never say "handoff" to the person as if they know the word' in flat(HANDOFF_SKILL.read_text()) + resume = section(VIBE_SKILL.read_text(), "### Finding a session to resume") + stale = re.search(r'"This one isn\'t finished yet\.[^"]*"', resume) + assert stale, resume + assert not HANDOFF_WORD.search(stale.group(0)) + description = HANDOFF_SKILL.read_text().split("---", 2)[1] + assert 'Use when someone says "handoff", "hand this off"' in description + + +def test_the_endings_line_is_a_non_blocking_notice_in_a_new_sessions_first_reply_only() -> None: + skill = VIBE_SKILL.read_text() + notices = section(skill, "### Notices that never wait") + assert "- Endings. In the first reply to a NEW session" in notices + assert ENDINGS_LINE in notices + assert "It is a notice, not a question" in notices + assert "nothing waits on it" in notices + assert "Never add it when resuming a session" in notices + starting = section(skill, "### Starting new") + assert starting.index("carries the endings line") < starting.index("ask what they want to work on") + assert "A resumed session gets no endings line." in section(skill, "### Mockup sessions") + assert ENDINGS_LINE not in section(skill, "### Finding a session to resume") + assert "the endings line in a new session's first reply" in flat(skill[: skill.index("## Harness notes")]) + + +def test_nothing_offers_the_branch_to_merge() -> None: + for name, text in plugin_texts().items(): + flattened = flat(text) + for match in BRANCH_TO_MERGE.finditer(flattened): + before = flattened[max(0, match.start() - 80) : match.start()] + assert NEGATION.search(before), (name, flattened[max(0, match.start() - 80) : match.end()]) + final = section(HANDOFF_SKILL.read_text(), "## 9. Hand it over") + assert "the pull request link, and that they take it through review and merge from there; never the branch to merge" in final + + +def test_handoff_opens_the_requested_pull_request_as_its_last_step() -> None: + handoff = HANDOFF_SKILL.read_text() + requested = section(handoff, "## A requested pull request") + assert "Handoff opens the pull request as its last step" in requested + assert "the ticket stays with the person" in requested + final = section(handoff, "## 9. Hand it over") + assert "On a requested pull request, skip assign mode" in final + assert "`vibe-environment-worker` in `pull-request` mode" in final + assert "it never enqueues or merges it" in final + for retired in ("Handoff itself still opens none", "It ends at the branch; no pull request is opened"): + assert retired not in flat(handoff), retired + assert "ends at the branch without opening a pull request" not in flat((PLUGIN_ROOT / "README.md").read_text()) + assert "Opening a requested pull request | `vibe-environment-worker` (`pull-request` mode)" in flat(handoff) + + +def test_the_environment_worker_opens_the_pull_request_from_the_template_and_never_merges() -> None: + worker = agent("vibe-environment-worker") + mode = section(worker, "## Pull-request mode") + for required in ("no push, no commit", "gh pr create --repo closedloop-ai/symphony-alpha --base main", + ".github/pull_request_template.md", "never from invention", + "only when Production impact supports it", "`[ui-flag:gated] <key>` or `[ui-flag:N/A] <reason>`", + "at most 20 lines", "at most 72 characters including the ` (#NNNN)`", + "say so in that section for the reviewer instead of inventing one", + "Never add the `warning-acknowledged` label, request reviewers, trigger CI by hand, " + "arm auto-merge, enqueue or merge", "never push to make it so"): + assert required in mode, required + assert "`pull-request`), the worktree path" in flat(worker) + record = RECORD_CONTEXT.read_text() + assert '"pull-request": [] }' in record + assert '"pull-request"]);' in record + + +def test_the_ticket_stays_with_the_person_on_the_pull_request_path() -> None: + template = section(TICKET_TEMPLATE.read_text(), "## Handoff") + assert "- Next: <the next owner the person chose at handoff, by full name>" in template + assert ("Next: <the person who ran the session, by full name> takes this through review and merge " + "in the pull request handoff opens from this branch.") in template + assert "The ticket stays assigned to them." in template + handoff_mode = section(agent("vibe-ticket-worker"), "## Handoff mode") + assert "that owner is the session's operator and the line takes the template's pull request form" in handoff_mode + + +def test_a_requested_pull_request_runs_the_tests_the_push_and_one_consistent_brief() -> None: + requested = section(HANDOFF_SKILL.read_text(), "## A requested pull request") + assert "Resume the SAME writer in `pull-request` mode" in requested + assert "step 7's commit and its push and publication through `vibe-environment-worker` in redeploy mode" in requested + assert "every brief in this handoff" in requested + assert "carries the `pull-request` phase" in requested + assert ("No brief may say both that the person takes the work to a pull request and that no tests are " + "authored") in requested diff --git a/tools/vibe-workers/src/claude-worker.test.ts b/tools/vibe-workers/src/claude-worker.test.ts index 9928f246..ea8b18d2 100644 --- a/tools/vibe-workers/src/claude-worker.test.ts +++ b/tools/vibe-workers/src/claude-worker.test.ts @@ -53,6 +53,17 @@ describe("production Claude CLI adapter", () => { expect(bound.agent.prompt).toContain("Never start another implementation writer, commit, or push."); } }); + it("admits the environment pull-request action with no record writes and the no-push footer", () => { + const value = setup(); + const item = { agentName: "vibe-environment-worker", mode: "record" as const, + recordAction: "pull-request" as const, exclusiveRecordTurn: true as const }; + const launch = (capabilities: typeof graph) => boundDefinition( + readDefinition(value.agentRoot, item.agentName, capabilities), + { worktree: value.worktree, agentRoot: value.agentRoot, requestId: "open-pr", input: "open the requested PR", + timeoutMs: 1000, capabilities, ...item }); + expect(launch(graph).agent.prompt).toContain("Never start another implementation writer, commit, or push."); + expect(() => launch([...graph, recordWrite])).toThrow(/outside this canonical action/); + }); it("selects the canonical scoped agent directly and resumes the same underlying ID across two requests", () => { const value = setup(); const entry = join(value.base, "claude-worker-link.mjs"); diff --git a/tools/vibe-workers/src/native-record.test.ts b/tools/vibe-workers/src/native-record.test.ts index 9e972b04..91a690a8 100644 --- a/tools/vibe-workers/src/native-record.test.ts +++ b/tools/vibe-workers/src/native-record.test.ts @@ -56,6 +56,16 @@ describe("native helper record CLI", () => { workerId: "different-primary" }).status).toBe(1); expect(run("acquire-record", { ...input, recordAction: "cancel" }).status).toBe(0); }, 10000); + it("grants the Codex environment helper its pull-request action and refuses it to other roles", () => { + const value = setup(); + const input = { ...grant(value), agentName: "vibe-environment-worker", requestId: "open-pr", recordAction: "pull-request" }; + expect(run("acquire-record", { ...input, agentName: "vibe-ticket-worker" }).status).toBe(1); + const acquired = run("acquire-record", input); + expect(acquired.status, acquired.stderr).toBe(0); + const { lease, recordAction } = JSON.parse(acquired.stdout); + expect(recordAction).toBe("pull-request"); + expect(run("release-record", release(input, lease)).status).toBe(0); + }, 10000); it("admits only one native helper acquisition or primary claim in a simultaneous production CLI race", async () => { const value = setup(); const helper = grant(value); const primary = { worktree: value.worktree, workerId: "native-primary", requestId: "primary-request" }; diff --git a/tools/vibe-workers/src/record-context.ts b/tools/vibe-workers/src/record-context.ts index eba03525..4cb54af7 100644 --- a/tools/vibe-workers/src/record-context.ts +++ b/tools/vibe-workers/src/record-context.ts @@ -12,11 +12,12 @@ export const recordModes: Record<string, Record<string, readonly string[]>> = { "vibe-ticket-worker": { create: ["create_document"], handoff: ["create_document_version", "upload_attachment"], assign: ["update_document"], cancel: ["create_document_version", "update_document"] }, "vibe-environment-worker": { create: ["create_document_version"], redeploy: ["create_document_version"], - flags: ["create_document_version"], desktop: ["create_document_version"] }, + flags: ["create_document_version"], desktop: ["create_document_version"], "pull-request": [] }, "vibe-prototype-worker": { share: ["create_document_version"] }, }; -export const recordActionSchema = z.enum(["progress", "create", "handoff", "assign", "cancel", "redeploy", "flags", "desktop", "share", "discard"]); +export const recordActionSchema = z.enum(["progress", "create", "handoff", "assign", "cancel", "redeploy", "flags", "desktop", "share", "discard", + "pull-request"]); export const requestContinuationSchema = z.object({ runtime: z.enum(["codex", "claude"]), requestId: identifierSchema, recordAction: z.enum(["flags", "desktop"]) }).strict(); export type RequestContinuation = z.infer<typeof requestContinuationSchema>;