技術記事(Zenn・Qiita)でできること

技術記事は、自分が試したことや直したことを、ほかの開発者に向けて書く文書です。 chaff は、読み手を待たせる書き出しや、生成文によく出る形を機械で見つけます。ジャンルは blog/tech です。ジャンルを決めないときも、chaff はこのジャンルで見ます。

技術記事でよく起きること

技術記事は、AI に下書きを頼むことが増えました。そのまま出すと、生成文の形が残ります。

書き癖例
どの記事にも当てはまる書き出し「近年、開発の現場ではコンテナがますます当たり前になっています」
予告だけの文「ポイントは、」「重要なのは、」「正直に言うと、」
誰も言っていないことを打ち消す対比「これは単なる待ち時間ではありません」
数が合わない予告「直したことは以下の 2 つです」の下に、箇条書きが 3 つある
行き先の無いリンク記事の中に無い見出しへのリンク
決まり文句の結び「いかがでしたでしょうか」「お役に立てれば幸いです」

chaff が見ること、見ないこと

このジャンルで見る主なルールは、次のとおりです。

ルール見つけるもの動く条件
padded-introどの記事にも当てはまる書き出しいつも
closing-cliche決まり文句の結びいつも
announcing-opener「ポイントは、」のような予告で始まる文の重なりいつも(info)
contrast-framing「単なる X ではなく」のような対比の重なりいつも(info)
colon-lead-inコロンで箇条書きへ渡す文の多さいつも(info)
ai-generated-composite生成文の形が、一つの記事に 3 種以上そろったこといつも
announced-count-mismatch予告した数と、箇条書きの数の食い違いいつも
broken-link記事の中に無い見出しへのリンクいつも
quote-without-source人の言葉として引いたのに、段落に出典が無い引用いつも(info)
latin-spacing日本語と英数字の間の空白の、付け方の揺れいつも(info)
empty-conclusion中身の無い結びnpx chaffjs test で AI に読ませたとき

見ないことも決めてあります。

  • コードが動くかは見ません。コマンドや設定の値が正しいかは、書いた人が試して確かめます。
  • 数字が本当かは見ません。「9 分から 2 分」が測った値どおりかは、書いた人が確かめます。
  • 外のサイトへのリンクが開けるかは見ません。見るのは、記事の中の見出しへのリンクだけです。
  • 「AI が書いた」とは判定しません。生成文に多い形が重なった所を、読み返す目印として示すだけです。

かけてみる

準備がまだなら、先に準備するを済ませます。次の記事を kiji.md という名前で保存しました。この記事は、このページのために書いたものです。

# Docker のビルドを速くした話——キャッシュという見落とされがちな鍵

近年、開発の現場ではコンテナがますます当たり前になっています。本記事では、私たちのチームが Docker のビルド時間を短くした方法を詳しく解説していきます。

## 何が問題だったのか

CI でのビルドに、毎回**約 9 分**かかっていました。

これは単なる待ち時間ではありません。**開発の速さそのものを奪う問題**です。

正直に言うと、私たちは半年ほどこの問題を見ないふりをしていました。気づけば、待ち時間で一日に何十分も溶かしていました。

## 原因

ポイントは、Dockerfile の書く順番です。

調べて分かったことは次の通りです:

- `COPY . .` を `RUN yarn install` より前に書いていた
- そのため、ソースを1行変えるだけで依存の入れ直しが走っていた
- CI では前回のキャッシュを使っていなかった

重要なのは、キャッシュが黙って捨てられていたことです。ビルドは失敗せず、静かに遅くなっていました。

## 直したこと

直したことは以下の 2 つです:

- `package.json` と `yarn.lock` だけを先に `COPY` し、依存を入れてからソースを `COPY` する
- `docker build` に `--cache-from` を付け、前回のイメージをキャッシュとして使う
- `.dockerignore` に `node_modules` を足す

