ルールを足す:AI やエンジニアに頼むとき

チームの書き方の決まりを、chaff が確かめられる形にする方法をまとめました。 AI やエンジニアに頼むときは、このページをそのまま渡せます。道は 4 つあり、上から順にやさしく、下ほど細かいことができます。

4 つの道

道向いているとき書く場所使える時期
組み込みルールの設定を変える既にあるルールで足りる(上限、表記の組、社内用語、必須の見出し)chaff.yamlいま
語や正規表現のルール決まった語句や文字の並びに、チームの言葉で指摘を付けたい(「弊社」を社外向けに使わない、など)chaff.yaml の custom_rules0.18.0
形態素解析のルール品詞や活用で決まる書き方(名詞のあとの「する」など)chaff.yaml の custom_rules0.18.0
Node の関数のルール数える・比べるなど、上の 3 つで書けないもの.mjs のファイルと custom_rules0.19.0

ほかのチームとも使うルールは、プラグインのパッケージにまとめて配れます。プラグインを作る にあります。

まず、既にあるルールで足りないかを確かめます。どんなルールがあるかは、リファレンス に例と一緒に並べてあります。端末では、次のコマンドで一覧が出ます。

npx chaffjs rules          ルールの一覧を、グループごとに表で出す
npx chaffjs rules --json   同じ一覧を、AI が読む形(JSON)で出す

書き方の決まりから chaff.yaml を作る

AI に頼むときの手順です。AI には npx chaffjs rules --json の出力を読ませます。この出力には、ルールごとに、見つけるもの(summary)、段階の意味(level_meaning)、例、ジャンルごとの動き(genres)が入っています。推測でルールの名前や数を書かせないためです。

順番AI がすること
1チームの決まりを、一つずつに分けて読む
2決まりごとに、summary と level_meaning が合うルールを選ぶ。数の決まりは、段階の数と比べて段階を選ぶか、数をそのまま書く
3文書の種類が分かれば genre を決め、genres でそのジャンルで動くかを確かめる
4表記の組は prefer、社内用語は jargon、必須の見出しは required_sections に書く
5既定と同じものは書かない。書いたら explain と、決まりに沿った短い見本で確かめる

同じ手順は、rules --json の how_to_write_settings_from_a_style_note にも入っています。 AI に渡すのは、JSON だけでも足ります。

例:「です・ます、1文80字まで」

技術資料の決まりが「です・ます、1文80字まで」だとします。 chaff.yaml に language: ja を書いてから rules --json で max-sentence-length を見ると、次のように出ます(一部)。数は言語で数え方が違うので、決まりの言語で見ます(英語なら語で数えます)。

{
  "id": "max-sentence-length",
  "summary": { "ja": "読み終える前に主語を見失うほど長い一文" },
  "level_meaning": { "ja": "一文 {limit} 字まで" },
  "levels": { "strict": 70, "normal": 100, "relaxed": 140 }
}

80 字はどの段階とも違うので、数をそのまま書きます。「です・ます」は no-mixed-desumasu が見ます。ただし、このルールは、どちらの調子が正しいかを決めず、混ざった文だけを指します。全部を「だ・である」で書いた文書は指しません。この違いは、チームに伝えておきます。

language: ja
genre: technical/spec

rules:
  # 1文80字まで
  max-sentence-length: 80
  # です・ます(だ・である が混ざったら指摘する)
  no-mixed-desumasu: normal

決まりに沿わない見本を一つ作って、かけてみます。実際の出力です。

$ npx chaffjs sample.md --compact

sample.md   technical/spec · 日本語   ジャンルは chaff.yaml から

  3:1     info    この文は 84 文字あります(80 文字まで)
                  max-sentence-length
  5:31    info    この文だけ他と文末の調子が違います(本文の中で 1 文)
                  no-mixed-desumasu

指摘 2 件、動いていない rule 77 件

どちらの決まりも指摘されたので、設定が効いています。指摘が出ないときは、npx chaffjs rules の表で、そのルールがいま off になっていないかを見ます。

組み込みルールの設定を変える

いちばんやさしい道です。書くのは chaff.yaml だけで、chaff のコードには触りません。

