docs(spec): design for the ask-don't-guess clarification doctrine
This commit is contained in:
@@ -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 — <default taken>` and never re-ask.
|
||||||
|
|
||||||
|
Pass B answers land in the contract's CLARIFICATIONS marked
|
||||||
|
`[gated <YYYY-MM-DD>]` — 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 <YYYY-MM-DD>]` — 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.
|
||||||
Reference in New Issue
Block a user