@uehaj/sys1grep 0.5.0-next.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.ja.md ADDED
@@ -0,0 +1,775 @@
1
+ # sys1grep — ファイルのための System 1、TypeSafe AI の Jev を使う
2
+
3
+ [English version](README.md)
4
+
5
+ 背景と設計の解説: [Jevのキラーアプリ、「意味で探す grep」を作った(Zenn)](https://zenn.dev/uehaj/articles/jev-semgrep-grep-by-meaning)
6
+
7
+ 正規表現ではなく **意味** で行を探す grep です。
8
+ 判定には [TypeSafe AI](https://typesafe.ai/) の System One モデル **Jev** を使います。
9
+ Jev は文章を生成せず、typed な質問に確率だけを返すモデルなので、1 行ごとに
10
+ 「この行は『ネットワーク障害』の意味に合うか」と聞き、返ってきた確率を閾値で切ります。
11
+
12
+ ```sh
13
+ ./sys1grep -n -e "顧客が怒っている、または不満を持っている" tickets.txt
14
+ ```
15
+
16
+ [![sys1grep のデモ: 日本語の意味で 6 言語の返金要求を探す / 「返金について」と「返金を求めている」の違い / -Q で答えを探す](docs/demo.svg)](https://uehaj.github.io/sys1grep/)
17
+
18
+ <sub>▶ デモをクリックするか <a href="https://uehaj.github.io/sys1grep/">uehaj.github.io/sys1grep</a> を開くと、ランディングページで全編のデモが見られます。</sub>
19
+
20
+ - 依存ゼロ。1 ファイル、Node.js 20.16 以降と `fetch` だけで動きます。
21
+ - 速い。30 行を 1 リクエストにまとめ、8 本並列で投げます。210 行のファイルが 1 秒弱で終わります。
22
+ - 意味は AND / OR / NOT で自由に組み合わせられます。
23
+ - **言語を問わない。** 意味も本文もどの言語でも構いません。日本語の意味でフランス語・ロシア語・中国語・韓国語の行が同じように見つかります。翻訳は挟まず、速度も費用も同じです。
24
+
25
+ ## 言語をまたいで探せる
26
+
27
+ 意味と本文の言語が違っていても構いません。Jev は語ではなく概念を照合するので、
28
+ 1 つの問い合わせでファイル中のあらゆる言語の行が対象になります。
29
+
30
+ **英語**の意味で**日本語**の行が見つかります。どの行にも angry / frustrated の語はなく、うち 2 行は日本語です。
31
+
32
+ ```sh
33
+ $ ./sys1grep -n -e "customer is angry or frustrated" tests/corpus.txt
34
+ 14:ユーザー山田さんからの問い合わせ: 返金してほしい、商品が壊れていた
35
+ 16:ユーザー佐藤さんからの問い合わせ: 注文した覚えのない請求が来ています。至急確認してください
36
+ 18:I want my money back. The item arrived broken and customer service ignored me.
37
+ 21:Your product ruined my weekend. Never buying from you again.
38
+ 23:This is the third time I'm writing. Nobody has replied to my previous emails.
39
+ ```
40
+
41
+ **日本語**の意味で**英語**の行が、日本語の行と同じ確信度で見つかります。
42
+
43
+ ```sh
44
+ $ ./sys1grep -n -p -e "返金の要求" tests/corpus.txt
45
+ 14:ユーザー山田さんからの問い合わせ: 返金してほしい、商品が壊れていた [0.97]
46
+ 18:I want my money back. The item arrived broken and customer service ignored me. [0.95]
47
+ ```
48
+
49
+ 日英に限った話ではありません。`tests/multi.txt` には仏・露・独・西・中・韓の返金要求と感謝の文が入っています。
50
+ 日本語の意味 1 つで 6 言語の返金要求がすべて見つかり、ロシア語で書いた意味でも同じ結果になります。
51
+
52
+ ```sh
53
+ $ ./sys1grep -n -p -e "顧客が返金を求めている" tests/multi.txt
54
+ 1:Je veux être remboursé, le produit est arrivé cassé. [0.98]
55
+ 3:Я требую вернуть деньги, товар не работает. [0.97]
56
+ 5:Ich möchte mein Geld zurück, das Gerät ist defekt. [0.97]
57
+ 7:Quiero un reembolso, el paquete llegó vacío. [0.97]
58
+ 9:我要求退款,商品坏了。 [0.97]
59
+ 11:환불해 주세요. 제품이 고장났어요. [0.97]
60
+
61
+ $ ./sys1grep -n -p -e "клиент требует возврат денег" tests/multi.txt
62
+ 1:Je veux être remboursé, le produit est arrivé cassé. [0.94]
63
+ 3:Я требую вернуть деньги, товар не работает. [0.97]
64
+ 5:Ich möchte mein Geld zurück, das Gerät ist defekt. [0.92]
65
+ 7:Quiero un reembolso, el paquete llegó vacío. [0.89]
66
+ 9:我要求退款,商品坏了。 [0.92]
67
+ 11:환불해 주세요. 제품이 고장났어요. [0.89]
68
+ ```
69
+
70
+ 多言語が混ざったログや問い合わせのダンプ、メンバーごとに問い合わせの言語が違うチームで効きます。
71
+ 注意点が 1 つあります。TypeSafe は英語の精度が最も高いと明記しており、手元の実測でも日本語の意味は
72
+ 閾値付近でややぶれます。際どい問い合わせは英語で書く方が安定します。
73
+
74
+ ## ベクトル検索と何が違うのか
75
+
76
+ 「X に関係のある行」が欲しいだけなら、埋め込みのコサイン類似度でも同じ行が出ます。違うのは判定の中身です。
77
+ sys1grep は話題の近さではなく、その行について **命題が成り立つか** を判定します。Jev は行と質問を同時に読んで
78
+ 答える(cross-encoder 型の)モデルなので、誰が何をしたか、否定、「求めている」のか「済んだ」のかで答えが変わります。
79
+ 行の埋め込みは質問を見る前に固定されるので、測れるのは話題の近さまでです。
80
+
81
+ 次の 6 行はどれも「返金の話」ですが、顧客が返金を求めているのは 2 行だけです。
82
+
83
+ ```sh
84
+ $ ./sys1grep -n -p -t 0 -e "customer is asking for a refund" tests/contrast.txt
85
+ 1:返金してほしい。商品が壊れていた [0.98]
86
+ 2:返金処理が完了しましたのでご確認ください [0.10]
87
+ 3:当社の返金ポリシーは購入後30日以内です [0.10]
88
+ 4:The manager denied the refund request yesterday [0.17]
89
+ 5:I demand a full refund immediately [0.94]
90
+ 6:Refunds are processed within 5 business days [0.08]
91
+ ```
92
+
93
+ 意味ごとに独立した確率が出るので、**論理的な AND と NOT がそのままブール演算になります**。
94
+ 集合の引き算や「否定クエリ」の工夫は要りません。
95
+
96
+ ```sh
97
+ # 返金の話だが、顧客が求めているのではない → 完了報告、ポリシー、却下、日数
98
+ $ ./sys1grep -n -e "about a refund" -v "the customer is asking for a refund" tests/contrast.txt
99
+ 2:返金処理が完了しましたのでご確認ください
100
+ 3:当社の返金ポリシーは購入後30日以内です
101
+ 4:The manager denied the refund request yesterday
102
+ 6:Refunds are processed within 5 business days
103
+
104
+ # 怒っている、かつ、それが顧客であってスタッフではない
105
+ $ ./sys1grep -n -e "someone is angry" -a "the customer, not the staff, is the one acting" tests/contrast.txt
106
+ 5:I demand a full refund immediately
107
+ 8:顧客が怒って電話を切った
108
+ ```
109
+
110
+ 7 行目の `カスタマーサポート担当者が怒って電話を切った` は、2 つ目の意味で 0.05 となり除外されます。
111
+ 7 行目と 8 行目のコサイン類似度はほぼ 1 です。
112
+
113
+ 実用上の帰結が 2 つあります。確率は較正されているので閾値 0.5 がどの質問にも使えます。コサイン類似度は
114
+ top-k か質問ごとの閾値調整が要ります。また索引を作らず、目の前のファイルにそのまま当てられます。
115
+ 裏返すと、問い合わせのたびにコーパス全体分を払うので、同じ大きなコーパスに何度も問い合わせるなら
116
+ ベクトル索引の方が安くて速いです。
117
+
118
+ ## 正規表現項
119
+
120
+ `-e`/`-a`/`-v '/pattern/flags'`(先頭と末尾が `/`、フラグは JavaScript のもの)はローカルで判定される
121
+ ただの正規表現で、リクエストは一切発生しません。同じ AND 項の絞り込みになり、これが当たった行だけが
122
+ その項の意味を尋ねられるので、意味の前に安い正規表現を置くと、費用も待ち時間も下がります。行が送られるのは、
123
+ どれかの項の正規表現がすべて当たったときだけです(正規表現の無い項はどの行にも当たる扱い)。そのため
124
+ `-e '/re/' -a A -e B` では、`re` を含まない行も送られ、`B` だけを尋ねられます。正規表現項だけの式は何も
125
+ 送りません。ただし `--sentence`(既定の `=jev`)は折り返しの切れ目を Jev に尋ねるので、送らずに済ませたい
126
+ ときは `--sentence=rules` を使います。`/…/flags` の形をしていないものは今まで通り意味です。`-e '/etc 以下のファイルを変更している'`
127
+ (閉じる `/` が無い)は影響を受けません。本当に `/` で始まり `/` で終わる意味は、正規表現と誤認されない
128
+ よう先頭にスペースを置けます。
129
+
130
+ ```sh
131
+ $ ./sys1grep -e '/ERROR|FATAL/' app.log # リクエストなし
132
+ $ ./sys1grep -e '/timeout/i' -a '顧客に影響が出ている' app.log # timeout を含む行だけ Jev へ
133
+ ```
134
+
135
+ 正規表現の名前付き・番号付きグループは、同じ AND 項の他の意味に `$<name>`、`$1`-`$99`、`$&`、`$$` として
136
+ 渡ります。ECMAScript の置換パターン構文(`String.prototype.replace` の `GetSubstitution`)そのもので、
137
+ 違いは 1 点だけ: `$<name>` が存在しないグループを指すのは(空文字列ではなく)エラーです(否定した正規
138
+ 表現のグループを指すのも同様)。`$n` が存在しないグループを指す場合は ECMAScript と同じくそのまま
139
+ 文字として残るので、`$100 以上の請求` は影響を受けません。
140
+
141
+ ```sh
142
+ $ ./sys1grep -e '/(?<date>\d{4}-\d\d-\d\d) (?<time>\d\d:\d\d)/' \
143
+ -a '$<time> が深夜(0時〜5時)であり、$<date> が週末である' app.log
144
+ # 2026-09-19 03:12 ... → 「03:12 が深夜(0時〜5時)であり、2026-09-19 が週末である」と尋ねる
145
+ ```
146
+
147
+ `$<name>` と単一引用符を勧めます。`$<name>` は sh/bash/zsh のダブルクォート内でも生き残りますが、
148
+ `$1`、`$time`、`${time}` はシェル自身に展開されてしまいます。`-p` は正規表現項に `1.00`/`0.00` を表示します。
149
+
150
+ 端末では、成り立った項の正規表現の一致をすべて grep の一致の色(太字の赤)で表示します。否定した正規表現には色を付けません。
151
+ `-o` は一致をそれぞれ 1 行ずつ出します(`grep -o` と同じ)。意味には「一致した部分」がないので、意味だけで当たった行は行全体を出します。
152
+
153
+ ```sh
154
+ $ ./sys1grep -o -n -e '/[A-Z]+-\d+/' -a 'チケットがまだ閉じていない' notes.txt # チケット番号を 1 行ずつ
155
+ ```
156
+
157
+ ## 送る量を減らす
158
+
159
+ Jev に送る行が増えるほど、費用も時間もかかります。いちばん安いのは、送らずに済ませた行です。絞り方を、
160
+ 大きく削れるものから順に並べます。
161
+
162
+ - **どのファイルを探すか。** `-r` は `.git`、`node_modules`、バイナリ、秘密情報らしいファイル、git が無視する
163
+ ものを飛ばします。[`git sys1grep`](#git-のサブコマンドとして-git-sys1grep) は追跡しているファイルだけを探します。
164
+ `--include` / `--exclude`(ファイル名のグロブ)と `--changed-within`(`30m`、`7d`、`today`、`this-week`、日付)で
165
+ さらに絞れます。言語や変更時期を指定する意味は、それだけでファイルを絞ります
166
+ ([意味からの絞り込み](#意味からの絞り込み))。
167
+ - **どの行を送るか。** [正規表現項](#正規表現項)はローカルで判定し、当たった行だけが同じ AND 項の意味を
168
+ 尋ねられます。空行は送りません。
169
+ - **何回判定するか。** [`--dedup`](#テンプレートごとに-1-行だけ判定する---dedup) は、ID・数値・時刻・パスだけが
170
+ 違う行をまとめて、テンプレートごとに 1 行だけ判定します。
171
+ - **払う前に確かめる。** `--dry-run` は何も送らず、検索するファイル、ファイルごとの送る行数、各リクエストと
172
+ その質問を表示します。最後の行には入力トークン数と、TypeSafe 本体なら料金の見積もりが出ます
173
+ (`~3178 input tokens, ~$0.000133`。誤差 1 割程度)。`-i` は同じ集計を端末に出し、`y` と答えたときだけ送ります。
174
+
175
+ ```sh
176
+ $ sys1grep --dry-run -r --include='*.log' --changed-within=today -e '/ERROR|FATAL/' -a '顧客に影響が出ている' logs/
177
+ ```
178
+
179
+ ## インストール
180
+
181
+ 使い方は 2 通りあります。コマンドラインツールとして使う(この節)か、Claude Code のスキルとして使う
182
+ (後述の [Claude Code から使う](#claude-code-から使う))か。スキルは `npx @uehaj/sys1grep` に自動で切り替わるので、
183
+ Claude Code からしか使わないならここでのインストールは不要で、API キーの設定だけで済みます。
184
+
185
+ Node.js 20.16 以降が必要です。ほかの依存はありません。
186
+
187
+ ```sh
188
+ npm install -g @uehaj/sys1grep
189
+ sys1grep --help
190
+ ```
191
+
192
+ インストールせずに試すなら `npx` で実行できます(初回だけダウンロードし、2 回目以降はキャッシュから起動します)。
193
+
194
+ ```sh
195
+ npx @uehaj/sys1grep -n -e "顧客が怒っている、または不満を持っている" tickets.txt
196
+ ```
197
+
198
+ 次に [TypeSafe のコンソール](https://console.typesafe.ai/) で取得した API キーを渡します。どれか 1 つで構いません。
199
+
200
+ ```sh
201
+ export SYS1GREP_API_KEY=your-key # 環境変数
202
+ echo 'SYS1GREP_API_KEY=your-key' > ~/.config/sys1grep/.env # ユーザー単位 (先に mkdir -p)
203
+ ```
204
+
205
+ 環境変数が優先で、足りない分は `~/.config/sys1grep/.env` から補います。カレントディレクトリの `.env` は読みません。
206
+ clone したばかりのリポジトリのものかもしれず、`SYS1GREP_URL` を通じてキーを別のサーバへ送らせ得るからです。
207
+ プロジェクト単位の設定は、自分で読み込ませてください: `node --env-file=.env "$(command -v sys1grep)" ...`。
208
+ `SYS1GREP_API_KEY` が無ければ `TYPESAFE_API_KEY` も使えます。
209
+
210
+ ### 既定のオプション
211
+
212
+ `SYS1GREP_OPTS` に書いたオプションは毎回の呼び出しに付きます。読み方は上の設定と同じです。空白で区切って
213
+ コマンドラインの前に置くので、コマンドラインが優先します。後に書いた値が効き、`--no-X` で既定のフラグを消せます。
214
+
215
+ ```sh
216
+ export SYS1GREP_OPTS='--level strict -j 8 -n'
217
+ sys1grep -e "決済の失敗" app.log # strict、8 並列、行番号付き
218
+ sys1grep --level loose --no-n -e "決済の失敗" app.log
219
+ ```
220
+
221
+ sys1grep を呼ぶスクリプトもこの既定値を拾います(grep が `GREP_OPTIONS` を廃止した理由です)。スクリプトからは
222
+ `SYS1GREP_OPTS= sys1grep ...` と空にして呼んでください。
223
+
224
+ ### 他のエンドポイント
225
+
226
+ API の設定は `SYS1GREP_API_KEY`(または `TYPESAFE_API_KEY`)、`SYS1GREP_URL`、`SYS1GREP_MODEL` の 3 つだけです。
227
+ TypeSafe の `POST /v1/systemone` と同じ形で話すエンドポイントなら使えます。キーは `SYS1GREP_URL` の先へそのまま
228
+ 送られるので、2 つは組にして設定してください。コマンドラインの `--sys1-model=ID`、`--sys1-url=URL`、`--sys1-api-key=KEY` は
229
+ この 3 つより優先します。コマンドラインのキーは `ps` やシェル履歴に残るので、キーはなるべく `~/.config/sys1grep/.env` に書いてください。
230
+
231
+ ```sh
232
+ # OpenRouter
233
+ SYS1GREP_URL=https://openrouter.ai/api/v1/systemone SYS1GREP_API_KEY=sk-or-... sys1grep -e ...
234
+ # Vercel AI Gateway
235
+ SYS1GREP_URL=https://ai-gateway.vercel.sh/typesafe/v1/systemone SYS1GREP_MODEL=typesafe-ai/jev SYS1GREP_API_KEY=vck_... sys1grep -e ...
236
+ # キーの要らない互換サーバ: Authorization ヘッダを付けずに送る
237
+ SYS1GREP_URL=http://localhost:8000/v1/systemone sys1grep -e ...
238
+ ```
239
+
240
+ 集計行には、エンドポイントが返した費用(`usage.cost`)を出します。TypeSafe 本体の場合は定価での推定を `~` 付きで出します。
241
+
242
+ ソースから使うなら `git clone https://github.com/uehaj/sys1grep.git && cd sys1grep && npm install -g .`、
243
+ またはそのまま `node sys1grep.mjs ...` で動きます。
244
+
245
+ ## 例
246
+
247
+ 例はすべて [`tests/corpus.txt`](tests/corpus.txt) に対するものです。サーバログ、日英の問い合わせ、
248
+ ソースコード、SQL、雑談が混ざった 51 行のファイルです。
249
+
250
+ ### 概念で探す。言語は問わない
251
+
252
+ ```sh
253
+ $ ./sys1grep -n -e "customer is angry or frustrated" tests/corpus.txt
254
+ 14:ユーザー山田さんからの問い合わせ: 返金してほしい、商品が壊れていた
255
+ 16:ユーザー佐藤さんからの問い合わせ: 注文した覚えのない請求が来ています。至急確認してください
256
+ 18:I want my money back. The item arrived broken and customer service ignored me.
257
+ 21:Your product ruined my weekend. Never buying from you again.
258
+ 23:This is the third time I'm writing. Nobody has replied to my previous emails.
259
+ 5 of 51 lines matched; 51 sent to Jev in 2 requests, 3225 input tokens
260
+ ```
261
+
262
+ どの行にも「angry」「frustrated」という語はありません。英語の意味で日本語の行も拾えています。
263
+
264
+ ### 問いに答えている行を探す (`-Q`)
265
+
266
+ `-e` は「その意味が成り立つか」を聞く。`-Q QUESTION` (`--question`) は、その問いに答えている行を探す。
267
+ そのため、問いは尋ねるときのままの疑問文で書ける:
268
+
269
+ ```sh
270
+ $ echo "ジョブは失敗した" | ./sys1grep -e "ジョブは成功しましたか?"
271
+ $ echo "ジョブは失敗した" | ./sys1grep -Q "ジョブは成功しましたか?"
272
+ ジョブは失敗した
273
+ ```
274
+
275
+ 意味として見ると、「ジョブは成功しましたか?」はこの行が述べていることではない (0.02) ので、`-e` では何も
276
+ 当たらない。問いとして見ると、この行は「いいえ、失敗した」と答えている (0.67) ので、`-Q` では当たる。
277
+ 質問している行は意味としては近いが、答えてはいない。Yes/No の問いなら、それを否定する行も答えたことに
278
+ なる:
279
+
280
+ ```sh
281
+ $ ./sys1grep -n -Q "whether the server is down" tests/intent.txt
282
+ 5:The server is down.
283
+ 6:The server is healthy and responding normally.
284
+ 2 of 17 lines matched; 17 sent to Jev in 1 request, 1087 input tokens, ~$0.000046
285
+ ```
286
+
287
+ 確認する行も否定する行も、どちらもサーバが落ちているかどうかを解消しているので一致する。`Is the server
288
+ down?` は尋ねているだけで答えていないので一致しない。`-Q X` は `-e "the line answers: X"` の略記なので、
289
+ `-a` / `-v` / `!` を `-e` と同じように併用でき、他の項とは OR で結ばれる。
290
+
291
+ 理由を尋ねる問いでも同じで、答えになる行だけが返る。同じ `tests/intent.txt` で、`-e "ジョブはなぜ失敗したか"` は
292
+ 「なぜジョブは失敗したのですか」(10 行目)にも当たるが、`-Q` では「ジョブはディスク容量不足のために失敗した」
293
+ (9 行目)だけが当たる。
294
+
295
+ ### OR で 2 つの意味。`-p` で確率も見る
296
+
297
+ ```sh
298
+ $ ./sys1grep -n -p -e "返金の要求" -e "配送先の変更依頼" tests/corpus.txt
299
+ 14:ユーザー山田さんからの問い合わせ: 返金してほしい、商品が壊れていた [0.97 0.02]
300
+ 17:ユーザー高橋さんからの問い合わせ: 配送先の住所を変更したいのですが [0.01 0.96]
301
+ 18:I want my money back. The item arrived broken and customer service ignored me. [0.96 0.01]
302
+ 22:Can I change the delivery address for order #8821? [0.02 0.96]
303
+ 4 of 51 lines matched; 51 sent to Jev in 2 requests, 4602 input tokens
304
+ ```
305
+
306
+ 末尾の括弧が、指定した順に各意味の確率です。閾値を決めるときの目安になります。
307
+
308
+ `--color`(端末では既定で有効)を付けると、確率が閾値に対して色分けされます。
309
+ `-t` 以上は緑、`-T` 未満は赤、あいだは黄です。行番号とファイル名は grep と同じ配色です。
310
+
311
+ ![色付き出力: 行番号は緑、確率は緑または赤](docs/color.svg)
312
+
313
+ ### AND NOT。ネットワーク障害のうちリトライ中のものを除く
314
+
315
+ ```sh
316
+ $ ./sys1grep -n -e "ネットワークやリモート接続の障害" -v "a retry is happening or was attempted" tests/corpus.txt
317
+ 4:2026-09-19 08:02:30 ERROR connection reset by peer while calling payment-gateway
318
+ 6:2026-09-19 08:02:35 ERROR timeout after 5000ms waiting for payment-gateway
319
+ 9:2026-09-19 08:10:44 ERROR DNS lookup failed for api.example.com
320
+ 11:2026-09-19 09:00:00 ERROR SSL handshake failed: certificate expired
321
+ 13:network unreachable: no route to host 10.0.0.5
322
+ 30:except ConnectionError as e:
323
+ 31: logger.error("upstream unreachable: %s", e)
324
+ 7 of 51 lines matched; 51 sent to Jev in 2 requests, 5112 input tokens
325
+ ```
326
+
327
+ 5 行目の `retrying payment-gateway request (attempt 2/3)` はネットワーク障害ですが、`-v` で落ちています。
328
+
329
+ ### 混合。(金融 AND 悪いニュース) OR 天気
330
+
331
+ ```sh
332
+ $ ./sys1grep -n -e "about economy, finance or markets" -a "the news is negative or a decline" -e "about weather" tests/corpus.txt
333
+ 36:今日の天気は晴れ、最高気温は28度です
334
+ 43:Stock prices fell 3% after the earnings report missed expectations.
335
+ 48:明日は雨の予報なので傘を持っていきます
336
+ 3 of 51 lines matched; 51 sent to Jev in 2 requests, 5673 input tokens
337
+ ```
338
+
339
+ `The central bank raised interest rates` は金融の話ですが下落ではないので外れています。
340
+
341
+ ### 厳しさのプリセット
342
+
343
+ ```sh
344
+ $ ./sys1grep --level strict -n -e "a security risk or dangerous destructive operation" tests/corpus.txt
345
+ 33:DROP TABLE sessions;
346
+ 49:API keys must never be committed to the repository.
347
+
348
+ $ ./sys1grep --level loose -n -e "a security risk or dangerous destructive operation" tests/corpus.txt
349
+ 11:2026-09-19 09:00:00 ERROR SSL handshake failed: certificate expired
350
+ 16:ユーザー佐藤さんからの問い合わせ: 注文した覚えのない請求が来ています。至急確認してください
351
+ 33:DROP TABLE sessions;
352
+ 49:API keys must never be committed to the repository.
353
+ ```
354
+
355
+ `strict` はモデルが確信している行だけ、`loose` は期限切れ証明書や身に覚えのない請求まで拾います。
356
+
357
+ ### ディレクトリを再帰検索、ファイル名だけ表示
358
+
359
+ ```sh
360
+ $ ./sys1grep -r -n -e "customer is asking for a refund" tests/tickets/
361
+ tests/tickets/a.txt:7:Ticket #16: I want a refund, the item was broken.
362
+ tests/tickets/sub/b.txt:1:The customer wants a refund for the broken lamp.
363
+
364
+ $ ./sys1grep -rl -e "customer is asking for a refund" tests/tickets/
365
+ tests/tickets/a.txt
366
+ tests/tickets/sub/b.txt
367
+ ```
368
+
369
+ `-r` はディレクトリを名前順にたどり、`.git`、`node_modules`、`.ssh`、`.aws`、`.gnupg`、`.kube`、`.docker`、
370
+ バイナリ(先頭 8 KB に NUL がある、または PDF。BOM 付きの UTF-16 はテキストとして読む)、
371
+ 秘密情報になりがちなファイル(`.env*`、`.netrc`、`.npmrc`、`.pypirc`、`.pgpass`、`.git-credentials`、`*.pem`、
372
+ `*.key`、`*.p12`、`*.pfx`、`*.jks`、`*.keystore`、`id_rsa*` など。大文字小文字は区別しない)を飛ばします。
373
+ **検索対象の行はすべて TypeSafe の API に送られる**ので、スキャンするつもりのディレクトリだけを指定してください。
374
+ git リポジトリの中では、git が無視するもの(`.gitignore`、`.git/info/exclude`、グローバルの除外ファイル)も `-r` で
375
+ 飛ばすので、ビルド成果物や手元だけのファイルは送られません。追跡中のファイルは、無視パターンに当たっても検索します。
376
+ コマンドラインで明示したファイルは、除外リストに該当しても、git が無視していても検索します。git が無視している
377
+ ディレクトリも、名前を指定すれば検索します(`sys1grep -r -e ... dist`)。
378
+ `-l` は一致したファイルを見つかった順に 1 回ずつ表示し、`-r` の有無にかかわらず使えます。`-c` は行の代わりにファイルごとの一致行数を出します。
379
+
380
+ ### 意味からの絞り込み
381
+
382
+ 一致を特定の種類のファイルに限定している意味は、そういうファイルの中でしか当たりません。`-r` と
383
+ `git sys1grep` では、意味ごとにまず Jev へ小さなリクエストを 1 つ送り、候補ごとに yes / no を聞きます。0.6 以上で
384
+ yes なら、ほかを送る前にファイルを絞ります。残りは読まず、送りもしません。絞り込みは Jev の答えとともに stderr
385
+ に出るので、誤りに気づけます。
386
+
387
+ ```sh
388
+ $ sys1grep -r -e 'Python でリトライ処理を書いている箇所' .
389
+ sys1grep: scope: *.py *.pyi *.pyw (from "Python files: 0.94")
390
+ sys1grep: scope: 12 of 340 files
391
+ $ sys1grep -r -e '昨日変えた箇所で認証を扱っている' src/
392
+ sys1grep: scope: modified since 2026-09-25 00:00 (from "what was changed yesterday: 0.91")
393
+ sys1grep: scope: 3 of 120 files
394
+ ```
395
+
396
+ - **言語・形式**: 26 の候補 (Python、JavaScript、TypeScript、Go、Rust、Java、Kotlin、Ruby、PHP、C、C++、C#、Swift、
397
+ Scala、R、シェルスクリプト、SQL、HTML、CSS、Markdown、YAML、JSON、TOML、XML、Dockerfile、Makefile)。拡張子と
398
+ ファイル名は GitHub Linguist による。複数に yes ならどれか (「JavaScript か TypeScript」)。
399
+ - **変更時期**: 14 の区間。1 分以内、1 時間以内、今日、昨日、1・2・3・7・30 日以内、今月、先週、先月、この 1 年、
400
+ 今年度 (4 月 1 日から)。yes のうちいちばん狭い区間を、その始まりだけで使います (昨日変えたファイルを今日また
401
+ 変えることがあるため)。
402
+ - **置き場所**: テストコード (`tests/ test/ __tests__/ spec/ e2e/`、`test_*.py *_test.* *.test.* *.spec.* *Test.java
403
+ *_spec.rb`、それに `*.rs` すべて。Rust は単体テストを同じファイルに書くため)、DB マイグレーション (`*migrat*`、
404
+ Flyway の `V1__*.sql`)、README、CHANGELOG (`CHANGELOG* CHANGES* HISTORY* NEWS*`)、文書 (`*.md *.rst *.adoc *.txt`、
405
+ `docs/`)、ソースコード (文書以外すべて。辞書に無い言語も含む)、ログ (`*.log *.log.N *.out *.err`、`logs/`)。
406
+ JS・Python・Go・Java・Ruby・Rust・PHP のパスの慣習で判定します。複数ならどれか (「README か CHANGELOG に」)。
407
+ - **git**: リポジトリの中では、時期をコミットで見ます。コミット済みのファイルは始まり以降のコミットがあるもの
408
+ (コミッター日時。checkout は mtime をすべて今にしてしまうため)、未コミットのファイルは mtime。状態は、未コミット
409
+ (作業ツリーとインデックスの HEAD との差分と未追跡のファイル)、ステージ、未追跡、このブランチ (`origin/HEAD`・
410
+ `main`・`master` から分かれた点から)、未プッシュ (`@{upstream}..HEAD`、無ければどのリモートにも無いコミット)、
411
+ 自分が書いた (`user.email` のコミットと未コミットのファイル)。作者は、コミットの多い 30 人 (`git shortlog`) を
412
+ 名前とメールで候補にし、yes ならその人のコミットが触れたファイルに絞ります。リポジトリの外ではどれも聞かず、
413
+ 時期は mtime で見ます。
414
+ - Jev は意味全体を、どの言語でも読みます。「案A、B、Cで比較」は C のファイルの話にならず、コメントに引用された
415
+ 日付はファイルを変えた日になりません。文面から何かを取り出すことはせず、候補は固定です。
416
+ - **項ごとに効く。** `-e A -e B` は A の絞り込みで除いたファイルでも B を探します。同じ AND 項の中と、カテゴリを
417
+ またぐとき (「Python のテストコード」) は絞り込みが重なります。否定した意味 (`-v`、`!`) は聞きません。意味の
418
+ 文面はそのまま送ります。
419
+ - 問い合わせは意味 1 つにつき小さなリクエスト 1 つで、`-r` / `git sys1grep` で絞れるファイルが見つかったとき
420
+ だけ、`-i` の答えの後に送ります。`--dry-run` では `[scope]` と表示します。`--include` と同じく、`-r` では
421
+ コマンドラインで指定したファイルと stdin は絞らず、`git sys1grep` の pathspec は他と同じく絞ります。`--no-auto-scope` で止められます (`--auto-scope` で戻せます)。
422
+
423
+ ### git のサブコマンドとして (`git sys1grep`)
424
+
425
+ `npm install -g` すると `git-sys1grep` も入るので、`git sys1grep` で呼べます。`git grep` と同じく git が追跡している
426
+ ファイルだけを探し(`.gitignore` 済みのものやビルド成果物は送られない)、FILE はカレントディレクトリからの pathspec です。
427
+
428
+ ```sh
429
+ $ cd tests && git sys1grep -l -e "customer is asking for a refund" fixture.txt tickets
430
+ fixture.txt
431
+ tickets/a.txt
432
+ tickets/sub/b.txt
433
+ ```
434
+
435
+ FILE を省くとカレントディレクトリ以下の追跡ファイルを全部探します。`-r` の除外リスト(`.env*`、鍵など)は追跡されていても適用します。
436
+ `--include`、`--exclude`、`--changed-within` は追跡ファイルを絞り込み、pathspec で指定したファイルも対象になります。
437
+ `--changed-within` は git の履歴ではなく作業ツリーのファイルの更新時刻を見ます。clone や checkout の直後は、書き出されたファイルがすべて
438
+ 「いま変わった」扱いになります。
439
+ ヘルプは `git sys1grep -h` です(`--help` は git が横取りして man ページを探しに行きます)。
440
+
441
+ ### 「〜でない」行を全部
442
+
443
+ ```sh
444
+ ./sys1grep -v "a timestamped server log line" mixed.txt # grep -v 相当
445
+ ./sys1grep -e "source code or SQL" -v "SQL" src.txt # コードだが SQL ではない
446
+ cat app.log | ./sys1grep -e "デプロイが失敗した、またはロールバックされた"
447
+ ```
448
+
449
+ ### 複数行にまたがるレコード (`-z`)
450
+
451
+ 判定の単位は行です。ログやソースにはそれでよいのですが、1 件のレコードが複数行にまたがるときは合いません。
452
+ `-z` を付けると単位が NUL 終端のレコードになります。`grep -z` と同じ意味なので、レコードを出力するツールと
453
+ そのまま繋がります。`git log -z`、`find -print0`、`xargs -0` などです。
454
+
455
+ 「このコミットはユーザーに見える振る舞いを変えている」という命題は、コミット全体について成り立つもので、
456
+ その中のどの 1 行についてでもありません。
457
+
458
+ ```sh
459
+ $ git log -z --format='%h %s %b' | ./sys1grep -z -n -e "ユーザーに見える振る舞いを変えている" -v "ドキュメントだけの変更"
460
+ 7:21120e9 Revert "feat: ship the /sys1grep Claude Code skill" ...
461
+ 8:51ae333 feat: ship the /sys1grep Claude Code skill
462
+ ```
463
+
464
+ 一致したレコードも NUL 終端で出力されるので、読むときは `tr '\0' '\n'` に通してください。
465
+ ファイル名 (`-l`) と件数 (`-c`) は grep と同じく改行のままです。`-z` のとき `-n` はレコード番号、
466
+ `-A` / `-B` / `-C` は前後のレコード数、`--chunk` は 1 リクエストのレコード数を数えます。
467
+
468
+ ### 1 文ずつ判定する (`--sentence`)
469
+
470
+ `--sentence` を付けると、行ではなく文ごとに判定します。出力は grep と同じく行のままです。当たった文がかかる
471
+ 行をすべて出し、端末では文の部分を太字の黄で強調します(その中の正規表現の一致は grep と同じ太字の赤)。文に分ける前に折り返した行をつなぐので、
472
+ 複数行にまたがる文も 1 文として判定します。[`tests/prose.txt`](tests/prose.txt) には、折り返した英語と日本語の
473
+ 段落が入っています。
474
+
475
+ ```sh
476
+ $ ./sys1grep -n --sentence -e "the author admits they made a mistake" tests/prose.txt
477
+ 1:I should have checked the input
478
+ 2:before shipping, and that was my
479
+ 3:mistake. Next time I will add a test
480
+ ```
481
+
482
+ 文は 1 行目から始まり、3 行目の `mistake.` で終わります。色が付くのはその部分だけで、
483
+ `Next time I will add a test` は別の文として判定され、一致しません。
484
+
485
+ 端末で意味を 2 つ渡し、`-C 3` で前後も出すと、文がどこで始まりどこで終わるかが色で分かります。3 行目と 9 行目は、当たった文の
486
+ 終わりまでだけ色が付き、4〜7 行目は文脈行(`-`)です:
487
+
488
+ ![--sentence -C 3 --color: 当たった文が太字の黄になる。3 行目は mistake. まで、9 行目は 返金してほしいです。 まで。4〜7 行目は文脈行](docs/sentence.svg)
489
+
490
+ `-o` を付けると、当たった文だけを 1 行ずつ出します。`grep -o` が一致した部分だけを出すのと同じです。
491
+ `-n` は文が始まる行の番号になります。日本語は空白を入れずにつなぎます。単語の間に空白を置かない
492
+ 中国語・タイ語・ラオ語・クメール語・ミャンマー語・チベット語も同様です。
493
+
494
+ ```sh
495
+ $ ./sys1grep -n -o --sentence -e "the author admits they made a mistake" -e "customer is asking for a refund" tests/prose.txt
496
+ 1:I should have checked the input before shipping, and that was my mistake.
497
+ 8:先週買った掃除機が初日から動かないので返金してほしいです。
498
+ ```
499
+
500
+ `-o` なしでは、`-c` と `-A` / `-B` / `-C` はいつもどおり行で数えます。`-o` 付きでは文で数えます。
501
+ `-z` と併用すると、レコードごとに文に分け、当たったレコードをまるごと出します。
502
+
503
+ Jev は長い行の中からでも当たる文を自分で見つけるので、判定の精度のために `--sentence` を使う必要はありません。
504
+ どの文が当たったかを見たいとき、`-o` で文そのものが欲しいとき、AND を 1 つの文の中で成り立たせたいときに
505
+ 使います。式は文ごとに評価されます。そのため、`-v X` だけの式では「X でない文を 1 つでも含む行」がすべて出ます。
506
+ 行全体として X でないものを探すなら、`--sentence` を付けずに使います。
507
+
508
+ 日本語や中国語では、チャットの発言、問い合わせ、メモの 1 行など、`。` で終わらない 1 行 1 件のデータがよくあります。
509
+ これをつなぐと、別々の項目が 1 つの「文」になってしまいます。そこで既定(`--sentence`、`--sentence=jev` と同じ)では、
510
+ 単語の間に空白を置かない文字に接する、句点の無い改行について、Jev に「この改行は文や項目の終わりか、文の途中の
511
+ 折り返しか」を聞きます。30 行を 1 リクエストにまとめて改行ごとに yes/no を聞き、0.7 以上なら行をつなぎません。
512
+ [`tests/corpus.txt`](tests/corpus.txt) では、これで日本語の問い合わせ 4 件が 1 件ずつに分かれ、`--sentence` でも
513
+ 行単位の検索と同じ返金の要求(14 行目と 18 行目)が見つかります。規則だけでは問い合わせがつながり、18 行目を
514
+ 取りこぼしていました。追加のリクエストの費用は、意味を 1 つ増やすのと同じくらいです。避けたいときは
515
+ `--sentence=rules` を使います。英語どうしの改行は聞きません。つないでも空白が残り、ピリオドで文が切れるためです。
516
+
517
+ 改行が文の途中になりえないところでは、行をつなぎません。空行、括弧や `;` に接する改行(JSON やコード)、
518
+ `-` `*` `+` `#` `>` `"` や数字で始まる行(箇条書き、見出し、引用、番号、日時)の前です。そのため JSONL は
519
+ 1 行 1 レコードのまま、行ごとに文に分けられます。文の区切りには `Intl.Segmenter`
520
+ ([Unicode UAX #29](https://unicode.org/reports/tr29/))を使います。`.` `!` `?` `。` `!` `?` で切り、
521
+ `Mr.` のような略語の後ろでも切ります。ログは文章ではないので、`ERROR ...` の次の `WARN ...` のように
522
+ 英字で始まるログ行が続くと、つながります。
523
+
524
+ ### テンプレートごとに 1 行だけ判定する (`--dedup`)
525
+
526
+ 費用は送るテキストの量に比例します。機械が吐くログの大半は、ID や数値だけが違う同じ骨格の繰り返しです。
527
+ `--dedup` は ID・ハッシュ・数値・日付と時刻・パス・URL をマスクして行をまとめ、グループごとに 1 行だけ送って、
528
+ その答えを残りの行にも使います。送るのはその行の原文で、出力も各行がそのまま出ます。
529
+
530
+ ```sh
531
+ $ ./sys1grep --dedup -n -e "a request failed" app.log
532
+ 1:worker request 3fa9c1e27b failed: connection reset
533
+ 2:worker request 88d0e41a5c failed: connection reset
534
+ 4:worker request 0b7f2a9e13 failed: connection reset
535
+ 3 of 6 lines matched; 4 sent to Jev (2 folded by --dedup, ~235 input tokens / ~$0.000010 saved, 21%) in 2 requests, 907 input tokens, ~$0.000038
536
+ ```
537
+
538
+ どの値をまとめてよいかは意味によります。「ディスク使用率が 90% を超えている」なら数値が、「夜間に起きた」なら
539
+ 時刻が答えを決めます。そこで先に、意味ごとに小さなリクエストを 1 つ送って、どの種類の値が判定を変えうるかを
540
+ Jev に聞き、その種類はまとめません(上の 2 リクエストのうち 1 つ)。同じファイルに
541
+ `-e "disk usage is above 90%"` で聞くと、`95%` と `12%` は別々に判定され、`5:disk usage 95%` だけが出ます。
542
+
543
+ 値を何も残さずにまとめた場合の実測では、43,071 行のシステムログが 550 テンプレート(バイト数で 1.2%)に、
544
+ `install.log` が 21.1% に縮みました。Claude Code のトランスクリプト(jsonl)は 56.8% までしか縮みません。
545
+ 機械が吐くログ向けの機能で、散文には共通の骨格がなく、時刻を読む意味ではほとんど縮みません。
546
+ `-z` や `--sentence` では、行ではなくレコードや文をまとめます。
547
+
548
+ 検索後の集計行には、まとめたことで浮いた分が `(2 folded by --dedup, ~235 input tokens / ~$0.000010 saved, 21%)`
549
+ のように出ます(上の例)。まとめた行を別のリクエストとして送った場合の推定値から、値の種類を聞いたリクエストの費用を引いた
550
+ 正味の値です。小さなファイルや散文ではマイナスになることもあります。
551
+
552
+ 手元のログがどこまで縮むかは、検索の費用を払う前に `node scripts/dedup-measure.mjs FILE...` で確かめられます。
553
+ 同じマスクを使い、API を呼ばずに行数・テンプレート数・送るバイト数の割合を数えます。`--keep=num,time` を
554
+ 付けると、その種類を残す意味の場合を見られます。
555
+
556
+ 同じリクエストの他の行は各行の文脈になり(#9)、`--dedup` はそれを変えます。そのため閾値に近い行は、
557
+ 全行を送ったときと判定が変わることがあります。
558
+
559
+ ### 行の代わりに要約を出す (`--summarize`)
560
+
561
+ 一致した行が多いとき、欲しいのはたいてい要旨、つまりそれらの行が探した意味について何を言っているかです。
562
+ `--summarize` は sys1grep が出力するはずの内容を `claude -p --model haiku`(ツールなし・設定なし・CLAUDE.md なし)に渡し、
563
+ 意味に照らして要約させ、行の代わりにその答えを表示します。
564
+
565
+ ```sh
566
+ $ sys1grep -r -n --summarize -e "API キーをファイルから読んでいる" .
567
+ キーは sys1grep.mjs:22 で ~/.config/sys1grep/.env から読み、./.env は読みません (sys1grep.mjs:18)。...
568
+ ```
569
+
570
+ 高いモデルは Jev が残した分しか読みません。このリポジトリの `git log`(168 コミット)に「なぜ `./.env` を読まなくなったか」
571
+ を尋ねた例では、Claude の入力が 22,059 トークンから 1,356 に、Jev を含む総額が $0.094 から $0.013 に下がり、答えは同じでした(#69)。
572
+ 答えが少数の行にあるときに効きます。
573
+
574
+ - 一致した行はもう一度マシンの外へ、Anthropic に送られます。
575
+ - `-n`・`-A/-B/-C`・`-p`・ファイル名は表示どおりに渡し、色は付けません。一致がなければ何も渡しません(終了コード 1)。
576
+ - `SYS1GREP_SUMMARIZER` は値を付けない `--summarize` の TOOL を(今は `claude` だけ)、`SYS1GREP_SUMMARIZER_MODEL` はそのモデルを決めます。
577
+ - `-q`・`-l`・`-c` は行を出さないので、一緒には使えません。
578
+ - `--dedup` では、Jev に送ったものと同じく、テンプレートごとの代表を 1 回だけ `(×N like it)` を付けて渡します。
579
+ 答えを使い回したすべての行は渡しません。
580
+ - 200 KB(約 5 万トークン)を超えるときは何も渡さず、大きさを示して終了コード 2 で止まります。TOOL の費用がかかる前です。
581
+ 先頭だけの要約は全体の要約に見えてしまうので、切り詰めはしません。
582
+
583
+ ## Claude Code から使う
584
+
585
+ sys1grep を代わりに走らせてくれる Claude Code のスキルがあります。探したいものを言葉で書くと、式を組み立てて
586
+ 検索し、`file:line` 付きで該当行を報告します。[`uehaj/uehaj-marketplace`](https://github.com/uehaj/uehaj-marketplace) マーケットプレースの
587
+ `uehaj` プラグインとして公開しています。
588
+
589
+ ```sh
590
+ claude plugin marketplace add uehaj/uehaj-marketplace
591
+ claude plugin install uehaj@uehaj-marketplace
592
+ ```
593
+
594
+ コマンドラインツールを別途インストールする必要はありません。スキルは PATH に `sys1grep` があればそれを、
595
+ 無ければ `npx @uehaj/sys1grep` を使います。必要なのは API キーの設定だけです([インストール](#インストール) を参照)。
596
+
597
+ あとは Claude Code の中で次のように打ちます。
598
+
599
+ ```
600
+ /uehaj:sys1grep 返金を求めている問い合わせ tickets/*.txt
601
+ /uehaj:sys1grep 未テストのまま入った修正 git log --oneline -200
602
+ ```
603
+
604
+ Claude Code のインストール単位はプラグインで、スキル単体は選べません。このスキルだけ欲しいときは
605
+ [skills CLI](https://skills.sh/) が `~/.claude/skills/` にコピーしてくれ、その場合は `/sys1grep` で呼びます。
606
+
607
+ ```sh
608
+ npx skills add uehaj/uehaj-marketplace --skill sys1grep -a claude-code -g
609
+ ```
610
+
611
+ スキルは意味を英語で書き、AND / OR / NOT を `-e` / `-a` / `-v` に振り分け、`-n` を付け、大きなディレクトリは
612
+ 課金に見合うファイルに絞り、最初の結果が怪しければ `--level loose` や `strict` で引き直します。
613
+ API キーとエンドポイントの読み方はコマンドラインと同じです(`SYS1GREP_API_KEY`、`~/.config/sys1grep/.env`)。
614
+
615
+ ## 使い方
616
+
617
+ ```
618
+ usage: sys1grep [OPTION]... -e MEANING|-Q QUESTION [-a MEANING] [-v MEANING]... [FILE...]
619
+
620
+ -e MEANING この意味に合う行 (複数指定は OR)
621
+ -Q, --question QUESTION QUESTION に答えている行 (尋ねている行ではない)。-e "the line answers: QUESTION"
622
+ と同じ (上の「問いに答えている行を探す」参照)
623
+ -a MEANING 直前の -e/-Q 項に AND で連結。-e A -a B -e C は (A and B) or C
624
+ -v MEANING 直前の -e/-Q 項に AND NOT で連結。-e A -v B は A and not B
625
+ 先頭に置けば単独の否定。-v B は not B (grep -v 相当)
626
+ !MEANING -e / -Q / -a / -v のどこでも、先頭に ! を付けるとその意味だけ否定
627
+ -e A -e '!B' は A or not B。-a '!C' は -v C と同じ
628
+ --level=LEVEL 厳しさ。肯定と否定の閾値をまとめて決める (既定 normal)
629
+ loose : -t 0.3 -T 0.7 多少あやしくても拾う
630
+ normal : -t 0.5 -T 0.5
631
+ strict : -t 0.7 -T 0.3 確信のある行だけ拾う
632
+ -t THRESH 肯定条件の閾値。確率 >= THRESH で一致 (--level より優先)
633
+ -T THRESH 否定条件の閾値。確率 < THRESH で「〜でない」と判定 (--level より優先)
634
+ -t 0.6 -T 0.3 なら 0.3〜0.6 の曖昧な行はどちらにも当たらない
635
+ -r ディレクトリを再帰的に探す (FILE 省略時はカレント)。.git、node_modules、
636
+ バイナリ、秘密情報らしいファイル、git が無視するものは飛ばす
637
+ --include=GLOB, --exclude=GLOB -r と git sys1grep で、名前が GLOB に合うファイルだけ (または合わないものだけ) を探す
638
+ (-r ではコマンドラインで指定したファイルは必ず探す。git sys1grep の pathspec は絞り込む)
639
+ --changed-within=WHEN -r と git sys1grep で、30m / 2h / 7d / 2w 以内、日付か日時以降、today / this-week /
640
+ this-month に更新したファイルだけを探す
641
+ --no-auto-scope 意味の文面からファイルを絞り込まない (意味からの絞り込みを参照)。--auto-scope で戻す
642
+ -l 一致した行ではなくファイル名だけを表示
643
+ -H, --with-filename 1 ファイルでもファイル名を付ける。--no-filename は常に付けない
644
+ -A NUM 一致行の後ろ NUM 行も表示 (grep と同じ。文脈行の区切りは - )
645
+ -B NUM 一致行の前 NUM 行も表示
646
+ -C NUM 前後 NUM 行を表示 (-A NUM -B NUM)
647
+ -c 一致した行数だけをファイルごとに表示 (grep -c 相当)
648
+ -q, --quiet 何も表示せず、最初の一致で止まる。一致があればエラーがあっても終了コード 0 (grep -q 相当)
649
+ --chunk=LINES 1 リクエストにまとめる行数 (既定 30)
650
+ 同じリクエストの行は互いの文脈になるので、小さくすると速さだけでなく曖昧な行の
651
+ 判定も変わる
652
+ -j N 同時リクエスト数 (既定 8)
653
+ -n 行番号を付ける
654
+ -z, --null-data 行ではなく NUL 終端のレコードごとに判定し、NUL 終端で出力する (前述の「複数行にまたがるレコード」を参照)
655
+ --sentence[=HOW] 行ではなく文ごとに判定する。HOW は jev (既定) か rules (前述の「1 文ずつ判定する」を参照)
656
+ -o --sentence と併用し、当たった文だけを出す
657
+ -p 各意味の確率を行末に表示 (閾値調整用)
658
+ --dry-run 何も送らず、送信先・検索するファイル・各リクエストとその質問を表示
659
+ --verbose 同じ表示を検索しながら stderr に出す
660
+ -i, --interactive --dry-run と同じ内容を見せ、端末で y と答えたときだけ検索する
661
+ --dedup テンプレートごとに 1 行だけ判定し、その答えを残りにも使う (前述の「テンプレートごとに 1 行だけ判定する」を参照)
662
+ --color[=WHEN] 色付け。auto (端末なら付ける、既定) / always / never。=WHEN 省略時は auto
663
+ ファイル名・行番号は grep と同じ配色。-p の確率は閾値以上を緑、
664
+ 否定側の閾値未満を赤、あいだを黄で表示。NO_COLOR にも従う
665
+ --sys1-model=ID, --sys1-url=URL, --sys1-api-key=KEY
666
+ API の設定。SYS1GREP_MODEL / SYS1GREP_URL / SYS1GREP_API_KEY より優先
667
+ -h, --help このヘルプ (LANG / LC_ALL / LC_MESSAGES が ja 以外なら英語)
668
+ -V, --version バージョンを表示して終了
669
+ ```
670
+
671
+ FILE を省略すると stdin を読みます。複数ファイルなら `file:` を前置きします。
672
+ 終了コードは grep と同じで、一致あり 0、なし 1、エラー 2(引数エラー、読めないファイル、API 障害)です。
673
+
674
+ ### 式の書き方
675
+
676
+ `-e` が OR の項を始め、`-a` / `-v` は直前の項に AND / AND NOT で連結します。
677
+ 意味の先頭に `!` を付けるとその意味だけを否定できます(シェルの履歴展開を避けるためシングルクォートで囲んでください)。
678
+
679
+ | コマンド | 意味 |
680
+ |---|---|
681
+ | `-e A -e B` | A or B |
682
+ | `-e A -a B` | A and B |
683
+ | `-e A -v B` | A and not B |
684
+ | `-e A -a B -v C -e D` | (A and B and not C) or D |
685
+ | `-e A -e '!B'` | A or not B |
686
+ | `-v B` | not B |
687
+
688
+ ## 仕組み
689
+
690
+ 1. 空行を除いた行を 30 行ずつ(文字数上限つき)のチャンクに分ける
691
+ 2. チャンクを `{"L000": "行1", "L001": "行2", ...}` という object として `state` に入れ、
692
+ 行 × 意味 の数だけ `noul`(yes/no 確率)質問を 1 リクエストにまとめて送る
693
+ 3. 8 本まで並列にリクエストし、結果はファイル順に出力する
694
+ 4. 各行について、意味ごとの確率を閾値で真偽に変え、AND / OR / NOT の式を評価する
695
+
696
+ 一緒に送った行は互いの文脈になります。Jev はチャンクのほかの行から、そのファイルで何が普通かを読み取って
697
+ 判定します。はっきり当たる行・当たらない行は動きませんが、曖昧な行は動きます。チャンクの区切りの位置は
698
+ ほとんど効きません(あるログの 200 行で、`--chunk 30` の区切りを 15 行ずらして反転したのは 1 行)。効くのは
699
+ 周りの行が少ない状態で判定することで、`--chunk 1` では同じ 200 行のうち 18 行が反転し、しかも単独での判定の
700
+ 方が当てになりませんでした(#9)。つまり `--chunk` は速さだけでなく結果も変えます。入力がごく短いときは
701
+ `--chunk` に関係なく文脈の少ない判定になります。1 行ずつは遅くもあります(30 行で 1 リクエスト約 0.2 秒、
702
+ 1 行ずつだと約 7 秒)。チャンクを大きくしすぎると閾値付近の行が落ちやすくなるので既定は 30 行です。
703
+ 確率は実行ごとに ±0.05 ほどぶれます。閾値を詰めるときは `-p` で確率を見てください。
704
+
705
+ 料金は入力 100 万トークンあたり 0.042 ドル(2026 年 9 月時点)で、2 つの意味で 30 行のチャンクが
706
+ 約 3,000 トークンです。上限はレート制限(1,200 リクエスト/分)で決まり、既定設定なら毎分およそ
707
+ 36,000 行です。
708
+
709
+ ## テスト
710
+
711
+ `tests/` に LLM-as-judge のテストがあります。`tests/corpus.txt`(51 行)に対して `tests/cases.json` の
712
+ 10 ケースを実行し、Claude(`claude -p`)が「各意味に本当に合致する行」を判定した結果と突き合わせて
713
+ precision / recall を出します。あわせて `-t` × `-T` を 0.05 刻みで総当たりし、最良の閾値を報告します。
714
+
715
+ ```sh
716
+ node --no-warnings tests/judge.mts [--model sonnet] [--rejudge]
717
+ ```
718
+
719
+ ジャッジの判定は `tests/verdicts.json` にキャッシュされ、2 回目以降はジャッジを呼びません。
720
+ 結果は `tests/report.md` に書き出されます。直近の結果は precision 0.94、recall 0.98 です。
721
+
722
+ ## 制約
723
+
724
+ - 検索した行はすべて api.typesafe.ai に送られます。そこにアップロードしたくないファイルには使わないでください。
725
+ - 空行は送らず、全意味の確率 0 として扱います。`-v X` には当たり、`-e X` には当たりません。
726
+ - 1 行は 2,000 文字で切って送ります。
727
+ - 1 リクエストの質問数上限は公式に書かれていませんが、420 質問は通っています。
728
+ - 429 / 529 は指数バックオフで 6 回まで再試行します。
729
+ - 精度は英語がもっとも高く、日本語も使えますがぶれは大きめです。
730
+
731
+ ## よくある質問
732
+
733
+ sys1grep の前に標準のコマンドを置けば済むので、本体に入れなかったオプションがあります。その組み合わせ方です。
734
+
735
+ ### 段落をまるごと 1 単位として判定したい。`--paragraph` はないのか
736
+
737
+ 先に段落を 1 行にまとめてから渡します。`fmt` は段落の中の行をつなぎ、段落の間の空行は残します。
738
+
739
+ ```sh
740
+ fmt -w 100000 essay.txt | sys1grep -n -e "the author admits they made a mistake"
741
+ ```
742
+
743
+ 出力の 1 行が 1 段落になり、`-n` は元のファイルではなく `fmt` の出力の行番号になります。`fmt` は日本語の行をつなぐとき
744
+ 間に空白を入れますが、Jev の判定には響きません。折り返しをまたぐ文を判定したいだけなら、どちらも要りません。
745
+ `--sentence` が行をつないで文に分けます。
746
+
747
+ ### 会話記録の JSONL を、発言 1 件ずつ判定したい
748
+
749
+ `jq` で本文を取り出し、発言ごとに NUL で終えてから、`-z` でレコードとして判定します。
750
+
751
+ ```sh
752
+ jq -j '.content + "\u0000"' chat.jsonl | sys1grep -z -n --sentence -e "the customer is asking for a refund" | tr '\0' '\n'
753
+ ```
754
+
755
+ `.content` は、本文が入っている場所に合わせて書き換えてください。発言 1 件が 1 レコードになるので、文が次の話者の
756
+ 発言とつながらず、JSON の記号も送られません。`-n` は発言の番号です。1 行に 1 つの JSON が並んだファイルは
757
+ そのままでも扱えます。`jq` を通すと単位がきれいになる、というだけです。
758
+
759
+ ### レコードの区切りが NUL ではない。`--record-separator` はないのか
760
+
761
+ 区切りを NUL に置き換えて `-z` を使います。`----` の行で区切られたレコードなら次のとおりです。
762
+
763
+ ```sh
764
+ perl -0777 -pe 's/\n----\n/\0/g' notes.txt | sys1grep -z -e "a decision was made" | tr '\0' '\n'
765
+ awk '/^----$/ { printf "%c", 0; next } { print }' notes.txt | sys1grep -z -e "a decision was made" | tr '\0' '\n'
766
+ ```
767
+
768
+ `git log -z`、`find -print0`、`xargs -0` はもともと NUL で区切るので、`-z` でそのまま扱えます (#6)。
769
+ 任意の区切り文字を受け付けると、エスケープ、マルチバイト、正規表現か文字列か、という問題を抱え込みます。
770
+ `perl` か `awk` の 1 行で済むことのために、それは割に合いません。`awk` の書き方は、複数文字の `RS` を
771
+ 受け付けない macOS の BSD awk でも動きます。
772
+
773
+ ## ライセンス
774
+
775
+ MIT