変えたいこと書き方
ルールの強さrules に strict normal relaxed off のどれかを書く
上限の数rules に数を書く(max-sentence-length: 80)
試験中のルールを動かすrules に段階を書く(doubled-word: normal)
表記を揃えるprefer に「使わない書き方: 使う書き方」の組を書く
社内用語を指すjargon に語を並べる
必須の見出しrequired_sections に見出しを並べる

書き方の細かいところは 設定 にあります。書いたあとは npx chaffjs rules の表で、そのルールがいまどの段階で動くかを確かめます。

ルールの細かい設定(0.18.0)

段階のほかに、ルールごとの設定(options)を書けます。いま設定を持つのは、カタカナ語の語末の長音を見る katakana-long-vowel です。たとえば「IEICE 準拠」なら、3 音以上の語は語末に「ー」を付けない決まりなので、次のように書きます。

rules:
  katakana-long-vowel: normal
options:
  katakana-long-vowel:
    ending: drop      # 語末の「ー」を付けない(コンピュータ、メモリ)
    min_morae: 3      # 3 音以上の語だけ
    except: [カー]     # この語はどちらの書き方でも指さない

style: ieice と 1 行書けば、同じ設定になります。どの設定があり、どの値を書けるかは、npx chaffjs explain katakana-long-vowel と rules --json に出ます。詳しくは チームの表記ルールを決める にあります。

語と正規表現のルール(0.18.0)

決まった語句や文字の並びを、チームの言葉で指摘するルールです。chaff.yaml の custom_rules に書きます。 words は語の一覧か「使わない書き方: 使う書き方」の組です。pattern は正規表現です。どのルールにも name(名前)、why(理由)、how_to_fix(直し方)、example(直す前と後)を書きます。指摘を読む人が、なぜ直すのかを分かるようにするためです。

custom_rules:
  - id: no-heisha
    type: words
    words: [弊社]
    name: 社外向けの文書で「弊社」と書いている
    why: 社外向けの文書では「当社」と書くと決めています。
    how_to_fix: 「当社」に直してください。
    example:
      before: 弊社の新製品をご紹介します。
      after: 当社の新製品をご紹介します。
  - id: date-with-slash
    type: pattern
    pattern: '\d{4}/\d{1,2}/\d{1,2}'
    name: 日付を「/」で書いている
    message: 「{matched}」は「2026年10月1日」の形で書きます
    why: 日付の書き方をそろえると、読み手が迷いません。
    how_to_fix: 「年」「月」「日」を使って書いてください。
    example:
      before: 締め切りは 2026/10/1 です。
      after: 締め切りは 2026年10月1日です。

words は書いたとおりの語に当たります。活用する語は、次の形態素解析のルールで書きます。書いたルールは、組み込みのルールと同じく rules で強さを変えられ、stet で一か所だけ黙らせられます。 pattern に ignore_case: true を添えると、大文字と小文字を区別せずに当てます。正規表現は動かす前に確かめ、長い行で止まらなくなる形((a+)+ など)は断ります。

形態素解析のルール(0.18.0)

語の品詞や、活用する前の形(原形)で当てるルールです。「〜することができる」を「〜できる」に、のような決まりは、文字だけでは活用のたびに書き分けが要ります。形態素解析で分けた語の並びで書けば、一つで足ります。条件は pos(品詞)、base(原形。lemma とも書けます)、surface(書かれたままの形)です。

custom_rules:
  - id: suru-koto-ga-dekiru
    type: tokens
    tokens:
      - { lemma: する }
      - { surface: こと }
      - { surface: が }
      - { lemma: できる }
    name: 「することができる」は「できる」で足りる
    message: 「{matched}」は「できる」で足ります
    why: 「確認することができます」は「確認できます」と同じ意味で、語が増えるだけです。
    how_to_fix: 「〜できる」に書き換えてください。
    example:
      before: 画面で確認することができます。
      after: 画面で確認できます。

どう分かれるかは文によって違います。書いたら、当てたい文と当てたくない文の見本で確かめます。

Node の関数のルール(0.19.0)

数える・比べるなど、上のどれでも書けないものは、Node の関数で書きます。関数は文書を受け取り、指摘の一覧を返します。chaff.yaml には、ほかのルールと同じく名前・理由・直し方・例を書き、 type: module と、関数のファイルの場所(module)を書きます。場所は chaff.yaml から見た相対パスです。

