Imported from tz5514/zet-claude-code-settings (
skills/grill-with-docs/SKILL.md). Install upstream withnpx skills add tz5514/zet-claude-code-settings --skill grill-with-docs. Copyright stays with the author.
Before asking any questions
Load the domain context for this session:
- Check if
CONTEXT-MAP.mdexists at the repo root.- If yes: read it, identify which context matches the user's topic, read that context's
CONTEXT.mdand every file in itsdocs/adr/. - If no: check for a root
CONTEXT.mdand a rootdocs/adr/directory. Read both if they exist. - When you read a context's
docs/adr/, read it recursively, including theactive/andarchived/subfolders — ADRs live in those subfolders, not at thedocs/adr/root, so reading only the root would see an empty directory and start this round with an empty history baseline.
- If yes: read it, identify which context matches the user's topic, read that context's
- Existing terms and decided ADRs are your starting constraints. Every question you ask must respect them. If the user's plan conflicts with an existing term or ADR, surface the conflict as a question instead of silently ignoring the prior decision.
- Take the open snapshot. The ADR list you just read — across
active/andarchived/— is this round's historical baseline. Record it now. This is the dividing line the ADR-lifecycle discipline relies on (see<supporting-info>→ "ADR supersession lifecycle"): everything in the snapshot is a historical ADR (immutable); anything you create after it is a this-grill-round ADR (freely editable). The time reference for "this round" is the most recent/grill-with-docsinvocation, so that when one conversation runs several grill rounds, the previous round's ADRs are correctly treated as history for this round.
What you are doing in this interview
Your primary job is not to walk a checklist of topics. It is to surface the implicit assumptions — the things the user takes for granted and would not think to mention, but which shape the spec. The user is here to be interviewed, not to find questions; if you do not surface an assumption, it will not appear in the conversation, and the resulting PRD will silently bake in a guess.
After every answer, ask yourself before anything else: in what they just said, what did they assume without saying? What context did they presume you already share? What edge cases or trade-offs did they skip past because the answer felt obvious to them? Those are your next questions.
When you identify an assumption to surface, classify its source before framing it: does this follow directly from what the user has said, or are you projecting from your priors? Frame the second kind as a hypothesis to check, not as the user's assumption: "I'm guessing you're assuming X here — is that right, or have you been thinking about it differently?" Never present a projected assumption as a fact about the user's mental model.
This is the engine that drives the interview. The techniques in <supporting-info> (sharpening fuzzy language, edge-case scenarios, cross-referencing code) are supporting tools that help you execute it — not goals of their own.
Treat depth as obligation, not courtesy. Every additional layer of probing brings more of what would otherwise stay buried in the user's head into the conversation.
A note on word choice. When phrasing your questions and proposals, prefer plain language over jargon. Words like "canonical", "artifact", "kind", "digest", "verdict" feel precise to you but make the conversation harder to follow. Use the simpler word the user would naturally use, unless the technical term carries a meaning that no plain word captures. The user is the domain expert here — speak their language, not yours.
This goes deeper than vocabulary. Your default mode of explanation should be: precise terms from the project's shared vocabulary (the CONTEXT.md glossary) plus a concrete user story illustrating the actual flow. Not abstract noun phrases like "entity transition" or "partial failure semantics" — those are how you think, not how the user lives the system. A user story is: a real role, doing a real action, encountering a real situation, with a real outcome. "A customer hits cancel on an order that has already shipped" lands. "Handling state transitions with side effects under partial failure" does not.
Reach for cross-domain analogies only when the abstraction is genuinely hard to describe directly, or when the user has signalled multiple times they are not following. Default to staying inside the project's own vocabulary and flows — that almost always communicates effectively without leaving the domain. A misplaced analogy can simplify away the very detail that matters.
Why this matters structurally, not as a courtesy: the grill-and-PRD phase is the only phase where the human is fully in the loop before AFK execution begins. If the user does not understand what is being decided, the user is not participating in the decision. A decision the user did not participate in becomes a black box in the spec — and every downstream gate that checks against that spec carries that blindness forward. "Speak so the user understands" is therefore not a polite habit; it is what keeps the spec sound.
Asking each question
The principle behind this filter: every question you ask should carry genuine decision content the user cannot delegate — to general engineering knowledge, or to you. Volume of questions is not the concern; the concern is asking questions whose answers you already know or could derive, which dilutes the signal of the questions that genuinely need the user's input.
Before you put forward a point, issue, or recommended answer, ground its load-bearing premises (when unsure whether a premise is load-bearing, treat it as one) — not just the headline claim — against the most direct source available, in this priority order:
- Codebase or existing docs. This tier fires only when the premise actually connects to something the codebase implements, names, or constrains — it is not a per-premise step; trigger it on a real connection to existing code, not for every premise (the triggers are listed in
<supporting-info>under "Cross-reference with code"). When it does fire, the code is the most reliable ground truth for what the system actually does, and an answer built on what you remember is weaker than one built on what the system is. For a brand-new feature the codebase will often have nothing to offer — that is expected, not a failure; move to the next tier rather than hunting for code that does not exist. If a check would need so much code that your context risks saturation, delegate it to an Explore subagent with a focused brief and use only its summary. - The web. If (1) cannot settle it, or local evidence is too thin for high confidence, you must search for high-credibility references — but a pure design judgement, with nothing factual to verify, is exempt and goes straight to tier 3. Treat web lookup as a normal, expected move — not a last resort.
- Your own speculation or design judgement. Only when neither of the above yields solid support — whether because the fact could not be verified, or because the point is a design call with nothing factual to verify. Advance it, but say plainly that its confidence is low.
Your own training knowledge does not count as high-confidence evidence — there is no "I just know" escape hatch: if you believe something is a fact or best practice but the codebase does not show it, find a web source; if you cannot, it drops to low confidence. A premise resting on a checkable fact must be checked; a pure design judgement, with nothing factual to verify, is not web-searched — it is the tier-3 case above, advanced with low confidence. This is where you do not save tokens: the cost of a search is far smaller than the cost of a confident, wrong premise being ratified into the spec.
When you put the point to the user, attach its basis inline. If it has a source (tier 1 or 2), wrap the descriptive text in a markdown link to that source (a code location or a web reference) rather than printing the raw URL. If it has no source — a tier-3 judgement or unverified guess, which by definition has nothing to link — do not manufacture a link; instead say in words, plainly, that this is your own low-confidence call. Let the confidence you state follow the tier — codebase or web can be high, a tier-3 call is low — never a self-rated number. Keep solid-source tags quiet and the no-source / low-confidence flags loud, so your basis-tags concentrate the user's scrutiny on what is weak rather than dispersing it.
This grounding catches only premises you actually surface as claims; a premise you are so sure of that you never state it can still slip through, and that residual is for the user and any downstream review to catch — not something this step can guarantee.
Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each candidate question, form your own recommended answer in your head. Hold this explicitly as your prior — your view, not anything the user has said yet. You will need this distinction later when the engine asks you to classify whether an assumption you are about to surface comes from the user or from your priors. Then run this filter before deciding whether to ask:
-
Is the decision under this question architecture-level? A decision is architecture-level if it meets ALL three ADR criteria: (a) hard to reverse, (b) surprising without context, (c) the result of a real trade-off. If yes — or if you are not sure whether it qualifies — you must ask, regardless of whether you can guess a default answer. Architecture-level decisions need explicit user ratification because they shape every downstream question and are expensive to undo. When in doubt, treat as architecture-level.
-
Otherwise: can you answer this from general engineering knowledge or the existing codebase? If yes, decide it yourself, state your choice in passing so the user can object if it is wrong, do not ask. Only ask non-architectural questions where the answer depends on project-specific context, business logic edge cases, or user-specific preferences that general knowledge cannot supply.
Ask the questions one at a time, waiting for feedback on each question before continuing. If a question can be answered by exploring the codebase, explore the codebase instead.
After each answer — documentation gate
Before asking the next question, run this gate.
Ratification check first: the routing in steps 1–3 applies only to answers the user has explicitly ratified — phrases like "yes, decided", "let's go with that", "OK that's the answer", or any equivalent explicit ratification signal in the user's language. If the user is still discussing, weighing, or has not given a clear "this is the answer", skip the routing — the decision is not yet made. The pending-items step at the end always runs.
Route the ratified answer — don't just dump it. The failure mode this prevents: checking "is this a term?", then "is this a decision?", and when neither fires, falling silent — so an answer that belongs in neither document vanishes without the user noticing. Walk every ratified answer through all three outcomes below. The first two fire independently and can both apply (one answer can be both a term and a decision). The third fires only when neither of the first two did, and it is spoken, not silent.
-
CONTEXT.md — did this answer pin down, rename, or redefine a domain term? If yes, update
CONTEXT.mdnow, before the next question.CONTEXT.mdis a glossary only — no implementation details, no specs, no scratch notes. Use the format in CONTEXT-FORMAT.md. -
An ADR — did this answer make a decision that meets ALL three ADR criteria: (a) hard to reverse, (b) surprising without context, (c) the result of a real trade-off? If any criterion is missing, skip the ADR. Otherwise write it now, before the next question. Use the format in ADR-FORMAT.md — the fixed three-section body
## Background→## Decisions→## Rationale, with at least one id-bearing atom in## Decisions(a single-decision ADR still lists its one atom), since the atoms are what later supersession scans compare against. Record the decision's basis in## Rationale— which tier of evidence backed it (codebase, web), or that it was ratified on unverified judgement — so a decision resting on a weak premise leaves an inspectable trace for the user and any downstream review. Keep the atoms as the sole statement of what was decided;## Backgroundand## Rationalecarry only the problem and the why, and must never restate the atoms.This is the ADR-lifecycle hinge. Writing or modifying an ADR here is governed by the supersession-lifecycle discipline in
<supporting-info>→ "ADR supersession lifecycle". Two things fire at this point, every time, no exceptions:- Immutability guard. If the decision you are about to record changes a historical ADR (one in the open snapshot), you may not edit that old file's decision content. Route to writing a new ADR that supersedes it. The only writes ever allowed on a historical ADR are: change its frontmatter
status, and add asuperseded_bylink. (Archival relocation is a position-not-content change and is allowed — see the lifecycle section.) - Supersession scan. The moment you create or modify a this-grill-round ADR, run the supersession scan over the current context's active historical ADRs and emit the two output channels (routine line every time; supersession-alert table only when something was superseded). The scan, marking, channels, archival, rollback, and execution strategy are all specified in the lifecycle section — follow it exactly.
- Immutability guard. If the decision you are about to record changes a historical ADR (one in the open snapshot), you may not edit that old file's decision content. Route to writing a new ADR that supersedes it. The only writes ever allowed on a historical ADR are: change its frontmatter
-
Neither — did the answer match neither of the above? Then it does not get filed — but say so in one line, so the user sees the outcome rather than a silent skip: "This isn't going into CONTEXT.md or an ADR — it's [a team convention / a local detail / something the code already answers] — so it stays in the conversation. Record it elsewhere yourself if you want it kept." That spoken "not filing this" is the entire purpose of this outcome: it turns a decision that fits neither document from something that silently evaporates into something the user can see and override. CONTEXT.md and ADRs are the only documents this gate writes — do not invent a third file type to hold the leftover.
Then, always — whether or not the answer was ratified:
-
Pending architecture-level items — maintain the running list, re-orient. Scan back through the conversation: which architecture-level questions have been asked but not yet ratified? This list serves two purposes: (a) it orients your next question to the whole design tree, not just the local exchange — architectural items that block other questions take precedence, and a pending item that makes a sub-question premature should defer that sub-question; (b) it is the running record that the closing gate will require.
Before asking the next question, state your current map in one line — addressed to the user — showing the pending items and where the next question sits. Example: "Pending: auth boundary, persistence layer (waiting on DB selection). Next question continues the auth boundary thread, specifically session handling." This externalisation verifies your map is current and lets the user correct it if it has drifted.
Do not batch updates to CONTEXT.md or ADRs. A resolved term or decided trade-off that isn't written down now will be forgotten by the next session.
Before declaring done — top-priority re-pass
When you reach the moment of thinking "I have no more questions" — do not enter the closing gate yet. Run this re-pass first.
What to generate. Up to three highest-priority questions you would ask next if you could only ask three more. Rank candidates by how strongly the answer would lock in something hard to reverse, be surprising without context, or settle a real trade-off — the more architectural the candidate (the closer it sits to those three ADR criteria), the higher it ranks. This is a relative ordering, not a per-candidate checklist: you are using these dimensions as the ranking axis, not ticking each candidate against three boxes.
For each candidate, write three things:
- The question itself.
- Your recommended answer, with its basis-level labelled (which tier it would rest on, or that it is only a low-confidence guess) so the user can see which candidates are grounded versus guesses. You need not do the full grounding work (e.g. a web search) just to list a candidate — that happens if and when you actually ask it — but never hide that a candidate's answer is only a guess.
- A one-line justification of why this candidate's answer cannot be derived from the answers to the other two listed candidates. If you cannot write this justification, drop the candidate — it is a duplicate of one already listed, no matter how it surfaced in your head.
Rules.
- List at most three. If you can think of more than three real candidates, state out loud "I can think of N more candidates beyond these three" and continue with only the top three this round — do not raise the cap.
- If you cannot think of three, list only what you can — do not pad with filler to reach three.
- Listing a candidate is a commitment: you cannot retract by saying "actually never mind". If you listed it as top priority, it must be asked.
Show the user, in one block.
- The 0–3 candidates with their justifications and your recommended answers.
- A short note: "You can override the priorities, add a candidate I missed, or say 'enough, move on' to skip to closing."
Outcomes.
- Zero candidates → proceed to the closing gate.
- One to three candidates → ask them one by one (respecting the existing one-question-at-a-time rule). After the last is ratified, run this re-pass again — not the closing gate. Each round may resolve some pending items but also surface new sub-branches from the answers.
- User says "enough, move on" → proceed to the closing gate immediately, regardless of how many candidates were listed. The user's call is the deciding signal; the closing gate then handles any unresolved pending items the normal way.
The user is opt-in, not on hook. Silence from the user on the candidate block means "go ahead, ask them" — the user is not required to explicitly approve every round. Only an explicit override (priority change, addition, or "enough, move on") changes the default flow.
Multi-round behaviour, and the handoff this re-pass does not catch.
- This re-pass does not converge on its own. Each round resolves up to three pending items but answers may open new sub-branches, triggering another round. Convergence comes from the user calling "enough, move on", not from candidates exhausting themselves. This is by design: the cost of one more round of questions (the user can say "enough") is far smaller than the cost of stopping too early (a missed question vanishes silently into AFK execution).
- What this re-pass catches (agent self-disclosure can expose it). The "I am thinking of more but talked myself out of it" failure — you can think of the candidate, you just rationalised it away. When forced to list top three, you can produce it. This is the failure mode this re-pass exists for.
- What this re-pass cannot catch (agent self-disclosure cannot expose it). Two failures share this property despite different origins: (a) blind spots — candidates you genuinely cannot think of, which by definition will not appear in your list; (b) padding — candidates listed insincerely just to pass the gate, where you know they are weak but will not admit it. Both fail in your self-report and cannot be caught from within. They route to a downstream independent reviewer with a different vantage. The user reading the candidate block is the first line of defence against padding (a padded candidate is visible to them, though catching it depends on their familiarity and attention); the downstream independent review is the second.
Closing the grill — pending-items gate
After the top-priority re-pass concludes (outcome 1: zero candidates, or outcome 3: user said "enough, move on"), you may not end the session yet. Run this gate:
- Catch the last answer. Run the documentation gate against the user's most recent ratified answer — the same gate you would have run before asking the next question. The closing gate is the only opportunity this answer has to be filed; if you skip this step it falls through.
- List every architecture-level question that was asked but never ratified — items where the user discussed but did not give a clear "this is the answer" signal.
- Present this list to the user. For each item, the user must either ratify a decision now (in which case the documentation gate runs immediately on that item), or explicitly say "leave this unresolved, I will decide later".
- Only after every item has been either ratified or explicitly deferred may the session end.
You cannot bypass this gate by declaring the conversation done. The user did not ask any of these architecture-level questions — you did. The responsibility to confirm they have all been put to rest, one way or another, is yours.
ADR supersession lifecycle
This is the discipline the documentation gate's ADR step hinges on. It keeps the ADR set a trustworthy, navigable chain of decision evolution: history is never rewritten, supersession is auto-marked and surfaced for review, and fully-superseded decisions are archived out of the scan set. ADR-FORMAT.md is the template; this section is the behaviour — when and how you identify, guard, scan, mark, output, archive, and roll back.
Historical vs this-grill-round — the open snapshot
- The open snapshot is the dividing line. The ADR list read when loading context at the start of this round (recursively across
active/andarchived/) is this round's historical baseline. In the snapshot ⇒ historical ADR. Created after it ⇒ this-grill-round ADR. - Time reference = the most recent
/grill-with-docsinvocation. When one conversation runs several grill rounds, the previous round's ADRs are correctly treated as history for this round. - Both folders are history. For the historical-vs-this-round split,
active/andarchived/ADRs both count as historical (both immutable). The death/alive split (active/vsarchived/) is a sub-classification of history that governs only what the scan iterates — it is not a second axis orthogonal to identification. - Safe default: when unsure whether an ADR is this-round or historical, treat it as historical (immutable; to change it, open a new file). The cost is asymmetric: misjudging a this-round ADR as historical at worst makes you open one extra ADR; misjudging a historical ADR as this-round would let you rewrite real history. So err toward historical.
- Deliberately not used for the split, each for a reason:
- file mtime — gets polluted by the allowed status writes (changing
status/ addingsuperseded_bytouches the file without changing the decision). - git-untracked status alone — across rounds that aren't committed, a real historical ADR can look untracked and be misjudged.
- memory — context compaction loses it.
- file mtime — gets polluted by the allowed status writes (changing
- Residual risk is covered downstream. If the snapshot doesn't survive compaction and a this-round ADR is misjudged as historical, the only effect is that the file can't be edited directly — but the correct way to change a decision is to write a new ADR superseding it anyway, and the supersession scan (below) will mark it when it is in fact superseded. Same result, no real harm.
Immutability guard
- On a historical ADR, exactly two content writes are allowed: (a) change frontmatter
status, (b) add asuperseded_bylink pointing at the new ADR that supersedes it. The decision content — the## Decisionsatoms and the decision-meaning of the prose — must never be modified. - The file's only other change is position, not content: when fully superseded, the whole file is relocated to
archived/(see Archival). This is explicitly defined as a non-content write and does not violate immutability — relocation and tampering are not the same thing. - When the user (or you) wants to change an old decision, the guard routes the behaviour to writing a new ADR that supersedes it, not editing the old file.
Supersession scan
- Trigger: every time a this-grill-round ADR is created OR modified — no exceptions. Do not give yourself the discretion to decide "this edit probably didn't affect any supersession relationship and skip" — that guess is exactly how a broken chain slips through. The scan is part of the documentation gate's ADR step.
- Scope: the current context's active historical ADRs only. Not across contexts. Not
archived/(a fully-superseded decision can't be superseded again). Archiving a file therefore retires it from all future scans. - Judgement is atom-against-atom. Take the atoms of the new/modified ADR and compare them against the already-recorded atoms of each candidate active historical ADR (the
## Decisionsblock). The method is decision-level NLI (natural-language inference) + atom decomposition: for each old atom, judge whether the new decision replaces/negates it.- You must go re-read X's recorded atoms before concluding "this supersedes X". Do not judge from memory, and do not re-interpret X's prose at scan time — re-reading a degraded long context at scan time is the largest error source; the atoms were decomposed cleanly at write time and the scan only compares against them.
- Aggregate: every atom of the old file replaced ⇒ fully superseded; some replaced ⇒ partially superseded; none replaced ⇒ not a supersession (a mere reference/extension does not count).
- Asymmetric-cost defaults:
- Lean toward recall on topically-related candidates — a miss is silent (no alert, chain breaks, worst outcome); a false positive gets caught at the alert table.
- When unsure whether it's full or partial, mark partial — misjudging full would make a downstream LLM treat the whole record as worthless and skip atoms that are still in force.
- Low-confidence judgements are flagged loudly in the alert table for priority review. (Confidence here = how clear-cut the atom comparison is — an obvious negation is high, semantic ambiguity is low — not a self-rated number.)
Auto-marking (bidirectional frontmatter)
- A judged supersession is auto-marked immediately — you do not wait for the user to approve it atom by atom. Write both sides:
- Old (superseded) file:
superseded_by, listed per atom as{ this, by }(every replaced atom of the old file, whether full or partial). - New (superseding) file:
supersedes, a back-pointer only.
- Old (superseded) file:
- Set the old file's
statusby how much was superseded: all atoms ⇒fully_superseded(⇒ this immediately triggers archival relocation — see Archival below; do not stop at the frontmatter write); some ⇒partially_superseded. The aggregatestatusflag must stay consistent with the per-atom list. - The schema holds multiple relationships: one ADR can be superseded by many; one new ADR can supersede many in a single round.
Two output channels
Auto-marking's only safety net is "the user sees it and rolls back what they disagree with" — and the user only rolls back what they actually see. So split the output into two independent channels to keep the signal high:
- Routine line (every time): one line — clear but short: what was written, that the scan ran, and the result (e.g. "Scanned vs N active historical ADRs → no supersession"). Its job is to prove the scan ran.
- Supersession alert (only when something was actually superseded): a table / bullet list, one row = one superseded historical ADR, carrying which atoms were marked, the basis (the re-read atoms), and the link — surfaced for the user to review, not buried in prose. Flag low-confidence judgements loudly here.
- Why two channels: printing the full table every time (mostly "no supersession") trains alert fatigue — the user learns to skim and then misses the rare real one. Compress the proof to one line; let the table speak only on the rare supersession, so the table's appearance is itself a high signal. This still honours the evidence discipline (ADR 0001): the judgement is not silent — it is laid out for review, with review coming after marking.
Archival
- The instant a file is marked
fully_superseded, relocate it fromactive/toarchived/(move only — don't delete, don't touch content; compatible with immutability). - A partially superseded file stays in
active/(it still has live atoms and can still be superseded); it moves only later, when the remaining live atoms are also superseded and the whole thing becomesfully_superseded. - Relocation does not break links: links use the stable number/filename and resolve across both folders (ADR-FORMAT.md).
- The archival action is listed in the supersession-alert table for review and is reversible via rollback.
Rollback — symmetric reversal
The human side of the "auto-mark + human review" safety valve. When the user reviews the alert table and rejects one auto-marking, they can ask to roll it back. The reversal must be symmetric — one supersession relationship touches two files (old + superseding new), and both sides are reversed together so no one-directional dangling link is left.
- Granularity = one whole ADR-to-ADR relationship (one
superseded_byentry with all its per-atom{ this, by }rows), reversed as a unit. Atom-level partial rollback (un-marking only some atoms within one relationship) is out of scope — do not add it. - The three symmetric actions for the rejected relationship:
- Old file: remove that
superseded_byentry; recomputestatusby the aggregate rule over the remaining supersession relationships — remaining ones still cover all atoms ⇒fully_superseded; cover only some ⇒partially_superseded; none remain ⇒accepted. (Same aggregate-consistency rule as marking.) - Superseding new file: remove the corresponding
supersedesback-pointer. - Archival reversal: if removing this relationship means the old file is no longer
fully_superseded, relocate it fromarchived/back toactive/.
- Old file: remove that
Execution strategy — scale, parallelism, model
How the scan runs depends on the size of the set of active historical ADRs to scan, but two bottom lines hold regardless of scale. This layer decides only how to execute; the judgement and marking logic above are unchanged (a delegated sub-agent scans read-only; marking is always done by the main agent; output always goes through the two channels).
- Small set (active historical ADRs are few — fully-superseded ones have been archived out of the scan): the main agent scans inline, synchronously, reading atoms and comparing. Fast, accurate, no startup cost — don't use a sledgehammer.
- Large set (many enough that inline isn't worth it): parallel synchronous delegation — split the set into chunks, fan out multiple sub-agents at once (each scanning one chunk), and the main agent blocks until all have reported back (join). Wall-clock ≈ the slowest chunk, not the sum of chunks. Delegated sub-agents use a lightweight fast model (e.g. Haiku) for read-only scanning and report findings; the main agent does the marking after the join.
- Delegation-threshold criterion (the shape is mandated; the concrete value is not): the threshold must be judged by a mechanically-measurable count/volume — number of active historical ADRs, total word/token count, and the like. It is forbidden to gate on the agent's self-assessed context usage — a CC / Codex agent cannot query its own context consumption and would measure it wrong. The concrete cutoffs (the count/volume boundary, how many chunks past the threshold) are not fixed here; they are landing parameters the implementer tunes to each context's ADR volume and the current model's context capacity. This spec fixes only the criterion shape: a mechanically-measurable quantity, never self-assessed context.
- Two bottom lines (scale-independent, always hold):
- Never background (asynchronous). A background task would suddenly inject its report when the user has moved on to something else, breaking the rhythm; a synchronous scan lands the result at the coherent checkpoint of "just finished writing this ADR". Shorten the pause with parallelism, not by backgrounding.
- Never silent. Auto-marking's safety valve is "the user sees it and can roll back"; a silent write tears the valve out. The scan always goes through the two output channels — never reduced to a silent, fully-automatic background pass to save effort.
Questioning techniques
These are tools for executing the main task — surfacing what the user assumed but did not say. Use whichever helps reveal an implicit assumption in the moment. Do not run them as a checklist of their own.
Sharpen fuzzy language
When the user uses vague or overloaded terms, propose a precise canonical term. "You're saying 'account' — do you mean the Customer or the User? Those are different things." Fuzzy language is often where an implicit assumption hides — the user has one specific meaning in mind but uses a word that has several.
When you propose a canonical term, hold it to two standards:
-
Domain-specific, not generic. Avoid overloaded technical words that mean too many things across software in general — "dispatch", "handler", "processor", "manager", "service" — when used as a domain action or entity name. They lose all specificity inside this project's vocabulary. Prefer the word a domain expert would naturally say.
-
Length serves clarity, not brevity. A good compound name encodes structured domain information so the reader does not need to look elsewhere to understand it. For an entity, encode the entity plus any qualifying state or kind ("ShippedOrder" beats "Order" when shipped and unshipped behave differently). For an action, encode the entity plus the change plus any qualifying condition ("CancelShippedOrder" beats "cancel" when the domain has multiple cancellation flows).
The test name
test_user_cannot_cancel_shipped_orderis the model. Decompose it: the actor (user), the action (cannot cancel), the entity (order), the state (shipped). Four pieces of domain information, all in the name itself — reading the name alone tells you what behaviour is being tested. Apply the same decomposition lens to any compound term you propose: what pieces of domain information should this name carry, and which become invisible if you shorten it? The point of this lens is not naming as an end in itself — it is that a bad name often hides multiple implicit assumptions (which flow does "cancel" refer to? which entity state?), and decomposing forces those assumptions into the open where you can ask about them.
If you cannot meet both standards, ask the user for their own terminology rather than offering a generic name. A bad proposal anchors the conversation toward bad naming and is worse than no proposal.
Discuss concrete scenarios
When domain relationships are being discussed, stress-test them with specific scenarios. Invent scenarios that probe edge cases and force the user to be precise about the boundaries between concepts. Useful when the user has described a happy path and implicitly assumed certain edge cases do not matter — surface those edge cases and make the assumption explicit.
Cross-reference with code
The code is your most reliable source of ground truth for what the system actually does right now. The user can misremember, CONTEXT.md can be stale, but the code is what is running. When user statements about system behaviour conflict with the code, the code wins as the record of what the system actually does — though "the code is what runs" doesn't mean "the code is what was decided"; sometimes the code is stale relative to a fresh decision (see the rename case below).
A separate authority applies for shared vocabulary, not system behaviour: when the user uses a term that conflicts with the ratified definition in CONTEXT.md, CONTEXT.md wins — the user may have used an unratified or outdated word. Don't silently translate; surface it: "You said X, but the ratified term in CONTEXT.md is Y — do you mean Y, or do we need to revisit this term?"
There are three trigger situations where you must cross-reference:
-
The user states how something currently works. Check whether the code agrees. If you find a contradiction, surface it: "Your code cancels entire Orders, but you just said partial cancellation is possible — is this a misremembered description, a new behaviour you want, or an existing bug?"
-
A CONTEXT.md term is being renamed or redefined. Check whether the codebase still uses the old name. If it does, the rename produces a downstream code-sync obligation — see the next paragraph.
-
You are about to write a new term to CONTEXT.md that describes a concept already present in the codebase. Check what the codebase already calls it. If the existing code uses a different name for the same concept, surface the divergence: "The code currently calls this
Foo— are we renaming toBar, or is this a different concept that just sounds similar?"
When you detect that a ratified rename in CONTEXT.md has corresponding occurrences in the existing codebase, surface this to the user explicitly. State it as a discrete codebase refactoring task that must be carried into the PRD: "This rename produces a refactoring task: rename {old} → {new} across the codebase (currently appears in {files or count}). This task should be tracked as a deliverable in the PRD." Do not start the code change yourself — grill is a planning phase, not an implementation phase. Confirm the user is aware the obligation will flow downstream.
When the cross-reference reveals the user's mental model differs from what the code actually does, the divergence is itself an implicit assumption being exposed — surface it for the user to resolve, don't quietly accept it and move on.