Getting started

chaff finds the places in a text that are hard to read. It never rewrites anything; fixing is the writer’s job. Start by running it once on something you wrote.

npx chaffjs article.md

To try it before installing anything, paste a text into the playground, which runs chaff in your browser (Checking in a browser).

Pick the kind of document

A contract’s sentences run longer than a blog’s, a poem repeats its words, and a transcript keeps what was said. Each is how that kind of document is written. Pick the kind of document (the genre) and chaff sets its rules and limits to that kind, with no settings to write.

npx chaffjs --genre legal/contract contract.md   check it as a contract, this run only
npx chaffjs init --genre legal/contract          write the genre into this folder's chaff.yaml
npx chaffjs genres                               list the genres and what each is for
DocumentGenre
Specifications, RFCs, design documentstechnical/spec
READMEs, documentation for developerstechnical/readme
Technical articles (the genre when none is set)blog/tech
Personal essays on a blogblog/essay
Articles an organisation publishesblog/owned-media
Proposals, plans, pitchesbusiness/proposal
Reports, white papers, internal documentsbusiness/report
Work email and lettersbusiness/email
Press releases and public noticesbusiness/press-release
Minutes and meeting notesbusiness/meeting-notes
Contracts, terms of service, privacy policieslegal/contract
Statutes, regulations, internal ruleslegal/statute
Court judgments and opinionslegal/judgment
Patent specifications and claimslegal/patent
User guides, how-to pages, helpdocs/manual
Questions and answersdocs/faq
Glossariesdocs/glossary
Papers and abstractsacademic/paper
Novels and storiesliterature/fiction
Literary essaysliterature/essay
Poems and verseliterature/poetry
Plays and scriptsliterature/play
Speeches written to be read aloudspeech/address
Records of what was said (press conferences, debates)speech/transcript

Payslips, bills, statements and other paperwork have no genre of their own: use business/report, or business/proposal for a quote (Payslips, bills and statements). What each genre leaves out, and what it adds, is on the Genres page.

With the genre set, the report opens with the genre’s guide: what a good document of that kind does, as questions to check the draft against. --no-guide leaves it out, and guide: in chaff.yaml changes it (Changing the genre’s guide).

With no genre set, a document is checked as a technical article (blog/tech). When it looks like another kind, the screen says so under the first line. The genre it is checked with does not change.

contract.md   blog/tech · English   genre from the default
   Looks like: Contract and terms. Try --genre legal/contract

Run it on your own writing

No settings file, no API key and no language setting are needed. Give it a file and it reads it.

npx chaffjs article.md           check this file
npx chaffjs .                    check every Markdown file here
npx chaffjs docs/ README.md      mix directories, files and globs

node_modules, dist, build and coverage are skipped. If nothing is found to check, the run fails. That way a run that checked nothing never looks like a pass.

Reading the screen

With no findings, it looks like this.

$ npx chaffjs article.md

article.md   blog/tech · English   genre from the default