custom_rules:
  - id: team-no-tbd-dates
    type: module
    module: ./chaff-rules/no-tbd-dates.mjs
    level: warning
    name: 未定のままの日付
    why: 読み手は日付をもとに予定を立てます。
    how_to_fix: 日付を書くか、決める人と期限を書きます。
    example:
      before: 公開日は未定です。
      after: 公開日は 10 月 1 日です。

ファイルは、関数を export default します。指摘は、文書の中の位置(start と end)で返します。

// chaff-rules/no-tbd-dates.mjs
export default (doc) =>
  doc.sentences.flatMap((sentence) => {
    const at = sentence.text.indexOf("未定");
    return at === -1 ? [] : [{ start: sentence.span.start + at, end: sentence.span.start + at + 2 }];
  });

品詞を読むルールは requires: [pos] を、語の一覧を読むルールは word_list を書きます。関数に渡るもの、返すもの、テストの書き方、壊れたときの出力は プラグインを作る にあります。 chaff.yaml と同じフォルダより外にあるファイルは、絶対パスで書いたときだけ読みます。読み込むと、そのコードが動くためです。

チームのルールを chaff のルールと同じ形で書く

custom_rules のルール(プラグインのルールも)は、chaff のルールのファイルが持つ欄も書けます。どれも省けます。省けば、これまでのチームのルールと同じに読みます。

custom_rules:
  - id: no-tbd
    type: words
    words: [TBD]
    name: { ja: TBD が残っている, en: TBD left in }
    why: { ja: 読み手が動けません。, en: A reader cannot act on it. }
    how_to_fix: { ja: 決めたことを書きます。, en: Write what was decided. }
    levels: { strict: error, normal: warning, relaxed: info }
    use_for: [business]
    group: slips
    summary: { ja: 決めずに残した TBD, en: A TBD nobody resolved }
    example:
      ja: { before: 期限は TBD。, after: 期限は 5 月 1 日。 }
      en: { before: Due TBD., after: Due 1 May. }
    rewrite:
      depth: light
      ja:
        direction: TBD を決めたことに置き換えます。決まっていなければ書き手に聞きます。
        pairs: [{ before: 期限は TBD。, after: 期限は[日付]。 }]
        keep: [文のほかの部分]
        avoid: [日付を作る]
欄決めること書かなければ
levelsstrict・normal・relaxed ごとの重さ。normal は必須です。level とどちらか一つにします。チームのルールは箇所を出すので、数は書けませんlevel、それも無ければ warning
use_forこのルールを使うジャンル、またはその頭(business)すべてのジャンル
groupchaff の分類のどこに並べるか(slips、wording など)team
summary何を見つけるかを一行で名前
example{ before, after } か、言語ごとの組どちらかの形で必須
rewritechaff fix-plan が直す人に渡す方向と、その深さ depth(AIっぽさを直す)how_to_fix

chaff のルール、チームのルール、プラグインのルールは一か所で確かめるので、同じ間違いには同じ文が出ます。 chaff の知らない値(無い分類の group、どのジャンルでもない use_for、深さでない depth)は実行を止め、その値を名指しします。

chaff 本体にルールを足す

どのチームにも役立つルールは、chaff 本体に足せます。 chaff の決まりで、ルールは機械で決まるものだけです。意味を読まないと決まらないものは、chaff test の側に置きます。足すときに書くものは、次のとおりです。

書くもの場所中身
ルールの定義packages/chaff/rules/<id>.yaml名前・理由・指摘の文・直し方・段階を日本語と英語で。読み手向けの group summary example not_flagged も書く。段階が数で変わるルールは level_meaning も。論文や規格に拠るルールは、参考文献 の項目の印を sources に並べる。ルールのページから、その項目へリンクが張られる。書き直される箇所を指すルール(AIっぽさ、読みやすさ)は rewrite も書く。言語ごとに、直す方向(direction)、自分で書いた直す前と後の組を 2〜3 個(pairs)、変えてはいけないもの(keep)、書き直す人がやりがちな間違い(avoid)
見つける処理packages/chaff/src/detectors/文書を受け取って指摘を返す関数。登録は専用のファイル detectors/registry/<how_to_find>.ts で、関数を detector という名前で出す。検出器の共有の一覧は書き換えない
語の一覧packages/lang-ja/lexicons/ と packages/lang-en/lexicons/語の一覧で見つけるルールだけ。言語ごとに書く
テストtest/test_<id>.ts指摘すべき例と、指摘してはいけない例の両方
見本への仕込みtest/fixtures/bench/plants/<id>.yaml と scripts/bench-plants/ のモジュールきれいな見本に誤りを一つ入れて、見つかるかを測る。YAML には planted: [ja, en](仕込む言語)か、仕込めない理由の not_planted: を書く。モジュールは仕込む誤りを MUTATIONS として出す。どちらも共有の一覧ではない
PR の題pull request何が見つかるようになったか。ルールの PR は docs/ChangeLog.md を書き換えない。ChangeLog は、リリースのときに、前のリリースから取り込んだ PR の題から書く

