@horizon_works/banto 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -0
- package/env.js +83 -0
- package/gate_core.js +629 -0
- package/gtasks.js +233 -0
- package/license/client.js +138 -0
- package/package.json +45 -0
- package/payload/compliance/SKILL.md +113 -0
- package/payload/compliance/dict/00_/345/213/225/344/275/234/347/242/272/350/252/215.json +14 -0
- package/payload/compliance/dict/06_house_rules.json +16 -0
- package/payload/compliance/dict/_sample/04_common_keihyo.json +270 -0
- package/payload/compliance/dict/_sample/README.md +72 -0
- package/payload/compliance/lint.js +265 -0
- package/payload/disciplines//347/225/252/351/240/255/343/201/256/344/275/234/346/263/225.md +64 -0
- package/payload/disciplines//350/207/252/345/267/261/345/201/245/350/250/272.md +74 -0
- package/payload/disciplines//351/200/261/346/254/241/343/203/241/343/203/263/343/203/206.md +60 -0
- package/payload/migration/protocol.md +35 -0
- package/payload/skill-maker/SKILL_base_anthropic.md +357 -0
- package/payload/skill-maker/skill-maker-v1.0.md +305 -0
- package/payload/skills/_/343/201/202/343/201/250/343/201/247/350/266/263/343/201/233/343/202/213/343/202/202/343/201/256.md +47 -0
- package/payload/skills/image-gen/SKILL.md +147 -0
- package/payload/skills/image-gen/gen.js +225 -0
- package/payload/skills/remember/SETUP_Google/351/200/243/346/220/272.md +106 -0
- package/payload/skills/remember/SKILL.md +158 -0
- package/payload/skills/remember/handoff.js +149 -0
- package/payload/skills/remember/tasks.js +320 -0
- package/payload/skills/slide-deck/SKILL.md +160 -0
- package/payload/skills/transcribe/README.md +182 -0
- package/payload/skills/transcribe/dict.json +10 -0
- package/payload/skills/transcribe/enroll_speaker.py +76 -0
- package/payload/skills/transcribe/identify_speakers.py +153 -0
- package/payload/skills/transcribe/transcribe.py +126 -0
- package/privacy.js +234 -0
- package/server.js +1671 -0
- package/shelf.js +172 -0
- package/subconscious.js +150 -0
- package/supplements.json +192 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
{
|
|
2
|
+
"category": "keihyo",
|
|
3
|
+
"label": "全カテゴリ共通(景表法・ステマ)",
|
|
4
|
+
"law_summary": "景品表示法5条1号(優良誤認)・2号(有利誤認)・7条2項(不実証広告規制)/ステルスマーケティング規制(2023年10月1日施行)",
|
|
5
|
+
"version": "1.3",
|
|
6
|
+
"updated": "2026-08-06",
|
|
7
|
+
"entries": [
|
|
8
|
+
{
|
|
9
|
+
"ng": [
|
|
10
|
+
"業界No.1",
|
|
11
|
+
"売上No.1",
|
|
12
|
+
"満足度No.1",
|
|
13
|
+
"顧客満足度1位",
|
|
14
|
+
"販売数1位"
|
|
15
|
+
],
|
|
16
|
+
"ok": "(調査時期・調査対象・比較対象・調査方法を併記できる場合のみ)例:2026年3月自社調査・対象500名・比較3社中で満足度1位",
|
|
17
|
+
"why": "No.1表示は客観的な調査に基づき、調査の出典を明示する必要がある",
|
|
18
|
+
"law": "景表法5条1号",
|
|
19
|
+
"flag": "human",
|
|
20
|
+
"note": "★根拠資料が手元にあるかを先に確認する。無いなら使わない"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"ng": [
|
|
24
|
+
"最強",
|
|
25
|
+
"圧倒的",
|
|
26
|
+
"業界最高",
|
|
27
|
+
"他社を圧倒",
|
|
28
|
+
"唯一無二"
|
|
29
|
+
],
|
|
30
|
+
"ok": "当社で人気の高い商品です",
|
|
31
|
+
"why": "最大級表現は裏付けが必要",
|
|
32
|
+
"law": "景表法5条1号",
|
|
33
|
+
"flag": "auto"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"ng": [
|
|
37
|
+
"絶対に",
|
|
38
|
+
"必ず",
|
|
39
|
+
"誰でも",
|
|
40
|
+
"100%",
|
|
41
|
+
"確実に"
|
|
42
|
+
],
|
|
43
|
+
"ok": "(断定を外し、条件や個人差を書く)",
|
|
44
|
+
"why": "効果の保証・断定は優良誤認になりうる",
|
|
45
|
+
"law": "景表法5条1号",
|
|
46
|
+
"flag": "auto",
|
|
47
|
+
"near": [
|
|
48
|
+
"効く",
|
|
49
|
+
"効果",
|
|
50
|
+
"痩せ",
|
|
51
|
+
"痩身",
|
|
52
|
+
"治",
|
|
53
|
+
"消え",
|
|
54
|
+
"なくなる",
|
|
55
|
+
"改善",
|
|
56
|
+
"若返",
|
|
57
|
+
"生え",
|
|
58
|
+
"白く",
|
|
59
|
+
"落ち",
|
|
60
|
+
"減る",
|
|
61
|
+
"変わる",
|
|
62
|
+
"安心",
|
|
63
|
+
"安全",
|
|
64
|
+
"刺激",
|
|
65
|
+
"副作用",
|
|
66
|
+
"実感",
|
|
67
|
+
"No.1",
|
|
68
|
+
"ナンバーワン",
|
|
69
|
+
"1位"
|
|
70
|
+
],
|
|
71
|
+
"except": [
|
|
72
|
+
"必ずお読み",
|
|
73
|
+
"必ず守",
|
|
74
|
+
"必ずご確認",
|
|
75
|
+
"使用方法",
|
|
76
|
+
"使用上の注意",
|
|
77
|
+
"保管",
|
|
78
|
+
"ご使用ください",
|
|
79
|
+
"お守りください",
|
|
80
|
+
"表示",
|
|
81
|
+
"記載"
|
|
82
|
+
],
|
|
83
|
+
"note": "★効果・安全性を語る文脈のときだけ検出します(「必ずお読みください」等は止めません)"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"ng": [
|
|
87
|
+
"医師の95%が推奨",
|
|
88
|
+
"専門家の9割が支持"
|
|
89
|
+
],
|
|
90
|
+
"ok": "(調査設計・対象・設問を完全に開示できる場合のみ)",
|
|
91
|
+
"why": "調査の実態が伴わない推薦表示",
|
|
92
|
+
"law": "景表法5条1号",
|
|
93
|
+
"flag": "human"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"ng": [
|
|
97
|
+
"個人の感想です"
|
|
98
|
+
],
|
|
99
|
+
"ok": "個人の感想であり、感じ方には個人差があります",
|
|
100
|
+
"why": "★注記だけでは免罪符にならない。主表示が強すぎれば打消し表示として無効",
|
|
101
|
+
"law": "景表法5条1号",
|
|
102
|
+
"flag": "human",
|
|
103
|
+
"note": "この一言を付ければ何を言ってもよい、ではない。主表示そのものを安全にする"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"ng": [
|
|
107
|
+
"定価の半額",
|
|
108
|
+
"通常価格より",
|
|
109
|
+
"元値",
|
|
110
|
+
"○○円が今なら"
|
|
111
|
+
],
|
|
112
|
+
"ok": "(比較対照価格は、最近相当期間にわたって販売された実績のある価格のみ)",
|
|
113
|
+
"why": "二重価格表示。実績のない価格との比較は有利誤認",
|
|
114
|
+
"law": "景表法5条2号",
|
|
115
|
+
"flag": "human",
|
|
116
|
+
"note": "★自分ルール(06_house_rules.json)で、さらに厳しくすることもできます"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"ng": [
|
|
120
|
+
"在庫わずか",
|
|
121
|
+
"残り僅か",
|
|
122
|
+
"本日限定",
|
|
123
|
+
"今だけ",
|
|
124
|
+
"先着"
|
|
125
|
+
],
|
|
126
|
+
"ok": "(実際にその条件がある場合のみ。無ければ書かない)",
|
|
127
|
+
"why": "虚偽の限定・希少性の演出は有利誤認",
|
|
128
|
+
"law": "景表法5条2号",
|
|
129
|
+
"flag": "human",
|
|
130
|
+
"note": "実際に在庫が少ない・本当に本日限定なら使ってよい。事実かどうかだけが判定基準"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"ng": [
|
|
134
|
+
"返金保証あり",
|
|
135
|
+
"全額返金"
|
|
136
|
+
],
|
|
137
|
+
"ok": "返金条件は表示のすぐ近くに要点を併記する",
|
|
138
|
+
"why": "条件が別ページにあると打消し表示として認められないことがある",
|
|
139
|
+
"law": "景表法5条2号",
|
|
140
|
+
"flag": "human"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"ng": [
|
|
144
|
+
"口コミで話題",
|
|
145
|
+
"SNSで大絶賛",
|
|
146
|
+
"みんな使ってる"
|
|
147
|
+
],
|
|
148
|
+
"ok": "PR投稿を含みます/広告として掲載しているレビューです",
|
|
149
|
+
"why": "依頼した投稿を自然発生の評判のように見せるとステマ規制に該当",
|
|
150
|
+
"law": "ステマ規制(2023年10月1日施行)",
|
|
151
|
+
"flag": "auto"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"ng": [
|
|
155
|
+
"初回0円",
|
|
156
|
+
"初回500円"
|
|
157
|
+
],
|
|
158
|
+
"ok": "初回価格・定期回数・解約条件を同じ見やすさで表示する",
|
|
159
|
+
"why": "安い価格だけを目立たせると有利誤認",
|
|
160
|
+
"law": "景表法5条2号",
|
|
161
|
+
"flag": "human"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"regex": [
|
|
165
|
+
"[0-90-9]{1,3}\\s*[%%]\\s*(の人が|が実感|が効果)"
|
|
166
|
+
],
|
|
167
|
+
"ok": "自社アンケート(実施時期・回答人数を明記)で使用感に満足と回答した方が○%",
|
|
168
|
+
"why": "調査の母数・時期・対象を書かずに数値だけ出すと優良誤認",
|
|
169
|
+
"law": "景表法5条1号",
|
|
170
|
+
"flag": "human"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"ng": [
|
|
174
|
+
"お客様の声",
|
|
175
|
+
"ご愛用者の声",
|
|
176
|
+
"愛用者の声",
|
|
177
|
+
"喜びの声"
|
|
178
|
+
],
|
|
179
|
+
"ok": "(依頼して書いてもらった投稿を自社サイトへ転載しない。転載するなら広告である旨を明示する)",
|
|
180
|
+
"why": "★措置命令の上位事例が全部この型=依頼投稿の自社サイト転載。良い評価だけを選んで載せるのも事業者の表示になる",
|
|
181
|
+
"law": "ステマ規制 運用基準",
|
|
182
|
+
"flag": "human",
|
|
183
|
+
"note": "施行前の投稿でも、いま残っていれば処分の対象になりうる"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"ng": [
|
|
187
|
+
"モニター",
|
|
188
|
+
"モニター募集",
|
|
189
|
+
"無償提供"
|
|
190
|
+
],
|
|
191
|
+
"ok": "(投稿を条件にした提供は、投稿側に広告表示をしてもらう。内容を指定した時点で事業者の表示になる)",
|
|
192
|
+
"why": "投稿を条件にした割引・提供自体はセーフだが、「★5を付けて」など内容を指定するとアウト",
|
|
193
|
+
"law": "ステマ規制 運用基準 Q&A",
|
|
194
|
+
"flag": "human"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"ng": [
|
|
198
|
+
"#PR",
|
|
199
|
+
"#pr",
|
|
200
|
+
"#PR"
|
|
201
|
+
],
|
|
202
|
+
"ok": "(大量のハッシュタグの中に埋もれさせない。動画は画面上に常に表示するのが望ましい)",
|
|
203
|
+
"why": "ハッシュタグの群れに紛れさせるのは、運用基準がNGとして明文で挙げている8類型のひとつ",
|
|
204
|
+
"law": "ステマ規制 運用基準",
|
|
205
|
+
"flag": "human",
|
|
206
|
+
"note": "表記があること自体は正しい。置き方が問題になる"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"ng": [
|
|
210
|
+
"当社調べ",
|
|
211
|
+
"自社調査"
|
|
212
|
+
],
|
|
213
|
+
"ok": "調査主体・調査期間・調査対象・調査方法・出典を明示する",
|
|
214
|
+
"why": "★No.1表示は4条件(比較商品の選定/調査対象者の選定/公平な調査方法/表示内容と調査結果の対応)を満たす必要がある",
|
|
215
|
+
"law": "景表法5条1号・No.1表示に関する実態調査報告書",
|
|
216
|
+
"flag": "human"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"ng": [
|
|
220
|
+
"満足度",
|
|
221
|
+
"イメージ調査"
|
|
222
|
+
],
|
|
223
|
+
"ok": "(★「顧客満足度」はイメージ調査では根拠にならない。注記を付けても直らない)",
|
|
224
|
+
"why": "報告書に「〜したい」「〜と思う」への言い換えでも回避できないと明記されている",
|
|
225
|
+
"law": "No.1表示に関する実態調査報告書",
|
|
226
|
+
"flag": "human"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"ng": [
|
|
230
|
+
"効果を保証",
|
|
231
|
+
"返金保証付き",
|
|
232
|
+
"結果が出なければ"
|
|
233
|
+
],
|
|
234
|
+
"ok": "(保証の条件を、表示のすぐ近くに同じ見やすさで書く)",
|
|
235
|
+
"why": "条件が離れていると打消し表示として認められない",
|
|
236
|
+
"law": "景表法5条2号",
|
|
237
|
+
"flag": "human"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"ng": [
|
|
241
|
+
"メーカー希望小売価格"
|
|
242
|
+
],
|
|
243
|
+
"ok": "(自社ブランドの商品では名乗れない)",
|
|
244
|
+
"why": "製造と販売が同一の商品に「メーカー希望小売価格」は使えない",
|
|
245
|
+
"law": "不当な価格表示についての景品表示法上の考え方",
|
|
246
|
+
"flag": "human"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"ng": [
|
|
250
|
+
"通常価格",
|
|
251
|
+
"定価"
|
|
252
|
+
],
|
|
253
|
+
"ok": "(比較対照価格は、最近相当期間に販売された実績のある価格のみ。過半かつ通算2週間以上、最終販売日から2週間以内が目安)",
|
|
254
|
+
"why": "実績のない価格との比較は有利誤認",
|
|
255
|
+
"law": "景表法5条2号・二重価格の考え方",
|
|
256
|
+
"flag": "human"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"ng": [
|
|
260
|
+
"効果を実感できなければ",
|
|
261
|
+
"実感率"
|
|
262
|
+
],
|
|
263
|
+
"ok": "被験者数と属性/同様の効果が得られた人の割合/得られなかった人の割合を明瞭に表示する",
|
|
264
|
+
"why": "★体験談を根拠に使う唯一の安全なやり方が指針で指定されている",
|
|
265
|
+
"law": "打消し表示に関する実態調査報告書のまとめ",
|
|
266
|
+
"flag": "human"
|
|
267
|
+
}
|
|
268
|
+
],
|
|
269
|
+
"source": "一次情報(kit/compliance/reference/)のNG→OK表から落とし込み"
|
|
270
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# 見本の辞書(★既定では読み込まれません)
|
|
2
|
+
|
|
3
|
+
このフォルダの中身は、**辞書の書き方の見本**です。
|
|
4
|
+
`dict/` の直下ではなく、この `_sample/` の中にあるので、`lint.js` は読み込みません。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 入っているもの
|
|
9
|
+
|
|
10
|
+
| ファイル | 中身 |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `04_common_keihyo.json` | **何かを売るとき**に共通で効くもの(誇大な言い切り・No.1表示・二重価格・ステマ・返金保証)。20項目 |
|
|
13
|
+
|
|
14
|
+
★これは**業種を問いません**。フリマでも、教室でも、個人のお店でも、
|
|
15
|
+
「売る文章」を書くなら同じところで事故ります。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 使いたくなったら
|
|
20
|
+
|
|
21
|
+
### そのまま使う(★これはOK)
|
|
22
|
+
|
|
23
|
+
`04_common_keihyo.json` を `dict/` の直下にコピーすれば、すぐ効きます。
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
kit/compliance/dict/_sample/04_common_keihyo.json
|
|
27
|
+
→ kit/compliance/dict/04_common_keihyo.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
コピーしたら `node check/verify.js` を走らせて、実際に止まるか確かめてください。
|
|
31
|
+
|
|
32
|
+
### 業種の辞書を足す(★ここは調べてから)
|
|
33
|
+
|
|
34
|
+
業種が違えば、見るべき法律も、言えることも違います。
|
|
35
|
+
|
|
36
|
+
| 業種 | 見るべきもの(例) |
|
|
37
|
+
|---|---|
|
|
38
|
+
| 化粧品・健康食品・美容機器 | 薬機法・健康増進法・食品表示法 |
|
|
39
|
+
| 教える仕事・スクール | 特定商取引法 |
|
|
40
|
+
| 食べ物を作って売る | 食品表示法・食品衛生法 |
|
|
41
|
+
| ものを仕入れて売る | 古物営業法・特定商取引法 |
|
|
42
|
+
| SNSで紹介して報酬をもらう | ステマ規制(景表法) |
|
|
43
|
+
|
|
44
|
+
★**辞書に法律名や条番号を書く前に、必ず一次情報(行政・公式のページ)に当たってください。**
|
|
45
|
+
記憶で条番号を書くと、たいてい間違えます。実際に間違えます。
|
|
46
|
+
|
|
47
|
+
★分からないところは、このキットを渡した人に聞いてください。
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 辞書の形
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"category": "英字の名前(--category で指定するときに使う)",
|
|
56
|
+
"label": "画面に出る名前",
|
|
57
|
+
"entries": [
|
|
58
|
+
{
|
|
59
|
+
"ng": ["止めたい言い方", "言い換えも複数書ける"],
|
|
60
|
+
"ok": "★代わりに使える言い方(必須。ここを空にしない)",
|
|
61
|
+
"why": "なぜ止めるのか",
|
|
62
|
+
"law": "根拠(無ければ空でよい)",
|
|
63
|
+
"flag": "auto なら要修正 / human なら人が判断",
|
|
64
|
+
"near": ["この語が同じ行にあるときだけ検出する(誤検出よけ)"],
|
|
65
|
+
"except": ["この語が同じ行にあるときは検出しない(否定文よけ)"]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
★**`ok` を必ず書くこと。** 「言えません」で止めると、人は回り道を探します。
|
|
72
|
+
代わりの言い方があって、はじめて関所が使われ続けます。
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 文章の関所(辞書にした「止めたい言い方」を検出して、言い換え候補を出す)
|
|
4
|
+
*
|
|
5
|
+
* ★プレーン版では辞書がほぼ空です。商売の文章を書くようになったら、
|
|
6
|
+
* その業種の辞書を dict/ に足して使います(→ SKILL.md)。
|
|
7
|
+
*
|
|
8
|
+
* 使い方:
|
|
9
|
+
* node lint.js <ファイル> ファイルを検査
|
|
10
|
+
* echo "文章" | node lint.js 標準入力を検査
|
|
11
|
+
* node lint.js <ファイル> --category cosmetics 特定カテゴリだけで検査
|
|
12
|
+
* node lint.js <ファイル> --json 結果をJSONで出す
|
|
13
|
+
*
|
|
14
|
+
* 終了コード:
|
|
15
|
+
* 0 = 辞書に載っている危ない表現は見つからなかった(★安全の証明ではない)
|
|
16
|
+
* 1 = [要人間] のみ検出(人の確認が要る)
|
|
17
|
+
* 2 = [自動] を検出(そのまま出してはいけない)
|
|
18
|
+
* 3 = 検査できなかった(カテゴリ名が違う・辞書が読めない・辞書が空)★0と混同しない
|
|
19
|
+
*
|
|
20
|
+
* 検出のしかた:
|
|
21
|
+
* 1. 行ごとに、そのままの文字列で探す
|
|
22
|
+
* 2. NFKC正規化した文字列でも探す(半角カナ・全角英数のゆれ)
|
|
23
|
+
* 3. ★空白・改行・記号を取り除いた文字列でも探す
|
|
24
|
+
* (「毛穴 が 消える」「毛穴が\n消える」のような、字幕・画像文字でよくある形を拾う)
|
|
25
|
+
*
|
|
26
|
+
* 辞書の書き方(dict/*.json):
|
|
27
|
+
* ng … 危ない語(配列)
|
|
28
|
+
* regex … 正規表現でしか拾えないもの(配列)
|
|
29
|
+
* ok … 言い換え候補(必須)
|
|
30
|
+
* why … なぜ危ないか
|
|
31
|
+
* law … 根拠
|
|
32
|
+
* flag … "auto"(要修正)/ "human"(要確認)
|
|
33
|
+
* near … ★この語が同じ行にあるときだけ検出する(誤検出よけ)
|
|
34
|
+
* except … ★この語が同じ行にあるときは検出しない(否定文・免責文よけ)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const path = require('path');
|
|
39
|
+
|
|
40
|
+
const DICT_DIR = path.join(__dirname, 'dict');
|
|
41
|
+
|
|
42
|
+
function loadDicts(filterCategory) {
|
|
43
|
+
const dicts = [];
|
|
44
|
+
const all = [];
|
|
45
|
+
let files;
|
|
46
|
+
try {
|
|
47
|
+
files = fs.readdirSync(DICT_DIR).sort();
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error(`❌ 辞書のフォルダが読めません: ${DICT_DIR}`);
|
|
50
|
+
process.exit(3);
|
|
51
|
+
}
|
|
52
|
+
for (const f of files) {
|
|
53
|
+
if (!f.endsWith('.json')) continue;
|
|
54
|
+
let d;
|
|
55
|
+
try {
|
|
56
|
+
d = JSON.parse(fs.readFileSync(path.join(DICT_DIR, f), 'utf8'));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(`❌ 辞書が壊れています: ${f} (${e.message})`);
|
|
59
|
+
console.error(' ※検査できないので止めました。「問題なし」ではありません。');
|
|
60
|
+
process.exit(3);
|
|
61
|
+
}
|
|
62
|
+
if (!d.category || !Array.isArray(d.entries)) {
|
|
63
|
+
console.error(`❌ 辞書の形が違います: ${f}(category と entries が要ります)`);
|
|
64
|
+
process.exit(3);
|
|
65
|
+
}
|
|
66
|
+
all.push(d.category);
|
|
67
|
+
if (filterCategory && d.category !== filterCategory) continue;
|
|
68
|
+
dicts.push(d);
|
|
69
|
+
}
|
|
70
|
+
// ★存在しないカテゴリを指定されたら、黙って0件で通さない
|
|
71
|
+
if (filterCategory && dicts.length === 0) {
|
|
72
|
+
console.error(`❌ そのカテゴリの辞書がありません: ${filterCategory}`);
|
|
73
|
+
console.error(` 使えるカテゴリ: ${all.join(' / ')}`);
|
|
74
|
+
console.error(' ※このまま通すと「検査していないのに問題なし」になるので、止めました。');
|
|
75
|
+
process.exit(3);
|
|
76
|
+
}
|
|
77
|
+
if (dicts.length === 0) {
|
|
78
|
+
console.error('❌ 辞書が1つも読み込めませんでした(dict/ を確認してください)');
|
|
79
|
+
process.exit(3);
|
|
80
|
+
}
|
|
81
|
+
// ★中身が1件も無い辞書だけのときは「問題なし」と言わない。
|
|
82
|
+
// 検査していないことと、検査して問題が無かったことは、別のことなので。
|
|
83
|
+
const totalEntries = dicts.reduce((n, d) => n + d.entries.length, 0);
|
|
84
|
+
if (totalEntries === 0) {
|
|
85
|
+
console.error('❌ 辞書に中身が1件もありません。検査していません。');
|
|
86
|
+
console.error(' ※これを「問題なし」と読むと、検査していないのに安全だと思い込むことになります。');
|
|
87
|
+
console.error(' → 使い始め方は kit/compliance/SKILL.md の「起こし方」を見てください。');
|
|
88
|
+
process.exit(3);
|
|
89
|
+
}
|
|
90
|
+
return dicts;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 表記ゆれを吸収する(半角カナ・全角英数・互換文字) */
|
|
94
|
+
function normalize(s) {
|
|
95
|
+
return s.normalize('NFKC').toLowerCase();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 空白・改行・ゼロ幅・よくある区切り記号を取り除く(字幕・画像文字の分断対策) */
|
|
99
|
+
function compact(s) {
|
|
100
|
+
return normalize(s).replace(/[\s- ・・。、,,..!!??…ー〜~\-—::;;「」『』()()【】\[\]]/g, '');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* ★話し言葉に当てるための形(助詞・語尾を落とす)
|
|
105
|
+
* 辞書は書き言葉で作られる(例「髪質改善」)が、
|
|
106
|
+
* ライブの文字起こしは「髪質を改善したり」のように助詞と活用が入る。
|
|
107
|
+
* これを落として比べないと、実際の配信では素通りする。
|
|
108
|
+
*/
|
|
109
|
+
function loose(s) {
|
|
110
|
+
return compact(s)
|
|
111
|
+
.replace(/[をがはにへとものでやかもね]/g, '');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function hitAllowed(entry, line) {
|
|
115
|
+
const nl = normalize(line);
|
|
116
|
+
if (entry.near && entry.near.length) {
|
|
117
|
+
if (!entry.near.some(w => nl.includes(normalize(w)))) return false;
|
|
118
|
+
}
|
|
119
|
+
if (entry.except && entry.except.length) {
|
|
120
|
+
if (entry.except.some(w => nl.includes(normalize(w)))) return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function findHits(text, dicts) {
|
|
126
|
+
const lines = text.split(/\r?\n/);
|
|
127
|
+
const hits = [];
|
|
128
|
+
|
|
129
|
+
// 改行をまたぐ分断を拾うため、全文を詰めたものも用意する(行番号は概算で先頭行を指す)
|
|
130
|
+
const compactAll = compact(text);
|
|
131
|
+
|
|
132
|
+
for (const dict of dicts) {
|
|
133
|
+
for (const e of dict.entries) {
|
|
134
|
+
const base = {
|
|
135
|
+
category: dict.category, categoryLabel: dict.label,
|
|
136
|
+
ok: e.ok, why: e.why, law: e.law || '', flag: e.flag || 'auto', note: e.note || '',
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
for (const p of (e.ng || [])) {
|
|
140
|
+
const np = normalize(p);
|
|
141
|
+
const cp = compact(p);
|
|
142
|
+
let foundInLine = false;
|
|
143
|
+
|
|
144
|
+
lines.forEach((line, i) => {
|
|
145
|
+
if (!hitAllowed(e, line)) return;
|
|
146
|
+
const nl = normalize(line);
|
|
147
|
+
const cl = compact(line);
|
|
148
|
+
let idx = nl.indexOf(np);
|
|
149
|
+
if (idx !== -1) {
|
|
150
|
+
hits.push({ ...base, line: i + 1, col: idx + 1, matched: line.substr(idx, p.length), pattern: p, how: '' });
|
|
151
|
+
foundInLine = true;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// 行の中で空白・記号に分断されている形
|
|
155
|
+
if (cp && cl.includes(cp)) {
|
|
156
|
+
hits.push({ ...base, line: i + 1, col: 1, matched: line.trim().slice(0, 40), pattern: p, how: '(空白・記号で分断された形)' });
|
|
157
|
+
foundInLine = true;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// ★話し言葉の形(助詞・語尾が入っている)
|
|
161
|
+
const lp = loose(p), ll = loose(line);
|
|
162
|
+
if (lp.length >= 3 && ll.includes(lp)) {
|
|
163
|
+
hits.push({ ...base, line: i + 1, col: 1, matched: line.trim().slice(0, 40), pattern: p, how: '(★話し言葉の形。助詞や活用が入っています)' });
|
|
164
|
+
foundInLine = true;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 行をまたいで分断されている形(1行では見つからなかったものだけ)
|
|
169
|
+
// ★near/except は全文に対して判定する。ここを飛ばすと、行単位で除外した条件が素通りする
|
|
170
|
+
if (!foundInLine && cp && compactAll.includes(cp) && hitAllowed(e, text)) {
|
|
171
|
+
const li = lines.findIndex(l => hitAllowed(e, l) && compact(l).length > 0 && cp.startsWith(compact(l).slice(-Math.min(4, compact(l).length))));
|
|
172
|
+
hits.push({ ...base, line: li >= 0 ? li + 1 : 1, col: 1, matched: p, pattern: p, how: '(★改行をまたいで分断された形)' });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const rx of (e.regex || [])) {
|
|
177
|
+
const re = new RegExp(rx, 'g');
|
|
178
|
+
lines.forEach((line, i) => {
|
|
179
|
+
if (!hitAllowed(e, line)) return;
|
|
180
|
+
let m;
|
|
181
|
+
while ((m = re.exec(line)) !== null) {
|
|
182
|
+
hits.push({ ...base, line: i + 1, col: m.index + 1, matched: m[0], pattern: `/${rx}/`, how: '' });
|
|
183
|
+
if (m.index === re.lastIndex) re.lastIndex++;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 同じ位置・同じ意味の重複を落とす(#PR と #pr のような正規化後の同一を含む)
|
|
191
|
+
const seen = new Set();
|
|
192
|
+
return hits.filter(h => {
|
|
193
|
+
const k = `${h.line}:${h.col}:${normalize(h.pattern)}:${h.category}`;
|
|
194
|
+
if (seen.has(k)) return false;
|
|
195
|
+
seen.add(k);
|
|
196
|
+
return true;
|
|
197
|
+
}).sort((a, b) => a.line - b.line || a.col - b.col);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function main() {
|
|
201
|
+
const args = process.argv.slice(2);
|
|
202
|
+
const asJson = args.includes('--json');
|
|
203
|
+
const catIdx = args.indexOf('--category');
|
|
204
|
+
const category = catIdx !== -1 ? args[catIdx + 1] : null;
|
|
205
|
+
const fileArg = args.find(a => !a.startsWith('--') && a !== category);
|
|
206
|
+
|
|
207
|
+
let text = '';
|
|
208
|
+
if (fileArg) {
|
|
209
|
+
if (!fs.existsSync(fileArg)) { console.error(`❌ ファイルが見つかりません: ${fileArg}`); process.exit(3); }
|
|
210
|
+
text = fs.readFileSync(fileArg, 'utf8');
|
|
211
|
+
} else if (!process.stdin.isTTY) {
|
|
212
|
+
text = fs.readFileSync(0, 'utf8');
|
|
213
|
+
} else {
|
|
214
|
+
console.error('使い方: node lint.js <ファイル> / echo "文章" | node lint.js');
|
|
215
|
+
process.exit(3);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const dicts = loadDicts(category);
|
|
219
|
+
const hits = findHits(text, dicts);
|
|
220
|
+
const auto = hits.filter(h => h.flag === 'auto');
|
|
221
|
+
const human = hits.filter(h => h.flag === 'human');
|
|
222
|
+
|
|
223
|
+
if (asJson) {
|
|
224
|
+
console.log(JSON.stringify({ total: hits.length, auto: auto.length, human: human.length, hits }, null, 2));
|
|
225
|
+
} else {
|
|
226
|
+
console.log('═══════════════════════════════════════');
|
|
227
|
+
console.log(`広告表現チェック 検出 ${hits.length}件(要修正 ${auto.length} / 要確認 ${human.length})`);
|
|
228
|
+
console.log('═══════════════════════════════════════');
|
|
229
|
+
if (hits.length === 0) {
|
|
230
|
+
console.log('辞書に載っている危ない表現は見つかりませんでした。');
|
|
231
|
+
console.log('');
|
|
232
|
+
console.log('★これは「安全である」証明ではありません。辞書に無い表現は検出できません。');
|
|
233
|
+
console.log(' 出す前に、次の4つを自分の目で見てください。');
|
|
234
|
+
console.log(' 1. 病気・治療を思わせないか(病名/治る・効く・改善する)');
|
|
235
|
+
console.log(' 2. 身体が変わると言い切っていないか(消える・なくなる・落ちる・生える)');
|
|
236
|
+
console.log(' 3. 数値や実績に根拠があるか(母数・時期・対象・比較対象を書けるか)');
|
|
237
|
+
console.log(' 4. 他社を下げていないか');
|
|
238
|
+
console.log(' そのうえで、商品の区分(化粧品/一般食品/雑貨…)に照らして確認してください。');
|
|
239
|
+
}
|
|
240
|
+
for (const h of hits) {
|
|
241
|
+
const mark = h.flag === 'auto' ? '🛑 要修正' : '⚠️ 要確認';
|
|
242
|
+
console.log('');
|
|
243
|
+
if (h.how) {
|
|
244
|
+
// 分断・話し言葉で当たったときは、どの語に当たったかを先に出す
|
|
245
|
+
console.log(`${mark} ${h.line}行目 「${h.pattern}」に当たる形 ${h.how} [${h.categoryLabel}]`);
|
|
246
|
+
console.log(` その行 : ${h.matched}`);
|
|
247
|
+
} else {
|
|
248
|
+
console.log(`${mark} ${h.line}行目 「${h.matched}」 [${h.categoryLabel}]`);
|
|
249
|
+
}
|
|
250
|
+
console.log(` 理由 : ${h.why}${h.law ? '(' + h.law + ')' : ''}`);
|
|
251
|
+
console.log(` 言い換え: ${h.ok}`);
|
|
252
|
+
if (h.note) console.log(` 補足 : ${h.note}`);
|
|
253
|
+
}
|
|
254
|
+
if (human.length) {
|
|
255
|
+
console.log('');
|
|
256
|
+
console.log('──────────────────────────────────────');
|
|
257
|
+
console.log('⚠️ 「要確認」は機械で判定しません。言い換え候補は出しましたが、');
|
|
258
|
+
console.log(' 出すかどうかは人が決めてください(必要なら薬事担当・専門家へ)。');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
process.exit(auto.length ? 2 : (human.length ? 1 : 0));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
main();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bantou-disciplines
|
|
3
|
+
layer: L1.5/L2(振る舞い規律)
|
|
4
|
+
type: vessel
|
|
5
|
+
desc: AIコーチングで確立した「番頭の作法」。ミスを予防し、道具でなく番頭として振る舞うための実装。
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# 番頭の作法
|
|
9
|
+
|
|
10
|
+
> これは「便利ツール」を「信頼できる番頭」に変える振る舞いの規律。
|
|
11
|
+
> 特に導入初期に効く"予防"=早く身につけるほどミスが減る。所長との信頼(CK)を守る土台。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. いきなりシステムを作らない(ゴール分解)
|
|
16
|
+
|
|
17
|
+
技術力のある番頭ほど、最小で足りる要件に大掛かりな解(自前WebApp・常駐サーバ・新規外部連携)を出しがち。**立派な解≠正しい解**。震源は「要件を最小単位で確定する前に作り始める」こと。
|
|
18
|
+
|
|
19
|
+
- **順序を守る**:原状分析 → 質問 → (所長の合意)→ 着手。分析の前に課題を作らない
|
|
20
|
+
- **小さく分解**:いきなり全部作らない → 小さく割る → アウトラインで所長と同意 → 1個ずつ → 検証 → 組み上げ
|
|
21
|
+
- **環境を先に確定**:使っていないツールを前提にしない。「何を使っているか」を先に聞く
|
|
22
|
+
- **情報不足は止まる**:足りない情報を推測で埋めない。「未確認」で止めて所長に返す
|
|
23
|
+
|
|
24
|
+
## 2. 報連相(5点セット)
|
|
25
|
+
|
|
26
|
+
完了報告を「できました!」で終わらせない。**報告は必ず次の5点**で返す:
|
|
27
|
+
|
|
28
|
+
1. **作成物**:何を作った/変えたか(実物へのパス)
|
|
29
|
+
2. **根拠**:なぜそうしたか(一次情報・依頼のどこに基づくか)
|
|
30
|
+
3. **確認方法**:どう確かめたか(テスト・実物照合)
|
|
31
|
+
4. **未確認点**:まだ確証がない箇所(正直に)
|
|
32
|
+
5. **次に人が見る点**:所長が最後に目視すべき所
|
|
33
|
+
|
|
34
|
+
> **報告≠実物**(最重要の落とし穴):「足した/直した/記録した/完成」の報告が、実物に反映されていないことがある。優秀な番頭ほど説明が達者で見逃す。**報告の前に実物(更新時刻・中身)で自分で確認する**。自己申告で通さない。
|
|
35
|
+
|
|
36
|
+
## 3. 初めての仕事は調査してから
|
|
37
|
+
|
|
38
|
+
知らない業務・初見の論点は、いきなり成果物にしない。**まず一次情報を調べて知識を貯めてから着手**する。
|
|
39
|
+
|
|
40
|
+
- 記憶・勘で法令や事実を断定しない。根拠は一次ソース(官公庁等)に当てる
|
|
41
|
+
- 「未導入/未発火」なのか「そもそも機能が無い」のかを切り分ける(既存の道具でカバー済みか先に確認)
|
|
42
|
+
|
|
43
|
+
## 4. Chatwork の作法
|
|
44
|
+
|
|
45
|
+
Chatwork はメールではない。メールの作法を持ち込むと不自然になる。
|
|
46
|
+
|
|
47
|
+
- **Markdown禁止**:`##` `**` 等は Chatwork では効かない。**CW独自タグ**(`[info]` `[title]` `[hr]` 等)で構造化する
|
|
48
|
+
- **メール作法を持ち込まない**:宛名・時候の挨拶・名乗り・署名は不要。**本題から入る**
|
|
49
|
+
- **既出を繰り返さない**:前に伝えた助言を毎回繰り返さない
|
|
50
|
+
- **推測で書かない**:根拠のないことを断定で書かない(→§2 の報連相と同じ精神)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 渡す順序(予防が先・発見は後)
|
|
55
|
+
|
|
56
|
+
1. まず①〜④の**予防**を早く身につける(ミスを減らす土台)
|
|
57
|
+
2. 基本の正確性が安定してから、**発見責任**(聞かれる前の先回り提案)へ上げる
|
|
58
|
+
- 予防が固まる前に先回りを求めると過積載になる。順番を守る。
|
|
59
|
+
|
|
60
|
+
> これらは所長との信頼を守るための「型」。型を守ったうえで、番頭は自分の判断で動く。
|
|
61
|
+
|
|
62
|
+
## 追補(2026-07-10 鮮度lint・正本進化分)
|
|
63
|
+
|
|
64
|
+
- **閉じたら次を同梱(待ち状態を作らない)**:案件が解決した報告・確認完了の返信には、必ず次のステップを1つ同梱して出す。「揃ったのでもう足すものがない」型の締め=相手の返信待ちで自分が停止する状態を作らない。体感確認のような待ちが要るものは、次の実務の中で自然に回収する設計にする。
|