Development record
Every question abcd asks passes its check before the person sees it
Summary
Delivers itd-2610230756259215, which resolves iss-2610230745597968 and iss-2610301855200679. One decision function in internal/core/question judges a question as the guard hook judges it today, and two callers share it: The hook, and a new read-only sub-verb, abcd guard question, that reports every fault grouped by rule and tab, the rows of each tab, what the hook would do with the draft in this checkout now, the binary's version, and the input ready to ask. The hook's refusal loses its ten-part cut and names every rule on every tab it hits, in the words the verb shows, with one line pointing at the verb and the question-writing agent. A question is abcd's only when a header is in abcd's number grammar (Q1, Q1a, Q2/5), whatever the status line says; the older role-word form (Product Q2) is still read as abcd's. The abcd:question-drafter agent becomes abcd's question-writing agent: It writes, runs the verb, fixes every fault, and checks again, at most five checks to a part, splits what does not fit into numbered parts, and returns only a draft the verb exits 0 on, or a refusal to the agent that called it. The GRILL text and the generated pages say so, and the brief's invariant 1 binds to every question carrying abcd's header. Three recorded labs are the evidence for the criteria that rest on a live model.
Grounding, read on 30 October 2026:
- The hook's whole question path is
internal/surface/cli/guard_question.go.questionGate(lines 89 to 112) reads the away record first throughawayAtQuestion(lines 119 to 132,autonomy.AwayAt, failing open onautonomy.ErrUnsafe).presentQuestionGate(lines 157 to 238) decodes the host input withdecodeQuestions(lines 323 to 340), reads the mode only where the badge shows (ahoy.Managedandmode.HasTier, lines 166 to 173), decides whether the question is abcd's by a chip or by a mode naming somebody (lines 174 to 186), runsquestion.CheckLimits(lines 195 to 198), refuses unless the findings are rows only (lines 203 to 211), then writes the open marker and the mode (lines 212 to 228) and the rows note (line 233). The decision and the writes are interleaved in one function; nothing else can call the decision. - The refusal is
writeLimitsRefusal(lines 410 to 439): One head line counting the refusing parts, themodeMadeAbcdsline (lines 387 to 394) for a question abcd's only through the mode, then at mostmaxRefusalParts(10, line 380) finding lines and an "and N more" line, then the rows findings underrowsNotRefusingLine.rowsNote(lines 284 to 295) is bounded the same way and names the drafter. - The check is
question.CheckLimits(internal/core/question/check.golines 123 to 142), fourteen rules. The header grammar ischipRe(line 113): A role word fromLimits.ChipRoles, a space,Q, a number, and an optional/total;ChipRole(lines 90 to 96) reads the role word, and the register rule falls back to it when no mode names anyone (forProductThinker, lines 387 to 396). The rows rule computes a per-tab split (line 334,splitRowsinrows.golines 25 to 36, unexported) that only the rows finding's text carries. - Rule 12 adds one finding per matching line of question text (check.go lines 324 to 329), so the findings grow with the payload, which is why the ten-part cut exists; past the counts the check reads only the first four tabs and four options (lines 138, 211).
- The structured question type is
question.Ask(ask.golines 14 to 44);Check(lines 98 to 193) is its structural check andFields(lines 209 to 243) composes the host's field view, leaving out aNow:orChange later:line the question does not carry (lines 216 to 225) so the limits check names the gap. - The asking rules are
question.AskingRules(asking.golines 35 to 95): The layout rule names the chip form (lines 51 to 61), the drafter rule (lines 63 to 66), and the chip-sets-the-mode rule (lines 93 to 94).internal/core/rules/grill.golines 25 to 31 generate the GRILL domain from them, andcmd/asking-sync/main.go(targets, lines 59 to 63;renderDrafter, lines 86 to 101) writes them intocommands/intent.mdandagents/question-drafter.md(make asking-sync,make asking-sync-check, Makefile lines 280 to 284). - The drafter page (
agents/question-drafter.md) returnsquestions,rowsandremaining(lines 45 to 76), refuses only a missing or contradictory part (lines 39 to 41, 77 to 78), and counts rows by hand from the generated block (lines 107 to 115). - The hook reads its payload under
maxHookStdinBytes(1 MiB,internal/surface/cli/cli.goline 1622;guard.golines 354 to 360) and dispatches the question tool atguard.golines 382 to 384. The guard's sub-verbs register atguard.golines 199 to 203.guard checkexits 0 on allow and warn, 1 on block, 2 when it cannot answer (brief17-guard.mdlines 51 to 80). - The binary's version is
core.Version(internal/core/core.goline 18). The mode store ismode.ReadAtand the markermode.MarkQuestionOpen(internal/core/mode/question.golines 56 to 73). The lab store is~/.abcd.noindex/lab/<root-sha>/<lab-id>/with one probe record per probe (internal/core/lab/lab.golines 1 to 28). - The brief's guard chapter carries a machine-checked sub-verbs table (
17-guard.mdlines 19 to 34,surface_coverageagainst.abcd/development/release/surface.json,internal/core/lint/config.golines 186 to 191) and the question gate section (lines 199 to 323). Invariant 1 is02-constraints/03-invariants.mdline 9.roles_vocabulary_test.golines 112 to 125 accepts a chip as a page's way of naming a question's addressee. - One fixture exists:
internal/surface/cli/testdata/questions/routing-2026-10-03.jsonwith its findings file.
Approach
- The decision function.
question.Decide(f Fields, s State, l Limits) Verdictis lifted out ofpresentQuestionGate.Stateis what the hook reads before it decides: Whether the person is away and whether that record was read, the mode's person and whether the store was read, whether the badge shows, and the binary's verbs.Verdictcarries the outcome, the findings, the per-tab addressee with anaddressee_changesnote where an older-form chip would re-set the mode (decision 15), the rows split of every tab, whether the result is partial and which stores and rules it could not apply, and whether the verdict depends on state (decision 5). The outcomes are decision 3's:admit,admit-with-rows-note,refuse,refuse-away,admit-unchecked, andunchecked. Order is the hook's today: Away first (refuse-awaybefore any limit), then the header test, then the limits.admit-uncheckedis any call with no header in abcd's grammar, in every mode state, since decision 36 supersedes the parenthesis in decision 3.uncheckedis each fail-open path: An away record or mode store that could not be read where the rules needed it, markedpartialwithunreadnaming the store and the rules it decides (the register rule for the mode, every rule for the away record). The rows split is exported asquestion.Rows(t Tab, l Limits) RowSplitwith the fieldsheader_and_frame,question_text,optionsandtotal, the figuressplitRowsalready computes. - One store reader.
questionState(cmd, cwd) (question.State, []unread)ininternal/surface/cliholds the reads both callers make:awayAtQuestion,mode.Root,ahoy.Managed,mode.HasTier,mode.ReadAt, andverbsOf. The hook calls it and thenDecide, and acts on the verdict as it acts today: A deny forrefuseandrefuse-away, the marker and the mode for an admission where the badge shows, the rows note foradmit-with-rows-note,questionFailOpenforunchecked. The writes stay in the hook, after the decision, so the verb can never reach them. - The verb.
abcd guard question [<file>|-] [--ask] [--json], a sibling ofguard check, registered beside it. It reads the draft from the file or standard input undermaxHookStdinBytes, callsquestionStateandDecide, and writes nothing (decision 9): No marker, no mode, no branch record, no log. Its help line is in the house form: "Check a draft question as the hook will judge it: Writes nothing; refuses an unreadable draft, a store it cannot read, and any finding." Exit 0 when there is no finding and every store was read; 1 on any finding, a rows overrun alone included; 2 when the input is not JSON, is over the cap, has neither shape, or when a store the rules depend on could not be read, even with no finding (decision 8, invariant 16), and, once spc-2610301950037469 lands, when a question limit the repository or the person set is refused (withrefusedand noready) or a layer of the question limits could not be read (the partial result naming the file). - Two input forms, chosen by the flag. Without
--askthe input is the host question tool's{"questions": [...]}, decoded asdecodeQuestionsdecodes it (unknown keys kept, a known key of the wrong type unreadable). With--askit isquestion.Ask, decoded with unknown fields refused, since abcd owns that schema;question.Check's structural findings are reported under the rulestructure, and the limits run overAsk.Fields(), so a missingNow:line is named by the limits check and never invented (decision 14).readyis the input's own bytes for the host form, somultiSelectand every key the check does not read survive, and the composition throughFieldsrendered as the host's input for--ask; it is absent when the outcome isrefuseorrefuse-away(decision 7). - The JSON.
_typeabcd/question-check/v1;versionfromcore.Version;hook(the outcome);depends_on_state;partialandunread;addresseeper tab withaddressee_changes;findings, each withtab,part,rule,value,limit,remedyandrefuses;by_rule, one entry per rule and tab with the first echoed value and a count;rowsper tab androws_limit;ready. spc-2610301950037469 adds three fields to the same type, so its name does not change:limits(rows,label_words,description_sentences, the figures in force),limits_source(the same three keys, eachbundled,.abcd/config.json, or~/.abcd.noindex/config.jsonwhere a personal value tightened it), andrefused(key,value,bounds,file, and for a personal value that loosenslimitandlimit_file) on the refusal path;rows_limitequalslimits.rows. The text render prints the outcome, the rows per tab, the grouped finding lines from the shared renderer, and the pointer line. - The grouped refusal and one renderer.
question.GroupFindings(findings) []Groupfolds findings into one group per rule and tab, in the order the first finding of each appeared, keeping one echoed value and a count.question.RenderGroups(w, groups)writes one line per group: The tab, the rule, the echoed value, the limit, the remedy, and "and N more such lines" when the count is above one. The deny becomes: The head line counting the refusing findings; the group lines for the refusing rules;rowsNotRefusingLineand the rows groups; and one pointer line, "Check the next draft withabcd guard questionbefore asking it, or draft it through the abcd:question-drafter agent, which does."maxRefusalParts, the "and N more part(s)" line, andmodeMadeAbcdsgo (the mode no longer makes a question abcd's). The bound is the rules times the checked tabs plus the call-level finding and the three fixed lines, because the check already stops at four tabs and four options. The rows note names the verb beside the drafter. - The header grammar, and the mode. The grammar becomes
^(?:(<role>) )?Q[1-9][0-9]*[a-z]?(?:/[1-9][0-9]*)?$: An optional role word fromChipRolesfor the older form, thenQ, a number, an optional part letter, and an optional total.question.IsAbcds(header, l)replaceshasAbcdChip's test;ChipRolekeeps returning the role word, and returns none for the number form. Rule 1's limit and remedy name the number form ("Q1") and say the older form is still read. The hook decides abcd's question by the header alone (decision 36): A call with no header in the grammar isadmit-uncheckedin every mode, and the branch at guard_question.go lines 175 to 186 goes with it. The register rule follows the mode for the number form: The hook passes the mode's person where the badge shows, andUnnamedelsewhere, whereforProductThinkeralready falls back to an older-form chip's role word. The mode writes: An admitted call with an older-form chip still sets the mode from the chip (chipState); an admitted number-form call sets nothing and is marked open under the current mode, so the answer's reset returns the mode the agent set withabcd mode product-thinkerorabcd mode facilitatorbefore asking (decision 37) to managed. Invariant 1 is reworded to bind to every question abcd asks through the host's question tool, each of which carries abcd's header, and to say that the header is what the guard checks the question by. - Pages and the brief.
commands/guard.mdgains aquestionsection besidecheckandhookand rewrites the question paragraphs ofhook(the header rule, the grouped refusal, the pointer);commands/mode.mdsays the agent sets the mode before a number-form question and the answer resets it;commands/intent.mdandagents/planning-interviewer.mdmove their examples from the chip to the number form with the mode set first, whichTestPluginQuestionBlocksNameTheAddresseealready accepts as a setter. The brief's guard chapter gains thequestionrow in its sub-verbs table, the verb's exit rules besideguard check's in "What a person gets back", and a rewritten question gate section;.abcd/development/release/surface.jsonanddocs/reference/cli/commands.mdare regenerated with the tree (decision 18). - The writer.
agents/question-drafter.mdkeeps its name (decision 31) and is rewritten as the question-writing agent. It labels every draft with a number header, resolves the binary as every plugin page does, runsabcd guard questionafter every edit, and returns the verb'sreadyasquestions, itsrows, itsversion, andchecked_by: abcd. It loops until the verb exits 0, at most five checks to a part (decision 32). It never refuses for length: Material too long for one screen is split into numbered parts as decision 33 says, independent decisions as tabs of one question (up to four) and a dependent part as the next question; too many answers are the dependent case, Q1a offering the first answers and "None of these", which leads on to Q1b with the rest and "Decide later"; one decision whose material alone is too long is quoted shorter and faithfully, with every answer's gain and cost kept, or split into the decisions it holds. Its refusals have three causes, each returned to the calling agent only: A missing or contradictory part of the brief, the verb's exit 2 (reported as the refusal and never a reason to count by hand, decision 11), and five checks spent, carrying the verb's last findings. Where the installed binary has noguard question, it returnschecked_by: hook-onlyand counts nothing by hand (decision 21); only where the host gives it no shell does it count by hand,checked_by: hand. In abcd's own checkout it runs the verb from the source tree, as the repository's own rule says, and records the version it reports. - GRILL.
AskingRuleschanges as decision 17 (revised) allows: The drafter rule says the writer runsabcd guard questionon each draft, returns what the check counted, and loops until it is clean, that the agent running an interview asks only what the writer returns and never writes or edits a question itself, and that a host with no agents checks its own draft with the same verb before asking; the layout rule names the number form (Q1,Q1a,Q2/5) and says the older form is still read; the last rule says the agent sets the mode to the addressee withabcd mode product-thinkerorabcd mode facilitatorbefore a question and the answer sets it back. Every rule that opens "In abcd's own interviews" keeps those words.make asking-syncregenerates both pages, andrenderDraftertells the writer to label every draft, run the verb after every edit, and return its rows, ready input and version, keeping the hand count for a host with no shell. - Labs. Three labs on one pinned binary and one fixed material each (decision 22): A managed snapshot repository with the plugin binary and no abcd checkout. Each lab's probes are three recorded runs, each transcript kept in
state/probes/<probe>/, and a criterion passes only when all three pass. The pass rules and the fixture files are written into the lab'sINTENTION.mdbefore any run, and the harvest is captured into a research note.
How each criterion is met
- Verb input and exit codes (exit 0, exit 1 on any finding, exit 2 on non-JSON, over-cap or neither shape,
--ask). Approaches 3 to 4. Tests:TestGuardQuestionExitsZeroOnACleanDraft,TestGuardQuestionExitsOneOnARowsOverrunAlone,TestGuardQuestionExitsTwoNamingTheShape(a text file, a payload one byte over the cap, and{"questions": 1}),TestGuardQuestionAskComposesTheDraft, ininternal/surface/cli/guard_question_verb_test.go. - The verb writes nothing. Approach 3. Test:
TestGuardQuestionWritesNothinghashes every file of a managed fixture checkout with the mode on the product thinker and no question open, runs the verb on a clean, a refused, and a rows-only draft, and compares the tree byte for byte, the mode file and the marker named first. - The verb equals the hook on the corpus; away; unread stores; partial exits 2. Approaches 1 to 3. Tests:
TestGuardQuestionAndHookDecideAlikeinguard_question_corpus_test.goruns both verbs on every file oftestdata/questions/corpus/(the 21-fault four-tab case, rebuilt with four number-form headers so that it is abcd's under decision 36 and carries 21 findings across the content rules the original broke, the 25-row case, one fault per rule, number-form and older-form headers and no header under each mode state, the person marked away, and an unreadable away record and an unreadable mode store) and asserts the verb's findings andhookequal the hook's deny, note, or silence in the same fixture state;TestGuardQuestionReportsRefuseAwayWhileAway;TestGuardQuestionMarksAPartialResult(outcomeunchecked,partialtrue,unreadnaming the store and the rules);TestGuardQuestionPartialNeverExitsZero. - Not abcd's question; the rows split; 21 findings; one group per rule and tab. Approaches 1, 5, 6. Tests:
TestGuardQuestionSaysAnUnlabelledDraftIsNotAbcds(under the product thinker's mode too);TestRowsSplitIsTheChecksininternal/core/question/decide_test.goon the 25-row case (8, 7, 10, 25);TestGuardQuestionListsAllTwentyOneFindings;TestGroupFindingsIsOneRuleOneTabinrender_test.go. - The deny names every rule on every tab with no "and N more" line; names the verb and the writer; stays bounded on a flood; one line for thousands of em-dash lines; finding lines byte-identical. Approach 6. Tests:
TestQuestionDenyNamesEveryRuleAndTab(asserts no "and N more part" line and one line per refusing rule and tab of the 21-fault case);TestQuestionDenyPointsAtTheVerbAndTheWriter;TestQuestionRefusalIsBoundedOnAFlood(rewritten: Thousands of tabs and options, the line count at most the rules times four plus the fixed lines);TestEmDashFloodTakesOneLinePerTab;TestVerbTextAndDenyShareTheirLines(the group lines of the verb's text render and of the deny compared byte for byte). - Composition order; a missing
Now:is named;readyis the input's bytes. Approach 4. Tests:TestAskComposedGivesMaterialStateThenQuestion(material first,Now:andChange later:next, the ask last, the decide-later option last) andTestAskMissingNowIsAFindingNotALineininternal/core/question/ask_test.go;TestGuardQuestionReadyKeepsUnreadKeys(multiSelectand an unknown key survive untouched). - The JSON carries the version. Approach 5. Test:
TestGuardQuestionJSONCarriesTheVersionwithcore.Versionset for the test. - The writer's page and its fallbacks (the generated block; exit 2 returned as a refusal; no shell counts by hand; the source tree in abcd's checkout; an older binary is hook-only). Approaches 9 to 10. Tests:
TestDrafterBlockIsGeneratedandTestDrafterBlockTellsTheWriterToRunTheVerbincmd/asking-sync/main_test.go(the block names the header, the verb after every edit,rows,readyandversion, and the hand count only for a host with no shell);TestWriterPageStatesItsRefusalsAndFallbacksininternal/core/lintreads the hand-written part for the three refusal causes,checked_bywith its three values, the five-check bound, and the source-tree sentence. The source-tree and older-binary behaviour are judged in the labs below, since they need a model run. - The GRILL domain as generated. Approach 10. Tests:
TestAskingRulesNameTheVerbAndTheWriterinasking_test.go(the drafter rule and the no-agents sentence nameabcd guard question, the one rule saying the interviewing agent asks only what the writer returns, the header rule namingQ1andQ2/5);TestGrillTextScopesRegisterToAbcdInterviewsandTestGrillTextCarriesTheAmendmentsupdated to the new phrases, still asserting the "In abcd's own interviews" prefix;TestGrillDomainIsGeneratedFromTheAskingRulesunchanged. - The header grammar; an unlabelled question passes unchecked under the product thinker's mode; the register rule follows the mode. Approach 7. Tests:
TestHeaderGrammarReadsNumberAndOlderFormsinlimits_test.go(Q1,Q1a,Q2/5,Q1a/4,Product Q2,Setup Q1/4read as abcd's;Q0,q1,Q1A,Builder Q1,Pick a themenot);TestUnlabelledQuestionRunsUncheckedWhateverTheModeinguard_question_limits_test.goreplacesTestModeMadeQuestionAbcdsSaysSo;TestNumberHeaderRegisterFollowsTheMode(aQ1tab naming a command is refused under the product thinker's mode and admitted under the facilitator's);TestOlderChipStillSetsTheModekeepsTestGuardChipSetsTheModeWhileManaged's case;TestNumberHeaderLeavesTheModeAndResetsOnTheAnswer. - Surfaces and the brief (the command page, the CLI reference, the help line; the sub-verbs row, the exit rules, surface coverage). Approach 8. Tests:
TestSentencesAreOneHouseFormand the surface drift test oversurface.json; thesurface_coveragelint over the brief's table;TestGuardPageDocumentsTheQuestionVerbininternal/core/lintreadscommands/guard.mdfor thequestionsection anddocs/reference/cli/commands.mdforabcd guard question. - Seen by a person: No refusal line before a drafted question; no tab cut at 80 columns; the second ask admitted after a refusal; the check by hand; the status line unchanged by a check; numbered parts; "None of these" leading to Q1b; a header that is only its number. Approaches 3, 6, 7, 9, 11. The by-hand check and the status line are
TestGuardQuestionWritesNothingand the verb's text render testTestGuardQuestionTextGroupsRowsAndOutcome. The rest are the labs: In every recorded run the hook's log shows no deny before a writer-returned question, every tab's rows are within 24 by the verb's own count, and the headers are number-form. - The writer loop and the re-brief (five checks to a part; the caller asks nothing until a draft passes). Approaches 9 to 10. The bound and the refusal shape are page tests above; the loop itself is judged in the labs, where each run's transcript is read for the verb calls per part and for any question asked after a writer refusal.
- The labs (the plugin binary works without abcd's checkout; the 25-row material in three runs with no file written and at most three verb calls; the 21-fault question refused and the second ask admitted in three sessions; the material refused for length on 2026-10-30 returned as parts the verb exits 0 on in three runs, none a refusal; three interviews of at least ten questions with no refusal). Approach 11. Lab A (
question-writer-25-rows): The writer on the 25-row case, the transcript checked for file writes and verb calls; the case is over the limit only at 24 rows, so the lab's snapshot repository setsquestion.rowsto 24 in its.abcd/config.json, since itd-2610301914350541 raised the default to 32 (a pinned binary predating that intent holds 24 by default and reads no such key). Lab B (question-writer-refusal): A live session told to ask the 21-fault question without the writer, refused, and its second ask admitted. Lab C (question-writer-parts): The writer on the 2026-10-30 material, copied into the lab'sINTENTION.mdfrom the session's transcript, every returned part run through the verb, and three interviews of at least ten questions each with the hook's log read for any deny. The first probe of each lab runsabcd guard question --jsonon a clean draft from the lab's plugin binary, outside any abcd checkout, and records its exit and version.
Out of scope, recorded elsewhere: Per-repository layout limits (itd-2610301914350541); tightening question.Check (decision 14); the content rules the check cannot see (iss-2610090816296964, iss-2610161739587645); the required "Unclear" option (iss-2610150551207288); refusing on the rows limit alone (iss-2610070637562567's ruling stands); and the binary's own Terminal-drawn interviews (the setup interview's Setup Q1, the written interview's Product Q1), which have no status line, so the drawn chip is what names the addressee there and the older form stays theirs.
Footprint
- packages: internal/core/question/check.go, internal/core/question/decide.go, internal/core/question/render.go, internal/core/question/rows.go, internal/core/question/asking.go, internal/core/question/limits.go, internal/core/question/ask.go, internal/surface/cli/guard_question.go, internal/surface/cli/guard_question_verb.go, internal/surface/cli/guard.go, cmd/asking-sync/main.go, agents/question-drafter.md, agents/planning-interviewer.md, commands/guard.md, commands/mode.md, commands/intent.md, docs/reference/cli/commands.md, .abcd/development/release/surface.json, .abcd/development/brief/04-surfaces/17-guard.md, .abcd/development/brief/02-constraints/03-invariants.md, .abcd/development/research/notes/2026-11-question-writer-labs.md
- tests: internal/core/question/decide_test.go, internal/core/question/render_test.go, internal/core/question/limits_test.go, internal/core/question/ask_test.go, internal/core/question/asking_test.go, internal/surface/cli/guard_question_verb_test.go, internal/surface/cli/guard_question_corpus_test.go, internal/surface/cli/guard_question_limits_test.go, internal/surface/cli/guard_question_rows_test.go, internal/surface/cli/guard_question_test.go, internal/surface/cli/roles_vocabulary_test.go, internal/surface/cli/testdata/questions/corpus/, internal/core/rules/grill_test.go, internal/core/lint/surfaceagents_test.go, cmd/asking-sync/main_test.go
Steps
- The shared decision function and the verb
- needs: none
- packages: internal/core/question/decide.go, internal/core/question/rows.go, internal/core/question/ask.go, internal/surface/cli/guard_question.go, internal/surface/cli/guard_question_verb.go, internal/surface/cli/guard.go, commands/guard.md, docs/reference/cli/commands.md, .abcd/development/release/surface.json, .abcd/development/brief/04-surfaces/17-guard.md
- tests: internal/core/question/decide_test.go, internal/core/question/ask_test.go, internal/surface/cli/guard_question_verb_test.go, internal/surface/cli/guard_question_corpus_test.go, internal/surface/cli/testdata/questions/corpus/, internal/core/lint/surfaceagents_test.go
- landed: ce643bd05b6f
- The grouped refusal and the one renderer
- needs: 1
- packages: internal/core/question/render.go, internal/surface/cli/guard_question.go, commands/guard.md, .abcd/development/brief/04-surfaces/17-guard.md
- tests: internal/core/question/render_test.go, internal/surface/cli/guard_question_limits_test.go, internal/surface/cli/guard_question_rows_test.go
- The header grammar, the mode, and invariant 1
- needs: 1
- packages: internal/core/question/check.go, internal/core/question/limits.go, internal/core/question/decide.go, internal/surface/cli/guard_question.go, commands/guard.md, commands/mode.md, .abcd/development/brief/04-surfaces/17-guard.md, .abcd/development/brief/02-constraints/03-invariants.md
- tests: internal/core/question/limits_test.go, internal/surface/cli/guard_question_limits_test.go, internal/surface/cli/guard_question_test.go, internal/surface/cli/guard_question_corpus_test.go
- The writer contract, the GRILL text, and the regenerated pages
- needs: 2, 3
- packages: internal/core/question/asking.go, cmd/asking-sync/main.go, agents/question-drafter.md, agents/planning-interviewer.md, commands/intent.md, commands/mode.md
- tests: internal/core/question/asking_test.go, internal/core/rules/grill_test.go, cmd/asking-sync/main_test.go, internal/surface/cli/roles_vocabulary_test.go, internal/core/lint/surfaceagents_test.go
- The recorded labs
- needs: 4
- packages: .abcd/development/research/notes/2026-11-question-writer-labs.md
- tests: internal/surface/cli/guard_question_verb_test.go
Frontmatter
| id | spc-2610301917245442 |
|---|---|
| type | spec |
| lifecycle | open |
.abcd/development/specs/open/spc-2610301917245442-questions-still-break-abcd-s-asking.md
open on GitHub ↗ · commit history ↗
References1
- itd-2610230756259215Every question abcd asks passes its check before the person sees itimplements
mentions9
- iss-2610070637562567After the hook commands became one-line wrappers, a guard refusal still reaches the person as 'PreToolUse:<tool> hook error: [sh "${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-use.sh"]: <message>', framed as an error, because the guard blocks by exiting 2 with its message on stderr. Claude Code's hooks reference documents a structured alternative for PreToolUse: exit 0 with hookSpecificOutput.permissionDecision "deny" and permissionDecisionReason on stdout, which blocks the call (a question included) and hands the model only the reason; the command echo in front of an exit-2 message is not documented at all. Unverified here: the guide that reported it worked from summarised documentation.
- iss-2610090816296964When abcd asks the person to choose, the question keeps running past the host's question box: a question over 24 rows at 80 columns was refused in a live session on the 0.13.2 plugin, and the agent cut its options down to a few words each to get it through. The product thinker, reading the refused draft, saw that its long text added nothing: a paragraph before the options described each option again, while the options' own labels and descriptions were already perfectly clear. The problem was repetition, not too little room, so the fix is a rule against it rather than a second channel for the overflow; a separate text file for the full options was considered and dropped on the product thinker's ruling of 2026-10-09.
- iss-2610150551207288When abcd offers the product thinker options, there is no option for 'this question is unclear'. On 2026-10-15, at the planning interview for itd-2609151838312703, the product thinker could not follow a question about an overlapping draft and had to type 'explain, unclear' into the host's free-text field. The asking rules let an option set end only in 'Decide later' or 'None of these', so a person who does not understand the question has no option to say so. The product thinker's words: when offering options to the PT, always offer 'Unclear, explain with an example' to ensure the user can select that.
- iss-2610161739587645During interviews abcd asks the product thinker and the technical facilitator questions that have one clearly best answer. On 2026-10-16 the product thinker said to ask only where there is a clear trade-off, or several equally valid options. The GRILL rule already says a decision with one defensible answer is recorded, not asked. But the planning interview's own steps in commands/intent.md tell the host to confirm the press release, ask for the mechanism claim, the scope conditions and the grounds, and walk every acceptance criterion one question at a time, so a host following the page asks single-answer questions by design. The question-drafter agent drafts whatever it is handed and does not push back when only one answer is defensible.
- iss-2610230745597968Questions still break abcd's asking limits after several rounds of fixes, on two accounts on 2026-10-23. (1) In a repository abcd manages, on another account, an agent bundled four separate issues into one question without going through the abcd:question-drafter agent, and the question check refused 21 parts at once: headers that were not role chips, no Decide later option, no Now: or Change later: lines, and question text that did not quote the thing being decided; the status line meanwhile read 'waiting on the product thinker'. (2) In abcd's own checkout, a question was refused because its text did not end with the question, and it also ran 25 rows against the 24-row limit (header and frame 8, question text 7, options 10), which the check reports but does not refuse, so it would have been shown and a narrow window might cut it; at the same time an abcd:question-drafter run spent 9 minutes 32 seconds and 174.2k tokens recounting over-limit tabs with a hand-written rows script. Earlier rounds include iss-2610071538055431, iss-2610100626327722, iss-2610161734179773 and the open iss-2610090816296964.
- iss-2610301855200679A question that breaks abcd's asking rules still reaches the person as an error, after the product thinker asked several times for it to stop (2026-10-30, and before in iss-2610230745597968). In one planning interview the abcd:question-drafter returned a refusal for 3 of 10 drafts because the quoted material and its options did not fit the 24-row limit for one decision; the calling agent then put over-limit questions to the person itself, and once skipped the drafter altogether, so the question check blocked the question in front of the person (no Decide later option, 28 rows). Three gaps let this through: the drafter may give up and hand back a refusal instead of working until its draft passes; it counts rows by its own reckoning rather than through abcd's check; and nothing makes the calling agent ask only a question that passed, so the hook's refusal is the first check the question meets, and the person sees it. The product thinker's proposal: a sub-agent keeps working on the question until it conforms, and only then is it put to the person.
- iss-2611011331010643A build lane can start a spec step whose wait on another spec's step is written only in prose, before that step has landed. A step's needs: line takes only the same spec's step numbers (internal/core/spec/steps.go), so a dependency across specs goes in prose. spc-2610301950037469 step 2 says it waits for spc-2610301917245442's steps 1 and 3; the readiness check cannot read that. On 2026-11-01 lane 2 of run-2610312053537139 started step 2 while the parent's step 1 was still on an unmerged pull request (#1315) and its step 3 had not been built. The implementer built the hook half, could not build the verb half, and the review returned FIX FIRST. Landing would have marked the whole step landed, and step 3, which reads the verb's limits field, would have started next. A full implement and preflight cycle was spent on a step that could not be finished.
- itd-2610301914350541A repository sets the layout limits of abcd's questions
- spc-2610301950037469A repository sets the layout limits of abcd's questions