# PRD — `review.default_reviewers` config key for `/ecl-review` reviewer selection

- **Status:** Draft
- **Date:** 2026-05-13
- **Issue:** `#3079`
- **Related ADR:** `0011-review-default-reviewers.md`

> This PRD is filed alongside its ADR under `docs/adr/` for co-location. The repo does not yet have a `docs/prd/` directory; if maintainers prefer one, this file can move there with the `0011-` prefix preserved.

## TL;DR

`/ecl-review` with no flags fans out to **every** detected CLI reviewer (Claude, Codex, Cursor, Gemini, OpenCode, plus local model servers such as ollama, lm-studio, llama.cpp). For users with many backends installed, this wastes wall-clock on timeouts and burns tokens on reviewers they don't want for routine work. Add a `review.default_reviewers` key under the existing `review.*` namespace in `.planning/config.json` that scopes the no-flag default to a user-chosen subset. Absent key preserves today's behavior. `--all` and individual flags continue to work unchanged. Follows eCL's **absent = enabled** convention.

## Problem Statement

eCL's `/ecl-review` workflow treats "no flags" as "invoke every CLI we can detect" (`workflows/review.md` line 52). That default is fine at install time — it makes the feature discoverable — but it's the wrong default for any user who has accumulated multiple reviewer CLIs plus local model servers. Each review probes up to ~10 backends, including ones that are slow, expensive, redundant for the change at hand, or not actually running (timeout waits on ollama, lm-studio, llama.cpp when the daemon is off).

The only existing workaround is editing `workflows/review.md` in place. That patch gets clobbered on every `/ecl-update`, requiring `/ecl-update --reapply` to restore. There is no machine-readable record of the user's intent — every machine the user works on needs the same patch reapplied. The issue reporter (`#3079`) and presumably others are paying a "tax" on every review that is purely a default-selection problem.

This is a small change with broad reach: it lands in a hot-path workflow that power users run many times per day.

## Goals

- Eliminate the recurring local-patch tax for multi-CLI users. A user who consistently wants only Gemini + Codex for routine reviews should be able to set that once and forget it.
- Cut median wall-clock time of a no-flag `/ecl-review` on multi-CLI machines (target: ≥40% reduction for users with ≥4 detected CLIs).
- Keep the change non-breaking. Absent config = today's behavior; nothing changes for the install-day experience.
- Match eCL's established config conventions (`review.models.*`, `review.*_host`, **absent = enabled**, namespacing under `review.*`) so users don't have to learn a new pattern.
- Stay one-line-shaped. The implementation should be a config read plus an intersection with the detected set — no new commands, no schema overhaul, no migration.

## Non-Goals

- **Per-phase or per-task reviewer routing** (e.g., "use Codex on Rust phases, Gemini on docs"). Useful, but a separate, larger design — track as a future ADR.
- **Reviewer scoring, weighting, or ensemble logic.** This is about *which* reviewers run, not *how* their output is aggregated.
- **Auto-detecting the "best" default reviewers based on usage history.** Out of scope; we want explicit user intent, not silent behavior drift.
- **Changing the `--all` semantics or the individual flag set** (`--gemini`, `--codex`, `--cursor`, …). They keep their current meaning.
- **A new top-level config namespace.** Reviewers belong under the existing `review.*` namespace.
- **GUI / TUI editing of the key in v1.** Editing the JSON file directly (or via `/ecl-settings` / `/ecl-config --integrations` if maintainers choose to support it later) is sufficient.

## Users & Use Cases

### Primary persona: "Multi-CLI power user"

A developer who has installed multiple coding CLIs (e.g., Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Kilo) plus one or more local inference servers. They run `/ecl-review` frequently — sometimes dozens of times per day during a sprint — and have a stable mental model of which 1–3 reviewers actually add signal for their day-to-day work.

### Secondary persona: "Single-CLI user with a sometimes-on local server"

