Skip to content

Commit 0bc3a91

Browse files
committed
feat: textlintルールプリセット「no-ai-list-formatting」と「no-ai-formal-expressions」を追加し、READMEとテストを更新
1 parent 1d86fcc commit 0bc3a91

9 files changed

+467
-222
lines changed

.github/copilot-instructions.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# GitHub Copilot カスタム指示
2+
3+
このプロジェクトは、AIが生成しがちな機械的な記述パターンを検出し、より自然な日本語表現を促すtextlintルールプリセットです。
4+
5+
## プロジェクトの方針
6+
7+
### 自然な日本語表現の推進
8+
- 機械的で定型的な表現よりも、人間らしい自然な表現を優先する
9+
- 読み手に親しみやすく、理解しやすい文章を心がける
10+
- 過度に形式的な表現を避け、適度にくだけた表現も取り入れる
11+
12+
### 建設的で中立的なメッセージング
13+
- ユーザーを批判的に指摘するのではなく、建設的な提案を行う
14+
- 「悪い」「間違い」のような否定的な表現を避ける
15+
- 「検討してください」「〜も考えられます」のような提案的な表現を使用する
16+
17+
## コーディング指針
18+
19+
### ルール実装時の注意点
20+
```typescript
21+
// ❌ 避けるべき表現
22+
const ruleError = new RuleError("これは悪い書き方です。修正してください。");
23+
24+
// ✅ 推奨される表現
25+
const ruleError = new RuleError("この表現は機械的な印象を与える可能性があります。より自然な表現を検討してみてください。");
26+
```
27+
28+
### メッセージの書き方
29+
- **中立的**: 「〜の可能性があります」「〜場合があります」
30+
- **提案的**: 「検討してください」「〜してみてください」
31+
- **配慮的**: 「読み手によっては」「人によっては」
32+
33+
## テスト作成の指針
34+
35+
### テストケースの構成
36+
- **valid**: 自然で問題のない表現
37+
- **invalid**: 機械的で改善の余地がある表現
38+
- メッセージは実際のエラーメッセージと完全に一致させる
39+
40+
### テストメッセージの例
41+
```typescript
42+
// 建設的で提案的なメッセージ
43+
{
44+
message: "リストアイテムで強調(**)とコロン(:)の組み合わせは機械的な印象を与える可能性があります。より自然な表現を検討してください。"
45+
}
46+
```
47+
48+
## ドキュメント作成の指針
49+
50+
### README での表現
51+
- 「悪い例」→「検出される例」
52+
- 「良い例」→「推奨される表現」
53+
54+
### 説明文の書き方
55+
- ユーザーの立場に立った説明
56+
- 理由と代替案の両方を提示
57+
- 押し付けがましくない表現
58+
59+
## 新機能追加時の考慮点
60+
61+
### 新しいルール追加時
62+
1. **パターンの妥当性**: 本当に機械的で改善の余地があるか
63+
2. **誤検出の可能性**: 人間が自然に書く場合もあるか
64+
3. **メッセージの適切性**: 建設的で中立的か
65+
4. **オプションの提供**: 無効化オプションを提供するか
66+
67+
### コードレビューのポイント
68+
- エラーメッセージが建設的か
69+
- テストケースが適切か
70+
- ドキュメントが親しみやすいか
71+
- オプション設定が柔軟か
72+
73+
## コミットメッセージとPRタイトル
74+
75+
### 推奨フォーマット
76+
```
77+
feat: 新しいルール「no-repetitive-expressions」を追加
78+
fix: メッセージの表現をより中立的に修正
79+
docs: READMEの説明をより分かりやすく改善
80+
test: テストケースのメッセージを実際の出力に合わせて修正
81+
```
82+
83+
## このプロジェクトが目指すもの
84+
85+
技術的な正確性だけでなく、ユーザーとの良好な関係を築きながら、より良い文章作成をサポートするツールとして成長していくこと。
86+
87+
AIと人間の協力により、お互いの強みを活かした自然で読みやすい日本語文章の作成を支援する。

README.md

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,66 @@
1-
# @textlint-ja/textlint-rule-no-ai-writing
1+
# textlint-rule-preset-ai-writing
22

3-
AIが生成した文章によく見られる記述パターンを検出し、より自然な日本語表現を促すtextlintルールです
3+
AIが生成した文章によく見られる記述パターンを検出し、より自然な日本語表現を促すtextlintルールプリセットです
44

5-
## 検出する記述パターン
5+
## 含まれるルール
66

7-
### 1. リストアイテムの強調パターン
7+
### 1. no-ai-list-formatting
8+
リストアイテムで機械的な印象を与える可能性のある記述パターンを検出します。
89

9-
**悪い例:**
10+
#### 1-1. リストアイテムの強調パターン
11+
12+
🔍 **検出される例:**
1013
```markdown
1114
- **重要**: これは重要な項目です
1215
- **注意**: 注意が必要な項目です
1316
```
1417

