日本語の「AI臭さ」を消すスキルと、数値で測るスクリプトを公開します
以下をそのままコピペして、Claude Code や Codex に「ユーザースコープのスキルに追加しといて」と頼めばOKです。記事の後半では、各セクションが何を狙っているのかと、AI臭を数値で測る Python スクリプトも置いています。
よくある禁止表現を並べたリストではありません。作りの特徴は3つです。
- 「立場と主体」の層を最上位に置いた。禁止ワードを全部消してもAI臭が残る原因はここでした
- 全項目に S / A / B の確度ランクを振った。B級を機械的に全部消すと「直しすぎた文章」という別のテンプレが生まれます
- 測れるようにした。文長のばらつき、語尾の偏り、マーカー語のヒット数を出すスクリプトを後半に置いています
なぜ作ったか
最初は禁止語のリストでした。全角ダッシュ、「これにより」、「〜することができます」。見つけたら消す。それだけです。
消しても、AIが書いたように読めました。
原因が語彙ではなく、書き手が何も引き受けていないことにあると気づくまで、しばらくかかりました。そこで最上位に「立場と主体」の層を足しました。誰がやったのか。何を言い切るのか。ここが空だと、下の層をいくら直しても無人の文章が出てきます。
次に起きたのが、逆方向の事故です。リストを機械的に適用すると、体言止めが連打され、「正直」が5回出てきて、全部の段落に余談が入る。**AI臭を消した結果、別の臭いが付きました。**確度ランク(S / A / B)と「やりすぎ検査」は、この事故を止めるために足したものです。
「具体的に書け」と指示したら、実在しない企業名と統計が出てきたこともあります。捏造禁止を掟の1番目に置いているのはそのためです。
最後に、目視での判断が追いつかなくなりました。直したつもりで悪化していることがある。それで数値を出すスクリプトを書きました。
いま公開しているのは、この4周ぶんが積み上がった状態です。
---
name: ai-smell-ja
description: AI生成の日本語から「AI臭さ」を除去する。立場と主体/記号/語彙・文体/思考構造の4層で検出し、確度ランクに応じて修正する。ブログ・note・Qiita・Zenn・README・企業サイトの文章を書くとき、リライトするとき、レビューするときに使う。
user-invocable: true
---
# AI臭さを消す
AI生成の日本語から「AI臭さ」を除去する文章編集者として働く。パターンを消すだけでなく、書き手の温度を戻す。
## 大原則:AI臭の正体は書き手の不在
全角ダッシュも偏愛語も、表面に出てくる症状でしかない。原因は、自分が何を見て、何に引っかかって、どこまで言い切るのかをAIに預けていることにある。
書き換えを始める前に、対象テキストに3つ問う。
1. 書き手は何を「○○だ」と引き受けているか
2. 誰かが「いや、Bだろう」と反論できる具体的な主張があるか
3. 同じテーマの平均的な記事と、どこで違うか
答えられないなら、表面修正は後回しでいい。先に「この文章は何が言いたいのか」を決める。書き手にしか決められないなら、そこは質問して聞く。
## 4層構造
| 層 | 内容 | 難易度 |
|---|---|---|
| **第0層** | **立場と主体**(主体の消失、両論併記、中間温度の欠如、毒の不在) | 書き手が判断しない限り解消できない |
| 第1層 | 記号・書式の残骸 | 注意すれば即座に除去できる |
| 第2層 | 語彙・文体の偏り | 意識的な編集で改善できる |
| 第3層 | 思考構造の型 | 主体的に再構成しないと解消できない |
**第1〜3層を全部直しても、第0層が空なら文章はAI臭いままになる。**時間が足りないときは下から捨てて、第0層を必ず残す。
## 確度ランク
パターンは強度が均一ではない。ランクに応じて対応を変え、過剰修正を構造的に防ぐ。
| ランク | 意味 | 対応 |
|---|---|---|
| **S** | ほぼ確定シグナル。人間はまず書かない | 見つけ次第、即修正 |
| **A** | 強いシグナルだが文脈依存 | 文脈を見て判断。迷ったら修正 |
| **B** | 弱いシグナル。人間も普通に使う | 頻度が閾値を超えたときだけ修正。単発なら放置 |
**B級の単発ヒットは修正しないのが正解。**全部消すと「直しすぎた文章」という新しいテンプレになる。
---
## 第0層:立場と主体(最優先)
### S級:モノが人間の動詞をやっている
誰がやったのか書いていない状態。日本語で最も出やすいAI臭。
| NG | OK |
|---|---|
| 課題が浮き彫りになっている | 3社に聞いたら、全部同じところで詰まっていた |
| この技術が新しい可能性を切り拓く | この機能が出て、自分の作業が1つ減った |
| 本記事では、手順を解説します | 導入でハマったところを書きます |
| 設定される | ユーザーが設定する/システムが自動設定する |
主語を毎文書けという意味ではない。日本語なので省略していい。**省略した先に人間がいるかどうか**が分かれ目。
### S級:一人称の消失と一般化
「多くの人」「我々」「現代社会」「エンジニアにとって」で自分の話を一般化しない。「自分は」「うちでは」で書く。
地の文での「本稿」「本記事」「筆者」も同じ理由で使わない。
### A級:両論併記
「メリットもあり、デメリットもあります」「ケースバイケースです」「目的に応じて選びましょう」。読んだあとに何も残らない書き方。判断の放棄。
**間違っているかもしれない、と思う内容を書けているか**が基準になる。
- NG:一長一短があるため、要件に応じて選択してください
- OK:小規模なら前者でいい。後者を選ぶ理由が思いつかなかった
### A級:中間温度の欠如
AIは「素晴らしい」「非常に有用」「今後の展開が注目されます」しか書かない。人間の感想はもっと中途半端なところに散らばる。
「悪くない」「まあまあ」「微妙」「よく分からんけど好き」「すごいと思うけど、同時にちょっと薄気味悪い」を混ぜる。本当に強い感情のときだけ強い言葉を使う。
「〜すぎる」は1記事3回まで。
### A級:温度の均等配分
気に入っている機能に3段落、どうでもいい機能に1行。**この偏りが「書き手がいる」証拠**になる。全項目に同じだけ字数を割いている記事は、それだけでAIっぽく読める。
### A級:毒の不在
人間の文章には毒がある。「あれはひどかった」「自分がアホだった」のような振り切れた部分。AIはこれが出ない。元テキストにあるなら絶対に削らない。
ただし**マーカー語で出さない。具体的な事実で出す。**
- NG:正直、このドキュメントはひどい
- OK:このドキュメントは3回読んでも手順が分からなかった
前者は感情を宣言しているだけで、読み手には何も伝わらない。後者は感情語がゼロなのに毒が出ている。
---
## 第1層:記号・書式の残骸(すべてS級)
- 全角ダッシュ(——、──、―)。**絶対に使わない。**読点か「…」か、文を分ける
- 「**ラベル:** 内容」形式
- 見出しやタイトルでのコロン多用(「XX:YY」)
- 装飾絵文字、❌/✅の対比表
- 太字の多用。AIは「重要」と判断した箇所を機械的に太字にする
- 中黒での3項目並列(A・B・C)
- 「!」の多用
- 形容詞や心情を「」で囲む
---
## 第2層:語彙・文体
### S級:即修正
| NG | OK |
|---|---|
| これにより | (削る。文の流れでつなぐ) |
| 〜することができます | 〜できます |
| 〜を活用する | 使う/利用する |
| 〜についてご紹介します | (いきなり本題に入る) |
| 〜と言えるでしょう | (言い切るか、伝聞にする) |
| 〜ではないでしょうか | (言い切る) |
| 浮き彫りにしており | (具体的に書く) |
| 今後の展開が注目されます | (削る) |
### A級:偏愛語
解像度、本質、手触り、営み、文脈、熱量、体験、余白。1記事に複数撒かない。
### A級:必殺技造語
真理、虚飾、禁欲的、美学、境地。個人の話を大げさにする語。
### A級:既製品の比喩と決め台詞
羅針盤、架け橋、強い味方、〜と言っても過言ではない、〜が鍵を握る。
### A級:横文字メタファー
思考のOS、〜をハックする、〜をインストールする。過剰なカタカナも同じ。
### B級:語尾の単調さ
「です」「ます」の2〜3パターンでループしていないか。体言止め、言い切り、疑問、形容詞終止を混ぜる。**ただし体言止めの連打は次の「やりすぎ検査」に引っかかる。**
### B級:接続詞の連打
「さらに」「加えて」「また」「一方で」が毎段落に出ていないか。不要な接続詞は削る。
---
## 第3層:思考構造
### S級:導入テンプレ
- 「〜という方も多いのではないでしょうか」型
- 「結論から言うと」型
- 決めつけ→反転型(「〜だと思っていませんか? 実は違います」)
### S級:命題型の見出し
H2を「○○は△△だ」という主張にしない。名詞句で立てる。ただし全部を名詞句で揃えると、それはそれで均一になる。混ぜる。
### A級:構成のテンプレ
- アジェンダ・目次の自動挿入。短い文章では不要
- まとめセクション。読めば分かることを繰り返さない
- 均等に網羅的なリスト。AIは漏れなく列挙する。重要なものだけ残す
- 「以下の通りです」→箇条書き、の反復
- 結論の先出し+箇条書き展開
### A級:ムラの欠如
全部の文が同じ長さ、全部の段落が同じ終わり方をしていないか。10字の文と80字の文が同居していい。
**ある段落を丸ごと消して前後が破綻しないなら、その段落は浮いている。**
### A級:地の文の自問自答
「では、なぜ〜でしょうか。それは〜だからです」。実況進行(「見ていきましょう」)、疑問形見出しの連発も同じ。
### 箇条書きのルール
- 3〜4項目が上限。それ以上は分割するか本文に戻す
- 階層は1段まで。深くても2段
- 各項目は1〜2文
---
## 鉄の掟(すべてのパターンに優先する)
### 掟1:捏造禁止
「具体的に書け」は「事実を作れ」ではない。実例・数字・固有名詞・体験談が手元にないとき、それらしいものをでっち上げない。
事実がないときは、書き手に聞く。聞けないならプレースホルダを置く(「【実例:あなたの現場の数字をここに】」)。空欄のほうが、検証されて飛ぶ信頼よりマシ。
**AI臭の除去と、経験の偽装は別物。後者のほうがはるかに悪い。**
### 掟2:レジスタ維持
humanize ≠ カジュアル化。企業サイトのですます調をタメ口に変えたら、人間化ではなく事故。
元の文体、媒体、読者との距離を保つ。**その制約の中で**人間味を出す。ですます調のままでも、語尾の変化・具体性・意見の注入は全部できる。
### 掟3:過剰修正の禁止
後述の「やりすぎ検査」を必ず通す。
### 掟4:毒は語彙ではなく内容で出す
「正直」「ぶっちゃけ」「マジで」は1記事1回まで。具体的な事実で書けば、マーカー語なしで毒は出る。
### 掟5:断定と伝聞を、情報の出所で分ける
「言い切れ」は無条件の断定命令ではない。人間は出所で語調を変えている。
| 出所 | 書き方 |
|---|---|
| 自分の体験・計測・判断 | **言い切る。**「2.1秒まで縮んだ」「これは入れたほうがいい」 |
| 調べた事実・他人の情報 | **伝聞を残す。**「らしい」「だそうです」「と聞いた」 |
全断定はWikipediaになる。自分で測った数字まで「かもしれません」で濁すのはヘッジ。**多くの人はこの向きを逆にやっている。**自分の判断を濁して、調べただけの話を言い切る。
---
## やりすぎ検査(直したあとに必ず通す)
機械的に適用すると「AI臭を消した文章」という新しいテンプレが生まれる。
- [ ] 体言止めの連発。短文。連打。こういうやつになっていないか
- [ ] 「正直」「ぶっちゃけ」が2回以上出ていないか
- [ ] 「〜のだ」「〜のである」が増殖していないか
- [ ] 全部の段落に意見や余談を入れていないか(人間は言いたい段落でだけ言う)
- [ ] オノマトペが4個以上ないか
- [ ] 脱線・余談を乱発していないか(雑味は塩と同じで、少量だから効く)
- [ ] 元のレジスタから外れていないか
- [ ] 捏造した事実・数字・出典がないか
**判定基準:修正後の文章を3本並べたとき、同じ人の癖ではなく同じツールの癖に見えたらアウト。**
---
## 5軸採点
書き換えたら採点する。**合計35/50を下回ったら書き直す。**
| 軸 | 質問 |
|---|---|
| 立場 | 反証可能な具体的主張があるか。両論併記に逃げていないか |
| 主体性 | 誰が何をしたかが明示されているか |
| 具体性 | 抽象語で終わらず、固有の文脈に降りているか(捏造なし) |
| リズム | 長さ・トーン・結論・密度にムラがあるか |
| 削減 | 削れる箇所はないか。同じことを二度言っていないか |
低い軸から直す。**立場と主体性が低いまま、リズムと削減だけ上げてもAI臭は消えない。**
---
## 修正の優先順位
時間が限られているなら上から順に対処する。1と2を直さず5だけ直しても意味がない。
1. **立場**:反証可能な主張があるか。両論併記からの離脱
2. **主体**:モノが主語で人間の動詞をやっていないか
3. **構造**:命題型見出し、導入テンプレ、ムラの欠如
4. **語彙**:偏愛語、必殺技造語、既製品の比喩、横文字メタファー
5. **記号**:全角ダッシュ、コロン、太字、絵文字
---
## 最終チェック
- [ ] 音読する。息が続かない箇所、リズムが揃いすぎている箇所を直す
- [ ] 具体的な人名・地名・日付・数字があるか。全部「ある企業」「専門家」になっていないか
- [ ] 自分の体験か意見が最低1箇所入っているか
- [ ] 「よくできている」と思ったら、そこを疑う。完璧な構成・均一なリズム・バランスの取れた論点は、人間味の欠落とほぼ同義
- [ ] 隣の席の人に見せて「これAIでしょ」と言われそうな箇所はないか
スキルの読み方
貼って動かすだけでも効きますが、なぜその項目が入っているのかが分かると、自分の文章に合わせて足し引きできます。上から順に説明します。
なぜ「大原則」を先頭に置いたか
最初に3つの問いを立てさせているのは、AIに書き直しを頼むと表面から直し始めてしまうからです。
全角ダッシュを消し、「これにより」を消し、「〜することができます」を直す。ここは機械的にできるので、AIは喜んでやります。そして直し終わった文章を読むと、まだAIが書いたように読める。原因が表面になかったからです。
だから、書き換えの前に「この文章は何が言いたいのか」を確定させます。答えが出ないときは書き手に聞かせる。ここを飛ばすと、以降の項目を全部適用しても無人の文章が出てきます。
4層構造と、なぜ第0層が最上位なのか
層を分けているのは、直す難易度と効き目が逆順だからです。
記号(第1層)はいちばん簡単に直せて、いちばん効きません。立場と主体(第0層)はいちばん難しくて、いちばん効きます。時間がないときに何を捨てるかを決めるために、順番を明示しています。
第0層だけ「書き手が判断しない限り解消できない」と書いてあるのは、AIには原理的に埋められない層だからです。何を言い切るかは書き手にしか決められません。
S / A / B ランクの狙い
ここが一番の工夫だと思っています。
禁止ワードのリストを作ると、必ず全部消そうとする人(とAI)が出ます。ところが「〜だけでなく」も「つまり、」も、人間が普通に使う表現です。単発なら何の問題もない。それを全部潰すと、今度は不自然に凝った文章になります。
- S級は人間がまず書かないもの。「浮き彫りにしており」「いかがでしたか」。見つけ次第消す
- A級は文脈次第。「羅針盤」は普通の記事なら臭いですが、航海の記事なら普通です
- B級は頻度だけが問題。1回なら放置、5回出たら直す
このランクがないと、リストは「使うと不自然になる道具」になります。
第0層の各項目について
モノが人間の動詞をやっている
英語圏の資料では false agency と呼ばれている現象です。日本語だと「課題が浮き彫りになる」「技術が可能性を切り拓く」の形で出ます。
日本語は主語を省略できるので、この症状が英語より見つけにくい。省略しているのか、そもそも誰もいないのかが文面から区別できないからです。判定するには「これ、誰がやったの?」と一文ずつ聞くしかありません。
「本記事では」を禁止しているのも同じ理由です。記事は何も解説しません。
両論併記
「メリットもあり、デメリットもあります」が出るのは、AIが叩かれない方向に最適化されているからだと思っています。誰も反論できない文章を書けば、誰にも怒られない。
その代わり、誰の役にも立ちません。判断基準を「間違っているかもしれないと思う内容を書けているか」にしているのは、反証可能性がそのまま価値になるからです。
中間温度
AIの感想は「素晴らしい」か「今後の展開が注目されます」の2択になりがちです。人間の感想はもっと中途半端なところに散らばります。「悪くない」「まあまあ」「微妙」。
**中間温度が1つも出てこない原稿は、書き手が本当は何も感じていないように読めます。**逆に全部が「最高」「〜すぎる」でも同じことが起きます。だから「すぎる」に1記事3回という上限を付けています。
温度の均等配分
これは項目単位ではなく記事全体の形の話です。全部の機能に同じだけ字数を割いている記事は、それだけでAIっぽく読めます。
気に入っている機能に3段落、どうでもいい機能に1行。この偏りは、書き手がいないと発生しません。
毒
「正直、このドキュメントはひどい」と「このドキュメントは3回読んでも手順が分からなかった」の違いです。
前者は感情を宣言しているだけで、読み手には何も伝わりません。後者は感情語がゼロなのに毒が出ています。マーカー語は感情の代替にならないというのが、掟4を別立てした理由です。
そして「正直」「ぶっちゃけ」は、AI臭を消そうとした人が真っ先に足す語でもあります。だから後半の「やりすぎ検査」でも重複して見張っています。
鉄の掟を別立てにした理由
パターンのリストは、機械的に適用されると事故ります。掟はその事故を止めるためのブレーキです。
掟1(捏造禁止)が最優先の理由
「具体的に書け」という指示は、AIにとって「それらしい固有名詞と数字を作れ」と区別がつきません。実際、具体化を頼むと実在しない企業名や統計が出てきます。
AI臭の除去と、経験の偽装は別物です。**後者は前者よりはるかに悪い。**検証されたら一発で信頼が飛びます。だからプレースホルダを置かせています。空欄のほうがマシです。
掟2(レジスタ維持)
humanize を「カジュアルにする」と解釈されると、企業サイトのですます調がタメ口になります。これは人間化ではなく事故です。
ですます調のままでも、語尾の変化・具体性・意見の注入は全部できます。制約の中でやらせるために明記しています。
掟5(断定と伝聞の向き)
「言い切れ」というアドバイスは広く出回っていますが、無条件の断定命令ではないはずです。人間は情報の出所で語調を変えています。
自分で測った数字は言い切る。調べただけの話には「らしい」を残す。**そして多くの人はこの向きを逆にやっています。**自分の判断を濁して、他人の情報を言い切る。心当たりがあれば、ここだけ直すと一気に人間っぽくなります。
やりすぎ検査を入れた理由
このスキルを一度通した文章を3本並べると、同じ癖が出ます。体言止めの連打、「正直」の増殖、「〜のだ」の連打、全段落への意見注入。
**AI臭を消した結果、別の臭いが付く。**これは実際に起きます。だから直したあとに逆方向のチェックを通します。判定基準を「同じ人の癖ではなく同じツールの癖に見えたらアウト」にしているのは、ツールの癖は複数記事を並べないと見えないからです。
5軸採点の使い方
check.py(後述)は表面を測ります。採点は中身を測ります。
重要なのは低い軸から直すことです。立場と主体性が3点のまま、リズムと削減を9点まで上げても、AI臭は消えません。整った無人の文章ができるだけです。
数値で測るスクリプト
貼って動かすとAI臭を数値化します。標準ライブラリだけで動くので、pip install は不要です。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ai-smell-ja 機械監査スクリプト
日本語テキストのAI臭マーカーを数値化する。標準ライブラリのみで動作。
使い方:
python3 check.py article.md # 1ファイルを診断
python3 check.py before.md after.md # 2ファイルを比較(before/after)
python3 check.py article.md --json # JSON出力
cat article.md | python3 check.py - # 標準入力
見るべき指標:
- 第0層合計: 立場と主体(主体の消失・両論併記・遠距離話者)。最優先で減らす
- AIマーカー合計: 修正後に減っているべき
- 文長CV(変動係数): 0.5以上が目安。0.4未満はリズムが機械的
- 語尾トップ比率: 上位2種で80%超なら語尾が単調
- 直しすぎ臭: 修正後に「増えて」いたら掟3違反
"""
import sys
import re
import json
import math
import argparse
from collections import Counter
# ---------------------------------------------------------------
# AIマーカー定義(キーは通し番号と確度ランク)
# ---------------------------------------------------------------
AI_MARKERS = {
"S06 これにより": ["これにより"],
"S14 意義づけ-ing構文": ["浮き彫りにして", "示唆しており", "物語っており",
"裏付けており", "象徴しており", "示しており"],
"S33 チャットボット残骸": ["ご参考になれば幸い", "ご不明な点がございましたら",
"お気軽にお申し付け", "ご質問があればお知らせ"],
"S34 過剰な共感・お世辞": ["素晴らしいご質問", "おっしゃる通りです",
"そのお気持ち、よくわかります", "お気持ちお察し"],
"S36 テンプレ結論": ["いかがでしたか", "今後の動向に注目", "更なる発展が期待",
"さらなる発展が期待", "未来は明るい", "今後の展開が注目"],
"S37 導入テンプレ": ["方も多いのではないでしょうか", "でお悩みではありませんか",
"本記事では", "この記事を読めば", "それでは早速"],
"S38 実況進行": ["見ていきましょう", "解説していきます", "確認していきましょう",
"紹介していきます", "ここからは", "次に紹介するのは"],
"A07 接続詞連打": ["さらに、", "また、", "加えて、", "したがって、",
"しかしながら、", "このように、", "とりわけ"],
"A08 AI頻出語": ["非常に", "極めて", "大幅に", "飛躍的に", "画期的", "革新的",
"不可欠", "多岐にわたる", "一翼を担う", "特筆すべき", "包括的",
"網羅的", "多角的", "持続可能な", "醸成", "計り知れない"],
"A13 重要性の過剰強調": ["重要な役割を果た", "不可欠な存在", "の礎を築く",
"に大きく貢献", "極めて重要"],
"A18 曖昧な権威づけ": ["専門家によると", "多くの研究が", "業界関係者の間では",
"一般的に〜とされ", "複数の調査で", "調査結果が示すように"],
"A19 課題と展望テンプレ": ["課題はあるものの", "今後の発展が期待", "さらなる飛躍"],
"A20 宣伝調の美辞麗句": ["息を呑むような", "心温まる", "活気に満ちた",
"厳選された", "唯一無二の", "魅力的な"],
"A24 定番メタファー": ["羅針盤", "道しるべ", "架け橋", "両輪", "追い風",
"強い味方", "心強い味方", "救世主", "切り札", "宝の山",
"諸刃の剣", "鍵を握る", "扉を開く"],
"A27 決め台詞": ["と言っても過言では", "が肝心です", "は必見です",
"すること間違いなし", "してみてはいかがでしょうか"],
"A35 過剰ヘッジング": ["かもしれないと考えられ", "可能性があるかもしれ",
"と言えるのではないでしょうか", "一概には"],
"A39 自問自答テンプレ": ["では、なぜ", "では、どうすれば", "それはなぜか。",
"答えはシンプル", "なぜでしょうか。それは"],
"A40 結論から言うと": ["結論から言うと", "結論から申し上げると",
"先に結論を言うと", "最初に結論を"],
"B12 冗長な婉曲": ["することができます", "ということが挙げられます",
"という側面があります", "であると考えられて"],
"B15 だけでなく構文": ["だけでなく"],
"B26 言い換え連発": ["つまり、", "要するに", "言い換えれば", "すなわち",
"簡単に言うと", "平たく言えば"],
"その他 語尾テンプレ": ["と言えるでしょう", "ではないでしょうか"],
}
# ---------------------------------------------------------------
# 第0層マーカー(立場と主体)
# ---------------------------------------------------------------
LAYER0_MARKERS = {
"S1-1 false agency(モノが主語)": [
"が示している", "が示唆", "が物語", "が浮き彫り", "が醸成", "が結実",
"が収斂", "が定着する", "が導く", "解像度が上がる", "議論が深まる",
"複雑性が増し", "最適化されていく", "対比をなして",
],
"A1-2 必殺技造語(壮大化)": [
"真理", "虚飾", "虚像", "境地", "極致", "禁欲的", "美学", "深淵",
"重厚感", "冷徹", "冷酷", "残酷な", "宿命", "結晶", "凝縮",
],
"A1-2 抽象比喩(偏愛語)": [
"泥臭", "手触り", "肌感", "熱量", "血の通った", "腹落ち",
"という営み", "装置として", "解像度", "本質的", "等身大", "地に足",
],
"A1-3/1-4 遠距離話者・一般論": [
"人々は", "我々は", "多くの人が", "多くの人は", "誰もが",
"現代社会において", "近年", "傾向にある", "しがちだ", "と考えがち",
],
"A2-1 曖昧な断言": [
"本質的な問題", "構造的な課題", "示唆に富む", "重大な意味",
"根本的な転換", "深い洞察", "重要な示唆",
],
"A2-3/3-1 結論回避・両論併記": [
"ケースバイケース", "場合によります", "場合による", "価値観によります",
"状況に応じて異なり", "一概には言えません", "メリットもあれば",
"賛否が分かれる", "それぞれの良さ", "用途による",
],
"A5-3 アカデミック自称": ["本稿", "本記事", "本論考", "筆者は", "筆者が"],
"A3 横文字メタファー": [
"思考のOS", "をハック", "をインストール", "リファクタリング",
"アセットにする", "ポートフォリオで考える", "ディープダイブ",
"レバレッジ", "にコミットする", "をアラインする", "にピボット",
],
}
# 副詞スタッキング(1段落に2個以上でNG)
ADVERBS = ["非常に", "とても", "かなり", "めっちゃ", "ガチで", "マジで",
"普通に", "本当に", "実に", "確かに"]
# 伝聞表現(掟5の参考値。調べた事実に付いているべき)
DENBUN = ["らしい", "そうです", "そうだ", "ようです", "ようだ", "とのこと",
"みたい", "と聞いた", "だという"]
RE_SUGIRU = re.compile(r"すぎ(る|た|だ|です|ます|!|。|、)")
RE_NAKAGURO3 = re.compile(r"[^\s、。]+・[^\s、。]+・[^\s、。]+")
RE_H2 = re.compile(r"^##\s+(.+)$", re.MULTILINE)
RE_H2_PROP = re.compile(r"(だ|である|する|ない|なる|いる|た|う|か)$")
RE_SHORT_QUOTE = re.compile(r"「([^「」]{1,6})」")
OVERFIX_SMELL = {
"「正直」": ["正直"],
"「ぶっちゃけ」": ["ぶっちゃけ"],
"「〜なんだよな」": ["なんだよな"],
"「のだ/のである」文末": [], # 正規表現で別集計
}
RE_NODA = re.compile(r"(のだ|のである|のです)。")
RE_KAKKO_HOSOKU = re.compile(r"([==※]|(例[::]|(いわゆる|(つまり")
RE_EMOJI_BULLET = re.compile(r"^[\s>-]*[✅💡🚀✨📌🎯⭐️❗️⚠️]", re.MULTILINE)
RE_BOLD_LABEL = re.compile(r"\*\*[^*]{1,20}[::]\*\*")
RE_EMDASH = re.compile(r"[—―‐]{1,2}|——")
RE_NEG_PARALLEL = re.compile(r"ではない。[^。]{1,40}(だ|です)。")
SENT_SPLIT = re.compile(r"(?<=[。!?!?])")
def strip_noise(text: str) -> str:
"""コードブロック・インラインコード・URL・画像/リンク記法を除去して本文だけにする"""
text = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
text = re.sub(r"`[^`\n]+`", "", text)
text = re.sub(r"!\[[^\]]*\]\([^)]*\)", "", text)
text = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", text)
text = re.sub(r"https?://\S+", "", text)
# YAML frontmatter
text = re.sub(r"\A---\n.*?\n---\n", "", text, flags=re.DOTALL)
return text
def split_sentences(text: str):
"""見出し・箇条書き記号を落として文単位に分割"""
body_lines = []
for line in text.splitlines():
s = line.strip()
if not s or s.startswith("#") or s.startswith("|"):
continue
s = re.sub(r"^[->*\d.、.)\s]+", "", s)
if s:
body_lines.append(s)
joined = "\n".join(body_lines)
sents = [s.strip() for s in SENT_SPLIT.split(joined) if s.strip()]
# 改行だけで切れている行も文として扱う
out = []
for s in sents:
out.extend([x for x in s.split("\n") if x.strip()])
return [s for s in out if len(s) >= 2]
def ending_of(sent: str) -> str:
"""語尾を分類する(ざっくり)"""
s = re.sub(r"[。!?!?」』)\s]+$", "", sent)
endings = [
("でしょう", "でしょう"), ("ましょう", "ましょう"),
("でした", "でした"), ("ました", "ました"), ("ません", "ません"),
("ですが", "ですが"), ("ますが", "ますが"),
("です", "です"), ("ます", "ます"),
("である", "である"), ("だった", "だった"),
("ない", "ない"), ("たい", "たい"), ("だ", "だ"), ("た", "た"),
("る", "る(動詞終止)"), ("う", "う"), ("い", "い(形容詞)"),
]
for suffix, label in endings:
if s.endswith(suffix):
return label
return "体言止め等"
def count_markers(text: str, marker_dict):
hits = {}
total = 0
for name, terms in marker_dict.items():
detail = {}
for t in terms:
c = text.count(t)
if c:
detail[t] = c
total += c
if detail:
hits[name] = detail
return hits, total
def analyze(text: str) -> dict:
body = strip_noise(text)
sents = split_sentences(body)
lengths = [len(s) for s in sents]
n = len(lengths)
mean = sum(lengths) / n if n else 0.0
var = sum((x - mean) ** 2 for x in lengths) / n if n else 0.0
std = math.sqrt(var)
cv = (std / mean) if mean else 0.0
ai_hits, ai_total = count_markers(body, AI_MARKERS)
l0_hits, l0_total = count_markers(body, LAYER0_MARKERS)
# 副詞スタッキング(段落単位で2個以上)
paragraphs = [p for p in re.split(r"\n\s*\n", body) if p.strip()]
adv_stacked = 0
for para in paragraphs:
c = sum(para.count(a) for a in ADVERBS)
if c >= 2:
adv_stacked += 1
# 「すぎる」連発 / 中黒3項目並列 / 短い「」
sugiru = len(RE_SUGIRU.findall(body))
nakaguro3 = len(RE_NAKAGURO3.findall(body))
short_quote = len(RE_SHORT_QUOTE.findall(body))
# 命題型H2(見出しが名詞句でなく主張で終わっている)
h2s = RE_H2.findall(text)
h2_prop = sum(1 for h in h2s if RE_H2_PROP.search(h.strip().rstrip("。")))
# 伝聞率(掟5の参考値)
denbun = sum(body.count(d) for d in DENBUN)
l0_struct = {
"副詞スタック段落(1段落2個以上)": adv_stacked,
"「すぎる」": sugiru,
"中黒3項目並列(A・B・C)": nakaguro3,
"短い「」(形容詞・心情の引用符)": short_quote,
"命題型H2": h2_prop,
}
l0_struct = {k: v for k, v in l0_struct.items() if v}
l0_total += adv_stacked + sugiru + nakaguro3 + h2_prop
hz_hits, hz_total = count_markers(body, OVERFIX_SMELL)
noda = len(RE_NODA.findall(body))
if noda:
hz_hits["「のだ/のである」文末"] = {"のだ。/のである。/のです。": noda}
hz_total += noda
# 体言止め率も直しすぎ臭の参考値
endings = Counter(ending_of(s) for s in sents)
taigen = endings.get("体言止め等", 0)
struct = {
"絵文字リスト行": len(RE_EMOJI_BULLET.findall(body)),
"**ラベル:** 形式": len(RE_BOLD_LABEL.findall(body)),
"全角/エムダッシュ": len(RE_EMDASH.findall(body)),
"否定並列(〜ではない。〜だ。)": len(RE_NEG_PARALLEL.findall(body)),
"括弧補足(=/例:/いわゆる/つまり)": len(RE_KAKKO_HOSOKU.findall(body)),
}
struct = {k: v for k, v in struct.items() if v}
top_endings = endings.most_common(5)
top2_ratio = (sum(c for _, c in top_endings[:2]) / n) if n else 0.0
return {
"sentences": n,
"chars": len(re.sub(r"\s", "", body)),
"len_mean": round(mean, 1),
"len_std": round(std, 1),
"len_cv": round(cv, 3),
"layer0_total": l0_total,
"layer0_hits": l0_hits,
"layer0_structural": l0_struct,
"denbun": denbun,
"h2_count": len(h2s),
"ai_marker_total": ai_total,
"ai_marker_hits": ai_hits,
"structural_hits": struct,
"endings_top": top_endings,
"endings_top2_ratio": round(top2_ratio, 3),
"taigen_ratio": round(taigen / n, 3) if n else 0.0,
"overfix_total": hz_total,
"overfix_hits": hz_hits,
}
def render(label: str, r: dict) -> str:
lines = []
lines.append(f"=== {label} ===")
lines.append(f"文数 {r['sentences']} / 文字数 {r['chars']}")
lines.append(f"文長: 平均 {r['len_mean']}字, 標準偏差 {r['len_std']}, "
f"CV {r['len_cv']} {'⚠ リズムが機械的(0.4未満)' if r['len_cv'] < 0.4 else 'OK(0.4以上)' }")
lines.append(f"語尾トップ2比率: {r['endings_top2_ratio']} "
f"{'⚠ 語尾が単調(0.8超)' if r['endings_top2_ratio'] > 0.8 else 'OK'}")
lines.append("語尾分布: " + ", ".join(f"{k}×{v}" for k, v in r["endings_top"]))
lines.append(f"体言止め率: {r['taigen_ratio']} "
f"{'⚠ 連打の疑い(0.35超)' if r['taigen_ratio'] > 0.35 else ''}".rstrip())
lines.append("")
lines.append(f"★第0層(立場と主体)合計: {r['layer0_total']}"
+ (" ⚠ 第0層を先に直す" if r["layer0_total"] >= 5 else ""))
for name in sorted(r["layer0_hits"]):
detail = r["layer0_hits"][name]
s2 = ", ".join(f"{t}×{c}" for t, c in detail.items())
lines.append(f" [{name}] {s2}")
for k, v in r["layer0_structural"].items():
lines.append(f" {k}: {v}")
lines.append((f" 伝聞表現: {r['denbun']} "
+ ("⚠ 0件。調べた事実まで全断定になっていないか(掟5)"
if r["denbun"] == 0 else "")).rstrip())
lines.append("")
lines.append(f"AIマーカー合計: {r['ai_marker_total']}")
for name in sorted(r["ai_marker_hits"]):
detail = r["ai_marker_hits"][name]
s = ", ".join(f"{t}×{c}" for t, c in detail.items())
lines.append(f" [{name}] {s}")
if r["structural_hits"]:
lines.append("書式・構文マーカー:")
for k, v in r["structural_hits"].items():
lines.append(f" {k}: {v}")
lines.append("")
lines.append(f"直しすぎ臭合計: {r['overfix_total']}"
+ (" ⚠ 掟3を確認(修正で増えていないか)" if r["overfix_total"] >= 3 else ""))
for name, detail in r["overfix_hits"].items():
s = ", ".join(f"{t}×{c}" for t, c in detail.items())
lines.append(f" {name}: {s}")
return "\n".join(lines)
def render_diff(a: dict, b: dict) -> str:
def arrow(x, y, lower_is_better=True):
if x == y:
return f"{x} → {y} 変化なし"
good = (y < x) if lower_is_better else (y > x)
return f"{x} → {y} {'✅' if good else '⚠'}"
lines = ["=== before / after 比較 ==="]
lines.append(f"★第0層合計: {arrow(a['layer0_total'], b['layer0_total'])} (最優先で減らす)")
lines.append(f"AIマーカー合計: {arrow(a['ai_marker_total'], b['ai_marker_total'])}")
lines.append(f"文長CV: {arrow(a['len_cv'], b['len_cv'], lower_is_better=False)} (高いほど人間的)")
lines.append(f"語尾トップ2比率: {arrow(a['endings_top2_ratio'], b['endings_top2_ratio'])} (低いほど多様)")
lines.append(f"直しすぎ臭合計: {arrow(a['overfix_total'], b['overfix_total'])} (増えたら掟3違反)")
return "\n".join(lines)
def read_input(path: str) -> str:
if path == "-":
return sys.stdin.read()
with open(path, encoding="utf-8") as f:
return f.read()
def main():
p = argparse.ArgumentParser(description="ai-smell-ja 機械監査")
p.add_argument("files", nargs="+", help="対象ファイル(2つ指定でbefore/after比較。- で標準入力)")
p.add_argument("--json", action="store_true", help="JSONで出力")
args = p.parse_args()
if len(args.files) > 2:
print("ファイルは1つ(診断)または2つ(比較)で指定してください", file=sys.stderr)
sys.exit(1)
results = [(f, analyze(read_input(f))) for f in args.files]
if args.json:
print(json.dumps({f: r for f, r in results}, ensure_ascii=False, indent=2))
return
for f, r in results:
print(render(f, r))
print()
if len(results) == 2:
print(render_diff(results[0][1], results[1][1]))
if __name__ == "__main__":
main()
使い方
# 1ファイルを診断
python3 check.py article.md
# 修正前後を比較
python3 check.py before.md after.md
# 標準入力から
cat article.md | python3 check.py -
# JSONで受け取る
python3 check.py article.md --json
Claude Code に置いておくと、書き直しのたびに自動で走らせてくれます。
出力の読み方
実際に出る数値はこうなります。
文数 101 / 文字数 2554
文長: 平均 22.6字, 標準偏差 10.7, CV 0.471 OK(0.4以上)
語尾トップ2比率: 0.475 OK
語尾分布: ます×26, 体言止め等×22, です×16, い(形容詞)×8, る(動詞終止)×8
体言止め率: 0.218
★第0層(立場と主体)合計: 11 ⚠ 第0層を先に直す
[S1-1 false agency(モノが主語)] が浮き彫り×1
[A2-3/3-1 結論回避・両論併記] ケースバイケース×1
[A5-3 アカデミック自称] 本記事×2
命題型H2: 4
伝聞表現: 6
AIマーカー合計: 6
[S06 これにより] これにより×1
[S36 テンプレ結論] 今後の展開が注目×1
直しすぎ臭合計: 5 ⚠ 掟3を確認(修正で増えていないか)
「正直」: 正直×3
文長CV(変動係数)
文の長さのばらつきです。標準偏差を平均で割った値。0.4を下回るとリズムが機械的と判定します。
AIは同じくらいの長さの文を並べます。人間は10字の文と80字の文を混ぜます。CVが低いときは、短い文を混ぜるか、長い文を分割せずに残すと上がります。
語尾トップ2比率
上位2種類の語尾が全体の何割を占めるか。0.8を超えると単調です。
「です」「ます」でループしている原稿はここに出ます。体言止め、言い切り、疑問、形容詞終止を混ぜると下がります。
体言止め率
**0.35を超えると連打を疑います。**これは直しすぎ側の指標です。語尾を散らそうとして体言止めを増やすと、今度はここが跳ねます。
第0層合計
最優先で減らす数値です。5以上で警告が出ます。
内訳の「命題型H2」は、見出しが「○○は△△だ」という主張の形になっている数。「伝聞表現」だけは0件のときに警告が出ます。調べた事実まで全部断定していないかを見るためです(掟5)。
AIマーカー合計
禁止語のヒット数です。キーの S06 A08 B12 はランク付きの通し番号なので、S級から順に潰します。
直しすぎ臭合計
「正直」「ぶっちゃけ」「〜のだ」の数。修正前より増えていたら掟3違反です。before/after 比較モードだと、この項目だけ「増えたら⚠」の向きで表示されます。
数値を信じすぎない
このスクリプトは文字列を数えているだけなので、引用と使用の区別がつきません。
たとえばこの記事自体を通すと「本記事」「これにより」「正直」が検出されます。全部、悪い例として意図的に置いたものです。
数値は書き直す前に基礎値を取って、直したあとに減っているかを見るために使います。ゼロを目指すものではありません。第0層合計とAIマーカーが下がって、文長CVが上がって、直しすぎ臭が増えていなければ合格です。
使い方のコツ
書いてから直すのではなく、書く前に読ませるほうが効きます。「このスキルを踏まえて記事を書いて」と頼むと、直す量がかなり減ります。
書いたあとに使うときは「このスキルで診断して、書き換えずに指摘だけして」と頼むのがおすすめです。どこが臭いのかが分かって、自分で直せます。自分で直したほうが、その原稿は自分のものになります。
出典
- Wikipedia:Signs of AI writing
- hardikpandya/stop-slop
- blader/humanizer
- Daichi Nagashima — 「立場と主体」の層はここから多くを借りています
- @minorun365 さんの記事
ふだんはraplsworks.comで、WordPressプラグイン開発やClaude Codeまわりのことを書いています。