Has Claude + ollama installed. Wants reviews from Claude every time, and from ollama only when explicitly asked. Today, every `/ecl-review` pays the ollama timeout cost when ollama isn't running.

### Secondary persona: "Cost-sensitive team lead"

Routine reviews should hit cheap/local reviewers; pre-merge reviews should hit the expensive ones via `--all` or explicit flags. Wants a config-level expression of "the cheap subset is my default."

### Use cases this enables

- "I only want Gemini + Codex for routine reviews." → set `review.default_reviewers: ["gemini", "codex"]`.
- "I want Claude only by default, and I'll opt into the others with flags." → set `["claude"]`.
- "I want today's behavior." → leave the key absent.
- "I want today's behavior just this once" on a configured project → `/ecl-review --all`.

## User Stories

Grouped by persona, ordered roughly by frequency.

**Multi-CLI power user**

- As a multi-CLI user, I want to declare which reviewers run by default so that `/ecl-review` doesn't probe backends I don't use.
- As a multi-CLI user, I want my preference to survive `/ecl-update` so I don't have to keep re-patching `workflows/review.md`.
- As a multi-CLI user, I want `--all` to still work so I can opt into a full review pre-merge without un-setting my config.
- As a multi-CLI user, I want individual flags (`--gemini`, `--cursor`, …) to keep working regardless of my default so ad-hoc runs aren't constrained by the default.

**Single-CLI user with sometimes-on local server**

- As a user with intermittent local servers, I want my default to exclude them so a stopped daemon doesn't cost me a 30-second timeout on every review.

**Cost-sensitive team lead**

- As a team lead, I want to commit `.planning/config.json` to the repo so everyone on the team gets the same review defaults.

**New user**

- As a new user, I want today's behavior preserved so the feature still "just works" out of the box without config.

**Edge cases**

- As a user with a typo in my default list, I want a clear warning that names an unknown reviewer rather than a silent skip.
- As a user whose configured reviewer is no longer installed, I want a clear note that it was dropped from this run.
- As a user who lists only reviewers that aren't installed, I want a clear error or a documented fallback (see Open Questions).

## Requirements

### Must-Have (P0)

- **P0-1. New config key.** `review.default_reviewers` is `string[]`. Each element validates against the existing slug pattern `^[a-zA-Z0-9_-]+$`. Schema parser accepts the key; rejects non-array or non-string-element values with a clear error. Empty array `[]` behavior is decided per Q-1.
- **P0-2. No-flag honors the key.** Given the key is `["gemini", "codex"]` and both are detected, running `/ecl-review` invokes only Gemini and Codex.
- **P0-3. Absent key preserves current behavior.** Given the key is unset, running `/ecl-review` runs every detected reviewer, identical to today.
- **P0-4. `--all` overrides the config.** Given the key is `["gemini"]`, running `/ecl-review --all` invokes every detected reviewer. Verbose mode shows which reviewers came from `--all` vs. the default.
- **P0-5. Individual flags override the config.** Given the key is `["gemini"]`, running `/ecl-review --cursor` invokes only Cursor. Running `/ecl-review --gemini --codex` invokes exactly those two regardless of the default.
- **P0-6. Graceful slug handling.** Unknown slug → start-of-run warning naming the offending slug; run continues with valid entries. Known slug but undetected → info-level note; run continues. Zero post-filter selections → error per Q-1.
- **P0-7. Docs updated.** `docs/CONFIGURATION.md` gets a `review.*` subsection (or an extension of an existing one) covering the new key. `workflows/review.md` references the key in the no-flag branch. Schema example at the top of `docs/CONFIGURATION.md` includes the key.
- **P0-8. Tests.** Unit and integration coverage per the test list in the ADR's **Tests expected to move with the seam** section.

### Nice-to-Have (P1)

