feat: add how-to as flowrite's fourth document kind - #67
Merged
Conversation
A how-to guide is the fourth Diátaxis genre and the one flowrite could not write: goal-oriented, for a reader who already knows the library and wants one job done. Ported from writer-assistant audit gap §6 — though the substantive material came from plugins/documentation/skills/docs-how-to-guide/, not from writer-assistant, whose copy is a 30-line summary and whose verify phase pointed at a checklist that never existed in that tree. The distinguishing section is "The Problem", and its "before" example needed a decision the sources got wrong. Both fence it as plain ```scala on the reasoning that painful code need not compile. mdoc-conventions:10 forbids exactly that, and :41 sweeps for it — so the drafter would add a modifier, watch the compile fail, and then obey "fix the example" by rewriting the pain away. The contrast the section exists for would vanish silently. So the before block is mdoc:compile-only real code that does not touch the documented library. That is what "before" means, it compiles because verbose working code compiles, and fact-checker.md exempts compiled blocks from the character-by-character signature scrutiny it applies to plain fences. The plain fence survives only for pseudocode, where the problem is architectural. The template also emits literal heading text and forbids its own vocabulary in headings. BACKLOG finding 3 blames numbered lists for `## 6. Concept 4: Bounded Windows`, but `## 1.` was mandated by tutorial-structure:46 — what leaked was the template's placeholder label. The checklist carries that rule, because the reviewer has nothing else mounted.
…o type behind them The table absorbed the fourth kind as designed — one row, two map entries, no new code path, no change to any subagent. tsc forces more of it than expected: a missing row fails at `KINDS[kind]` in agent.ts and in agent.test.ts, not just at the two `Record<DocKind, string>` maps. So the wiring cannot be half-done. The prose around the table is where three kinds was baked in, and none of it is checked. The tutorial gate bullet read "a learning-oriented walkthrough of a task or topic" — "of a task" IS the how-to's definition, so a fourth bullet alone would have left tutorial a superset that absorbs every how-to request. Both bullets now name the reader's intent (understand vs finish), with the discriminator stated and one ✅/❌ pair. The worked ambiguity example moves from data-type/tutorial to tutorial/how-to: the pair that shares a directory needs it more than the pair that does not. set_document_kind's subject description is amended too — the classifying model reads it on the same turn. A misclassification is silent. run-telemetry.ts is kind-agnostic by construction and keys on the docs/ path segment, which is `guides` for both kinds; the reviewer gets whichever checklist the gate chose. So a misfiled how-to is reviewed against the tutorial checklist, passes, and files verdict: passed. Hence the care with the prose rather than a validator. Two new assertions cover what nothing covered: - GATE_INSTRUCTIONS names every DOC_KINDS member. Presence only; whether the discriminator is any good needs a live run. - Every mounted skill has a non-empty name. A SKILL.md missing `name:` loads as undefined, and the duplicate-skill test then compares undefined to undefined and passes. The pairwise-distinctness test becomes a loop over pairs. As three named assertions it covered nothing a fourth kind added — which is exactly how it stood when this kind arrived. withKind now takes DocKind, so it never needs this edit again.
…tutorial None of this is per-kind wiring — the table needed none. It is the text around the table that was written when three kinds was the whole world, and the two pieces that matter most are role DESCRIPTIONS rather than role instructions: examples_builder advertised itself as "for a tutorial (one per concept)" and docs_integrator as "Integrates a finished tutorial". That is the roster text the model reads when choosing whom to delegate to, and BACKLOG finding 2 records the examples phase having silently never run — a role that describes itself as tutorial-only is a plausible way for a how-to run to skip it. Both are now kind- neutral; the other five already were. examples-builder.md keeps `<tutorial-id>` as its placeholder (nothing keys on the word, and the file is a working prompt) but loses two enumerations that read as exhaustive and excluded how-to, one of which offered "a tutorial's Putting It Together, or a reference page's Running the Examples" — a how-to has both, so a literal reading gave it neither. fixtures/tinyproject/AGENTS.md told every run that docs/guides/ is where tutorials go. That file is workspace-discovered into the writer's prompt, so a how-to run read it as instruction. It now names both guide kinds and says to list the directory first, since a shared directory means an id collision overwrites somebody else's page and nothing checks. archive-docs.sh gains the else branch on an unmatched label. The log is prefixed with the label of the kind the GATE classified, not the one the launcher asked for, so a misclassified run makes every grep miss — no token-usage.json, no verdict, no insights, no run report, and a closing summary indistinguishable from success. An operator would diagnose a crash. The launcher's header says to name a task rather than a subject, and records that Ctrl-C after the classification line is the cheap check on the tutorial/how-to boundary — the INT trap already archives, so that needs no new code. package.json gains a typecheck script. Note npm cannot run it, or `test`, because devEngines demands pnpm; it names the canonical command, and the binary is what actually gets run. The new test reads all four launchers and asserts each archives under its own row's label. The pin above it was titled "labels match what archive-docs.sh greps for" while opening no script; this is that assertion. Verified by breaking the label and watching it fail.
The audit's estimate for this gap — "one KINDS row plus a structure and a checklist skill" — was right about the architecture and undercounted the prose, and that asymmetry is the finding worth keeping. tsc forces the row, both Record<DocKind, string> maps and the KINDS[kind] index; it cannot reach GATE_INSTRUCTIONS, a schema description, two subagent descriptions, examples-builder.md's enumerations, or the fixture's AGENTS.md. A fifth kind will cost the same three files plus the same hand-search. The README's "how a kind was added" recipe was stale in a way that would have sent an implementer to modules that no longer exist (research.ts, design-doc-plan.ts, write-doc.ts — deleted with the other phase tools). It now records what this change actually cost, including which half tsc covers. BACKLOG finding 11 lists the four unproven genre behaviours in priority order, with the fixture limits that bound whatever a run says: tinyproject has no boilerplate to suffer, so the before-block has nothing real to be painful about and the drafter will invent it — the one thing fact-check can neither confirm nor deny. A passing run proves the plumbing, not the genre. Five observations recorded rather than fixed. The one most likely to bite someone else: finding 3's diagnosis is wrong. It blames numbered lists for `## 6. Concept 4: Bounded Windows`, but `## 1.` was mandated by tutorial-structure:46 — what leaked was the template's placeholder label, which rule 27 does not enumerate and no checklist item covered. Also filed: :showLineNumbers vs :show-line-numbers with neither form empirically proven, and that a writer-assistant file is not evidence the behaviour it describes ever ran — its how-to verify phase read a checklist that never existed in that tree.
This was referenced Aug 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes audit gap §6 of
WRITER-ASSISTANT-MIGRATION.md. flowrite wrote three of the four Diátaxisgenres; the missing one was the how-to guide — one task, one canonical path, a working result, for a
reader who already knows the library.
Ask for it in words like any other kind:
bash fixtures/tinyproject/scripts/run-how-to-guide.sh "compose two optics over one field"What the port cost, versus what the audit estimated
The audit said "one
KINDSrow plus a structure and a checklist skill — the table was built forexactly this." That was right about the architecture and undercounted the prose.
No phase, no delegation, no schema, no new entry point, and no subagent changed:
designeranddrafterreadstructureBlock(docKind())at their own render, so both picked up the new templateuntouched.
tscforces more than expected — a missing row fails at theKINDS[kind]index as well asat the two
Record<DocKind, string>maps, so the wiring cannot be half-done.What
tsccannot reach is every place three kinds had been written out in text, and that is where thework was:
GATE_INSTRUCTIONS,set_document_kind's subject description, two subagent descriptions(the roster text a delegating model reads),
examples-builder.md's enumerations, and the fixture'sAGENTS.md. A fifth kind will cost the same three files plus the same hand-search.Three findings that changed the design
1. The tutorial gate bullet already claimed the how-to's territory. It read "a learning-oriented
walkthrough of a task or topic" — "of a task" is the how-to's definition, so a fourth bullet alone
would have left
tutoriala superset absorbing every how-to request. Both guide bullets now name thereader's intent (UNDERSTAND vs FINISH), and the worked ambiguity example moved from
data-type/tutorialtotutorial/how-to: the easy pair gave its example to the hard one.This matters because misclassification is silent end to end.
run-telemetry.tsis kind-agnosticby construction and keys on the
docs/path segment, which isguidesfor both guide kinds; thereviewer is handed whichever checklist the gate chose. A how-to misfiled as a tutorial is reviewed
against the tutorial checklist, passes, and files
verdict: passed.Worse, the log is prefixed with the label of the kind the gate classified, not the one the launcher
asked for — so
archive-docs.sh's greps all miss and it produces an artifact-free archive whosesummary reads like a clean run. Added an
elsebranch that says so.2. The "before" block cannot be a plain ```scala fence. Both source trees fence it that way, on the
reasoning that painful code need not compile.
mdoc-conventions:10forbids exactly that and `:41`sweeps for it, so the drafter would add a modifier, hit the compile error, and obey "fix the example"
by rewriting the pain away — silently, with review passing.
The premise was wrong: "what you write without this library" is ordinarily verbose working code.
So the before block is
mdoc:compile-onlyreal code that does not touch the documented library. Itcompiles, keeps the contrast, and sidesteps
fact-checker.md:16's character-by-character scrutiny ofuncompiled fences — which would otherwise have made a correct page fail its own run on a blocking
not-in-sourcedrift. Plain fences survive for pseudocode only.fact-checker.mddeliberatelynot edited: that failure is loud, and the rule is to wrap only what has been watched to fail.
3.
BACKLOG.mdfinding 3's diagnosis is wrong. It blames the templates' numbered lists for a runemitting
## 6. Concept 4: Bounded Windows, but## 1.was compliant —tutorial-structure:46mandates it. What leaked was the template's placeholder label (
Concept sections (3-6, …)→Concept 4:), which is rule 27's category but not among the terms rule 27 enumerates, so no revieweritem covered it. The new template emits literal headings and its checklist forbids template vocabulary
and ordinals in headings. Left open for the other three kinds — fixing it there changes the drafting
prompt for every existing kind.
Tests
105 pass (was 102),
tscclean. Five existing tests loopDOC_KINDSand cover the new kind for free.Three hardcoded tests now derive from
DOC_KINDS— most importantly pairwise-distinctness, whichexisted to catch "one kind's map entry pointing at another kind's import" and, as three named
assertions, covered nothing a fourth kind added.
Three new assertions close silent gaps:
GATE_INSTRUCTIONSnames everyDOC_KINDSmember — the one enumeration with no type behind it.name; aSKILL.mdmissingname:loads asundefined, andthe duplicate-skill test then compares
undefinedtoundefinedand passes.match what archive-docs.sh greps for" while opening no script. Verified by breaking a label and
watching it fail.
Not verified — and this is the honest part
No run has produced a page. The key is exhausted until 2026-09-01.
BACKLOG.mdfinding 11 liststhe four unproven behaviours in priority order; the cheapest is one turn — start the launcher, Ctrl-C
after the classification line (the INT trap archives).
Two fixture limits bound whatever that run says:
tinyprojecthas no boilerplate to suffer, so thebefore-block has nothing real to be painful about and the drafter will invent it — the one thing
fact-check can neither confirm nor deny. And don't run it on
Lens;docs/guides/lens.mdis committedand already how-to-flavoured, so it overwrites a page instead of exercising the empty-start path.
Also here
plainTools: []— a how-to documents real API, but only what its one task needs, socheck_method_coveragewould report a large miss on a correct page.:showLineNumbersvs:show-line-numbersis in the tree 1-to-4 and neither form is proven (noarchive contains a built embed — finding 2). Filed, not fixed; a majority vote is not evidence.
plugins/documentation/skills/docs-how-to-guide/, not fromwriter-assistant, whose 30-line copy sat behind averify.tsthat told the model to read achecklist that never existed in that tree. Relevant to the remaining audit gaps: a
writer-assistantfile is not evidence the behaviour it describes ever ran.package.jsongains atypecheckscript.npmcannot run it, ortest, becausedevEnginesrequires pnpm; the binaries work directly, which is what every verification step uses.