さらに、ビルドの結果を `docs/build-time.md` にまとめました。詳しくは[ビルド時間の記録](#ビルド時間の記録)を見てください。

## 結果

ビルドは 9 分から 2 分になりました。単に速くなっただけでなく、チームの生産性も飛躍的に向上しました。

いかがでしたでしょうか。この記事が皆さんのお役に立てれば幸いです。

ジャンルを付けてかけます。--compact は、1 件を 2 行にまとめて出す印です。

$ npx chaffjs kiji.md --genre blog/tech --compact

kiji.md   blog/tech · 日本語   ジャンルは --genre から

  1:39    info    節の長さのばらつきが 25% しかありません(35% 以上ほしい)
                  section-length-uniformity
  3:1     warning 「近年」は、どの記事にも当てはまる書き出しです
                  padded-intro
  3:1     warning 文の長さのばらつきが 35%(36% 以上ほしい)
                  sentence-rhythm
  9:1     info    「単なる」など、対比の枠が 1000 字あたり 3.8 個あります(1.5 個まで)
                  contrast-framing
  11:1    info    「正直に言うと」など、予告で始まる文が 3 個あります(3 個から)
                  announcing-opener
  11:1    warning 「section-length-uniformity、sentence-rhythm、padded-intro、closing-cliche、contrast-framing、announcing-opener、colon-lead-in」が同じ文書にそろっています(7 種、3 種から)
                  ai-generated-composite
  15:1    info    「ポイントは」など、予告で始まる文が 3 個あります(3 個から)
                  announcing-opener
  17:1    info    コロンで箇条書きへ渡す文が 1000 字あたり 3.8 個あります(2 個まで)
                  colon-lead-in
  20:12   info    日本語と後ろの数字のあいだを詰めています(この文書はふつう空ける。5 箇所のうち 1 箇所が違う)
                  latin-spacing
  20:13   info    日本語と前の数字のあいだを詰めています(この文書はふつう空ける。5 箇所のうち 1 箇所が違う)
                  latin-spacing
  23:1    info    「重要なのは」など、予告で始まる文が 3 個あります(3 個から)
                  announcing-opener
  27:1    info    コロンで箇条書きへ渡す文が 1000 字あたり 3.8 個あります(2 個まで)
                  colon-lead-in
  27:11   warning 「2 つ」と予告していますが、すぐ下の箇条書きは3項目です
                  announced-count-mismatch
  33:46   warning リンク「[ビルド時間の記録](#ビルド時間の記録)」が指す「#ビルド時間の記録」の見出しがこの文書にありません
                  broken-link
  37:22   info    「だけでなく」など、対比の枠が 1000 字あたり 3.8 個あります(1.5 個まで)
                  contrast-framing
  39:1    warning 「いかがでしたでしょうか」で締めています
                  closing-cliche
  39:13   warning 「お役に立てれば幸いです」で締めています
                  closing-cliche

指摘 17 件、動いていない rule 59 件

行の頭の 3:1 は、3 行目の 1 字目という意味です。 warning は直したほうがよい所、info は読み返す目印です。info は実行を失敗にしません。

最初に出る指針

ジャンルを決めると、chaff は結果の最初にそのジャンルの指針を出します。よい技術記事が満たすことを、原稿を確かめる問いの形で並べたものです。その下に、このジャンルで特に効く rule の名前が 1 行で出ます。

問いの多くはルールでは確かめられないので、指摘より先に読みます。 AI に書き直させるときも、AI は同じ指針に沿って書きます(AI にジャンルの指針を渡す)。

$ npx chaffjs kiji.md --genre blog/tech

指針: 技術ブログ(blog/tech)

  下の指摘より先に、原稿がこれを満たしているかを確かめてください。

  - 冒頭で、主張を 1 つに絞って言い切っているか(何が分かり、何ができるようになる記事か)
  - 手順とコードが、書いてあるとおりに実行して同じ結果になるか
  - 使ったバージョン(言語・ライブラリ・OS)と、試した日付を書いているか
  - 試してうまくいかなかったこと、制約、前提を書いているか
  - 結びが本文の言い直しではなく、次に読むもの・試すものを示しているか

  このジャンルで特に効く rule: closing-cliche, empty-conclusion, padded-intro, paragraph-length-variance, quote-without-source, rule-of-three, section-length-uniformity, sentence-rhythm
  chaff.yaml の guide: で書き換えられます。出さないときは --no-guide。

════════════════════════════════════════════════════════════

kiji.md   blog/tech · 日本語   ジャンルは --genre から
…

パスや内容から推測したジャンルと、既定のジャンルでは、指針を出しません。違う種類の文書の指針は、かえって読み手を迷わせるためです。 --compact と --no-guide でも出しません。指針の文は、チームが chaff.yaml の guide: で書き換えられます(ジャンルの指針を変える)。

指摘の意味と直しかた

行指摘意味直しかた
3padded-intro「近年、」は、どの記事にも書ける書き出しです何に困っていたかから書き始めます。「CI のビルドに毎回 9 分かかっていました」
9、37contrast-framing「単なる X ではありません」と、誰も言っていないことを打ち消しています打ち消しを消し、言いたいことだけを書きます
11、15、23announcing-opener「正直に言うと」「ポイントは」「重要なのは」と、予告で文を始めています予告を消し、中身から書き始めます
17、27colon-lead-in「次の通りです:」と、コロンで箇条書きへ渡す文が続いています箇条書きの項目がつながっているなら、文に戻してつながりを書きます
20latin-spacingほかは「9 分」と空けているのに、「1行」だけ詰めています「1 行」と空けて、記事の中で揃えます
27announced-count-mismatch「2 つ」と書いたのに、箇条書きは 3 つあります数か箇条書きの、違うほうを直します
33broken-link「ビルド時間の記録」という見出しが、記事にありません見出しを足すか、リンクを外します
39closing-cliche結びの決まり文句です消して、読み手が次にすることで終えます
1section-length-uniformity節の長さが揃いすぎています中身の多い節を厚くします。長さを合わせるために文を足しません
11ai-generated-composite上の形が、一つの記事にいくつもそろいました部分直しで足りなければ、記事を書き直します(下の節)

「一日に何十分も溶かしていました」「静かに遅くなっていました」のような比喩も生成文に多い形です。この記事では、chaff はこれを指摘していません。機械は、書き癖をすべて見つけるわけではありません。

生成文の形が多いときは書き直す

指摘を一つずつ消しても、太字と箇条書きが続く記事は生成文に読めます。そのときは、節ごとに書き直します。手順は AIっぽさを直す にあります。

直し方直すもの向いているとき
部分直し指摘された所だけ中身も構成もよく、言い回しだけが気になるとき
書き直し節ごとに、記事の全体の形部分直しをしても、まだ生成文に読めるとき

書き直したあとは、npx chaffjs compare kiji.md 書き直した記事.md で、数やコマンドが落ちていないかを確かめます。書き直しの例と compare の画面も、同じページにあります。

意味を読む検査は AI に頼む

「結果」の節の結びに中身があるかは、意味を読まないと決まりません。 npx chaffjs test は、機械で絞り込んだ箇所だけを AI に送ります。 --dry-run を付けると、送らずに、送る箇所を確かめられます。

npx chaffjs test kiji.md --genre blog/tech --dry-run    AI に送る箇所を見せる(送らない)

画面の前半は、機械の検査と同じです。その後に、次が出ます。

kiji.md   全 24 文のうち 1 箇所を送ります(API は呼んでいません)

      1 箇所  結びに中身がない  (機械で絞り込み済み)

  最初の 1 件に送る文章:

    ビルドは 9 分から 2 分になりました。単に速くなっただけでなく、チームの生産性も飛躍的に向上しました。

    いかがでしたでしょうか。この記事が皆さんのお役に立てれば幸いです。


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

  合計 1 箇所を送ります。--dry-run なので API は呼んでいません。
  送り先: anthropic / claude-opus-5(認証なし)

実際に AI に読ませるには、API key が要ります。置き方は README にあります。

chaff.yaml の始め方

技術記事を置くフォルダに、次の chaff.yaml を置きます。

genre: blog/tech
language: ja

この chaff.yaml を置くと、npx chaffjs kiji.md --compact だけで、上の指摘が出ます。読み返す目印(info)が要らないルールは、rules: に off と書いて止めます(section-length-uniformity: off など)。チームで書き方を決めたら、チームの表記ルールを決める を読んで書き足します。

次に読むページ