2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIが書いた日本語から「AI臭さ」を消すスキルと、採点スクリプトを公開します

2
Posted at

日本語の「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が上がって、直しすぎ臭が増えていなければ合格です。


使い方のコツ

書いてから直すのではなく、書く前に読ませるほうが効きます。「このスキルを踏まえて記事を書いて」と頼むと、直す量がかなり減ります。

書いたあとに使うときは「このスキルで診断して、書き換えずに指摘だけして」と頼むのがおすすめです。どこが臭いのかが分かって、自分で直せます。自分で直したほうが、その原稿は自分のものになります。

出典


ふだんはraplsworks.comで、WordPressプラグイン開発やClaude Codeまわりのことを書いています。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?