一度に渡した複数のファイルどうしを比べるルール(あるファイルだけ違う書き方の語など)は、別の種類の関数で書きます。関数は、その回の文書をすべて受け取り、指摘を、それがあるファイルのパスと一緒に返します(型は CrossDetector)。登録は detectors/cross-registry/<how_to_find>.ts で、ルールの定義には requires: [documents] を書きます。

chaff は、ファイルを二つ以上かフォルダを渡したときだけこのルールを動かします。一つだけのときは、その理由を添えて「動いていないルール」に並べます。指摘はどれも自分のファイルの行と桁を指すので、stet、baseline、SARIF はほかの指摘と同じに扱います。 example には、before と after と同じ回に渡す三つ目のファイル other: を書きます。

どのジャンルで動くかも、ルールの定義に書きます。use_for に、見る group とジャンルを並べます。わざとそう書くジャンル(法務の「〜を行う」など)でルールを止めるときは、off_for にその group かジャンルと、止める理由を書きます。

use_for: [business, legal, docs]
off_for:
  legal: 'Drafting writes "in the event that" and "for the purpose of" on purpose.'

止めたルールは、そのジャンルを理由にして「動いていないルール」に並びます。 genres.yaml には、ジャンルそのものについてのこと(名前、説明、文書の種類、見分け方、ジャンルが決める強さ)だけを書き、新しいルールのためには書き換えません。

新しいルールは status: experimental で入ります。測るまでは、--experimental を付けたときだけ動きます。 yarn rules:measure --apply が置き場所を決めます。corpus/rules-measure-documents.json に固定した corpus の文書に chaff をかけるので、どの手元でも同じ文書で測ります。先に yarn corpus:fetch を走らせます。固定した文書が欠けていると止まり、取ってきた文書でも固定していないものは使いません。人の文書にどれだけ指摘が出るかから、ルールの status と severity を書きます。測って止めた group は、off_for に measured by yarn rules:measure と書かれます。ルールの置き場所が corpus/rules-measure.json の測った結果と合わなくなると、yarn test が止まります。

指摘がどれだけ正しいかでも決めます。isamu/chaff-corpus では、実際の文書に chaff が出す指摘を一つずつ、人が正しい・誤検出と判定しています。 yarn rules:precision --corpus <checkout> はその corpus に手元の chaff(先に yarn build)をかけ、ルール・ジャンルの group・言語ごとに、判定済みの指摘のうち正しかった数を corpus/rules-precision.json に書きます。判定していない指摘は数えません。note が extraction artefact: か dataset artefact: で始まる判定も数えません。文書の取り出し方の問題で、ルールの問題ではないからです。

ある group で判定済みの指摘が正しい割合が二割以下なら、そのルールはその group で止まります。ただし、3 つ以上の文書から 15 件以上の判定があり、その group でルールが指摘を出すどの言語でもそうなっているときだけです。 off_for は両方の言語でルールを止めるので、それを示す判定の無いまま指摘を出している言語があれば、group は止めません。この線を決めたとき、corpus の判定の正しさは低い塊と高い塊に分かれていて、線はその間にあります。判定が少ないと一件で割合が大きく動き、一つ二つの文書は、その文書だけの書き方から何百件も指摘を出すことがあります。 genres.yaml でその group のジャンルが手でルールを動かしているときは止めず、人が決めるように一覧に出します。ルールのファイルが precision_hold に理由つきで挙げた group も止めません。誤検出が一つの型にまとまっていて、ルールを止めるのでなく、その型を外すように直す予定の group です。 yarn rules:precision --apply はファイルを書き、yarn rules:measure --apply と同じように置き場所を決めます(どちらも両方のファイルを読みます)。止めた group は同じ形で off_for に書かれます。測るときは --apply が書いた止め(CHAFF_IGNORE_MEASURED_OFFS=1)を無視して chaff をかけます。止めたせいで、止めた根拠の指摘が見えなくなることはなく、次に測っても同じ置き場所になります。