- **P1-1.** `/ecl-config --integrations` extends to set `review.default_reviewers` interactively, aligning with the existing interactive config flow for reviewers.
- **P1-2.** `--no-default` flag that runs the full detected set without `--all` semantics — slightly different intent expression. Drop if equivalent to `--all` (Q-2).
- **P1-3.** Verbose-mode "selection source" line in `/ecl-review` output: `Running reviewers (default): gemini, codex (set in .planning/config.json)`.
- **P1-4.** Per-command override env var (`ECL_REVIEW_DEFAULT=...`) for CI scenarios where mutating `config.json` is undesirable.

### Future Considerations (P2)

- **P2-1.** Per-phase or per-file-type reviewer profiles (`review.profiles.frontend: ["claude", "cursor"]`). The shape of `default_reviewers` is deliberately chosen not to foreclose this — a future `review.profiles.*` map can coexist.
- **P2-2.** Reviewer "groups" or aliases (`review.groups.cheap = ["ollama", "gemini-flash"]`). Same — leave room under `review.*`.
- **P2-3.** Auto-suggestion that detects repeated flag patterns and offers to persist them. Natural follow-up but explicitly out of scope here.

## Behavior Specification

### Precedence (highest first)

1. Individual reviewer flags (`--gemini`, `--codex`, `--cursor`, …) — always win.
2. `--all` — full detected set, ignores config.
3. `review.default_reviewers` in config — subset, intersected with detected set.
4. No config, no flags — full detected set (today's behavior).

This matches the principle of least surprise: explicit user input (flags) always wins over persisted preference (config), and persisted preference only fills the gap when the user hasn't said anything else.

### Resolution pseudocode

```text
detected = detect_clis()                  # unchanged
if any individual flag passed:
    selected = flags_to_set(flags) ∩ detected
elif --all:
    selected = detected
elif config.review.default_reviewers is set:
    valid    = filter(config.review.default_reviewers, is_known_slug)
    # warn on each invalid slug
    selected = valid ∩ detected
    # info on each valid-but-undetected slug
    if selected is empty:
        error with actionable message      # see Q-1
else:
    selected = detected                    # today's behavior
```

### Validation

- Slug pattern: `^[a-zA-Z0-9_-]+$` (already in use for `review.models.<cli>`).
- Type: JSON array of strings; anything else → schema error at config load.
- Empty array: see Q-1 (proposed: schema error).
- Slug case normalization: lowercase-on-read.
- Duplicates: de-dup silently.

### Logging

- One line per `/ecl-review` start identifying the source of selection (default config / `--all` / explicit flags / no config). Surfaced under `--verbose` per Q-5.
- Slug warnings/infos as described in P0-6.

## Success Metrics

### Leading indicators (1–4 weeks post-release)

- **Adoption proxy.** Count of `.planning/config.json` files containing `review.default_reviewers` (only countable if/when eCL ever ships opt-in telemetry; otherwise qualitative via Discussions).
- **Issue echo.** Closure of `#3079` and zero new issues reporting the same wipe-on-update problem within 60 days.
- **Patch-removal proxy.** Maintainer observes no further PRs or Discussion threads about patching `workflows/review.md` defaults within 60 days.

### Lagging indicators (1–3 months post-release)

- **Median wall-clock per `/ecl-review`** on machines with ≥4 detected CLIs (self-reported or telemetry). Target: ≥40% reduction for users who opt in.
- **User-perceived signal-to-noise** on review output (qualitative; gather via GitHub Discussions or a single follow-up question on the issue).

### Measurement notes

eCL doesn't ship usage telemetry today. Most of these metrics rely on qualitative signal: issue activity, Discussion threads, and a follow-up on `#3079`. That's appropriate for a config addition of this size — we don't need a metrics pipeline to validate it.

## Edge Cases & Error Handling

- **Config key missing** → today's behavior (all detected).
- **Config key is `[]`** → schema error per proposed Q-1 resolution. Message: `review.default_reviewers is empty; remove the key to use the default-all behavior or list at least one reviewer.`
- **Config key contains an unknown slug** → warn, drop the unknown entry, continue with the rest.
- **Config key contains a known slug not detected on this host** → info, drop, continue.
- **Config key contains only undetected slugs** → error with actionable message: `All configured default reviewers are missing on this host: [...]. Install at least one, or pass --all / specific flags.`
- **Config key is malformed (e.g., string instead of array)** → schema error at config load, with file path and line number where the parser supports it.
- **Slug case sensitivity** → lowercase-normalize on read; document this.
- **Duplicates in the array** → de-dup silently.
- **User passes `--all` and individual flags together** → existing behavior preserved; this change does not alter that interaction. Confirm in tests.

## Open Questions

- **Q-1. Empty-array semantics.** Should `review.default_reviewers: []` be a schema error, or should it fall back to "all detected"? *Proposal: schema error.* **Blocking.** Affects schema validation and tests.
- **Q-2. `--no-default` flag.** Is this meaningfully different from `--all`? *Proposal: drop unless implementation surfaces a concrete difference.*
- **Q-3. `/ecl-config --integrations` integration.** Land in this pass or as a fast follow? *Proposal: fast follow; depends on contributor bandwidth.*
- **Q-4. Slug case handling.** Lowercase-on-read (proposed) or exact-match enforcement at the schema layer?
- **Q-5. Verbose-mode "selection source" line.** Always-on or only under `--verbose`? *Proposal: `--verbose` only.*
- **Q-6. Cross-runtime sanity.** Any cross-runtime concerns for Codex, OpenCode, Gemini CLI, or Kilo given the existing `resolve_model_ids: "omit"` pattern? *Proposal: none expected — the change operates on detection, not runtime; add at least one non-Claude integration test to confirm.*

## Rollout Plan

This is a small, additive, non-breaking change. No migration is required.

1. **Implementation** (one PR) — schema addition, resolution logic, unit + integration tests per P0-8, docs update per P0-7.
2. **Pre-release sanity** — dogfood on a multi-CLI setup; confirm `--all` and individual flags still behave.
3. **Release** in the next minor version (no semver-major bump needed — additive).
4. **Changelog & announcement** — call out in release notes; link to `#3079`; show the two-line config example.
5. **Monitor** `#3079` and any new issues mentioning "default reviewers" or "review.md patch" for 60 days.
6. **Optional fast follow** — P1-1 (`/ecl-config --integrations` integration) if there's contributor bandwidth.

## Timeline Considerations

- No hard deadlines. Quality-of-life fix, not contractual or compliance-driven.
- No dependencies on other in-flight work.
- Size: estimated ≤1 day of engineering for implementation + tests + docs.

## Out-of-Scope (Restated)

- No new commands.
- No new top-level config namespaces.
- No changes to `--all` or individual flag semantics.
- No reviewer-output aggregation changes.
- No per-phase reviewer profiles in v1 — the namespace is left open.
- No GUI/TUI editing of the key in v1.

## Appendix A: Example config

```json
{
  "review": {
    "default_reviewers": ["gemini", "codex"]
  }
}
```

With this config, `/ecl-review` invokes only Gemini and Codex. `/ecl-review --all` invokes every detected reviewer. `/ecl-review --cursor` invokes only Cursor.

## Appendix B: Glossary

- **Reviewer / CLI / backend.** Any code-review-capable CLI or model server eCL can invoke (Claude, Codex, Cursor, Gemini, OpenCode, Kilo, ollama, lm-studio, llama.cpp, …).
- **Detected set.** The list of reviewers `detect_clis` finds on the current host at review time.
- **Slug.** The lowercase short name of a reviewer used in flags and config (e.g., `gemini`, `codex`).
- **"Absent = enabled" pattern.** eCL's convention that missing config keys default to a sensible enabled state. Here, missing `review.default_reviewers` means "all detected."

## References

- Feature issue: `#3079`
- Configuration reference: `docs/CONFIGURATION.md` — `review.models.<cli>`, `review.*_host`, and the **absent = enabled** pattern
- Workflow file owning the no-flag branch: `workflows/review.md` (line 52)
- Companion ADR: `0011-review-default-reviewers.md`