────────────────────────────────────────────────────────────

  No findings   All judged by machine
              (the same text gives the same result every time)

  The text was not changed. Fixing it is the writer's job.

  70 rules did not run:
      abstract-length (the blog/tech genre does not check it)
      adverb-without-negation (not a rule for en)
      adversative-ga-repeat (not a rule for en)
      agentless-passive (the blog/tech genre does not check it)
      attachment-not-attached (the blog/tech genre does not check it)
      baito-keigo (not a rule for en)
      bold-label-list (not a rule for en)
      bracket-width-consistency (not a rule for en)
      citation-reference-mismatch (the blog/tech genre does not check it)
      citation-style-mix (the blog/tech genre does not check it)
      clause-chain (not a rule for en)
      colloquial-opener (not a rule for en)
      colon-lead-in (not a rule for en)
      concrete-evidence-density (the blog/tech genre does not check it)
      connective-hierarchy (not a rule for en)
      cross-doc-broken-link (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cross-doc-duplicate-definition (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cross-doc-fact-conflict (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cross-doc-term-variant (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cushion-phrase-density (the blog/tech genre does not check it)
      defined-name-repeated (the blog/tech genre does not check it)
      defined-term-form (the blog/tech genre does not check it)
      double-keigo (not a rule for en)
      email-greeting-closing (the blog/tech genre does not check it)
      email-subject-length (the blog/tech genre does not check it)
      empty-conclusion (it reads meaning; npx chaffjs test runs it)
      exclamation-width-consistency (not a rule for en)
      figure-reference-order (the blog/tech genre does not check it)
      fullwidth-alnum-consistency (not a rule for en)
      hankaku-kana (not a rule for en)
      hiragana-fukushi (not a rule for en)
      humble-for-others (not a rule for en)
      idiom-misuse (not a rule for en)
      ime-residue (not a rule for en)
      kana-function-word (not a rule for en)
      kanji-compound-slip (not a rule for en)
      katakana-density (not a rule for en)
      katakana-long-vowel (not a rule for en)
      kutoten-consistency (not a rule for en)
      latin-spacing (not a rule for en)
      max-kanji-continuous (not a rule for en)
      max-ten (not a rule for en)
      misconversion (not a rule for en)
      missing-final-period (not a rule for en)
      no-doubled-joshi (not a rule for en)
      no-mixed-desumasu (not a rule for en)
      no-nakaguro-parallel (not a rule for en)
      obligation-without-deadline (the blog/tech genre does not check it)
      party-order (not a rule for en)
      party-role-name (the blog/tech genre does not check it)
      preamble-length (the blog/tech genre does not check it)
      quote-unnamed-speaker (the blog/tech genre does not check it)
      ra-nuki (not a rule for en)
      repeated-sentence-ending (not a rule for en)
      request-without-deadline (the blog/tech genre does not check it)
      requirement-smell (the blog/tech genre does not check it)
      risk-disclosure (the blog/tech genre does not check it)
      sasete-itadaku (not a rule for en)
      stray-space (not a rule for en)
      taigen-dome-in-prose (not a rule for en)
      tari-unpaired (not a rule for en)
      teki-overuse (not a rule for en)
      toki-as-condition (not a rule for en)
      topic-predicate-distance (not a rule for en)
      translationese-density (not a rule for en)
      undefined-acronym (the blog/tech genre does not check it)
      undefined-term (the blog/tech genre does not check it)
      unsourced-number (the blog/tech genre does not check it)
      vague-deadline (the blog/tech genre does not check it)
      yori-as-from (not a rule for en)

AI-likeness: not scored (too short: 66 words; scored from 200)

With findings, each one gets its own block. This is a run on a short article written for this guide.

$ npx chaffjs sample.md

sample.md   blog/tech · English   genre from the default

─── line 3 ───────────────────────────────────────────────────

    Our end-to-end suite spent most of its time building the same fixtures again on every run, so we started keeping them in…

  ·  Sentence too long

     This sentence runs 41 words (limit 40)
     In a long sentence the reader loses the subject before reaching the verb.

     → Split it in two at the conjunction.

     Relax this rule:  npx chaffjs relax max-sentence-length


─── line 11 ──────────────────────────────────────────────────

    The restoreFixtures step restores the fixtures.

  ·  Heading echoed

     The first sentence repeats the heading "restoreFixtures"
     When the first sentence repeats the heading, the reader gains nothing by reading on. A sentence that states a figure the heading does not have (an amount, a number, a date) gives the content even when it repeats the heading, and is not reported.

     → Start from what the heading promised, not from the heading itself.

     Relax this rule:  npx chaffjs relax heading-echo


────────────────────────────────────────────────────────────

  2 notes   All judged by machine
              (the same text gives the same result every time)

  The text was not changed. Fixing it is the writer's job.

  70 rules did not run:
      abstract-length (the blog/tech genre does not check it)
      adverb-without-negation (not a rule for en)
      adversative-ga-repeat (not a rule for en)
      agentless-passive (the blog/tech genre does not check it)
      attachment-not-attached (the blog/tech genre does not check it)
      baito-keigo (not a rule for en)
      bold-label-list (not a rule for en)
      bracket-width-consistency (not a rule for en)
      citation-reference-mismatch (the blog/tech genre does not check it)
      citation-style-mix (the blog/tech genre does not check it)
      clause-chain (not a rule for en)
      colloquial-opener (not a rule for en)
      colon-lead-in (not a rule for en)
      concrete-evidence-density (the blog/tech genre does not check it)
      connective-hierarchy (not a rule for en)
      cross-doc-broken-link (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cross-doc-duplicate-definition (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cross-doc-fact-conflict (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cross-doc-term-variant (it compares documents, and there is only one to compare (it runs when chaff is given two or more files, or a folder))
      cushion-phrase-density (the blog/tech genre does not check it)
      defined-name-repeated (the blog/tech genre does not check it)
      defined-term-form (the blog/tech genre does not check it)
      double-keigo (not a rule for en)
      email-greeting-closing (the blog/tech genre does not check it)
      email-subject-length (the blog/tech genre does not check it)
      empty-conclusion (it reads meaning; npx chaffjs test runs it)
      exclamation-width-consistency (not a rule for en)
      figure-reference-order (the blog/tech genre does not check it)
      fullwidth-alnum-consistency (not a rule for en)
      hankaku-kana (not a rule for en)
      hiragana-fukushi (not a rule for en)
      humble-for-others (not a rule for en)
      idiom-misuse (not a rule for en)
      ime-residue (not a rule for en)
      kana-function-word (not a rule for en)
      kanji-compound-slip (not a rule for en)
      katakana-density (not a rule for en)
      katakana-long-vowel (not a rule for en)
      kutoten-consistency (not a rule for en)
      latin-spacing (not a rule for en)
      max-kanji-continuous (not a rule for en)
      max-ten (not a rule for en)
      misconversion (not a rule for en)
      missing-final-period (not a rule for en)
      no-doubled-joshi (not a rule for en)
      no-mixed-desumasu (not a rule for en)
      no-nakaguro-parallel (not a rule for en)
      obligation-without-deadline (the blog/tech genre does not check it)
      party-order (not a rule for en)
      party-role-name (the blog/tech genre does not check it)
      preamble-length (the blog/tech genre does not check it)
      quote-unnamed-speaker (the blog/tech genre does not check it)
      ra-nuki (not a rule for en)
      repeated-sentence-ending (not a rule for en)
      request-without-deadline (the blog/tech genre does not check it)
      requirement-smell (the blog/tech genre does not check it)
      risk-disclosure (the blog/tech genre does not check it)
      sasete-itadaku (not a rule for en)
      stray-space (not a rule for en)
      taigen-dome-in-prose (not a rule for en)
      tari-unpaired (not a rule for en)
      teki-overuse (not a rule for en)
      toki-as-condition (not a rule for en)
      topic-predicate-distance (not a rule for en)
      translationese-density (not a rule for en)
      undefined-acronym (the blog/tech genre does not check it)
      undefined-term (the blog/tech genre does not check it)
      unsourced-number (the blog/tech genre does not check it)
      vague-deadline (the blog/tech genre does not check it)
      yori-as-from (not a rule for en)

AI-likeness: not scored (too short: 111 words; scored from 200)

From top to bottom, the screen says:

Part of the screenWhat it means
First lineThe file, its genre, its language, and where the genre came from
─── line 3 ───One finding starts here; the number is the line
The indented sentenceThe sentence the finding is about
· and ⚠How serious the finding is (· a note, ⚠ a warning), followed by its title
The two lines under the titleWhat is happening, and why it is hard to read
The line starting with →How to fix it
Relax this ruleThe command that adjusts that rule for your team
The count at the endHow many findings, and that all of them were judged by machine
Rules that did not runThe rules not used this time, each with the reason

The same text gives the same result every time. Rules that did not run are listed with their reasons, so you can see what was checked and what was not.

Three ways to respond to a finding

Any of them is fine.

WayWhenWhat to do
Fix itThe finding is rightRewrite the text
Silence this spotThe finding is right, but here it is on purposeWrite <!-- stet: rule-id — reason -->
Change the ruleIt does not fit how your team writesType npx chaffjs relax rule-id --why "reason"

The third one matters. Without a way to change the rule, “too noisy, stop using it” is where it ends.

Choosing what to silence

stet marks a finding as intended. It is written as a Markdown comment, so readers never see it. There are three reaches to choose from.

Written asSilences
<!-- stet: bold-density — a glossary, on purpose -->The paragraph just below it (or the one heading, list or table)
<!-- stet-section: bold-density — a list -->Up to the next heading
<!-- stet-file: ai-tell, rule-of-three — mostly quotations -->The whole file

Write the reason after —, so whoever reads it later can see why it was silenced. If you keep silencing the same rule, it is time to change the rule instead.