respond · git:20261013.5905b55 · 2026-10-13 · sha256 91c4fc79ebb25ebe
respond git:20261013.5905b55A
Immutable. This exact content never changes and is served at /api/v1/blob/91c4fc79ebb25ebe.
---
name: respond
description: Use when addressing review feedback on a pull request or merge request. Use --own to work through the findings from a prior review --own round.
---
# flowyeah:respond — PR/MR Review Response Pipeline
Addresses review feedback on a pull request or merge request. Fetches unresolved comments, evaluates them via a configurable skill, presents them for interactive triage, implements approved fixes, replies to comment threads, resolves conversations, and conditionally re-requests review.
With `--own`, the source of items swaps from unresolved PR comments to findings produced by `/flowyeah:review --own [N]`. The pipeline reads `.flowyeah/review-approved-{N}.md`, runs the same evaluation + triage + implement flow, and closes the review round by setting `Phase: Responded` on the review session. No thread replies, resolves, or re-requests are sent because the findings were never public. Multiple rounds of `review --own` → `respond --own` can iterate on the same PR without explicit finalization between rounds.
Completes the PR lifecycle: `build` creates PRs, `review` evaluates them, `respond` addresses feedback.
```
flowyeah:respond [--own] [<number>]
```
## Invariant: Primary Checkout Is Untouched
**All code mutation happens inside the worktree resolved at step 5 — the branch's existing worktree, or `.flowyeah/worktrees/<slug>/` created there.** The checkout the pipeline was invoked from (the "primary checkout") stays as the user left it. See `primary-checkout.md` at the plugin root for the read-only command table, what the rule excludes, and how `tree-guard.sh` enforces it.
This applies through the full lifecycle: steps 0-4 (config, identify, fetch, evaluate, triage) need only read-only operations against the primary checkout; steps 5-7 (worktree setup, implement, test) operate exclusively inside the worktree (`cd` into it before any code edit); steps 8-10 (push, re-request, cleanup) push from the worktree and remove it without touching the primary.
`finalize`-style cleanup is not a separate command for respond; the pipeline self-cleans at step 10. To abort mid-pipeline, remove `.flowyeah/respond-state-{N}.md` and `.flowyeah/respond-decisions-{N}.md` manually (or use `/flowyeah:status clean`).
## Argument Parsing
The `--own` flag may appear in any position. The positional argument (if present) is the PR number; otherwise auto-detect from the current branch via the respond adapter.
**Run from the main checkout.** If `git rev-parse --show-toplevel` contains `.flowyeah/worktrees/` or `.flowyeah/review-worktrees/`, STOP: "Run respond from the main checkout" — session files written inside a worktree are invisible to session-inject and tree-guard on the primary. Steps 5-7 `cd` into the worktree themselves.
In `--own` mode:
- Platform-specific fetch (step 2 normal path) is replaced by reading the findings file.
- Thread reply, thread resolve, and re-request-review steps are skipped (no public threads exist for self-findings).
- Cleanup marks the linked review session as `Phase: Responded` instead of leaving it alone.
## Pipeline
```dot
digraph respond {
rankdir=TB;
node [shape=box];
validate [label="0. Validate Config\n(keys, adapters, auth)"];
identify [label="1. Identify PR/MR"];
fetch [label="2. Fetch Review Comments"];
praise_filter [label="2.5 Praise Filter" shape=diamond];
evaluate [label="3. Evaluate Comments\n(skill and/or instructions)" style=dashed];
triage [label="4. Interactive Triage"];
worktree [label="5. Setup Worktree" shape=diamond];
implement [label="6. Implement Fixes"];
test [label="7. Test" shape=diamond];
push_reply [label="8. Push & Reply"];
rerequest [label="9. Re-request Review"];
cleanup [label="10. Cleanup Worktree"];
validate -> identify -> fetch -> praise_filter -> evaluate -> triage;
praise_filter -> push_reply [label="all praise\n(normal mode)" style=dashed];
triage -> worktree [label="implement items"];
triage -> push_reply [label="replies only\nskip 5-7"];
worktree -> implement -> test -> push_reply;
push_reply -> rerequest -> cleanup;
}
```
If triage yields only replies (no code changes), skip steps 5-7 and go directly to step 8.
## Configuration
Uses `flowyeah.yml` from the project root (see `config-schema.md` at the plugin root for full schema and defaults). **If missing, load `setup.md` from the plugin root and follow its interactive setup instructions before proceeding.**
The respond skill uses:
| Key | Purpose |
|-----|---------|
| `git_host` | Determines respond adapter |
| `code_review.evaluation_skill` | Skill invoked to evaluate each comment — the *how* (optional) |
| `code_review.instructions` | Project-specific guidelines fed into comment evaluation — the *what* (optional) |
| `testing.command` | Shell command to run tests |
| `testing.scope` | `related` or `full` — scope for test runs |
| `commits.writer` | Agent or `null` for commit messages |
| `worktree.symlinks` | Paths symlinked from worktree to main checkout |
| `worktree.env` | Environment variables for worktree |
| `worktree.setup` | Commands run after worktree creation (step 5, via `worktree-lifecycle.md`) |
| `worktree.teardown` | Commands run before worktree removal (step 10, owned worktrees only) |
| `language` | Language for thread replies |
## Platform Detection
The respond adapter is determined from `git_host` in `flowyeah.yml`:
| `git_host` | Respond adapter |
|------------|-----------------|
| `github` | `adapters/github/respond.md` |
| `gitlab` | `adapters/gitlab/respond.md` |
Load the respond adapter once at the start. **If the git host adapter has no `respond.md`, STOP** — that adapter doesn't support review responses. All platform-specific operations (fetch comments, reply, resolve, re-request) go through the adapter.
## Session (Lightweight)
**Coexistence check first (normal mode):** before creating the state file, check for `.flowyeah/review-state-{N}.md`. If it exists with any phase other than `Responded`, STOP and ask: "A review session for PR #{N} is active (phase: X). Running respond concurrently on the same PR can conflict — finalize the review first?" Review and respond must not run concurrently on the same PR. (`--own` mode skips this — its preflight already requires a review session at `Phase: Delegated`.)
Create `.flowyeah/respond-state-{N}.md` for compaction resilience:
```markdown
# Current State
Type: respond
Status: Responding
PR/MR: <number>
Branch: <source_branch>
Platform: <adapter>
Comments: <count> total
Phase: <current_phase>
Worktree: <path of the worktree resolved at step 5, or "none">
WorktreeOwned: <true if respond created it, false if it already existed>
```
`<slug>` is the branch name with `/` flattened to `-` (see `worktree-lifecycle.md`, Directory Naming) — build worktrees follow the same rule, so branch `feat/5588` maps to `feat-5588` regardless of which skill created it. `Worktree` defaults to `none` and is set by step 5 when the worktree is resolved (created or reused). `WorktreeOwned` records whether respond created the worktree itself — only owned worktrees are torn down in step 10 (normal mode); reused worktrees are left alone. Both fields are cleared back to `none`/`false` once cleanup completes. The hook injects them as-is on every prompt so context recovery can verify worktree state.
**Valid `Phase` values** (map to steps, used for crash recovery):
| Phase | Step | Recovery action |
|-------|------|-----------------|
| `Validating Config` | 0 | Re-run from start |
| `Identifying PR` | 1 | Re-run from start |
| `Fetching Comments` | 2 | Re-run from step 2 |
| `Evaluating` | 3 | Re-run from step 2 (comments lost) |
| `Interactive Triage` | 4 | Read `respond-decisions-{N}.md`, re-present any finding without a recorded decision. Decisions already recorded (including `Final critique:`) are preserved. |
| `Setting Up Worktree` | 5 | Read `respond-decisions-{N}.md`, retry worktree setup |
| `Implementing` | 6 | Read `respond-decisions-{N}.md`. For each `Action: implement` entry, check `git log` in the worktree for a commit whose subject references the finding (file + subject). Resume from the first implement decision with no corresponding commit. Re-apply the re-surface safety net on remaining pending decisions. |
| `Testing` | 7 | Re-run tests |
| `Pushing & Replying` | 8 | Check which threads already have replies, continue with remaining |
| `Re-requesting Review` | 9 | Check if re-request was sent, retry if not |
| `Cleaning Up Worktree` | 10 | In `--own` mode, check `review-state-{N}.md`'s phase — if not yet `Responded`, re-apply step 10. In normal mode, check if the worktree still exists and retry removal. Always verify respond state files are gone before declaring the session clean. |
After the user makes triage decisions (step 4), persist results to `.flowyeah/respond-decisions-{N}.md`:
```markdown
# Triage Decisions
## Comment 1
- Thread: <thread_id>
- File: app/services/payment_service.rb:42
- Confidence: —
- Action: implement
- Final critique:
Assessment: agree
Reasoning: null pointer possible when OAuth skips email scope
Recommendation: implement
## Comment 2
- Thread: <thread_id>
- File: (general)
- Confidence: —
- Action: reject
- Final critique:
Assessment: disagree
Reasoning: single implementation, YAGNI applies
Recommendation: reject
## Comment 3
- Thread: <thread_id>
- File: app/controllers/api/v1/payments_controller.rb:18
- Confidence: —
- Action: discuss
- Reply: Good point, but the validation happens upstream in the service layer — see PaymentValidator#call
- Final critique:
Assessment: needs-clarification
Reasoning: behaviour depends on which layer owns validation
Recommendation: discuss
```
This file ensures that if compaction or a crash interrupts after triage, decisions are recoverable.
Respond sessions use `respond-state-{N}.md` (not `state.md`) so they never interfere with build sessions in worktrees. Respond and build sessions can coexist (different PRs). Respond and review sessions should not coexist for the same PR.
Update `respond-state-{N}.md` after each phase transition. The hook injection ensures state survives compaction.
Both `respond-state-{N}.md` and `respond-decisions-{N}.md` are removed in step 10 (cleanup).
### Cross-Round Persistence: `own-rejections-{N}.md`
Only used in `--own` mode. This file is **long-lived** — it survives across `review --own` / `respond --own` rounds and is removed only by `/flowyeah:review finalize {N}`. Its purpose: record findings the author rejected with reasoning so the next round's review agents do not re-flag the same concerns.
Path: `.flowyeah/own-rejections-{N}.md` (at the main checkout, never inside a worktree).
Schema:
```markdown
# Previously Rejected Findings (PR #{N})
## Rejection 1
- File: app/services/foo.rb:42
- Label: issue
- Confidence: 75
- Subject: Missing null check on user.email
- Rejected at: 2026-05-04T15:30:00Z
- Reasoning: |
Field is non-nullable at the DB level (see schema.rb).
The agent did not account for the migration in commit abc1234.
## Rejection 2
- File: (general)
- Label: suggestion
- Confidence: —
- Subject: Extract validation into a service object
- Rejected at: 2026-05-04T18:12:00Z
- Reasoning: |
YAGNI — single call site, premature abstraction.
```
**Field semantics:**
| Field | Required | Source |
|-------|----------|--------|
| `File:` | yes | Copied from the matching entry in `respond-decisions-{N}.md`. Use `(general)` for findings without `file:line`. |
| `Label:` | yes | Conventional Comments label from the original finding, with decorations preserved (e.g., `issue (blocking)`, `suggestion (non-blocking)`). Mirrors the format used in `review-approved-{N}.md` and `respond-decisions-{N}.md`. |
| `Confidence:` | yes | The finding's confidence score, copied from `respond-decisions-{N}.md`. Write `—` when the source carries none — reviewer comments in normal mode have no score, and findings persisted before the field existed have none either. Recorded so that rejection rate can be read against the score the review assigned: a band that is rejected as often as it is kept is a band that is not earning its place. |
| `Subject:` | yes | The first non-empty line of the original finding body, stripped of label prefix. Used as the dedup key together with `File:`. |
| `Rejected at:` | yes | ISO 8601 UTC timestamp at the moment cleanup ran. |
| `Reasoning:` | yes | Multi-line block (literal `|` YAML scalar). Copied from the `Final critique:` block of the matching `respond-decisions-{N}.md` entry — specifically the `Reasoning:` sub-field. If the user rejected without discussion, the round-1 critique reasoning is what lands here. |
**Dedup key:** `(File, Subject)`. Two entries with the same key must not coexist; the later write replaces the earlier one (and updates `Rejected at:`).
**Lifecycle:** `own-rejections-{N}.md` is written by `respond --own` step 10, read by `review --own` step 2.8a, and removed by `review finalize {N}`.
Specifically, `own-rejections-{N}.md` is NOT removed by `respond --own` cleanup — only `respond-decisions-{N}.md` and `respond-state-{N}.md` are.
## Steps
### 0. Validate Configuration
Before starting, validate the loaded `flowyeah.yml`:
1. **Load schema:** read `config-schema.md` from the plugin root.
2. **Check required keys:** `git_host` must point to an adapter with `respond.md`.
3. **Check evaluation skill:** if `code_review.evaluation_skill` is present, verify the skill exists. If missing, **warn** (don't block) — proceed without it in step 3.
4. **Load review instructions:** if `code_review.instructions` is present, validate the path is relative and the file exists (per validation rules in `config-schema.md`). Read the file contents once and carry them through the pipeline. If missing or invalid, **error** (block) — this matches review's handling.
5. **Run validation rules:** execute relevant checks from the "Validation Rules" section of the schema.
6. **Detect legacy state files:** if `.flowyeah/respond-state.md` (without a PR number) exists, STOP with: *"Found legacy `.flowyeah/respond-state.md` — this file format is no longer supported. Remove it manually with `rm .flowyeah/respond-state.md .flowyeah/respond-decisions.md 2>/dev/null` and re-run."* Do not attempt to migrate.
7. **Auth verification:** verify credentials for the git host adapter.
8. **Report all issues at once** — collect validation failures and present together.
If validation fails, STOP with actionable error messages.
### 1. Identify PR/MR
If `<number>` is provided, use it. Otherwise, detect from current branch via the respond adapter.
Display PR/MR summary: title, author, branch, review state.
### 1.5 `--own` Mode Preflight
**Skip this section if `--own` was not provided.**
Before proceeding, verify all of the following. Report every failure as STOP with the indicated message:
1. **Main checkout only:** if `git rev-parse --show-toplevel` contains `.flowyeah/worktrees/`, STOP: *"respond --own must start from the main checkout, not from inside a worktree."*
2. **Review session exists for this PR:** require `.flowyeah/review-state-{N}.md` to exist. If missing, STOP: *"No review session for PR #{N}. Run `/flowyeah:review --own {N}` first."*
3. **Review phase is Delegated:** parse the `Phase:` line from `review-state-{N}.md`.
- If `Phase: Responded`, STOP: *"Previous round complete. Run `/flowyeah:review --own {N}` for a new round."*
- If `Phase: Findings Delivered`, STOP: *"Review pipeline is still at the Next Action menu — return to that session and pick `Delegate`."*
- If any other phase, STOP: *"Review for PR #{N} is mid-pipeline (Phase: X). Finalize or resume the review session first."*
4. **Findings file present:** require `.flowyeah/review-approved-{N}.md` to exist and contain at least one `## Finding ` heading. If missing or empty, STOP: *"No approved findings for PR #{N}. Run `/flowyeah:review finalize {N}` and start over."*
5. **Worktree exists for branch:** parse `Branch:` from `review-state-{N}.md` and look up its worktree as step 5 does. If none is found, STOP: *"No worktree for branch `X`. Run `/flowyeah:build` first to create one."*
On success, echo: *"Responding to <count> findings from PR #{N}."*
### 2. Fetch Review Comments
**If `--own` mode:** skip the adapter fetch. Read `.flowyeah/review-approved-{N}.md` and construct synthetic comments from it using this mapping:
| respond field | `--own` value |
|---|---|
| Reviewer name | `self (review --own)` |
| Comment body | the Conventional Comments block from the finding (`**label (decoration):** subject` plus body) |
| File path + line | from the finding's `- File:` line |
| Confidence | from the finding's `- Confidence:` line (`—` when absent — findings persisted before the field existed) |
| Source (card field) | `own review` |
| Thread ID | synthetic: `own-{N}-{index}` where index is 1-based position in the file |
| Review state | `SELF_REVIEW` |
| Conventional parse | already in format — direct parse |
Group by reviewer is trivial (all `self`). If no findings are parseable, STOP: *"review-approved-{N}.md exists but contains no parseable findings — cannot proceed."*
If parsing succeeded but zero synthetic comments result (all parsed findings were filtered out — distinct from the unparseable case above, which STOPs), the subsequent steps have nothing to do — report "No findings to respond to", remove `.flowyeah/respond-state-{N}.md` and `.flowyeah/respond-decisions-{N}.md` (if created), and exit. Do not mark `Phase: Responded` — the round didn't happen. A leftover state file would keep `tree-guard` blocking the branch indefinitely.
**Normal mode** (no `--own`): continue with the existing adapter fetch below.
Via the respond adapter, fetch all unresolved review comments/threads. For each comment capture:
- Reviewer name
- Comment body (raw text)
- File path + line (if inline)
- Thread ID (for replying/resolving)
- Conventional Comment parsed fields (if format matches)
- Review state of reviewer's most recent review
Group comments by reviewer.
**Conventional Comments parsing:** when a comment's body matches the `**<label> [decorations]:** <subject>` format, parse the label, decorations, and subject. Treat non-matching comments as free-form — they are equally valid.
If no unresolved comments found, report, remove `.flowyeah/respond-state-{N}.md` (and `respond-decisions-{N}.md` if created), and exit. A leftover state file would keep `tree-guard` blocking the branch indefinitely.
### 2.5 Praise Filter
Conventional Comments items with label `praise` are not actionable feedback — they recognize good work. Routing them through the per-finding `[d]/[i]/[r]` triage adds friction without value, so they are auto-handled before evaluation.
After step 2, partition the comment list into two groups:
- **Praise** — Conventional Comments parse succeeded and `label == "praise"` (decorations such as `(non-blocking)` are ignored for matching).
- **Actionable** — everything else: other labels, free-form text, or parse failures. Hedged positives without the `praise` label (e.g., "looks good?") stay actionable on purpose — they may be questions in disguise.
For each praise item, upsert a decision into `.flowyeah/respond-decisions-{N}.md` with `Action: praise-ack`. Do not invoke `evaluation_skill` and do not surface these in step 4. Pass only the actionable group forward to step 3.
**Schema — normal mode** (write a short `Reply:` in the language configured in `language`; the agent picks something context-appropriate, a one-line thanks is fine, echoing what the reviewer called out is better when it reads naturally):
```markdown
## Comment 4
- Thread: <thread_id>
- File: app/models/payment.rb:80
- Action: praise-ack
- Reply: Valeu! Esse refactor estava encostado há tempos.
```
**Schema — `--own` mode** (omit `Reply:` entirely — the synthetic thread has no platform-side reply target):
```markdown
## Finding 4
- Thread: own-{N}-4
- File: app/models/payment.rb:80
- Action: praise-ack
```
**Downstream handling:**
| Mode | Step 8 | Step 9 | Step 10 |
|------|--------|--------|---------|
| Normal | Post `Reply` to the thread, then resolve it. Reply-only action — counts toward the "replies only" path for re-request gating. | Treated like any reply-only thread. | No special handling. |
| `--own` | Skipped — synthetic thread has no platform counterpart. | Skipped (whole step). | Not persisted to `own-rejections-{N}.md` (praise is not a rejection). |
**Short-circuit:** if the actionable group is empty (every comment was praise), skip steps 3-7 entirely. In normal mode, go directly to step 8 to post the auto-thanks and resolve. In `--own` mode, report "Only praise — nothing to respond to." and skip to step 10.
### 3. Evaluate Comments
Operates only on the actionable group from step 2.5 — praise has already been routed.
**Skip only if neither `code_review.evaluation_skill` nor `code_review.instructions` is configured** — present comments raw without recommendations in step 4.
The plug that runs depends on what is configured (`evaluation_skill` is the *how* — general method; `code_review.instructions` is the *what* — project specifics):
- **Both configured:** invoke `evaluation_skill` via the Skill tool, passing the instructions contents as additional project context.
- **Instructions only (no skill):** evaluate each comment inline against the instructions — mirroring how `review` runs "Project Review Guidelines" as a direct inline check (`review/SKILL.md`, step 3b).
- **Skill only:** invoke `evaluation_skill` via the Skill tool with no project instructions.
When instructions are present in either branch, **act** on them — do not treat them as passive text. Instructions may reference external resources (a Linear issue and sub-issue, a docs URL); resolve those using available tools (Linear access, WebFetch) to verify compliance. If a referenced resource cannot be fetched, degrade gracefully: mark that finding's assessment `unavailable` with the reason (see per-finding errors below) rather than implying compliance that was not verified.
This step runs identically in `--own` mode: the synthetic comments built during self-audit flow through the same branches.
In every branch, for each comment produce:
- **Assessment**: agree / disagree / needs-clarification
- **Reasoning**: brief technical justification (verified against codebase)
- **Recommended action**: implement / reject / discuss
**Per-finding evaluation errors:** if evaluation fails for a specific comment/finding — an `evaluation_skill` error (timeout, exception, malformed output) **or** a failure resolving an external resource referenced by `code_review.instructions` (unreachable issue, failed fetch) — do not abort the entire run. Instead, mark that finding's critique as unavailable and proceed. In step 4, the per-finding UX shows:
```
┌─ Critique ───────────────────────────────────────────────
│ Assessment: unavailable (evaluation skill error)
│ Reasoning: <error message or "evaluation skipped">
│ Recommended: —
└─────────────────────────────────────────────────────────
```
The `[d]iscuss` option is still available — a discussion round may succeed where the initial evaluation didn't. The `[i]` and `[r]` options use pure human judgment.
### 4. Interactive Triage (per-finding discussion loop)
Present findings from the step 2.5 actionable group one at a time (praise-ack items are never triaged). For each, show the finding plus its round-1 critique (from step 3), then enter a loop until the user decides. No code is modified in this step — all decisions are recorded and implementation happens in batch during step 6.
**MANDATORY: one finding per prompt.** Do not collapse multiple findings into a single review memo, table, or batch approval form. Each finding gets its own prompt with its own `[d]/[i]/[r]` action. Persist the decision, then advance to the next finding.
**Render before you ask.** The action prompt for a finding — whether the `[d]/[i]/[r]` text menu or a question-tool call — may only be issued in a response whose own visible message text already contains that finding's card and critique blocks. Before asking, check your output for the `═══` card header and for the code fence wrapping it; if either is missing, render the card first. A decision prompt without the card on screen asks the user to judge a finding they cannot see.
#### Anti-pattern (do not do this)
```
✗ WRONG — batch triage memo
Triage — 6 findings from review --own #8880
Finding 1 · nitpick · in-scope-now
File: …
- Assessment: agree
- Recommendation: implement
Finding 2 · nitpick · in-scope-now
…
Proposed triage: implement 1, 2, 3 · reject 4, 5, 6.
Confirmar com ok, ou d 3 / r 2 para alterar.
```
This shape is forbidden because it:
- Drops the **Finding Card** format (no `═══` header, no `┌─│└` comment box), collapsing each finding into a one-line header — the "wall of text" the card exists to prevent.
- Skips the per-finding discussion loop (no `[d]` round possible on one finding in isolation).
- Invents a `d 3` / `r 2` syntax that is not in the skill.
- Defers persistence — decisions only hit `respond-decisions-{N}.md` after bulk confirm, so a compaction mid-memo loses everything.
- Merges step 3 (evaluation output) and step 4 (interactive triage) into a single blob.
#### Per-finding UX
For each item `i` in order (severity then confidence when the evaluation provides them — normal-mode reviewer comments often carry neither, in which case preserve the fetched order), render the **Finding Card** — read `finding-card.md` at the plugin root for the template and its fields; `flowyeah:review` renders the same card from the same file. Use the `Finding <i> of <N>` title line, since findings are presented one at a time here. Follow the card with a boxed critique block and the action menu:
```
<Finding Card for item i — see finding-card.md>
┌─ Critique (round 1) ─────────────────────────────────────
│ Assessment: <agree | disagree | needs-clarification>
│ Reasoning: <evaluation_skill's reasoning from step 3>
│ Recommended: <implement | reject | discuss>
└─────────────────────────────────────────────────────────
[d]iscuss [i]mplement [r]eject
>
```
Accept a single-letter action:
- **`d` — discuss:** prompt the user for a follow-up question, then dispatch an additional `evaluation_skill` round with the extended-contract input (see below). Display the revised critique. Stay in the loop, re-showing the `[d] [i] [r]` menu.
- **`i` — implement:** record the decision, move to the next finding. Do not modify code.
- **`r` — reject:** record the decision, move to the next finding.
In normal (non-`--own`) mode, the menu also shows `[s]` (discuss-with-reviewer: reply to the reviewer and mark for thread reply in step 8). In `--own` mode, only `[d] [i] [r]` are offered — there is no reviewer to reply to.
#### Example discuss round
```
> d
Your counter or question:
> Check the actual isolation level — my Rails config should be READ COMMITTED.
[critique round 2...]
┌─ Critique (round 2) ─────────────────────────────────────
│ Assessment: agree (revised)
│ Reasoning: config/initializers/database.rb:14 sets
│ :read_committed. The race is real.
│ Recommended: implement
└─────────────────────────────────────────────────────────
[d]iscuss [i]mplement [r]eject
>
```
#### `evaluation_skill` contract (extended)
`evaluation_skill` is invoked twice in this skill:
1. **Step 3** (initial critique, once per finding): input `{ finding }`, output `{ assessment, reasoning, recommendation }`. This is the existing contract.
2. **Step 4** (discussion rounds, on-demand): input `{ finding, prior_rounds, user_question }` where `prior_rounds` is an ordered list of `{ assessment, reasoning, recommendation, user_question? }` entries (round 1 has no `user_question`). Output is the same shape: `{ assessment, reasoning, recommendation }`.
**Backward compatibility:** skills that only implement the round-1 contract still work — for discussion rounds, fall back to calling the skill with `{ finding }` augmented with a prose preamble summarizing prior rounds and the user's question. The result is lower-quality but functional.
#### Persistence
After every decision (implement/reject), upsert an entry for that finding in `.flowyeah/respond-decisions-{N}.md` — write the block if it doesn't exist, or replace the existing block for that finding. Mid-discussion (before a terminal `[i]`, `[r]`, or `[s]` — the `[s]` reply path persists an `Action: discuss` entry with its `Reply:`), no persistence happens: decisions are only recorded when the user commits to a terminal action. Schema:
```markdown
# Triage Decisions
## Finding 1
- Thread: own-{N}-1
- File: app/models/payment.rb:42
- Confidence: 85
- Action: implement
- Final critique:
Assessment: agree (after round 2 discussion)
Reasoning: race is real under READ COMMITTED isolation
Recommendation: implement
## Finding 2
- Thread: own-{N}-2
- File: (general)
- Confidence: 40
- Action: reject
- Final critique:
Assessment: disagree
Reasoning: single implementation, YAGNI applies
Recommendation: reject
## Comment 3
- Thread: <thread_id>
- File: app/controllers/api/v1/payments_controller.rb:18
- Confidence: —
- Action: discuss
- Reply: Validation lives upstream in PaymentValidator#call — see service layer
- Final critique:
Assessment: needs-clarification
Reasoning: behaviour depends on which layer owns validation
Recommendation: discuss
```
`Confidence:` carries through the score captured at step 2 — the `- Confidence:` line of the finding in `--own` mode, `—` for reviewer comments in normal mode, which have none. It is written verbatim and never invented; step 10 copies it into `own-rejections-{N}.md` so rejection rate stays readable against the score the review assigned.
For normal-mode respond, `Thread:` uses the platform's real thread ID and headings use `## Comment` instead of `## Finding`. The `Final critique:` block is new in both modes and stores the **last** round's assessment (after any discussion). If the user rejects without discussing, `Final critique:` mirrors the round-1 critique. The `[s]` path (normal mode only, see Per-finding UX) produces `Action: discuss` plus a `Reply:` field capturing the user's reply to post at step 8.
#### Termination
When all findings have a decision, exit the loop. Display a one-line summary:
```
Triage complete: <implement_count> implement, <reject_count> reject.
```
Proceed to step 5 (setup worktree) if any decisions are `implement`. Otherwise, in `--own` mode, stop here with "No findings to implement — review round closed." and proceed directly to step 10 (cleanup). In normal mode, proceed to step 8 (push & reply) to post replies for rejected items and for `[s]`-path discussion replies.
### 5. Setup Worktree
**Only if there are items marked "implement."** If all items are "reject" or "discuss", skip to step 8.
Ask git for the worktree that has the PR branch checked out — it may live anywhere, not only under `.flowyeah/worktrees/`:
```bash
git worktree list --porcelain | awk -v ref="branch refs/heads/<branch>" \
'/^worktree /{n++; path=substr($0, 10)} n > 1 && $0 == ref {print path}'
```
The first entry is the primary checkout, which the invariant above keeps untouched, so the lookup skips it.
If a path comes back, reuse that worktree. Otherwise, create one (git refuses a second worktree for a branch that is already checked out, so never create without looking up first):
```bash
SLUG=$(printf '%s' "<branch>" | tr '/' '-')
git worktree add ".flowyeah/worktrees/$SLUG" <branch>
```
Then follow the **Setup** procedure from `worktree-lifecycle.md` (at the plugin root):
1. **Symlinks** — resolve `worktree.symlinks` from `flowyeah.yml`
2. **Environment** — resolve `worktree.env`, persist to `respond-state-{N}.md` under `## Worktree Env`, export, run `worktree.setup` commands
Track whether the worktree was **created** by this respond session (vs. reused) — only cleanup worktrees that respond created.
### 6. Implement Fixes
Navigate to the worktree. Read `.flowyeah/respond-decisions-{N}.md` (main checkout path) into memory — it contains all triage decisions including `Final critique:` blocks.
For each decision with `Action: implement`, in the order they appear:
1. **Read context:** file, surrounding lines, related code paths identified by the final critique.
2. **Make the change** informed by the `Final critique:` reasoning — not just the finding's raw body. The critique may have been revised through discussion and reflects the user's latest understanding.
3. **Commit** using `commits.writer` from config. Commit message should reference the finding (file + subject). One commit per implemented decision is the default.
**Grouping:** when the decisions already queued for implementation naturally touch the same file or the same concern, you may group them into a single commit. Do not force grouping if it obscures intent. One-commit-per-finding is always acceptable.
#### Re-surface on conflict (safety net)
After each commit, before moving to the next implement decision, scan the remaining pending implement decisions and check for potential invalidation:
```
for later_decision in remaining_pending_implement_decisions:
if later_decision.file == current_decision.file
and abs(later_decision.line - current_decision.line) <= 20:
PAUSE and present to user:
"Fix for finding <i> changed <file> near line <line>.
Finding <j> was also in this region (line <j_line>).
Does its decision still hold?
[k]eep decision [r]e-open discussion [s]kip finding <j>"
```
User responses:
- **`k`** — keep the existing decision, continue.
- **`r`** — re-open the discussion loop from step 4 for just that finding, then update the decision. Resume implementation from the next pending decision.
- **`s`** — change finding `<j>`'s action to `reject` and record the reason ("skipped after re-surface: superseded by finding <i>'s fix"). Continue.
The 20-line proximity window is a heuristic. Cross-file refactors won't trigger the safety net — that's acceptable; the user can always interrupt the implementation manually if they notice a bigger landscape shift.
#### After all implement decisions are done
Proceed to step 7 (Test). Do not push yet.
### 7. Test (if code changed)
**Only if code was changed in step 6.**
Run tests using `testing.command` and `testing.scope` from config. If tests fail, **STOP** and report failures. Do not push or reply.
### 8. Push & Reply
**In `--own` mode:**
1. Push the branch as normal.
2. **Skip thread reply entirely** — there are no threads to reply to.
3. **Skip thread resolve entirely** — there are no threads to resolve.
**In normal mode:**
1. **Push the branch** (from the worktree, if one was used)
2. **Reply to each comment thread** via the respond adapter:
- **Implemented:** describe what was done (e.g., "Fixed — added guard clause for nil `user.email`")
- **Rejected:** provide technical reasoning (e.g., "Single implementation — applying YAGNI here. The factory pattern would add indirection without a second consumer.")
- **Discuss:** post the user's reply text
- **Praise-ack (auto):** post the `Reply` recorded at step 2.5 verbatim — do not rewrite it here
3. **Resolve each thread after replying**
### 9. Re-request Review
**In `--own` mode, skip this step entirely** — there are no external reviewers to re-request from. Proceed directly to step 10.
Re-request behavior depends on whether code was changed (steps 5-7 executed) or only replies were posted (skipped to step 8).
#### When code was changed (push happened)
Pushing new commits dismisses existing approvals (standard GitHub branch protection behavior). Re-request from **all** reviewers and notify previously-approved reviewers.
**GitHub:**
| State | Re-request? | Reason |
|-------|-------------|--------|
| `CHANGES_REQUESTED` | Yes | Reviewer blocked the PR and needs to re-evaluate |
| `COMMENTED` | Yes | Reviewer left feedback and should see the response |
| `APPROVED` | Yes | Approval was dismissed by the new push — needs re-approval |
| `DISMISSED` | No | Review was dismissed before this cycle |
**Post a PR comment** (not a thread reply) notifying previously-approved reviewers that their approval was dismissed. Mention them by username so they get a notification:
```
@reviewer1 @reviewer2 — o push de correções invalidou a aprovação anterior.
Podem re-aprovar quando conveniente? As mudanças foram: <brief summary of what changed>.
```
Write the comment in the language configured in `language` from `flowyeah.yml`.
#### When no code was changed (replies only)
No push happened, so approvals remain intact. Only re-request from reviewers with pending feedback.
**GitHub:**
| State | Re-request? | Reason |
|-------|-------------|--------|
| `CHANGES_REQUESTED` | Yes | Reviewer blocked the PR and needs to re-evaluate |
| `COMMENTED` | Yes | Reviewer left feedback and should see the response |
| `APPROVED` | No | Approval still valid — no code changed |
| `DISMISSED` | No | Review was dismissed |
**GitLab:** use presence of unresolved threads as proxy (GitLab lacks a `CHANGES_REQUESTED` state). After resolving all threads from a reviewer, re-add that reviewer to the reviewers list. When code was changed, re-add all previous reviewers.
Via the respond adapter.
### 10. Cleanup
Cleanup has two paths depending on mode.
#### `--own` mode
Do **not** remove the worktree — preflight item 5 required it to exist before this session. Run the items in this order — `Phase: Responded` is the completion marker crash recovery keys on, so it is written **last**; every earlier item is idempotent, so a crash mid-cleanup re-applies step 10 safely. Specifically:
1. **Persist cross-round rejections:** read `.flowyeah/respond-decisions-{N}.md` and update `.flowyeah/own-rejections-{N}.md` per the rules below. Create the rejections file (with header `# Previously Rejected Findings (PR #{N})` and a single trailing blank line) if it does not exist. The rules:
- For each `## Finding K` block in `respond-decisions-{N}.md` with `Action: reject` — upsert a `## Rejection M` block into `own-rejections-{N}.md`. The dedup key is `(File, Subject)`. If a matching entry exists, replace it: overwrite all fields (`File`, `Label`, `Confidence`, `Subject`, `Reasoning`) with the new values derived from the matching `respond-decisions-{N}.md` entry, and set `Rejected at:` to the current timestamp. Otherwise append a new entry. `M` is the next available 1-based index after the highest existing `## Rejection` heading. (Indices are never reused after deletion; gaps in the sequence are expected and benign.) See the schema in the "Cross-Round Persistence" section above for field mapping.
- For each `## Finding K` block with `Action: implement` — if a matching entry exists in `own-rejections-{N}.md` (same `File:` and `Subject:`), remove it. The user has overridden their previous rejection by choosing to fix the issue this round; the rejection no longer applies. If no match exists, do nothing.
- `Action: discuss` blocks (only present in normal mode) and `Action: praise-ack` blocks are ignored — neither represents a rejection. Same for any other action value.
- If after all updates `own-rejections-{N}.md` contains no `## Rejection ` headings (regardless of whether the `# Previously Rejected Findings` header line is present), delete the file. A header-only file is noise.
2. **Remove the findings file:** `rm -f .flowyeah/review-approved-{N}.md` (findings are consumed).
3. **Remove respond state files:** `rm -f .flowyeah/respond-state-{N}.md .flowyeah/respond-decisions-{N}.md`. Do **not** touch `own-rejections-{N}.md` here — it persists across rounds.
4. **Update the review session state:** rewrite `.flowyeah/review-state-{N}.md` (at the main checkout path) so that `Phase:` is `Responded`. Leave all other fields intact. Touch the file's mtime.
5. **Do not touch** the worktree or any files inside it.
Display the end-of-round guidance:
```
Round complete. Next options:
/flowyeah:review --own {N} — start another review round
/flowyeah:review finalize {N} — close out the review relationship (when done)
```
#### Normal mode (worktree created by this respond session)
Follow the **Teardown** procedure from `worktree-lifecycle.md` (at the plugin root):
1. **Close IDE windows** — prevent VSCode from freezing
2. **Run teardown commands** — read env from `respond-state-{N}.md ## Worktree Env`, export, run `worktree.teardown`
3. **Remove worktree** — `git worktree remove`
Then remove session files (`respond-state-{N}.md` and `respond-decisions-{N}.md`).
#### Normal mode (worktree already existed)
Skip worktree removal. Remove session files (`respond-state-{N}.md` and `respond-decisions-{N}.md`) only.
## Crash Recovery
If a respond session is interrupted (compaction, crash, user abort):
1. The hook injects `respond-state-{N}.md` into the next prompt
2. Resume from the last recorded phase using the recovery table above
3. If the phase was before "Interactive Triage" (step 4), re-run from that phase
4. If during or after triage, read `respond-decisions-{N}.md` to recover previously made decisions
5. If all replies were already posted and review re-requested, clean up both state files
## Comment Language
Thread replies are written in the language configured in `language` from `flowyeah.yml`. Default: `en`.
## Error Handling
| Error | Action |
|-------|--------|
| PR/MR not found | Ask user for number/URL |
| No unresolved comments | Report and exit cleanly |
| Evaluation skill not found | Warn, proceed without evaluation |
| Worktree creation fails | Report error, suggest manual resolution |
| Tests fail | Stop, report failures, don't push |
| Reply fails (401, 403, 429, 5xx) | Retry up to 2 times, then STOP and report |
| Thread resolve fails | Log warning, continue (non-critical) |
| Re-request review fails | Log warning, continue (non-critical) |
## Timing
After all steps complete (or on early exit), display a summary:
```
Respond complete — N items (M implemented, K rejected, J discussed, P praise auto-acked)
Code changes: yes/no | Tests: passed/skipped
In --own mode: review round closed, Phase: Responded
In normal mode: Re-request review: yes (2 reviewers) / no
```
Omit the `P praise auto-acked` field when `P == 0`.