From 9eb69346ceaa5de6ce0a754130c7f11b42e78940 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Wed, 16 Sep 2026 20:39:15 +0200 Subject: [PATCH] docs(spec): design for the ask-don't-guess clarification doctrine --- .../specs/2026-09-16-ask-dont-guess-design.md | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-16-ask-dont-guess-design.md diff --git a/docs/superpowers/specs/2026-09-16-ask-dont-guess-design.md b/docs/superpowers/specs/2026-09-16-ask-dont-guess-design.md new file mode 100644 index 0000000..97143cd --- /dev/null +++ b/docs/superpowers/specs/2026-09-16-ask-dont-guess-design.md @@ -0,0 +1,221 @@ +# Ask, don't guess: clarification doctrine for the orchestrators + +Date: 2026-09-16. Branch: `feature/ask-dont-guess`. Status: draft for user review. + +## Problem + +The orchestrators (`/ship-feature`, `/feat`, `/bugfix`, `/hotfix`, +`/init-project`) settle choices the user never made. Reported symptom: "add a +share icon" ships with the icon wherever the executor put it; nobody asked +left or right. + +Two causes, both written in the doctrine: + +1. `lib/contract-interview.md` STEP 2 asks a question only when a testable + outcome, a scope, or a non-contradictory constraint is missing. A request + can pass all three and still leave every visible choice open. Taste is + invisible to the trigger, so raising the 3-question cap would change + nothing. +2. When an executor halts with `NEED-DECISION`, `skills/feat/SKILL.md:153` + and `skills/bugfix/SKILL.md:165` instruct the orchestrator to "make the + decision HERE", twice, before escalating. The question reaches the user + after two guesses. + +The parent rule both inherit is `CLAUDE.global.md:51`: "One question upfront +if needed — don't interrupt mid-task." + +## Decisions taken with the user (2026-09-15/16) + +- The global rule changes, for all work, skill or not. `/hotfix` included. +- Three classes of open choice trigger a question: VISIBLE (placement, label, + wording, color, order, click behavior), PUBLIC NAME (command, flag, + endpoint, env var, file the user reads), SCOPE (should X change too, X + unnamed in the request). Class 4, internal technical choices with no + observable effect, never triggers one. +- Questions are asked when they arise, mid-task included, batched when + possible. + +## Design + +### 1. Parent rule, `CLAUDE.global.md` + +Line 51 becomes: + + - Ask rather than guess. A choice visible in the result (placement, + wording, order, behavior), a name that becomes public (command, flag, + endpoint, file), or a scope the request does not settle → ask, even + mid-task. Batch what can be batched. Internal technical choices with + no observable effect stay yours. + *Exception: skill-mandated gates and checkpoints (orchestrator + validation gates, approval gates, darwin checkpoints) always fire.* + +The exception line is kept as is. + +The rule "Bug received → fix directly: check logs, find root cause, resolve +autonomously." gains "; a visible choice in the fix still gets asked" so the +two lines do not contradict each other. `link.sh:20` symlinks this file to +`~/.claude/CLAUDE.md`; no other copy exists. + +### 2. Shared trigger, `lib/contract-interview.md` STEP 2 + +STEP 2 is renamed CLARIFY and split into two passes. Pass A keeps the three +existing gap checks and runs at contract time, against the request. Pass B is +the open-choice sweep: defined here, run once per flow at the PLAN step +(section 3), against the plan just written, because that is where a visible +choice becomes concrete. Replacement text: + + ## STEP 2 — CLARIFY (ask, never guess) + + Two passes, both in the main loop, both may talk to the human. + + **Pass A — gaps.** Run here, against the request. Ask if one of these is + missing AND not derivable from the repo: + - a testable expected outcome + - an unambiguous scope (what is allowed to change) + - non-contradictory constraints + + **Pass B — open choices.** Defined here, run ONCE at the flow's PLAN step + (see "Where pass B fires"), against the plan just written — that is + where choices become concrete. Enumerate every choice the run will + settle that the request leaves open; keep those in these classes: + 1. VISIBLE — the user would see it in the result: placement, label, + wording, color, order, what a click does. + 2. PUBLIC NAME — a name that outlives the run: command, flag, endpoint, + env var, a file the human will read. + 3. SCOPE — "should X change too?", where the request does not name X. + + NEVER ask class 4 — internal technical choices with no observable + effect (function decomposition, data shape, local naming, layout inside + an already-scoped zone). Those are delegated; asking them is the noise + that makes classes 1-3 ignorable. Never ask what the repo or the + request already answers — verify paths/APIs/behavior yourself first. + + No question cap. Each pass asks what it finds, in ONE batch. A request + that leaves nothing open goes through silently. More than 5 open + choices in pass B = the request is under-specified: list them, say so, + stop — do not fire a questionnaire. "You decide" / "peu importe" is an + answer: record it as `A: delegated — ` and never re-ask. + + Pass B answers land in the contract's CLARIFICATIONS marked + `[gated ]` — the contract is already on disk by then. + +Two new sections follow STEP 4 in the same file: + + ## MID-RUN CLARIFICATION (the channel executors halt into) + + An executor cannot talk to the human. It halts with `NEED-DECISION`, + the exact question, the options it sees, and a `CLASS:` tag (visible | + public-name | scope | internal). `/hotfix`: the hotfixer keeps + `DONE | BLOCKED`; a BLOCKED carrying the tag follows the same routing + instead of escalating to `/bugfix`. The orchestrator re-reads the + class — the tag is a hint, not a verdict — then routes: + - visible / public-name / scope → ASK THE HUMAN, verbatim question and + options. Never decide these yourself, never spend a round-trip + guessing. + - internal → decide here, note the decision, re-dispatch. The only case + the orchestrator settles alone; max 2 such round-trips → escalate. + + Every answer, human or orchestrator, appends to the contract's + CLARIFICATIONS marked `[gated ]` — the same micro-gate as + scope enrichment — and to the plan handed to the FRESH re-dispatched + executor, which reads the decision from disk, never from a transcript. + + ## HOW TO ASK (LRN-102) + + The harness reliably renders only the turn's FINAL text; text printed + before a tool call may be swallowed. So: + - up to 4 questions → one `AskUserQuestion` call; option descriptions + carry the context; print nothing the user needs before the call. + - more than 4, or a list handed back for re-specification → plain + text, end the turn. + +The per-flow weight table row for hotfix changes from "Zero questions ever" +to "Pass A silent autofill. Pass B runs at LOCATE against the 1-2 target +files' visible effect; a typo fix asks nothing." + +### 3. Where pass B fires, per flow + +| Flow | Pass B runs at | Against | +|---|---|---| +| feat | STEP 1 PLAN, before 1b CHALLENGE | the PLAN checklist | +| bugfix | STEP 3 FIX PLAN | the FIX PLAN | +| hotfix | STEP 1 LOCATE | the 1-2 target files' visible effect | +| ship-feature | STEP 2 PLAN, after the brainstorm | the plan, minus what the brainstorm already settled (in CLARIFICATIONS) | +| init-project | STEP 3 DESIGN, before VALIDATION GATE #1 | the DESIGN, minus what the interview and brainstorm settled | +| onboard | unchanged | its STEP 3 interview already asks scope in one block; the global rule covers leftovers | + +Each listed step gains one line: "Run pass B of +`$HOME/.claude/lib/contract-interview.md` against this plan; ask the batch +before continuing." + +### 4. Per-flow edits + +- `skills/feat/SKILL.md`: STEP 1's "If the approach is ambiguous: ask the + user ONE focused question BEFORE dispatching — never after (the executor + cannot relay questions)" is replaced by the pass B line. STEP 3's + `NEED-DECISION` handling is replaced by a pointer to MID-RUN CLARIFICATION. + STEP 1b's "surfacing any deferred BLOCKER via STEP 1's one-question gate" + is repointed to the pass B batch. +- `skills/bugfix/SKILL.md`: STEP 3 gains the pass B line; STEP 5's + `NEED-DECISION` handling is replaced by the pointer. The RULES line + "re-dispatched FRESH on every round-trip (NEED-DECISION, …)" stays true. +- `skills/hotfix/SKILL.md`: STEP 1 gains the pass B line. STEP 1.7's + "**zero questions ever**" becomes "pass A silent autofill; pass B was + asked at STEP 1". The hotfixer keeps `DONE | BLOCKED` and its + revert-not-loop identity; STEP 4 relays a tagged BLOCKED as a question + instead of escalating to `/bugfix`. +- `skills/ship-feature/SKILL.md`: STEP 2 PLAN gains the pass B line. +- `skills/init-project/SKILL.md`: line 62's "No new questions (the interview + already asked)" becomes "Pass A is covered by the interview; pass B runs + at STEP 3 against the DESIGN". STEP 3 gains the pass B line. +- `agents/interviewer.md`: the FAILURE MODES rows and the 2-round budget + keep working for gaps. A class 1-3 item still open after round 2 gets ONE + more targeted question; it never lands in OPEN DECISIONS as `(assumed)`. + The DO NOT entry "Exceed the 2-round budget" gains "except the one + targeted class 1-3 question". +- `agents/feater.md`, `agents/bugfixer.md`: the halt trigger "A plan hole or + an open choice (naming, data shape, API surface, dependency)" gains "a + user-visible choice (placement, wording, behavior)". The NOTES grammar for + `NEED-DECISION` gains `CLASS: visible | public-name | scope | internal`. +- `agents/hotfixer.md`: the NOTES grammar for BLOCKED gains the same + `CLASS:` tag when the blocker is an open choice. + +### 5. Locks and tests + +`lib/tests/contract-verifier.test.sh`: drop `"silent when complete" "ZERO +questions"` and `"question budget" "max 3 questions"`. Add locks on: +`"goes through silently"`, `"No question cap"`, `"PUBLIC NAME"`, `"NEVER ask +class 4"`, `"More than 5 open choices"`, `"delegated —"`, `"## MID-RUN +CLARIFICATION"`, `"CLASS:"`, `"## HOW TO ASK"`. + +`lib/tests/loops-light.test.sh:84`: `"hotfix zero questions" "questions +ever"` becomes `"hotfix pass B at locate" "pass B"`. + +`lib/tests/gates.test.sh` locks on `contract-interview.md` (oracle doctrine) +are untouched; the ORACLES section does not move. + +Behavioral check, run once by hand after the edits, in a fixture repo: +`/feat "add a share icon to the header"` must ask placement before +dispatching; `/feat "add a share icon at the right end of the header, label +Share, opens the native share sheet"` must ask nothing. + +### 6. Out of scope + +- `README.md`, `USAGE.md`, `ARCHITECTURE.md`: none mentions the question + doctrine (grep 2026-09-16). A `/doc` pass after merge covers any flow + description that drifts. +- `CHANGELOG.md` entry and registries (a BDR superseding the "one question + upfront" rule) happen at the CAPITALIZE step, not in this spec. +- TODO.md line 199 (C2 self-contradiction audit of `CLAUDE.global.md`) stays + open; this spec fixes only the contradiction it exposes. + +### 7. Risks + +- Chattiness. Three classes, a mid-run channel, no cap. The class 4 + exclusion and the over-5 guard are the two brakes. Watch in real use; + LRN-047 records that a frequent ignored nag is itself a risk. +- `/hotfix` identity. Its value is speed and silence. Pass B at LOCATE adds + one possible batch before touching anything. If it fires on most + hotfixes, the class definitions are too wide, not the flow. +- Executor mis-tagging. A class 4 tagged as visible offloads a decision to + the user. The orchestrator re-reads the class; the tag is a hint.