手引きの画面に、ルールが増えるたびに変わる行は書き写しません。画面には、動いていないルールを並べる所に {not-run}(一覧の下のヒントも含みます)、--compact が最後に出す集計の行に {counts} と書きます。一覧から、あるルールの行だけを見せるときは {not-run: <rule>} と書きます。一覧はいちばん長い id に合わせて詰めるので、書き写した行はルールが増えるたびに変わります。サイトを作るときに、chaff の出力からどれも入れます。

yarn test は、どの画面もその文書にかけ直し、chaff の出力と違えば止まります。文書は、ページの file= の付いた塊と、site/src/screens/<言語>/<ページ>/ のファイルです。違いは node scripts/guide-screens.ts --check ja/<ページ>.md で見られます。

新しいルールが画面を変えたときは、yarn screens:update を走らせ、ページの差分を読みます。画面を chaff のいまの出力に書き直し、「…」の行はできるだけ元の場所に残します。ページを指定すると、そのページだけを直します(yarn screens:update ja/commands.md)。

scripts/guide-screens.ts の UNCHECKED に挙げた画面はかけられないので、そのまま残します。画面に指摘が増えたなら、ページの説明もあわせて直します。

ルールの定義に、そのルールに要る読み手向けの欄が欠けていると、yarn test が止まります。 example の before が指摘されないとき、after が指摘されるときも止まります。リファレンスのページに、動かない例が載らないためです。

最後に、実際の文書で確かめます。

yarn test     テストをすべて動かす
yarn bench    見本に仕込んだ誤りを、ルールが見つけるかを測る
yarn planted  誤りを仕込んだ文書の組と、そのきれいな版にかけて、種類ごとに見つけた数を出す
yarn corpus   集めた実際の文書にかけて、corpus/expected/ からの増減を見る

yarn test は、コアの数だけテストのプロセスを並べます。同じ手元でほかの作業コピーもテストするときは、CHAFF_TEST_JOBS=4 yarn test で 4 つに抑えます。

yarn planted は、test/fixtures/planted/<組>/ の組(contracts、email、reports、technical)にかけます。組は、一つのジャンルの自分で書いた文書の集まりです。どの文書にも、きれいな版と、誤りを仕込んだ版があります。組の manifest.json には、仕込んだ誤りの種類、行、それを指摘するはずのルールが並んでいます。二つの行の組を崩す誤り(卒業日より後の入学日など)は、組の相手の行を pairedLine に書けます。日付の組を比べるルールは後に書いた方の行で指すので、名前を挙げたルールの指摘なら、その二つの行のどちらでも見つけたと数えます。誤りの種類ごとに、その行で指摘できた数と、きれいな版に出た指摘を出し、組の expected.json と比べます。見つけた数が減った、きれいな版に記録に無い指摘が出た、記録にある種類や組が無くなった、仕込んだ数が変わった(manifest を書き換えた)ときは止まります。見つけた数が増えた、記録したきれいな版の指摘が消えたときは止まらず、良くなった所として出します。--update で記録するまで出し続けます。新しいルールが、組に仕込んだ種類の誤りを見つけるときは、その誤りを manifest に足します。まだどのルールも見つけない種類にも、見つけるはずのルールの名前が書いてあります。足したら yarn planted --update を走らせ、expected.json の差分を読みます。組の名前を渡すと、その組だけにかけます(yarn planted contracts)。

yarn corpus で増えた指摘は、一つずつ読みます。誤った指摘なら、その形を例にしてテストに足し、直します。増えた指摘が正しいと確かめたら、yarn corpus --update で corpus/expected/ を更新します。ルールごとに 1 ファイル(<id>.txt、文書ごとの件数)と、文書の一覧(_documents.txt)に分けてあります。新しいルールは自分のファイルを足すだけで、ほかのルールの PR と同じ行を書き換えません。見本の結果も同じで、yarn bench --update で test/fixtures/bench/expected/ を更新します。ルールごとに 1 ファイル(表の行と、仕込んだ誤りの結果)なので、新しいルールはここでも自分のファイルを足すだけです。