設定
chaff.yaml は、ジャンル・言語・ルールの強さをチームに合わせるためのファイルです。書くのは既定から変えたものだけで、このファイルが無くても chaff は動きます。
chaff.yaml を作る
npx chaffjs init --genre legal/contract 契約書のための chaff.yaml を作る
npx chaffjs init 端末なら、番号の付いた一覧からジャンルを選ぶ
端末で --genre を付けずに init を動かすと、ジャンルを何向けかと一緒に並べ、番号か名前を尋ねます(Enter で blog/tech)。スクリプトや CI では尋ねず、blog/tech にします。
この場所に chaff.yaml と .gitignore ができます。実際に動かすと、次のように出ます。
$ npx chaffjs init --genre legal/contract
作成しました:
…/chaff.yaml 規範の宣言。commit してください
…/.gitignore 作成しました
ジャンルは legal/contract にしました。違う場合は chaff.yaml の genre を直してください。
一覧: npx chaffjs genres
次:
npx chaffjs . この場所の Markdown を全部見る
画面のパスは短くしてあります。作られる chaff.yaml の中身は次のとおりです。
# chaff.yaml — このチームの文章規範
#
# ここに書くのは「既定から変えたもの」だけです。書かなければ既定で動きます。
# このファイルを消しても chaff は動きます。
#
# 値は 4 つの言葉から選びます。数字を書く必要はありません。
#
# strict きびしく見る
# normal ふつう(既定)
# relaxed ゆるく見る
# off 見ない
#
# コマンドでも変更できます。理由がコメントとして自動で残ります。
#
# npx chaffjs relax bold-density --why "図の説明で太字を多用するため"
# npx chaffjs explain bold-density そのルールの意図を読む
# npx chaffjs rules --json AI に設定を書かせるときに渡す
# この場所に置く文書の種類(契約書・利用規約・プライバシーポリシー)。ほかの種類: npx chaffjs genres
genre: legal/contract
# チームが書く固有名詞(組織名・製品名)。1 つの名前として読み、漢字の連なりに数えない。
# names:
# - 個人情報保護委員会
# このジャンルの指針(よい文書が満たすこと)。検査の結果より先に出て、AI が書き直すときの指示になる。
# 既定は chaff に入っている文。replace で置き換え、add で足し、off で出さない。
# guide:
# legal/contract:
# add:
# - 金額は税込みか税抜きかを書いているか
# 既定から変えたものだけを書く。
rules:
一緒にできる .gitignore には .env* が入っています。意味を読む検査の API key を .env に書いても、うっかり commit しないためです。
ジャンルと言語を決める
ジャンルは文書の種類です。次の 3 つを決めます。
| 何を | どう |
|---|---|
| 動かすルール | その種類の書き方を咎めるだけのルールを止めます(契約書は定義した言葉をわざと繰り返します)。人の書いたその種類の文書の大半に出るルールも、測って止めます |
| 閾値 | 同じ normal でも、法令や論文の一文はメールより長くてよい |
| 文書の読み方 | legal/statute は法令の書き方の知識で読みます(下の「文書の種類を決める」) |
ジャンルが止めたルールは、「動いていません」の一覧にジャンルを理由に出ます。たとえば ngram-repetition(ジャンル legal/contract では見ないため) です。
chaff.yaml の rules はジャンルより強いので、ngram-repetition: normal と書けば動きます。どのジャンルが何を変えるかはジャンルのページに、一覧の表ははじめかたにあります。
genre を書かなければ、文書の front matter の genre:(か type:)、パス、内容の順に決まります。
front matter は、その文書 1 つだけを別の種類にしたいときに使えます。chaff.yaml の genre か、当たる by_path があればそちらが勝ちます。強い順に、--genre、by_path、chaff.yaml の genre、front matter、パス、内容です。
README.md、*-spec.md、docs/ は技術文書として見ます。決めた根拠は、画面の 1 行目に出ます。
chaff-spec.md technical/spec · 日本語 ジャンルはパスから
どれでも決まらなければ blog/tech として見ます。別の種類に見えるときは、画面が見当を出します(「契約書・規約のようです。—genre legal/contract を試せます」)。見当を出しても、見るジャンルは変えません。違うときは genre に書きます。
一覧は npx chaffjs genres で、ジャンルごとに何向けかと一緒に見られます。一覧に無いジャンルを書くと、chaff は何も検査せずに止まり、どこに書いたジャンルかを言います。文書の front matter の genre: が一覧に無いときは使わず、そう言ってから、書いていないときと同じようにジャンルを決めます。言語も自動で決まり、language に ja か en を書けば固定できます。言語の決まり方、日英の混ざった文書、どのルールがどの言語で動くかは言語にあります。
文書の種類を決める
法令のように、書き方に決まりのある文書があります。法令は番地を第二十二条第二項と漢数字で書くので、これを漢字の続けすぎと数えると誤りになります。そうした種類ごとの知識は、chaff に同梱した設定ファイル(profiles/*.yaml)に書いてあり、選ばれた文書にだけ効きます。いま同梱しているのは、日本語の法令(statute)、日本語の不動産の物件広告(listing)、日本語の不動産の売買の精算書(property-sale)、日本語の詩(verse)、日本語の戯曲(play)です。物件広告では、速さを書いていない徒歩の分数も、業界の決まりの速さで距離と比べます。精算書では、「消費税込み」とだけ書いて税率を書いていない仲介手数料も、仲介手数料にかかる消費税 10% で計算を確かめます。詩では行の終わりで文を切ります。句点の無い行が続く連を、一つの長い文として読みません。戯曲では、括弧に入れたト書きを、前後の台詞と別に文に分けます。
書かなければ、ジャンルが決めます(legal/statute は statute で、literature/poetry は verse で、literature/play は play で読みます)。ジャンルも決めていなければ、内容から決まります。「第一条 」のように条で始まる行が 3 行以上あれば、法令として読みます。交通・賃料・敷金・間取りのような物件概要の項目の行が並んでいれば、物件広告として読みます。「決済日:」のように、売買代金・手付金・残代金・仲介手数料・売買契約日・決済日の項目で始まる行が 4 行以上あれば、精算書として読みます。代金の 3 行(売買代金・手付金・残代金)だけでは選びません。決まった種類は、chaff tree の 1 行目に出ます。
$ npx chaffjs tree draft.txt
(doc :language "ja" :path "draft.txt" :profile "statute" :line 1
…
違うときは profile に書きます。none と書くと、内容からも選びません。パスごとに変えるときは、by_path にも profile を書けます。同梱していない種類を書くと、chaff は何も検査せずに止まり、どこに書いた種類かと、使える種類を言います。
profile: statute
既定で動くルール
ほとんどのルールは、chaff.yaml に何も書かず、--experimental も付けずに動きます。どれを既定で動かすかは、人が書いた実際の文書でそのルールがどれだけ指摘を出すかを測って決めています(リファレンスの「どのルールが既定で動くか」)。
| 印 | どう動くか |
|---|---|
| 既定で動く | 人の文書にめったに出ないルールです。そのまま指摘します |
| 既定で動く(情報) | 人の文書にもよく出るルールです。指摘は info(情報)として出て、実行を失敗にしません |
| ジャンルで止める | そのジャンルの文書の大半に出るルールです。「動いていません」の一覧に、ジャンルを理由に出ます |
| 試験中 | まだ測っていない新しいルールです。--experimental か rules に強さを書くと動きます |
情報の指摘を減らしたいときは、ゆるくするか、止めます。
npx chaffjs relax ngram-repetition --why "言い回しの繰り返しは文体として許す"
npx chaffjs off ngram-repetition --why "このチームでは見ない"
ジャンルが止めたルールを動かすときは、rules に強さを書きます(max-sentence-length: normal)。
ルールの強さを変える
どんなルールがあるかは、リファレンス に例と一緒に並べてあります。ルールの強さは、rules の下に 4 つの言葉で書きます。
rules:
bold-density: relaxed
| 値 | 意味 |
|---|---|
strict | きびしく見る |
normal | ふつう(既定) |
relaxed | ゆるく見る |
off | 見ない |
言葉の裏にある数字は、explain で見られます。
$ npx chaffjs explain max-sentence-length
一文が長すぎる (max-sentence-length)
長い文は、読んでいるうちに主語を見失います。
直しかた: 接続助詞のところで 2 文に割ってください。それだけで読めるようになります。
書き直しの深さ: light(語と文を直す。構成と文体は残す)。chaff fix-plan --depth でこれより浅い深さを選ぶと、計画はこのルールを直さず、名前だけを挙げます。
設定できる値:
strict 一文 70 字まで
→ normal 一文 100 字まで
relaxed 一文 140 字まで
off 見ない
この数字は 既定 のものです。ほかに technical / blog / blog/essay / business / business/email / docs / legal / legal/statute / legal/judgment / academic で別の数字を持っています。
いまは normal です。
変える: npx chaffjs relax max-sentence-length --why "理由"
同じ normal でも、ジャンルによって数字が違います。数字ではなく言葉で書くのは、ジャンルに合った数字を chaff に選ばせるためです。
試験中のルールを 1 つだけ動かす
試験中のルールは、既定では動きません。
rules に名前を書くと、そのルールだけが動きます。
--experimental は、試験中のルールをすべて一度に動かします。
rules:
announced-count-mismatch: normal
enable は、relax と同じように、ルールの説明のコメントを付けてこの行を書きます。
$ npx chaffjs enable announced-count-mismatch
announced-count-mismatch を normal にしました(…/chaff.yaml)
検査は、動かしたルールを一度だけ言います(試験中の rule を 1 件、設定により有効にしています: announced-count-mismatch)。止まっている試験中のルールの explain と、動いていないルールの一覧にも、同じコマンドが出ます。
数えるもののないルール
番号の抜けや、日付と曜日の食い違いは、あるか無いかだけです。数える上限がありません。こうしたルールでは、4 つの言葉が指摘の重さを変えます。relaxed にしても指摘は消えず、一段軽く出ます。
| ルール | strict | normal | relaxed |
|---|---|---|---|
numbering-gap dangling-reference date-weekday-mismatch total-mismatch | (無い) | エラー | 注意 |
duplicate-definition date-order doubled-word announced-count-mismatch dangling-figure-reference date-range-reversed percent-sum-mismatch unfilled-placeholder agreement-slip | エラー | 注意 | 参考 |
エラーが 1 件でも残ると、chaff は失敗で終わります。注意と参考だけなら成功で終わります。
explain でも、数字の代わりに重さが出ます。
$ npx chaffjs explain numbering-gap --genre legal/statute
…
設定できる値(数える上限は無く、指摘の重さが変わります):
→ normal エラー
relaxed 注意
off 見ない
…
見なくてよいときは off で止めます。数を書いても normal として動くだけなので、chaff は設定を読んだときにそう言います。
4 つで足りないとき
手本にしたい文章が relaxed より長いこともあります。そのときは、上限を正の数で書けます。
rules:
max-sentence-length: 260
数で書いたルールの強さは normal として扱われます。いま効いている数は、後で説明する chaff rules --json で確かめられます。
チームの表記を揃える
同じものを「サーバー」とも「サーバ」とも書くと、読み手は別のものかと迷います。使わない書き方と使う書き方の組を、prefer に並べます。
prefer:
サーバー: サーバ
左の書き方を見つけると、preferred-term が右の書き方に直すよう指摘します。左が辞書形の動詞(行う: おこなう)なら、活用した形(行います・行った)も見つけます。
prefer に何も書かなければ、何も言いません。
社内用語と必須の見出しを決める
jargon と required_sections は、チームが中身を決めるルールです。
chaff は中身を持たず、chaff.yaml に書いたものだけを見ます。何が社内用語かも、何の節が必須かも、組織ごとに違うためです。
jargon: # 社内でしか通じない語
- 横展開
- 握る
required_sections: # この種類の文書に無いと困る見出し
- リスク
- 費用
1:1 error 「リスク、費用」の見出しがありません
required-sections
5:1 warning 「横展開」は社内でしか通じないかもしれません(そういう語が 3 箇所)
internal-jargon
| 項目 | 当たりかた |
|---|---|
jargon | 辞書形の動詞(握る・巻き取る)は、活用した形(握った・巻き取ります)にも当たります。名詞(巻き取り)は書いた形のまま当たります。語幹(巻き取)でも書けます |
required_sections | 見出しとの部分一致です。リスク は「リスクと対策」でも満たされます |
required_sections の既定の重さは error です。好みの問題ではなく、チームが決めたのに守られなかったことを表すからです。
チームの固有名詞を並べる
組織の正式名称は、漢字が長く続いても割れません。「個人情報保護委員会」を「個人情報保護の委員会」とは書けません。辞書は正式名称を普通の名詞に割って読むので(厚生+労働省)、chaff には名前かどうかが分かりません。何が自分たちの名前かはチームが知っているので、names に並べます。
names:
- 個人情報保護委員会
- 国土交通省鉄道局総務課
- Bank of England
並べた名前は、書かれたとおりの綴りで当たり(大文字と小文字は区別します)、次のルールで 1 つの名前として読みます。
| ルール | 並べた名前の扱い |
|---|---|
max-kanji-continuous | 漢字の連なりの中の名前は数えません。名前の前後に続く漢字は、それぞれ別の連なりとして数えます |
ngram-repetition | 名前にかかる語句は、繰り返しに数えません。名前を何度書いても言い回しの繰り返しにはなりません |
undefined-acronym | 並べた名前(JAXA)と、名前の中の略語(NTT Docomo の NTT)には展開を求めません |
proper-noun-density | 何語に割れても、1 つの名前を 1 つの固有名詞として数えます。名前は数えるので、密度の指摘は消えません |
名前の外に続く漢字は今までどおり数えます。「個人情報保護委員会事務局総務課長補佐」なら、「事務局総務課長補佐」を測ります。
names は名前の並びで書きます(2025 のような数も文字として読みます)。並びでない値や、名前として読めない項目は、実行のはじめに理由を示して読み飛ばします。
鉤括弧でまるごとくくった漢字の連なり(「英国大使館別荘記念公園」)は、names に書かなくても数えません。書き手が 1 つの名前として示しているからです。括弧の中にひらがなが混ざれば、中の連なりは今までどおり数えます。
パスごとに変える
一つの場所に、種類の違う文書が混ざることもあります。by_path で、パスごとに設定を変えられます。
genre: blog/tech
by_path:
- files: ["business-ja/**/*.md"]
genre: business/report
- files: ["blog-en/**/*.md"]
language: en
- files: ["laws/**/*.txt"]
profile: statute
後に書いたものが勝ちます。照合は設定ファイルのある場所からの相対です。どこで実行しても、結果が変わりません。
by_path は files を持つ項目の並びです。フォルダとジャンルの対(guides: docs/manual)のような別の形や、files の無い項目は使わず、検査の前にそう言います。
Markdown 以外のファイルも見る
フォルダを渡すと、chaff はその中の Markdown(.md .markdown .mdx)を検査します。
include に書いたファイル名の glob に合うファイルも、あわせて検査します。--include を付けると、その実行だけ同じことをします。
include:
- "*.yaml"
- "*.txt"
npx chaffjs tests/fixtures/ --include "*.yaml"
YAML(.yaml .yml)は、文字列の値だけを読みます。キー、引用符、コメント、数や true は文章ではありません。値は 1 つずつ別に読みます。指摘はファイルの行と桁を指すので、custom_rules の pattern でテストの期待値に残った TODO: も見つけられます。
YAML として読めないファイルは、ただのテキストとして読みます。
.txt のようなほかのファイルも、ただのテキストとして読みます。コマンドラインで名前を挙げたファイルは、拡張子にかかわらず検査します。
直す計画の深さを決める
chaff fix-plan の計画をどこまで深く書き直すものにするかを、チームで決めておけます。
fix_plan:
depth: light # light / structure / register
light は語と文だけ、structure は節・見出し・段落の組み替えまで、register は文体を変えるところまでです。深いほうは浅いほうを含みます。決めた深さより深いルールの指摘は、計画に名前と箇所の数だけが並びます。
--depth を付けると、その実行だけ --depth が勝ちます。
3 つ以外の値を書くと、fix-plan は計画を出さずに止まり、使える値とその意味を言います。それぞれのルールの深さは npx chaffjs explain <ルール> と npx chaffjs rules --json の rewrite_depth で見られます。直し方との関係は「AIっぽさを直す」にあります。
ジャンルの指針を変える
ジャンルにはそれぞれ指針があります。その種類のよい文書が満たすことを、原稿を確かめる問いの形で並べたものです。ジャンルを決めると、chaff は指摘より先に指針を出し、fix-plan も指針から始めます(例)。
chaff に入っている文は既定です。チームは guide: の下で変えられます。キーはジャンルか群で、群(legal)に書くと legal/* のどのジャンルにも効きます。
| 書き方 | すること |
|---|---|
add: | いまの行の後に足す |
replace: | 書いた言語の行を置き換える。書かなかった言語は元のまま |
off | そのジャンル(群なら群のどのジャンルも)の指針を出さない |
行は ja: と en: の下に並べます。言語を分けずに 1 行か並びだけを書くと、両方の言語に効きます。
guide: off と書くと、どの指針も出しません。次の chaff.yaml は、契約書の指針に 1 行足し、説明書(マニュアル・FAQ・用語集)の指針を止めます。
genre: legal/contract
guide:
legal/contract:
add:
ja:
- 支払いの期限を、いつから数えて何日と書いているか
docs: off
$ npx chaffjs keiyaku.md
指針: 契約書・規約(legal/contract)
下の指摘より先に、原稿がこれを満たしているかを確かめてください。
- 当事者の呼び方(甲・乙、定義した名前)を、最後まで同じ形で使っているか
- 定義語を一度だけ定め、定義どおりの形で使っているか
- 義務ごとに、誰が・何を・いつまでにするかが読み取れるか
- 金額・日付・期間・条番号の参照が、条項どうしと別表とで一致しているか
- 解除・損害賠償・準拠法・紛争の解決を定めた条項があるか
- 支払いの期限を、いつから数えて何日と書いているか
このジャンルで特に効く rule: dangling-figure-reference, dangling-reference, date-range-reversed, date-weekday-mismatch, defined-name-repeated, defined-term-form, duplicate-definition, numbering-gap, obligation-without-deadline, party-order, party-role-name, requirement-smell, toki-as-condition, total-mismatch, undefined-term, vague-deadline
書いたところ: genres.yaml → chaff.yaml
chaff.yaml の guide: で書き換えられます。出さないときは --no-guide。
════════════════════════════════════════════════════════════
keiyaku.md legal/contract · 日本語 ジャンルは chaff.yaml から
…
「書いたところ」の行は、指針を書いた場所です。チームの行と chaff の行を見分けられます。次は、契約書の指針をチームの確認項目に置き換える例です。
genre: legal/contract
guide:
legal/contract:
replace:
ja:
- 当事者を最後まで「甲」「乙」と書いているか
- 支払いの期限を、いつから数えて何日と書いているか
- 合意管轄を東京地方裁判所としているか
$ npx chaffjs keiyaku.md
指針: 契約書・規約(legal/contract)
下の指摘より先に、原稿がこれを満たしているかを確かめてください。
- 当事者を最後まで「甲」「乙」と書いているか
- 支払いの期限を、いつから数えて何日と書いているか
- 合意管轄を東京地方裁判所としているか
このジャンルで特に効く rule: dangling-figure-reference, dangling-reference, date-range-reversed, date-weekday-mismatch, defined-name-repeated, defined-term-form, duplicate-definition, numbering-gap, obligation-without-deadline, party-order, party-role-name, requirement-smell, toki-as-condition, total-mismatch, undefined-term, vague-deadline
書いたところ: genres.yaml → chaff.yaml
chaff.yaml の guide: で書き換えられます。出さないときは --no-guide。
════════════════════════════════════════════════════════════
keiyaku.md legal/contract · 日本語 ジャンルは chaff.yaml から
…
指針を変えられる場所は 4 つあります。強い場所ほど後から重ねるので、弱い場所の行を置き換えたり足したりできます。
| 場所 | 強さ |
|---|---|
chaff.yaml の guide: | いちばん強い |
style: で選んだスタイルの guide: | |
ルールの束の manifest の guide:、コードのプラグインの guide | |
chaff に入っている genres.yaml | いちばん弱い |
1 つの場所の中では、guide: off、群の書き方、ジャンルの書き方の順に重ねます。知らないジャンルや群、読めない書き方は標準エラーに出し、残りはそのまま効かせます。
npx chaffjs rules --json --genre legal/contract の guide に、いま効いている指針が出ます。from は書いた場所の並びです。
設定が効いているか確かめる
いまの設定は、chaff rules --json で確かめます。今の値・使える値・なぜ今 off なのか・変更コマンドが 1 つに入っています。
AI に設定を書かせるときは、この出力を渡します。
npx chaffjs rules --json いまの設定を JSON で出す
出力は長いので、ここでは max-sentence-length: 260 と書いたときの 1 件を抜き出します。
{
"id": "max-sentence-length",
"layer": "L1",
"status": "stable",
"name": {
"ja": "一文が長すぎる",
"en": "Sentence too long"
},
"why": {
"ja": "長い文は、読んでいるうちに主語を見失います。",
"en": "In a long sentence the reader loses the subject before reaching the verb."
},
"how_to_fix": {
"ja": "接続助詞のところで 2 文に割ってください。それだけで読めるようになります。",
"en": "Split it in two at the conjunction."
},
"use_for": [
"blog",
"business",
"technical",
"legal",
"docs",
"academic",
"literature",
"speech"
],
"level_sets": "limit",
"levels": {
"strict": 70,
"normal": 100,
"relaxed": 140
},
"levels_you_can_set": [
"strict",
"normal",
"relaxed",
"off"
],
"your_setting": {
"level": "normal",
"limit": 260,
"from": "…/chaff.yaml"
},
"now": {
"level": "normal",
"limit": 260,
"set_as": "number"
}
}
now が、いま実際に効いている値です。
level_sets は、段階が何を変えるかです。limit は数える上限、severity は指摘の重さです。数えるもののないルールでは、levels と now に数の代わりに重さ(error / warning / info)が出ます。試験中のルール(まだ測っていない新しいルール)なら、now に動いていない理由と、そのルールだけを動かすコマンドが出ます。
"now": {
"level": "off",
"why_off": "experimental な rule は既定で動かさない",
"turn_on_with": "npx chaffjs enable <rule>"
}
書き間違いは、画面にも出ます。知らないルール名(たいていは綴り違い)と読めない値は、検査と rules --json が stderr に警告します。黙って捨てると、効いていない設定を効いていると思い込むためです。
rules:
max-sentense-length: strict
bold-density: loose
$ npx chaffjs article.md
chaff: …/chaff.yaml: max-sentense-length というルールはありません(npx chaffjs rules --json で一覧が出ます)
chaff: …/chaff.yaml: bold-density の値 "loose" は読めません(strict / normal / relaxed / off か、正の数)
…
警告が出たら、chaff.yaml の綴りと値を直します。