15-
**良い例:**
18+
**推奨される表現:**
1619
```markdown
1720
- 重要な項目: これは重要な項目です
1821
- 注意事項: 注意が必要な項目です
1922
```
2023

21-
### 2. 絵文字を使ったリストアイテム
24+
#### 1-2. 絵文字を使ったリストアイテム
2225

23-
**悪い例:**
26+
🔍 **検出される例:**
2427
```markdown
2528
- ✅ 完了した項目
2629
- ❌ 失敗した項目
2730
- 💡 アイデア項目
2831
- 🔥 ホットな話題
2932
```
3033

31-
**良い例:**
34+
**推奨される表現:**
3235
```markdown
3336
- 完了した項目
3437
- 失敗した項目
3538
- アイデア項目
3639
- 注目の話題
3740
```
3841

42+
### 2. no-ai-formal-expressions
43+
定型的で機械的な印象を与える可能性のある表現を検出します。
44+
45+
🔍 **検出される例:**
46+
```markdown
47+
以下のような手順で進めます。
48+
次のような点に注意してください。
49+
具体的には以下の通りです。
50+
```
51+
52+
**推奨される表現:**
53+
```markdown
54+
次の手順で進めます。
55+
以下の点に注意してください。
56+
具体的には次のとおりです。
57+
```
58+
3959
## Install
4060

41-
Install with [npm](https://www.npmjs.com/package/@textlint-ja/textlint-rule-no-ai-writing):
61+
Install with [npm](https://www.npmjs.com/package/textlint-rule-preset-ai-writing):
4262

43-
npm install @textlint-ja/textlint-rule-no-ai-writing
63+
npm install textlint-rule-preset-ai-writing
4464

4565
## Usage
4666

@@ -49,31 +69,42 @@ Via `.textlintrc`(Recommended)
4969
```json
5070
{
5171
"rules": {
52-
"@textlint-ja/no-ai-writing": true
72+
"preset-ai-writing": true
5373
}
5474
}
5575
```
5676

5777
## Options
5878

79+
各ルールに対して個別にオプションを設定できます。
80+
5981
```json
6082
{
6183
"rules": {
62-
"@textlint-ja/no-ai-writing": {
63-
"allows": ["許可したいテキスト"],
64-
"disableBoldListItems": false,
65-
"disableEmojiListItems": false
84+
"preset-ai-writing": {
85+
"no-ai-list-formatting": {
86+
"allows": ["許可したいテキスト"],
87+
"disableBoldListItems": false,
88+
"disableEmojiListItems": false
89+
},
90+
"no-ai-formal-expressions": {
91+
"allows": ["許可したいテキスト"]
92+
}
6693
}
6794
}
6895
}
6996
```
7097

7198
### Options説明
7299

100+
#### no-ai-list-formatting
73101
- `allows`: 指定したテキストを含む場合、エラーを報告しません
74102
- `disableBoldListItems`: `true`にすると強調リストアイテムの検出を無効にします
75103
- `disableEmojiListItems`: `true`にすると絵文字リストアイテムの検出を無効にします
76104

105+
#### no-ai-formal-expressions
106+
- `allows`: 指定したテキストを含む場合、エラーを報告しません
107+
77108
Via CLI
78109

79110
```

package.json

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
{
2-
"name": "@textlint-ja/textlint-rule-no-ai-writing",
2+
"name": "textlint-rule-preset-ai-writing",
33
"version": "1.0.0",
4-
"description": "textlintルール:AIっぽい記述パターンを検出し、より自然な日本語表現を促すルール",
4+
"description": "textlintプリセット:AIっぽい記述パターンを検出し、より自然な日本語表現を促すルール集",
55
"keywords": [
66
"textlintrule",
7+
"textlint-rule-preset",
78
"japanese",
89
"ai-writing",
9-
"natural-writing"
10+
"natural-writing",
11+
"preset"
1012
],
11-
"homepage": "https://github.com/textlint-ja/textlint-rule-no-ai-writing",
13+
"homepage": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing",
1214
"bugs": {
13-
"url": "https://github.com/textlint-ja/textlint-rule-no-ai-writing/issues"
15+
"url": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing/issues"
1416
},
1517
"repository": {
1618
"type": "git",
17-
"url": "https://github.com/textlint-ja/textlint-rule-no-ai-writing.git"
19+
"url": "https://github.com/textlint-ja/textlint-rule-preset-ai-writing.git"
1820
},
1921
"license": "MIT",
2022
"author": "azu",
21-
"main": "lib/textlint-rule-no-ai-writing.js",
23+
"main": "lib/index.js",
2224
"directories": {
2325
"lib": "lib",
2426
"test": "test"

src/index.ts

Lines changed: 13 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,15 @@
1-
import type { TextlintRuleModule } from "@textlint/types";
2-
3-
export interface Options {
4-
// If node's text includes allowed text, does not report.
5-
allows?: string[];
6-
// Disable specific pattern checks
7-
disableBoldListItems?: boolean;
8-
disableEmojiListItems?: boolean;
9-
}
10-
11-
const report: TextlintRuleModule<Options> = (context, options = {}) => {
12-
const { Syntax, RuleError, report, getSource, locator } = context;
13-
const allows = options.allows ?? [];
14-
const disableBoldListItems = options.disableBoldListItems ?? false;
15-
const disableEmojiListItems = options.disableEmojiListItems ?? false;
16-
17-
// AI-like emoji patterns commonly used in lists
18-
const emojiPatterns = [
19-
"✅",
20-
"❌",
21-
"⭐",
22-
"💡",
23-
"🔥",
24-
"📝",
25-
"⚡",
26-
"🎯",
27-
"🚀",
28-
"🎉",
29-
"📌",
30-
"🔍",
31-
"💰",
32-
"📊",
33-
"🔧",
34-
"⚠️",
35-
"❗",
36-
"💻",
37-
"📱",
38-
"🌟"
39-
];
40-
41-
return {
42-
[Syntax.ListItem](node) {
43-
const text = getSource(node);
44-
45-
if (allows.some((allow) => text.includes(allow))) {
46-
return;
47-
}
48-
49-
// Check for bold list item pattern: - **text**: description
50-
if (!disableBoldListItems) {
51-
const boldListPattern = /^[\s]*[-*+]\s+\*\*([^*]+)\*\*\s*:/;
52-
const boldMatch = text.match(boldListPattern);
53-
if (boldMatch) {
54-
const matchStart = boldMatch.index ?? 0;
55-
const matchEnd = matchStart + boldMatch[0].length;
56-
const matchRange = [matchStart, matchEnd] as const;
57-
const ruleError = new RuleError(
58-
"リストアイテムで強調(**)とコロン(:)の組み合わせはAIっぽい記述です。より自然な表現を使用してください。",
59-
{
60-
padding: locator.range(matchRange)
61-
}
62-
);
63-
report(node, ruleError);
64-
}
65-
}
66-
67-
// Check for emoji list items
68-
if (!disableEmojiListItems) {
69-
for (const emoji of emojiPatterns) {
70-
const emojiIndex = text.indexOf(emoji);
71-
if (emojiIndex !== -1) {
72-
const matchRange = [emojiIndex, emojiIndex + emoji.length] as const;
73-
const ruleError = new RuleError(
74-
`リストアイテムで絵文字「${emoji}」を使用するのはAIっぽい記述です。テキストベースの表現を使用してください。`,
75-
{
76-
padding: locator.range(matchRange)
77-
}
78-
);
79-
report(node, ruleError);
80-
break; // Only report the first emoji found in each list item
81-
}
82-
}
83-
}
84-
}
85-
};
1+
import noAiListFormatting from "./rules/no-ai-list-formatting";
2+
import noAiFormalExpressions from "./rules/no-ai-formal-expressions";
3+
4+
const preset = {
5+
rules: {
6+
"no-ai-list-formatting": noAiListFormatting,
7+
"no-ai-formal-expressions": noAiFormalExpressions
8+
},
9+
rulesConfig: {
10+
"no-ai-list-formatting": true,
11+
"no-ai-formal-expressions": true
12+
}
8613
};
8714

88-
export default report;
15+
export default preset;

src/rules/no-ai-formal-expressions.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { TextlintRuleModule } from "@textlint/types";
2+
3+
export interface Options {
4+
// If node's text includes allowed text, does not report.
5+
allows?: string[];
6+
}
7+
8+
const rule: TextlintRuleModule<Options> = (context, options = {}) => {
9+
const { Syntax, RuleError, report, getSource, locator } = context;
10+
const allows = options.allows ?? [];
11+
12+
// AI-like formal expressions that sound robotic
13+
const formalPatterns = [
14+
//g,
15+
//g,
16+
//g,
17+
//g,
18+
//g,
19+
//g
20+
];
21+
22+
return {
23+
[Syntax.Str](node) {
24+
const text = getSource(node);
25+
26+
if (allows.some((allow) => text.includes(allow))) {
27+
return;
28+
}
29+
30+
for (const pattern of formalPatterns) {
31+
const matches = text.matchAll(pattern);
32+
for (const match of matches) {
33+
const index = match.index ?? 0;
34+
const matchRange = [index, index + match[0].length] as const;
35+
const ruleError = new RuleError(
36+
`「${match[0]}」のような定型的な表現は、読み手によっては機械的な印象を与える場合があります。より自然な表現も検討してみてください。`,
37+
{
38+
padding: locator.range(matchRange)
39+
}
40+
);
41+
report(node, ruleError);
42+
}
43+
}
44+
}
45+
};
46+
};
47+
48+
export default rule;

0 commit comments

Comments
 (0)