kanzaki 0.2.0 → 0.3.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 CHANGED
@@ -1,10 +1,10 @@
1
1
  # Kanzaki
2
2
 
3
- ステージされた変更を、Markdownで書いたチェックリストに照らしてLLMがレビューするCLIツールです。
3
+ 変更内容を、Markdownで書いたチェックリストに照らしてLLMがレビューするCLIツールです。既定では `git diff --staged` を起点にしますが、`--working-tree` / `--range` / `--files` で任意のレビュー範囲に切り替えられます。
4
4
 
5
5
  ## 概要
6
6
 
7
- `kanzaki check` を実行すると、`git diff --staged` の内容を `.kanzaki/rules.md` に書かれたルールと照合し、LLM(OpenAIまたはAnthropic)に判定させます。違反が見つかった場合、重要度に応じてコミットをブロックするか警告を出します。
7
+ `kanzaki check` を実行すると、変更内容を `.kanzaki/rules.md` に書かれたルールと照合し、LLM(OpenAIまたはAnthropic)に判定させます。違反が見つかった場合、重要度に応じてコミットをブロックするか警告を出します。
8
8
 
9
9
  チェック対象はコードに限りません。ドキュメント、リサーチノート、設定ファイルなど、テキストベースの差分であれば利用できます。ルールは自然言語で記述するため、フォーマッターや構文チェッカーでは検出できない「意味的な問題」を拾うことを目的としています。
10
10
 
@@ -14,21 +14,22 @@
14
14
 
15
15
  `kanzaki check` は次の手順で動作します。
16
16
 
17
- 1. **ステージの確認** — `git diff --staged --name-only` で変更ファイルを取得します。変更がなければ終了します。
18
- 2. **認証情報のロード**CLIフラグ → 環境変数 → `~/.config/kanzaki/credentials.json` の優先順位で解決します。
19
- 3. **ルール解析**`.kanzaki/rules.md` から次の3要素を抽出します。
20
- - `- [ ]` で始まる行 → ルール(重要度とテキスト)
17
+ 1. **起点の決定**既定は `git diff --staged`。フラグで `--working-tree`(`git diff HEAD`)、`--range <a..b>`、`--files <paths...>` に切り替え可能。起点フラグ同士は排他です。
18
+ 2. **対象ファイルの取得**起点に応じて変更ファイル(または指定ファイル)の一覧を決定します。0件なら終了します。
19
+ 3. **認証情報のロード**CLIフラグ → 環境変数 → `~/.config/kanzaki/credentials.json` の優先順位で解決します。
20
+ 4. **ルール解析** `.kanzaki/rules.md` から次の要素を抽出します。
21
+ - `- [ ]` で始まる行 → ルール(重要度・判定スコープ・テキスト)
21
22
  - `## ヘッダー (*.ts)` の形式 → グループ名とファイルスコープ
22
23
  - それ以外のテキスト → LLMに渡す補足コンテキスト
23
- 4. **フォーマット検証** — ルールファイルの書式エラー(空ルール、重複、閉じ括弧忘れなど)を検出します。エラーが1件でもあればLLMを呼ばずに `exit(1)` で中断します。詳細は [ルールファイルのフォーマットチェック](#ルールファイルのフォーマットチェック) を参照してください。
24
- 5. **Git情報の取得** — `git diff --staged` の出力と、変更ファイルの全文(バイナリや100KB超は除外)を取得します。
25
- 6. **ルールのフィルタリング** — 変更ファイルにマッチしないスコープのルールを除外します。ここで適用対象ルールが0件になった場合は警告を出して `exit(0)` します。
26
- 7. **プロンプト構築** — コンテキスト、ルール一覧、diff(最大50KB)、ファイル全文(各最大20KB)を1つのプロンプトにまとめます。
27
- 8. **LLM呼び出し** — OpenAIはJSON mode、AnthropicはテキストレスポンスからJSONを抽出します。各ルールについて `{ rule, passed, reason }` が返されます。
28
- 9. **結果出力** — `!error` ルールが1件でも失敗した場合は `exit(1)` でコミットをブロックします。`!warn` のみの失敗なら警告を表示して `exit(0)` します。
29
- 10. **フィードバック書き出し(任意)** — `--emit-feedback` 指定時、違反があれば `.kanzaki/reviews/<タイムスタンプ>.md` にコーディングエージェント向けの指示ファイルを出力します。詳細は [エージェント向けフィードバック出力](#エージェント向けフィードバック出力) を参照してください。
24
+ 5. **フォーマット検証** — ルールファイルの書式エラー(空ルール、重複、閉じ括弧忘れなど)を検出します。エラーが1件でもあればLLMを呼ばずに `exit(1)` で中断します。詳細は [ルールファイルのフォーマットチェック](#ルールファイルのフォーマットチェック) を参照してください。
25
+ 6. **差分とファイル内容の取得**起点に応じた diff と、対象ファイルの全文(バイナリや100KB超は除外)を取得します。`range` の場合は終端リビジョンでの内容を `git show` で読み、`files` の場合は現在の作業ツリーから読みます。
26
+ 7. **ルールのフィルタリング** — 対象ファイルにマッチしないファイルスコープのルールを除外します。`@state(<glob>)` で追加参照先が指定されていれば、`git ls-files` からマッチするファイルを追加で読み込みます。ここで適用対象ルールが0件になった場合は警告を出して `exit(0)` します。
27
+ 8. **プロンプト構築** — コンテキスト、起点情報、ルール一覧(判定スコープ付き)、diff(最大50KB)、ファイル全文(各最大20KB)を1つのプロンプトにまとめます。
28
+ 9. **LLM呼び出し** — OpenAIはJSON mode、AnthropicはテキストレスポンスからJSONを抽出します。各ルールについて `{ rule, passed, reason }` が返されます。
29
+ 10. **結果出力** — `!error` ルールが1件でも失敗した場合は `exit(1)` でコミットをブロックします。`!warn` のみの失敗なら警告を表示して `exit(0)` します。
30
+ 11. **フィードバック書き出し(任意)** — `--emit-feedback` 指定時、違反があれば `.kanzaki/reviews/<タイムスタンプ>.md` にコーディングエージェント向けの指示ファイルを出力します。詳細は [エージェント向けフィードバック出力](#エージェント向けフィードバック出力) を参照してください。
30
31
 
31
- pre-commitフック(Husky等)で実行することを想定していますが、CI/CDや手動実行でも同じように使えます。
32
+ pre-commitフック(Husky等)で実行することを想定していますが、CI/CDや手動実行でも同じように使えます。CI では `--range origin/main..HEAD` のようにPRのdiff範囲を渡すと便利です。
32
33
 
33
34
  ---
34
35
 
@@ -119,6 +120,26 @@ echo "npx kanzaki check" > .husky/pre-commit
119
120
  | `!error`(デフォルト) | 違反時に `exit(1)` でコミットをブロック |
120
121
  | `!warn` | 警告表示のみ、コミットは続行 |
121
122
 
123
+ ### 判定スコープ
124
+
125
+ 各ルールは「差分だけを見る」か「ファイル現状も見る」かを選べます。タグはseverityの前後どちらにも書け、省略時は `diff`。
126
+
127
+ | 記法 | 動作 |
128
+ |------|------|
129
+ | (未指定/`@diff`) | この変更が新たに違反を持ち込んでいないかを判定。既存の違反はスルー |
130
+ | `@state` | 対象ファイルの現状に対して判定。既存の違反も違反として扱う |
131
+ | `@state(<glob>, ...)` | `@state` に加えて、変更されていない参照ファイルもコンテキストに含める(例: 用語集、スキーマ) |
132
+
133
+ ```markdown
134
+ ## コード (*.ts, *.js)
135
+ - [ ] !error @state console.log が残っていないこと
136
+ - [ ] !warn @state(docs/glossary.md) 用語が glossary と一致していること
137
+ ```
138
+
139
+ - `@diff` は既存コードの負債で毎回ブロックされないpre-commit用途に向いています
140
+ - `@state` は「このファイルが現時点で規約を満たしているか」を問うような、静的解析に近い用途に向いています
141
+ - `files-only` 起点(`--files`)では全ルールが実質 `state` として評価されます
142
+
122
143
  ### 補足コンテキスト
123
144
 
124
145
  チェックリスト以外のテキスト(段落など)はLLMへの補足情報として送信されます。プロジェクトの前提や背景をここに書くことで、判定の精度が変わることがあります。
@@ -232,8 +253,10 @@ Q4 決算報告プレゼンテーション。
232
253
  | ヘッダーの括弧不一致 | `## Security (*.ts` (閉じ括弧なし) |
233
254
  | 空の括弧 | `## Foo ()` |
234
255
  | 空のチェックリスト項目 | `- [ ]` (本文なし) |
235
- | severityタグのみで本文なし | `- [ ] !warn` |
256
+ | タグのみで本文なし | `- [ ] !warn` / `- [ ] @state` |
236
257
  | 不正なseverityタグ | `- [ ] !err foo` (`!error` / `!warn` のみ有効) |
258
+ | `@state(...)` の閉じ括弧忘れ | `- [ ] @state(*.md foo` |
259
+ | `@state()` の空括弧 | `- [ ] @state() foo` |
237
260
  | 不正なチェックボックス形式 | `* [ ] foo` や `- [x] foo` |
238
261
  | 同一グループ内の重複ルール | 同じグループに同一のルールテキストが2回以上 |
239
262
 
@@ -289,6 +312,11 @@ Q4 決算報告プレゼンテーション。
289
312
  | `--no-block` | エラーでも `exit(1)` せず、警告のみ |
290
313
  | `-o, --emit-feedback` | 違反をまとめたmarkdownを `.kanzaki/reviews/` に書き出す(エージェント向け) |
291
314
  | `-v, --verbose` | 詳細出力 |
315
+ | `--working-tree` | ステージではなく作業ツリー(`git diff HEAD`)をレビュー |
316
+ | `--range <a..b>` | 任意のリビジョン範囲をレビュー(例: `origin/main..HEAD`) |
317
+ | `--files <paths...>` | 指定ファイルの現状をレビュー(diffなし、git管理外もOK) |
318
+
319
+ `--working-tree` / `--range` / `--files` は排他で、同時に指定するとエラーになります。いずれも指定しなければ既定の staged 起点です。
292
320
 
293
321
  ---
294
322
 
package/dist/bin.js CHANGED
@@ -7,7 +7,7 @@ import { existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSyn
7
7
  import { resolve as resolve4, dirname } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
  import { createInterface } from "readline";
10
- import { execSync as execSync2 } from "child_process";
10
+ import { execSync, execFileSync as execFileSync2 } from "child_process";
11
11
 
12
12
  // src/config.ts
13
13
  import { existsSync as existsSync2 } from "fs";
@@ -220,16 +220,25 @@ function parseRulesFromContent(content) {
220
220
  if (invalidTagMatch) {
221
221
  errors.push({ line: lineNumber, message: `Unknown severity tag "${invalidTagMatch[0]}". Use !error or !warn.` });
222
222
  }
223
- const { severity, text } = parseSeverity(rawText);
224
- if (text.length === 0) {
225
- errors.push({ line: lineNumber, message: `Empty rule. Checklist item has only a severity tag with no description.` });
223
+ const unclosedScopeMatch = rawText.match(/@state\s*\([^)]*$/i);
224
+ if (unclosedScopeMatch) {
225
+ errors.push({ line: lineNumber, message: `Missing closing parenthesis in @state(...): "${rawText}"` });
226
+ }
227
+ const parsed = parseRuleTags(rawText);
228
+ if (parsed.text.length === 0) {
229
+ errors.push({ line: lineNumber, message: `Empty rule. Checklist item has only tag(s) with no description.` });
226
230
  continue;
227
231
  }
232
+ if (parsed.scopeHasEmptyParens) {
233
+ errors.push({ line: lineNumber, message: `Empty @state() parentheses. Use "@state" alone or "@state(<glob>, ...)".` });
234
+ }
228
235
  rules.push({
229
236
  group: currentGroup,
230
- text,
231
- severity,
237
+ text: parsed.text,
238
+ severity: parsed.severity,
232
239
  filePatterns: currentPatterns,
240
+ scope: parsed.scope,
241
+ stateExtraPatterns: parsed.stateExtraPatterns,
233
242
  lineNumber
234
243
  });
235
244
  continue;
@@ -269,16 +278,48 @@ function parseHeaderWithPatterns(header) {
269
278
  }
270
279
  return { name: header, patterns: [] };
271
280
  }
272
- function parseSeverity(rawText) {
273
- const warnMatch = rawText.match(/^!warn\s+(.+)$/i);
274
- if (warnMatch) {
275
- return { severity: "warn", text: warnMatch[1].trim() };
276
- }
277
- const errorMatch = rawText.match(/^!error\s+(.+)$/i);
278
- if (errorMatch) {
279
- return { severity: "error", text: errorMatch[1].trim() };
281
+ function parseRuleTags(rawText) {
282
+ let severity = "error";
283
+ let scope = "diff";
284
+ let stateExtraPatterns = [];
285
+ let scopeHasEmptyParens = false;
286
+ let text = rawText;
287
+ const severityRegex = /^!(error|warn)\b\s*/i;
288
+ const scopeRegex = /^@state(?:\(([^)]*)\))?\s*/i;
289
+ let progressed = true;
290
+ while (progressed) {
291
+ progressed = false;
292
+ const sevMatch = text.match(severityRegex);
293
+ if (sevMatch) {
294
+ severity = sevMatch[1].toLowerCase() === "warn" ? "warn" : "error";
295
+ text = text.slice(sevMatch[0].length);
296
+ progressed = true;
297
+ continue;
298
+ }
299
+ const scopeMatch = text.match(scopeRegex);
300
+ if (scopeMatch) {
301
+ scope = "state";
302
+ const inside = scopeMatch[1];
303
+ if (inside !== void 0) {
304
+ const trimmedInside = inside.trim();
305
+ if (trimmedInside.length === 0) {
306
+ scopeHasEmptyParens = true;
307
+ } else {
308
+ stateExtraPatterns = trimmedInside.split(",").map((p) => p.trim()).filter(Boolean);
309
+ }
310
+ }
311
+ text = text.slice(scopeMatch[0].length);
312
+ progressed = true;
313
+ continue;
314
+ }
280
315
  }
281
- return { severity: "error", text: rawText };
316
+ return {
317
+ severity,
318
+ scope,
319
+ stateExtraPatterns,
320
+ text: text.trim(),
321
+ scopeHasEmptyParens
322
+ };
282
323
  }
283
324
  function formatRulesForPrompt(rules) {
284
325
  const grouped = /* @__PURE__ */ new Map();
@@ -289,12 +330,17 @@ function formatRulesForPrompt(rules) {
289
330
  }
290
331
  const sections = [];
291
332
  for (const [group, groupRules] of grouped) {
292
- const scope = groupRules[0]?.filePatterns.length > 0 ? ` (applies to: ${groupRules[0].filePatterns.join(", ")})` : "";
293
- sections.push(`### ${group}${scope}`);
333
+ const fileScope = groupRules[0]?.filePatterns.length > 0 ? ` (applies to: ${groupRules[0].filePatterns.join(", ")})` : "";
334
+ sections.push(`### ${group}${fileScope}`);
294
335
  for (let i = 0; i < groupRules.length; i++) {
295
336
  const r = groupRules[i];
296
- const tag = r.severity === "warn" ? "[WARNING]" : "[ERROR]";
297
- sections.push(`${i + 1}. ${tag} ${r.text}`);
337
+ const severity = r.severity === "warn" ? "WARNING" : "ERROR";
338
+ const meta = [`severity=${severity}`, `scope=${r.scope}`];
339
+ if (r.scope === "state" && r.stateExtraPatterns.length > 0) {
340
+ meta.push(`also_consult=${r.stateExtraPatterns.join("|")}`);
341
+ }
342
+ sections.push(`- Rule #${i + 1} [${meta.join(", ")}]`);
343
+ sections.push(` text: ${r.text}`);
298
344
  }
299
345
  sections.push("");
300
346
  }
@@ -315,47 +361,145 @@ function matchGlob(filePath, pattern) {
315
361
  }
316
362
 
317
363
  // src/core/git.ts
318
- import { execSync } from "child_process";
364
+ import { execFileSync } from "child_process";
319
365
  import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
320
- import { resolve as resolve3 } from "path";
321
- function getStagedChanges() {
322
- const diff = exec("git diff --staged");
323
- const filesRaw = exec("git diff --staged --name-only");
324
- const files = filesRaw.split("\n").map((f) => f.trim()).filter(Boolean);
325
- return { diff, files };
366
+ import { resolve as resolve3, isAbsolute, relative } from "path";
367
+ function getReviewSource(opts) {
368
+ switch (opts.kind) {
369
+ case "staged":
370
+ return getStagedSource();
371
+ case "workingTree":
372
+ return getWorkingTreeSource();
373
+ case "range":
374
+ if (!opts.range) throw new Error("range option is required for range source");
375
+ return getRangeSource(opts.range);
376
+ case "files":
377
+ if (!opts.files || opts.files.length === 0) {
378
+ throw new Error("files option requires at least one path");
379
+ }
380
+ return getFilesSource(opts.files);
381
+ }
326
382
  }
327
- function getFileContexts(files) {
328
- const repoRoot = getRepoRoot();
383
+ function getFileContextsForSource(source, extraPaths = []) {
384
+ if (source.kind === "range") {
385
+ const endRef = extractEndRef(extractRangeFromLabel(source.label));
386
+ const paths = dedupe([...source.files, ...extraPaths]);
387
+ const contexts2 = [];
388
+ for (const p of paths) {
389
+ if (isBinaryPath(p)) continue;
390
+ const content = readFileAtRef(endRef, p);
391
+ if (content === null) continue;
392
+ if (content.length > 1e5) continue;
393
+ contexts2.push({ path: p, content });
394
+ }
395
+ return contexts2;
396
+ }
329
397
  const contexts = [];
330
- for (const file of files) {
331
- const absPath = resolve3(repoRoot, file);
332
- if (!existsSync3(absPath)) continue;
333
- if (isBinaryPath(file)) continue;
398
+ const seen = /* @__PURE__ */ new Set();
399
+ const repoRoot = safeRepoRoot();
400
+ const collect = (path) => {
401
+ if (seen.has(path)) return;
402
+ seen.add(path);
403
+ if (isBinaryPath(path)) return;
404
+ const absPath = isAbsolute(path) ? path : repoRoot ? resolve3(repoRoot, path) : resolve3(process.cwd(), path);
405
+ if (!existsSync3(absPath)) return;
334
406
  try {
335
407
  const content = readFileSync3(absPath, "utf-8");
336
- if (content.length > 1e5) continue;
337
- contexts.push({ path: file, content });
408
+ if (content.length > 1e5) return;
409
+ contexts.push({ path, content });
338
410
  } catch {
339
411
  }
340
- }
412
+ };
413
+ for (const f of source.files) collect(f);
414
+ for (const f of extraPaths) collect(f);
341
415
  return contexts;
342
416
  }
343
- function getRepoRoot() {
344
- return exec("git rev-parse --show-toplevel").trim();
417
+ function getStagedSource() {
418
+ const diff = execGit(["diff", "--staged"]);
419
+ const files = splitFiles(execGit(["diff", "--staged", "--name-only"]));
420
+ return { kind: "staged", label: "staged", diff, files };
421
+ }
422
+ function getWorkingTreeSource() {
423
+ const diff = execGit(["diff", "HEAD"]);
424
+ const files = splitFiles(execGit(["diff", "HEAD", "--name-only"]));
425
+ return { kind: "workingTree", label: "working tree (vs HEAD)", diff, files };
426
+ }
427
+ function getRangeSource(range) {
428
+ const diff = execGit(["diff", range]);
429
+ const files = splitFiles(execGit(["diff", range, "--name-only"]));
430
+ return { kind: "range", label: `range:${range}`, diff, files };
431
+ }
432
+ function getFilesSource(paths) {
433
+ const repoRoot = safeRepoRoot();
434
+ const normalized = paths.map((p) => {
435
+ if (!isAbsolute(p)) return p;
436
+ if (repoRoot) {
437
+ const rel = relative(repoRoot, p);
438
+ if (!rel.startsWith("..")) return rel.split("\\").join("/");
439
+ }
440
+ return p;
441
+ });
442
+ return { kind: "files", label: "files", diff: "", files: normalized };
345
443
  }
346
444
  function hasStagedChanges() {
347
- const output = exec("git diff --staged --name-only");
348
- return output.trim().length > 0;
445
+ try {
446
+ const output = execGit(["diff", "--staged", "--name-only"]);
447
+ return output.trim().length > 0;
448
+ } catch {
449
+ return false;
450
+ }
349
451
  }
350
- function exec(command) {
452
+ function getRepoRoot() {
453
+ return execGit(["rev-parse", "--show-toplevel"]).trim();
454
+ }
455
+ function safeRepoRoot() {
351
456
  try {
352
- return execSync(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
457
+ return getRepoRoot();
458
+ } catch {
459
+ return null;
460
+ }
461
+ }
462
+ function execGit(args) {
463
+ try {
464
+ return execFileSync("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
353
465
  } catch (error) {
354
466
  const err = error;
355
- throw new Error(`Git command failed: ${command}
467
+ throw new Error(`Git command failed: git ${args.join(" ")}
356
468
  ${err.stderr ?? err.message}`);
357
469
  }
358
470
  }
471
+ function readFileAtRef(ref, path) {
472
+ try {
473
+ return execFileSync("git", ["show", `${ref}:${path}`], {
474
+ encoding: "utf-8",
475
+ stdio: ["pipe", "pipe", "pipe"]
476
+ });
477
+ } catch {
478
+ return null;
479
+ }
480
+ }
481
+ function splitFiles(raw) {
482
+ return raw.split("\n").map((f) => f.trim()).filter(Boolean);
483
+ }
484
+ function dedupe(arr) {
485
+ return Array.from(new Set(arr));
486
+ }
487
+ function extractRangeFromLabel(label) {
488
+ return label.startsWith("range:") ? label.slice("range:".length) : label;
489
+ }
490
+ function extractEndRef(range) {
491
+ const tripleIdx = range.indexOf("...");
492
+ if (tripleIdx >= 0) {
493
+ const end = range.slice(tripleIdx + 3).trim();
494
+ return end.length > 0 ? end : "HEAD";
495
+ }
496
+ const doubleIdx = range.indexOf("..");
497
+ if (doubleIdx >= 0) {
498
+ const end = range.slice(doubleIdx + 2).trim();
499
+ return end.length > 0 ? end : "HEAD";
500
+ }
501
+ return range;
502
+ }
359
503
  var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
360
504
  ".png",
361
505
  ".jpg",
@@ -394,8 +538,9 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
394
538
  ".lock"
395
539
  ]);
396
540
  function isBinaryPath(filePath) {
397
- const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
398
- return BINARY_EXTENSIONS.has(ext);
541
+ const dot = filePath.lastIndexOf(".");
542
+ if (dot < 0) return false;
543
+ return BINARY_EXTENSIONS.has(filePath.slice(dot).toLowerCase());
399
544
  }
400
545
 
401
546
  // src/llm/openai.ts
@@ -630,18 +775,29 @@ ${error}`);
630
775
  }
631
776
 
632
777
  // src/core/reviewer.ts
633
- var SYSTEM_PROMPT = `You are a strict quality reviewer. Your job is to review changes (git diff) against a checklist of rules defined by the user.
778
+ var SYSTEM_PROMPT = `You are a strict quality reviewer. Your job is to review a set of changes (or a snapshot of files) against a checklist of rules defined by the user.
634
779
 
635
780
  The rules may cover ANY domain \u2014 code, documentation, research, writing, presentations, design, or any other type of output. Evaluate each rule based on its intent, not just literal text matching.
636
781
 
637
- For each rule in the checklist, determine whether the staged changes comply with it.
782
+ Each rule is printed as:
783
+
784
+ - Rule #N [severity=..., scope=..., (optional) also_consult=...]
785
+ text: <the rule text>
786
+
787
+ The \`scope=\` field controls evaluation mode:
788
+ - scope=diff \u2192 Judge whether THIS CHANGE introduces a new violation. Pre-existing violations that the change does not touch should NOT cause the rule to fail.
789
+ - scope=state \u2192 Judge whether the CURRENT STATE of the relevant files satisfies the rule, regardless of what was changed. Pre-existing violations SHOULD cause the rule to fail. Consult the full file contents, not just diff hunks.
790
+
791
+ The \`also_consult=\` field (pipe-separated globs) lists extra files included in the prompt for cross-file state checks (e.g. glossary, schema).
792
+
793
+ When returning results, the "rule" field MUST contain ONLY the text shown on the \`text:\` line \u2014 no "Rule #N" prefix, no severity/scope tags, no brackets, no numbering.
638
794
 
639
795
  IMPORTANT:
640
796
  - Rules marked [ERROR] are critical. Be strict when evaluating them.
641
797
  - Rules marked [WARNING] are advisory. Be fair but flag clear violations.
642
- - Only evaluate rules that are RELEVANT to the changes. If a rule clearly does not apply to the content being changed, mark it as passed with reason "Not applicable to these changes."
798
+ - Only evaluate rules that are RELEVANT to the files under review. If a rule clearly does not apply, mark it as passed with reason "Not applicable."
643
799
  - Use the context section (if provided) to understand the project's goals and constraints.
644
- - Look at both the diff AND the full file context to make accurate judgments.
800
+ - If no diff is provided (files-only review), treat every rule as a state check against the provided files.
645
801
 
646
802
  You MUST respond with valid JSON in this exact format:
647
803
  {
@@ -651,9 +807,9 @@ You MUST respond with valid JSON in this exact format:
651
807
  ],
652
808
  "summary": "<one-line overall summary>"
653
809
  }`;
654
- async function review(config, rules, staged, fileContexts, rulesContext) {
810
+ async function review(config, rules, source, fileContexts, rulesContext) {
655
811
  const provider = createProvider(config);
656
- const userPrompt = buildUserPrompt(rules, staged, fileContexts, rulesContext);
812
+ const userPrompt = buildUserPrompt(rules, source, fileContexts, rulesContext);
657
813
  const rawResult = await provider.review(SYSTEM_PROMPT, userPrompt);
658
814
  return mapSeverities(rawResult, rules);
659
815
  }
@@ -670,22 +826,31 @@ function createProvider(config) {
670
826
  throw new Error(`Unknown provider: ${config.provider}`);
671
827
  }
672
828
  }
673
- function buildUserPrompt(rules, staged, fileContexts, rulesContext) {
829
+ function buildUserPrompt(rules, source, fileContexts, rulesContext) {
674
830
  const parts = [];
675
831
  if (rulesContext && rulesContext.trim().length > 0) {
676
832
  parts.push("## Project Context\n");
677
833
  parts.push(rulesContext);
678
834
  parts.push("");
679
835
  }
836
+ parts.push(`## Review Source
837
+ `);
838
+ parts.push(`Source: ${source.label}`);
839
+ if (source.kind === "files") {
840
+ parts.push("Mode: files-only (no diff). Evaluate each rule against the current state of the files below.");
841
+ }
842
+ parts.push("");
680
843
  parts.push("## Checklist Rules\n");
681
844
  parts.push(formatRulesForPrompt(rules));
682
- parts.push("## Staged Changes (git diff)\n");
683
- parts.push("```diff");
684
- parts.push(truncate(staged.diff, 5e4));
685
- parts.push("```\n");
845
+ if (source.diff.trim().length > 0) {
846
+ parts.push("## Changes (git diff)\n");
847
+ parts.push("```diff");
848
+ parts.push(truncate(source.diff, 5e4));
849
+ parts.push("```\n");
850
+ }
686
851
  if (fileContexts.length > 0) {
687
852
  parts.push("## Full File Context\n");
688
- parts.push("The following are the full contents of modified files for additional context:\n");
853
+ parts.push("The following are the full contents of the files under review:\n");
689
854
  for (const ctx of fileContexts) {
690
855
  const ext = ctx.path.split(".").pop() ?? "";
691
856
  parts.push(`### ${ctx.path}
@@ -772,7 +937,7 @@ function report(result, verbose) {
772
937
  // src/core/feedback.ts
773
938
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
774
939
  import { join } from "path";
775
- function writeFeedbackFile(result, rules, staged, outputDir) {
940
+ function writeFeedbackFile(result, rules, source, outputDir) {
776
941
  const failures = result.results.filter((r) => !r.passed);
777
942
  if (failures.length === 0) return null;
778
943
  if (!existsSync4(outputDir)) {
@@ -780,11 +945,11 @@ function writeFeedbackFile(result, rules, staged, outputDir) {
780
945
  }
781
946
  const now = /* @__PURE__ */ new Date();
782
947
  const filePath = join(outputDir, `${formatTimestamp(now)}.md`);
783
- const content = buildFeedbackMarkdown(failures, rules, staged, now, result.summary);
948
+ const content = buildFeedbackMarkdown(failures, rules, source, now, result.summary);
784
949
  writeFileSync2(filePath, content, "utf-8");
785
950
  return filePath;
786
951
  }
787
- function buildFeedbackMarkdown(failures, rules, staged, now, summary) {
952
+ function buildFeedbackMarkdown(failures, rules, source, now, summary) {
788
953
  const ruleMap = /* @__PURE__ */ new Map();
789
954
  for (const r of rules) {
790
955
  ruleMap.set(r.text.toLowerCase(), r);
@@ -802,14 +967,19 @@ function buildFeedbackMarkdown(failures, rules, staged, now, summary) {
802
967
  lines.push(summary);
803
968
  lines.push(``);
804
969
  }
970
+ lines.push(`- \u30EC\u30D3\u30E5\u30FC\u8D77\u70B9: ${source.label}`);
805
971
  lines.push(`- \u30A8\u30E9\u30FC: ${errorCount} \u4EF6`);
806
972
  lines.push(`- \u8B66\u544A: ${warnCount} \u4EF6`);
807
- lines.push(`- \u5909\u66F4\u30D5\u30A1\u30A4\u30EB: ${staged.files.join(", ")}`);
973
+ lines.push(`- \u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB: ${source.files.join(", ")}`);
808
974
  lines.push(``);
809
975
  lines.push(`## Instructions for Coding Agents`);
810
976
  lines.push(``);
811
977
  lines.push(`\u4EE5\u4E0B\u306F \`kanzaki check\` \u304C\u691C\u51FA\u3057\u305F\u30EB\u30FC\u30EB\u9055\u53CD\u3067\u3059\u3002\u5404\u9055\u53CD\u306B\u3064\u3044\u3066\u3001\u6307\u6458\u5185\u5BB9\u3092\u8AAD\u307F\u3001\u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB\u306E\u8A72\u5F53\u7B87\u6240\u3092\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
812
- lines.push(`\u4FEE\u6B63\u5F8C\u306F \`git add\` \u3067\u518D\u5EA6\u30B9\u30C6\u30FC\u30B8\u3057\u3001\`kanzaki check\` \u3092\u5B9F\u884C\u3057\u3066\u9055\u53CD\u304C\u89E3\u6D88\u3055\u308C\u305F\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
978
+ if (source.kind === "staged") {
979
+ lines.push(`\u4FEE\u6B63\u5F8C\u306F \`git add\` \u3067\u518D\u5EA6\u30B9\u30C6\u30FC\u30B8\u3057\u3001\`kanzaki check\` \u3092\u5B9F\u884C\u3057\u3066\u9055\u53CD\u304C\u89E3\u6D88\u3055\u308C\u305F\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
980
+ } else {
981
+ lines.push(`\u4FEE\u6B63\u5F8C\u306F\u540C\u3058\u8D77\u70B9\u3067 \`kanzaki check\` \u3092\u518D\u5B9F\u884C\u3057\u3001\u9055\u53CD\u304C\u89E3\u6D88\u3055\u308C\u305F\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002`);
982
+ }
813
983
  lines.push(`\u30EB\u30FC\u30EB\u5B9A\u7FA9\u305D\u306E\u3082\u306E\u3092\u5909\u66F4\u3059\u308B\u3053\u3068\u3067\u9055\u53CD\u3092\u56DE\u907F\u3059\u308B\u3053\u3068\u306F\u7981\u6B62\u3055\u308C\u3066\u3044\u307E\u3059\uFF08\u30EB\u30FC\u30EB\u306E\u5909\u66F4\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F\u30E6\u30FC\u30B6\u30FC\u306B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\uFF09\u3002`);
814
984
  lines.push(``);
815
985
  lines.push(`## Violations`);
@@ -821,13 +991,17 @@ function buildFeedbackMarkdown(failures, rules, staged, now, summary) {
821
991
  lines.push(``);
822
992
  if (rule) {
823
993
  lines.push(`- **\u30B0\u30EB\u30FC\u30D7**: ${rule.group}`);
824
- const scope = rule.filePatterns.length > 0 ? rule.filePatterns.join(", ") : "\u5168\u30D5\u30A1\u30A4\u30EB";
825
- lines.push(`- **\u9069\u7528\u30B9\u30B3\u30FC\u30D7**: ${scope}`);
994
+ const fileScope = rule.filePatterns.length > 0 ? rule.filePatterns.join(", ") : "\u5168\u30D5\u30A1\u30A4\u30EB";
995
+ lines.push(`- **\u9069\u7528\u30B9\u30B3\u30FC\u30D7**: ${fileScope}`);
996
+ lines.push(`- **\u5224\u5B9A\u30B9\u30B3\u30FC\u30D7**: ${rule.scope === "state" ? "state\uFF08\u30D5\u30A1\u30A4\u30EB\u73FE\u72B6\uFF09" : "diff\uFF08\u5DEE\u5206\uFF09"}`);
997
+ if (rule.stateExtraPatterns.length > 0) {
998
+ lines.push(`- **\u8FFD\u52A0\u53C2\u7167**: ${rule.stateExtraPatterns.join(", ")}`);
999
+ }
826
1000
  if (rule.lineNumber) {
827
1001
  lines.push(`- **\u30EB\u30FC\u30EB\u5B9A\u7FA9**: rules.md:${rule.lineNumber}`);
828
1002
  }
829
1003
  }
830
- lines.push(`- **\u5909\u66F4\u30D5\u30A1\u30A4\u30EB**: ${staged.files.join(", ")}`);
1004
+ lines.push(`- **\u5BFE\u8C61\u30D5\u30A1\u30A4\u30EB**: ${source.files.join(", ")}`);
831
1005
  lines.push(``);
832
1006
  lines.push(`**\u9055\u53CD\u7406\u7531**:`);
833
1007
  lines.push(``);
@@ -855,7 +1029,7 @@ var __filename = fileURLToPath(import.meta.url);
855
1029
  var __dirname = dirname(__filename);
856
1030
  function createCli() {
857
1031
  const program2 = new Command();
858
- program2.name("kanzaki").description("LLM-powered semantic pre-commit linter").version("0.2.0");
1032
+ program2.name("kanzaki").description("LLM-powered semantic pre-commit linter").version("0.3.0");
859
1033
  program2.command("init").description("Create .kanzaki/rules.md rules file").action(async () => {
860
1034
  const cwd = process.cwd();
861
1035
  const rulesPath = resolve4(cwd, ".kanzaki", "rules.md");
@@ -880,9 +1054,19 @@ function createCli() {
880
1054
  console.log(chalk2.dim("You can run 'kanzaki check' directly, or set it up with husky/lint-staged."));
881
1055
  console.log(chalk2.dim("Run 'kanzaki login' to authenticate."));
882
1056
  });
883
- program2.command("check").description("Review staged changes against rules").option("-p, --provider <provider>", "LLM provider (openai / anthropic)").option("-m, --model <model>", "Model name").option("-r, --rules <path>", "Path to rules file", ".kanzaki/rules.md").option("--api-key <key>", "API key (prefer KANZAKI_API_KEY env var)").option("--no-block", "Warn only, don't block commit").option("-o, --emit-feedback", "Write feedback markdown (for coding agents) to .kanzaki/reviews/").option("-v, --verbose", "Verbose output").action(async (opts) => {
1057
+ program2.command("check").description("Review changes against rules").option("-p, --provider <provider>", "LLM provider (openai / anthropic)").option("-m, --model <model>", "Model name").option("-r, --rules <path>", "Path to rules file", ".kanzaki/rules.md").option("--api-key <key>", "API key (prefer KANZAKI_API_KEY env var)").option("--no-block", "Warn only, don't block commit").option("-o, --emit-feedback", "Write feedback markdown (for coding agents) to .kanzaki/reviews/").option("-v, --verbose", "Verbose output").option("--working-tree", "Review working tree changes against HEAD (staged + unstaged)").option("--range <range>", "Review diff for a revision range (e.g. main..HEAD)").option("--files <paths...>", "Review current state of the specified files (no diff)").action(async (opts) => {
884
1058
  try {
885
- if (!hasStagedChanges()) {
1059
+ const sourceFlagsUsed = [
1060
+ opts.workingTree ? "--working-tree" : null,
1061
+ opts.range ? "--range" : null,
1062
+ opts.files ? "--files" : null
1063
+ ].filter((v) => v !== null);
1064
+ if (sourceFlagsUsed.length > 1) {
1065
+ console.error(chalk2.red(`Cannot combine ${sourceFlagsUsed.join(" and ")}. Choose exactly one source.`));
1066
+ process.exit(1);
1067
+ }
1068
+ const sourceKind = opts.workingTree ? "workingTree" : opts.range ? "range" : opts.files ? "files" : "staged";
1069
+ if (sourceKind === "staged" && !hasStagedChanges()) {
886
1070
  console.log(chalk2.yellow("No staged changes found. Nothing to review."));
887
1071
  process.exit(0);
888
1072
  }
@@ -923,26 +1107,39 @@ function createCli() {
923
1107
  console.log(chalk2.dim(`Context: ${rulesContext.length} chars of additional context`));
924
1108
  }
925
1109
  }
926
- const staged = getStagedChanges();
927
- const fileContexts = getFileContexts(staged.files);
928
- const applicableRules = filterRulesByFiles(rules, staged.files);
1110
+ const source = getReviewSource({
1111
+ kind: sourceKind,
1112
+ range: opts.range,
1113
+ files: opts.files
1114
+ });
1115
+ if (source.files.length === 0) {
1116
+ console.log(chalk2.yellow(`No files to review (source: ${source.label}).`));
1117
+ process.exit(0);
1118
+ }
1119
+ const applicableRules = filterRulesByFiles(rules, source.files);
929
1120
  if (applicableRules.length === 0) {
930
1121
  console.log(chalk2.yellow("No applicable rules for changed files. Skipping review."));
931
1122
  process.exit(0);
932
1123
  }
1124
+ const extraPaths = collectExtraStatePaths(applicableRules, source.files);
1125
+ const fileContexts = getFileContextsForSource(source, extraPaths);
933
1126
  if (config.verbose) {
934
- console.log(chalk2.dim(`Files changed: ${staged.files.join(", ")}`));
1127
+ console.log(chalk2.dim(`Source: ${source.label}`));
1128
+ console.log(chalk2.dim(`Files: ${source.files.join(", ")}`));
1129
+ if (extraPaths.length > 0) {
1130
+ console.log(chalk2.dim(`Extra state files: ${extraPaths.join(", ")}`));
1131
+ }
935
1132
  if (applicableRules.length < rules.length) {
936
1133
  console.log(chalk2.dim(`Rules filtered: ${applicableRules.length}/${rules.length} applicable`));
937
1134
  }
938
1135
  }
939
1136
  console.log(chalk2.dim("Reviewing changes with LLM..."));
940
- const result = await review(config, applicableRules, staged, fileContexts, rulesContext);
1137
+ const result = await review(config, applicableRules, source, fileContexts, rulesContext);
941
1138
  const { errorCount } = report(result, config.verbose);
942
1139
  if (opts.emitFeedback) {
943
1140
  const rulesDir = dirname(resolve4(config.rulesPath));
944
1141
  const reviewsDir = resolve4(rulesDir, "reviews");
945
- const feedbackPath = writeFeedbackFile(result, applicableRules, staged, reviewsDir);
1142
+ const feedbackPath = writeFeedbackFile(result, applicableRules, source, reviewsDir);
946
1143
  if (feedbackPath) {
947
1144
  console.log(chalk2.dim(`\u2192 Feedback written to ${feedbackPath}`));
948
1145
  }
@@ -978,7 +1175,7 @@ function createCli() {
978
1175
  } else if (opts.useClaude) {
979
1176
  console.log(chalk2.dim("Checking local Claude CLI installation..."));
980
1177
  try {
981
- const version = execSync2("claude --version", { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
1178
+ const version = execSync("claude --version", { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
982
1179
  console.log(chalk2.dim(`Found: ${version}`));
983
1180
  } catch {
984
1181
  console.error(chalk2.red("Claude CLI not found."));
@@ -1099,6 +1296,36 @@ function promptSecret(prompt) {
1099
1296
  stdin.on("data", onData);
1100
1297
  });
1101
1298
  }
1299
+ function collectExtraStatePaths(rules, alreadyIncluded) {
1300
+ const patterns = Array.from(
1301
+ new Set(rules.flatMap((r) => r.stateExtraPatterns))
1302
+ );
1303
+ if (patterns.length === 0) return [];
1304
+ let trackedFiles = [];
1305
+ try {
1306
+ const raw = execFileSync2("git", ["ls-files"], {
1307
+ encoding: "utf-8",
1308
+ stdio: ["pipe", "pipe", "pipe"]
1309
+ });
1310
+ trackedFiles = raw.split("\n").map((f) => f.trim()).filter(Boolean);
1311
+ } catch {
1312
+ return [];
1313
+ }
1314
+ const included = new Set(alreadyIncluded);
1315
+ const matched = /* @__PURE__ */ new Set();
1316
+ for (const file of trackedFiles) {
1317
+ if (included.has(file)) continue;
1318
+ if (patterns.some((p) => matchGlobSimple(file, p))) {
1319
+ matched.add(file);
1320
+ }
1321
+ }
1322
+ return Array.from(matched);
1323
+ }
1324
+ function matchGlobSimple(filePath, pattern) {
1325
+ const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLE_STAR}}/g, ".*").replace(/\?/g, "[^/]");
1326
+ const regex = new RegExp(`(^|/)${regexStr}$`, "i");
1327
+ return regex.test(filePath);
1328
+ }
1102
1329
 
1103
1330
  // src/bin.ts
1104
1331
  var program = createCli();
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/auth.ts","../src/core/parser.ts","../src/core/git.ts","../src/llm/openai.ts","../src/llm/anthropic.ts","../src/llm/claude-cli.ts","../src/core/reviewer.ts","../src/core/reporter.ts","../src/core/feedback.ts","../src/bin.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { existsSync, writeFileSync, readFileSync, mkdirSync, chmodSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createInterface } from \"node:readline\";\nimport { execSync } from \"node:child_process\";\n\nimport { loadConfig } from \"./config.js\";\nimport { parseRulesFile, filterRulesByFiles } from \"./core/parser.js\";\nimport { getStagedChanges, getFileContexts, hasStagedChanges, getRepoRoot } from \"./core/git.js\";\nimport { review } from \"./core/reviewer.js\";\nimport { report } from \"./core/reporter.js\";\nimport { writeFeedbackFile } from \"./core/feedback.js\";\nimport {\n saveCredentials,\n clearCredentials,\n loadCredentials,\n hasCredentials,\n loginWithOAuthPKCE,\n} from \"./auth.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name(\"kanzaki\")\n .description(\"LLM-powered semantic pre-commit linter\")\n .version(\"0.2.0\");\n\n // ── init ──────────────────────────────────────────────\n program\n .command(\"init\")\n .description(\"Create .kanzaki/rules.md rules file\")\n .action(async () => {\n const cwd = process.cwd();\n\n // ルールファイル作成\n const rulesPath = resolve(cwd, \".kanzaki\", \"rules.md\");\n const rulesDir = dirname(rulesPath);\n \n if (!existsSync(rulesDir)) {\n mkdirSync(rulesDir, { recursive: true });\n }\n\n if (existsSync(rulesPath)) {\n console.log(chalk.yellow(\"⚠ .kanzaki/rules.md already exists, skipping.\"));\n } else {\n const template = loadTemplate();\n writeFileSync(rulesPath, template, \"utf-8\");\n console.log(chalk.green(\"✓ Created .kanzaki/rules.md\"));\n }\n\n // reviewsフォルダ(フィードバック出力先)はGit管理対象外にする\n const kanzakiGitignore = resolve(rulesDir, \".gitignore\");\n if (!existsSync(kanzakiGitignore)) {\n writeFileSync(kanzakiGitignore, \"reviews/\\n\", \"utf-8\");\n console.log(chalk.green(\"✓ Created .kanzaki/.gitignore\"));\n }\n\n console.log();\n console.log(chalk.dim(\"Edit .kanzaki/rules.md to customize your review rules.\"));\n console.log(chalk.dim(\"You can run 'kanzaki check' directly, or set it up with husky/lint-staged.\"));\n console.log(chalk.dim(\"Run 'kanzaki login' to authenticate.\"));\n });\n\n // ── check ─────────────────────────────────────────────\n program\n .command(\"check\")\n .description(\"Review staged changes against rules\")\n .option(\"-p, --provider <provider>\", \"LLM provider (openai / anthropic)\")\n .option(\"-m, --model <model>\", \"Model name\")\n .option(\"-r, --rules <path>\", \"Path to rules file\", \".kanzaki/rules.md\")\n .option(\"--api-key <key>\", \"API key (prefer KANZAKI_API_KEY env var)\")\n .option(\"--no-block\", \"Warn only, don't block commit\")\n .option(\"-o, --emit-feedback\", \"Write feedback markdown (for coding agents) to .kanzaki/reviews/\")\n .option(\"-v, --verbose\", \"Verbose output\")\n .action(async (opts) => {\n try {\n // ステージされた変更の確認\n if (!hasStagedChanges()) {\n console.log(chalk.yellow(\"No staged changes found. Nothing to review.\"));\n process.exit(0);\n }\n\n // 設定ロード\n const config = loadConfig({\n provider: opts.provider,\n apiKey: opts.apiKey,\n model: opts.model,\n rulesPath: opts.rules,\n verbose: opts.verbose ?? false,\n noBlock: opts.noBlock === false, // commander の --no-block は block=false にする\n });\n\n // ルールファイルの存在確認\n if (!existsSync(config.rulesPath)) {\n console.error(chalk.red(`Rules file not found: ${config.rulesPath}`));\n console.error(chalk.dim(\"Run 'kanzaki init' to create one.\"));\n process.exit(1);\n }\n\n // ルール解析\n const { rules, context: rulesContext, errors: parseErrors } = parseRulesFile(config.rulesPath);\n\n if (parseErrors && parseErrors.length > 0) {\n console.error(chalk.red.bold(`\\n❌ Found ${parseErrors.length} formatting error(s) in ${config.rulesPath}:`));\n parseErrors.forEach((err) => {\n console.error(chalk.yellow(` Line ${err.line}: `) + err.message);\n });\n console.error(chalk.dim(\"\\nPlease fix these errors before committing.\"));\n process.exit(1);\n }\n\n if (rules.length === 0) {\n console.log(chalk.yellow(\"No rules found in rules file. Skipping review.\"));\n process.exit(0);\n }\n\n const errorRules = rules.filter((r) => r.severity === \"error\").length;\n const warnRules = rules.filter((r) => r.severity === \"warn\").length;\n\n if (config.verbose) {\n console.log(chalk.dim(`Provider: ${config.provider} (${config.model})`));\n console.log(chalk.dim(`Rules: ${errorRules} errors, ${warnRules} warnings`));\n if (rulesContext) {\n console.log(chalk.dim(`Context: ${rulesContext.length} chars of additional context`));\n }\n }\n\n // diff取得\n const staged = getStagedChanges();\n const fileContexts = getFileContexts(staged.files);\n\n // ファイルスコープでルールをフィルタリング\n const applicableRules = filterRulesByFiles(rules, staged.files);\n\n if (applicableRules.length === 0) {\n console.log(chalk.yellow(\"No applicable rules for changed files. Skipping review.\"));\n process.exit(0);\n }\n\n if (config.verbose) {\n console.log(chalk.dim(`Files changed: ${staged.files.join(\", \")}`));\n if (applicableRules.length < rules.length) {\n console.log(chalk.dim(`Rules filtered: ${applicableRules.length}/${rules.length} applicable`));\n }\n }\n\n // LLMレビュー\n console.log(chalk.dim(\"Reviewing changes with LLM...\"));\n const result = await review(config, applicableRules, staged, fileContexts, rulesContext);\n\n // 結果表示\n const { errorCount } = report(result, config.verbose);\n\n // エージェント向けフィードバックの書き出し(オプトイン)\n if (opts.emitFeedback) {\n const rulesDir = dirname(resolve(config.rulesPath));\n const reviewsDir = resolve(rulesDir, \"reviews\");\n const feedbackPath = writeFeedbackFile(result, applicableRules, staged, reviewsDir);\n if (feedbackPath) {\n console.log(chalk.dim(`→ Feedback written to ${feedbackPath}`));\n }\n }\n\n // errorのみブロック(warnはブロックしない)\n if (errorCount > 0 && !config.noBlock) {\n process.exit(1);\n }\n } catch (error) {\n console.error(chalk.red(`Error: ${(error as Error).message}`));\n process.exit(1);\n }\n });\n\n // ── login ─────────────────────────────────────────────\n const SUPPORTED_PROVIDERS = [\"openai\", \"anthropic\"] as const;\n type SupportedProvider = typeof SUPPORTED_PROVIDERS[number];\n\n program\n .command(\"login\")\n .description(\"Authenticate with a supported LLM provider\")\n .option(\"-p, --provider <provider>\", `Provider to use (${SUPPORTED_PROVIDERS.join(\" / \")})`)\n .option(\"--use-chatgpt\", \"Log in with ChatGPT Plus/Pro subscription (OAuth)\")\n .option(\"--use-claude\", \"Use the local Claude CLI as a subprocess\")\n .action(async (opts) => {\n if (opts.useChatgpt) {\n // OpenAI OAuth Flow\n\n\n console.log(chalk.dim(\"Starting OAuth Authorization Code Flow...\"));\n try {\n const token = await loginWithOAuthPKCE();\n\n const expiresAt = new Date(Date.now() + token.expires_in * 1000).toISOString();\n saveCredentials({\n provider: \"openai\",\n apiKey: \"\",\n oauthToken: token.access_token,\n refreshToken: token.refresh_token,\n expiresAt,\n });\n\n console.log(chalk.green(\"\\n✓ Authenticated via OAuth\"));\n } catch (error) {\n console.error(chalk.red(`OAuth failed: ${(error as Error).message}`));\n console.error(chalk.dim(\"Try 'kanzaki login' with an API key instead.\"));\n process.exit(1);\n }\n } else if (opts.useClaude) {\n // Claude CLI subprocess flow (OpenClaw style)\n console.log(chalk.dim(\"Checking local Claude CLI installation...\"));\n\n try {\n const version = execSync(\"claude --version\", { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"pipe\"] }).trim();\n console.log(chalk.dim(`Found: ${version}`));\n } catch {\n console.error(chalk.red(\"Claude CLI not found.\"));\n console.error(chalk.dim(\"Install it from https://docs.claude.com/en/docs/claude-code and run 'claude login' first.\"));\n process.exit(1);\n }\n\n saveCredentials({\n provider: \"anthropic\",\n apiKey: \"\",\n useClaudeCli: true,\n });\n\n console.log(chalk.green(\"\\n✓ Configured to use local Claude CLI\"));\n console.log(chalk.dim(\"Kanzaki will invoke 'claude -p' for reviews, using your existing Claude CLI session.\"));\n console.log(chalk.dim(\"Credentials stored in ~/.config/kanzaki/credentials.json\"));\n } else {\n // API Key入力(--provider 必須)\n if (!opts.provider) {\n console.error(chalk.red(\"Authentication method is required.\"));\n console.error(chalk.dim(\"Use one of:\"));\n console.error(chalk.dim(` kanzaki login --provider <${SUPPORTED_PROVIDERS.join(\" | \")}> (API key)`));\n console.error(chalk.dim(\" kanzaki login --use-chatgpt (ChatGPT OAuth)\"));\n console.error(chalk.dim(\" kanzaki login --use-claude (Claude CLI subprocess)\"));\n process.exit(1);\n }\n\n if (!SUPPORTED_PROVIDERS.includes(opts.provider as SupportedProvider)) {\n console.error(chalk.red(`Unknown provider: ${opts.provider}`));\n console.error(chalk.dim(`Supported providers: ${SUPPORTED_PROVIDERS.join(\", \")}`));\n process.exit(1);\n }\n\n const provider = opts.provider as SupportedProvider;\n const label = provider === \"openai\" ? \"OpenAI\" : \"Anthropic\";\n const key = await promptSecret(`Enter your ${label} API key: `);\n\n if (!key) {\n console.error(chalk.red(\"No API key provided.\"));\n process.exit(1);\n }\n\n saveCredentials({ provider, apiKey: key });\n console.log(chalk.green(`✓ Saved ${provider} credentials`));\n console.log(chalk.dim(\"Credentials stored in ~/.config/kanzaki/credentials.json\"));\n }\n });\n\n // ── logout ────────────────────────────────────────────\n program\n .command(\"logout\")\n .description(\"Remove saved credentials\")\n .action(() => {\n clearCredentials();\n console.log(chalk.green(\"✓ Credentials removed\"));\n });\n\n // ── status ────────────────────────────────────────────\n program\n .command(\"status\")\n .description(\"Show authentication status\")\n .action(() => {\n const creds = loadCredentials();\n if (!creds || (!creds.apiKey && !creds.oauthToken && !creds.useClaudeCli)) {\n console.log(chalk.yellow(\"Not authenticated.\"));\n console.log(chalk.dim(\"Run 'kanzaki login' to authenticate.\"));\n return;\n }\n\n console.log(chalk.bold(\"Kanzaki Status\"));\n console.log(` Provider: ${chalk.cyan(creds.provider)}`);\n\n if (creds.useClaudeCli) {\n console.log(` Auth: ${chalk.cyan(\"Claude CLI subprocess\")} ${chalk.green(\"(active)\")}`);\n } else if (creds.oauthToken) {\n const expired = creds.expiresAt && new Date(creds.expiresAt) < new Date();\n console.log(` Auth: ${chalk.cyan(\"OAuth\")}${expired ? chalk.red(\" (expired)\") : chalk.green(\" (active)\")}`);\n } else {\n const masked = creds.apiKey.slice(0, 7) + \"...\" + creds.apiKey.slice(-4);\n console.log(` Auth: ${chalk.cyan(\"API Key\")} (${masked})`);\n }\n });\n\n return program;\n}\n\nfunction loadTemplate(): string {\n // まずパッケージ内のtemplateを試す\n const templatePath = resolve(__dirname, \"..\", \"templates\", \"rules.md\");\n if (existsSync(templatePath)) {\n return readFileSync(templatePath, \"utf-8\");\n }\n\n // フォールバック: デフォルトテンプレート\n return `# Kanzaki レビュールール\n\n## 品質\n- [ ] !error 変更内容がプロジェクトの既存のスタイルや規約と一貫していること\n- [ ] !error プレースホルダーやTODOが残っていないこと\n- [ ] !warn 内容が明確・簡潔で、不要な繰り返しがないこと\n\n## 正確性\n- [ ] !error 事実誤認や誤解を招く情報が含まれていないこと\n- [ ] !warn 適切な箇所に出典・参考文献が記載されていること\n\n## セキュリティ (*.ts, *.js, *.py)\n- [ ] !error ハードコードされたシークレット・APIキー・パスワードが含まれていないこと\n`;\n}\n\n/**\n * ターミナルでAPIキーを安全に入力させる(入力は非表示)。\n */\nfunction promptSecret(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n // 入力を非表示にする\n process.stdout.write(prompt);\n const stdin = process.stdin;\n const wasRaw = stdin.isRaw;\n\n if (stdin.isTTY && stdin.setRawMode) {\n stdin.setRawMode(true);\n }\n\n let input = \"\";\n const onData = (char: Buffer) => {\n const c = char.toString();\n if (c === \"\\n\" || c === \"\\r\") {\n stdin.removeListener(\"data\", onData);\n if (stdin.isTTY && stdin.setRawMode) {\n stdin.setRawMode(wasRaw ?? false);\n }\n process.stdout.write(\"\\n\");\n rl.close();\n resolve(input);\n } else if (c === \"\\x03\") {\n // Ctrl+C\n process.exit(1);\n } else if (c === \"\\x7f\" || c === \"\\b\") {\n // Backspace\n input = input.slice(0, -1);\n } else {\n input += c;\n process.stdout.write(\"*\");\n }\n };\n\n stdin.on(\"data\", onData);\n });\n}\n\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport dotenv from \"dotenv\";\nimport { loadCredentials, getActiveApiKey } from \"./auth.js\";\n\nexport interface KanzakiConfig {\n provider: \"openai\" | \"anthropic\";\n apiKey: string;\n model: string;\n rulesPath: string;\n verbose: boolean;\n noBlock: boolean;\n /** ChatGPT OAuth認証を使用しているか */\n useOAuth: boolean;\n /** Claude CLIをサブプロセスとして利用するか */\n useClaudeCli: boolean;\n}\n\nconst DEFAULT_MODELS: Record<string, string> = {\n openai: \"gpt-5.4\",\n anthropic: \"claude-sonnet-4-20250514\",\n};\n\n/**\n * CLI引数とenv変数からKanzaki設定をロードする。\n * 優先順位: CLIフラグ → env変数 → 保存済みクレデンシャル\n */\nexport function loadConfig(overrides: Partial<KanzakiConfig> = {}): KanzakiConfig {\n // .env ファイルがあれば読み込む\n const envPath = resolve(process.cwd(), \".env\");\n if (existsSync(envPath)) {\n dotenv.config({ path: envPath });\n }\n\n // 保存済みクレデンシャルを読み込む\n const stored = loadCredentials();\n\n const provider = (\n overrides.provider\n ?? process.env.KANZAKI_PROVIDER\n ?? stored?.provider\n ?? \"openai\"\n ) as KanzakiConfig[\"provider\"];\n\n // APIキー解決: CLIフラグ → env → 保存済み\n let apiKey = overrides.apiKey ?? process.env.KANZAKI_API_KEY ?? \"\";\n if (!apiKey && stored) {\n apiKey = getActiveApiKey(stored);\n }\n if (!apiKey) {\n throw new Error(\n \"API key is required. Run 'kanzaki login' or set KANZAKI_API_KEY environment variable.\",\n );\n }\n\n const model = overrides.model ?? process.env.KANZAKI_MODEL ?? DEFAULT_MODELS[provider] ?? \"gpt-5.4\";\n\n const rulesPath = overrides.rulesPath ?? process.env.KANZAKI_RULES_PATH ?? \".kanzaki/rules.md\";\n\n return {\n provider,\n apiKey,\n model,\n rulesPath: resolve(process.cwd(), rulesPath),\n verbose: overrides.verbose ?? false,\n noBlock: overrides.noBlock ?? false,\n useOAuth: !!(stored?.oauthToken),\n useClaudeCli: !!(stored?.useClaudeCli),\n };\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst CONFIG_DIR = resolve(homedir(), \".config\", \"kanzaki\");\nconst CREDENTIALS_PATH = resolve(CONFIG_DIR, \"credentials.json\");\n\nexport interface StoredCredentials {\n provider: \"openai\" | \"anthropic\";\n apiKey: string;\n /** OAuth token (if authenticated via OAuth) */\n oauthToken?: string;\n /** OAuth refresh token */\n refreshToken?: string;\n /** Token expiry (ISO string) */\n expiresAt?: string;\n /** Claude CLIをサブプロセスとして利用するフラグ */\n useClaudeCli?: boolean;\n}\n\n/**\n * 保存済み認証情報を読み込む。存在しない場合は null を返す。\n */\nexport function loadCredentials(): StoredCredentials | null {\n if (!existsSync(CREDENTIALS_PATH)) return null;\n\n try {\n const raw = readFileSync(CREDENTIALS_PATH, \"utf-8\");\n return JSON.parse(raw) as StoredCredentials;\n } catch {\n return null;\n }\n}\n\n/**\n * 認証情報を ~/.config/kanzaki/credentials.json に保存する。\n */\nexport function saveCredentials(credentials: StoredCredentials): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n writeFileSync(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), \"utf-8\");\n}\n\n/**\n * 保存済み認証情報を削除する。\n */\nexport function clearCredentials(): void {\n if (existsSync(CREDENTIALS_PATH)) {\n writeFileSync(CREDENTIALS_PATH, \"{}\", \"utf-8\");\n }\n}\n\n/**\n * 認証情報が保存されているか確認する。\n */\nexport function hasCredentials(): boolean {\n const creds = loadCredentials();\n return creds !== null && (!!creds.apiKey || !!creds.oauthToken || !!creds.useClaudeCli);\n}\n\n/**\n * 有効なAPIキーを取得する。\n * OAuth tokenがある場合はそちらを優先(有効期限チェック付き)。\n */\nexport function getActiveApiKey(creds: StoredCredentials): string {\n // Claude CLIを使う場合は、APIキーはsubprocess側で管理するのでダミー値を返す\n if (creds.useClaudeCli) {\n return \"claude-cli\";\n }\n\n // OAuthトークンが有効ならそれを使う\n if (creds.oauthToken) {\n // expiresAtが未設定(Claudeセッショントークン等)なら常に有効とみなす\n if (!creds.expiresAt) {\n return creds.oauthToken;\n }\n const expiry = new Date(creds.expiresAt);\n if (expiry > new Date()) {\n return creds.oauthToken;\n }\n }\n\n // フォールバック: 通常のAPIキー\n return creds.apiKey;\n}\n\n// ── OAuth Authorization Code Flow (PKCE) ─────────────────\n\nimport { createServer } from \"node:http\";\nimport { randomBytes, createHash } from \"node:crypto\";\nimport open from \"open\";\n\nconst OPENAI_AUTH_URL = \"https://auth.openai.com/oauth/authorize\";\nconst OPENAI_TOKEN_URL = \"https://auth.openai.com/oauth/token\";\nconst OPENAI_CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\"; // Correct client ID used by the platform\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\n\nexport interface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n}\n\nfunction generatePKCE(): { verifier: string; challenge: string } {\n const verifier = randomBytes(32).toString('base64url');\n const challenge = createHash('sha256').update(verifier).digest('base64url');\n return { verifier, challenge };\n}\n\nexport async function loginWithOAuthPKCE(): Promise<TokenResponse> {\n const { verifier, challenge } = generatePKCE();\n const state = randomBytes(16).toString('base64url');\n \n const scope = encodeURIComponent(\"openid profile email offline_access api.connectors.read api.connectors.invoke\");\n const authUrl = `${OPENAI_AUTH_URL}?response_type=code&client_id=${OPENAI_CLIENT_ID}&code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=${scope}&state=${state}&id_token_add_organizations=true&codex_cli_simplified_flow=true`;\n\n console.log(\"Opening browser for authentication...\");\n console.log(\"If your browser does not open automatically, please open this link:\");\n console.log(authUrl);\n \n try {\n await open(authUrl);\n } catch {\n // Ignore error if `open` fails (e.g. no default browser found)\n }\n\n return new Promise((resolve, reject) => {\n const server = createServer(async (req, res) => {\n try {\n if (!req.url?.startsWith('/auth/callback')) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const url = new URL(req.url, `http://${req.headers.host}`);\n const error = url.searchParams.get('error');\n const errorDesc = url.searchParams.get('error_description');\n const code = url.searchParams.get('code');\n const returnedState = url.searchParams.get('state');\n\n if (error) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(`<h1>Authentication Failed</h1><p>${error}: ${errorDesc || 'Unknown error'}</p>`);\n server.close();\n return reject(new Error(`OAuth error: ${error} - ${errorDesc}`));\n }\n\n if (!code) {\n res.writeHead(400);\n res.end(\"Missing authorization code\");\n server.close();\n return reject(new Error(\"No authorization code received\"));\n }\n\n if (returnedState !== state) {\n res.writeHead(400);\n res.end(\"Invalid state\");\n server.close();\n return reject(new Error(\"OAuth state mismatch\"));\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(\"<h1>Authentication Successful!</h1><p>You can close this tab and return to your terminal.</p>\");\n\n server.close();\n\n // Exchange code for token\n const tokenRes = await fetch(OPENAI_TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: OPENAI_CLIENT_ID,\n grant_type: 'authorization_code',\n code,\n code_verifier: verifier,\n redirect_uri: REDIRECT_URI\n })\n });\n\n if (!tokenRes.ok) {\n const body = await tokenRes.text();\n return reject(new Error(`Token exchange failed: ${tokenRes.status} ${body}`));\n }\n\n const tokenData = await tokenRes.json() as TokenResponse;\n resolve(tokenData);\n\n } catch (err) {\n res.writeHead(500);\n res.end(\"Internal Server Error\");\n server.close();\n reject(err);\n }\n });\n\n server.listen(1455, 'localhost');\n });\n}\n\n\n","import { readFileSync } from \"node:fs\";\n\nexport type Severity = \"error\" | \"warn\";\n\nexport interface Rule {\n /** ルールが属するグループ (Markdownのヘッダー) */\n group: string;\n /** ルールのテキスト */\n text: string;\n /** 重要度: error = ブロック, warn = 警告のみ */\n severity: Severity;\n /** 対象ファイルのglobパターン(未指定=全ファイル対象) */\n filePatterns: string[];\n /** このルールが定義された行番号 (1-indexed)、重複検出用 */\n lineNumber?: number;\n}\n\nexport interface ParseError {\n /** エラーが発生した行番号 (1-indexed) */\n line: number;\n /** エラーまたは警告のメッセージ */\n message: string;\n}\n\nexport interface ParsedRulesFile {\n /** パースされたルール一覧 */\n rules: Rule[];\n /** ルール以外の自由記述テキスト(LLMへのコンテキスト) */\n context: string;\n /** フォーマットエラー(あれば) */\n errors: ParseError[];\n}\n\n/**\n * .kanzaki.md ファイルを解析し、ルールとコンテキストを抽出する。\n *\n * 対応形式:\n * - `- [ ] ルールテキスト` → severity: error (デフォルト)\n * - `- [ ] !error ルールテキスト` → severity: error (明示)\n * - `- [ ] !warn ルールテキスト` → severity: warn\n *\n * ヘッダー(##)はグループ名として使用される。\n * ヘッダーに括弧でglobパターンを指定可能:\n * - `## Security (*.ts, *.js)` → このグループのルールは .ts/.js ファイルのみに適用\n *\n * チェックリスト項目でもヘッダーでもない行は「コンテキスト」として収集される。\n */\nexport function parseRulesFile(filePath: string): ParsedRulesFile {\n const content = readFileSync(filePath, \"utf-8\");\n return parseRulesFromContent(content);\n}\n\nexport function parseRulesFromContent(content: string): ParsedRulesFile {\n const lines = content.split(\"\\n\");\n const rules: Rule[] = [];\n const contextLines: string[] = [];\n const errors: ParseError[] = [];\n let currentGroup = \"General\";\n let currentPatterns: string[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const trimmed = line.trim();\n const lineNumber = i + 1;\n\n // ヘッダーを検出してグループ名とファイルパターンを更新\n const headerMatch = trimmed.match(/^#{1,6}\\s+(.+)$/);\n if (headerMatch) {\n const headerText = headerMatch[1].trim();\n const { name, patterns } = parseHeaderWithPatterns(headerText);\n currentGroup = name;\n currentPatterns = patterns;\n\n // 括弧の不一致を検出(閉じ括弧忘れ等)\n if (headerText.includes(\"(\") && !headerText.includes(\")\")) {\n errors.push({ line: lineNumber, message: `Missing closing parenthesis in file scope: \"${headerText}\"` });\n }\n\n // 空の括弧を検出\n const emptyParenMatch = headerText.match(/\\(\\s*\\)/);\n if (emptyParenMatch) {\n errors.push({ line: lineNumber, message: `Empty file scope parentheses: \"${headerText}\". Remove \"()\" or specify glob patterns inside.` });\n }\n continue;\n }\n\n // 空のチェックリスト項目を検出 (- [ ] の後にテキストなし)\n const emptyRuleMatch = trimmed.match(/^-\\s*\\[[\\s]?\\]\\s*$/);\n if (emptyRuleMatch) {\n errors.push({ line: lineNumber, message: `Empty rule. Checklist item has no text.` });\n continue;\n }\n\n // チェックリスト項目を検出 (- [ ])\n const ruleMatch = trimmed.match(/^-\\s*\\[[\\s]?\\]\\s+(.+)$/);\n if (ruleMatch) {\n const rawText = ruleMatch[1].trim();\n\n // 不正なseverityタグの検出(タイポ)\n const invalidTagMatch = rawText.match(/^!(err|warning|info|critical|block)\\b/i);\n if (invalidTagMatch) {\n errors.push({ line: lineNumber, message: `Unknown severity tag \"${invalidTagMatch[0]}\". Use !error or !warn.` });\n }\n\n const { severity, text } = parseSeverity(rawText);\n\n // severityタグ直後に本文がない場合\n if (text.length === 0) {\n errors.push({ line: lineNumber, message: `Empty rule. Checklist item has only a severity tag with no description.` });\n continue;\n }\n\n rules.push({\n group: currentGroup,\n text,\n severity,\n filePatterns: currentPatterns,\n lineNumber,\n });\n continue;\n }\n\n // それ以外の行の解析\n if (trimmed.length > 0) {\n // リスト項目の形式ミスの検出 (* [ ] や - [x] など)\n if (trimmed.match(/^[-*+]\\s*\\[(.*?)\\]/)) {\n errors.push({ line: lineNumber, message: `Invalid rule format. Checkbox must be '- [ ]'. Found: \"${trimmed}\"` });\n }\n\n // 通常のコンテキストとして収集\n contextLines.push(trimmed);\n }\n }\n\n // 同一グループ内での重複ルールを検出\n const seen = new Map<string, number>();\n for (const rule of rules) {\n const key = `${rule.group}\\u0000${rule.text.toLowerCase()}`;\n const firstLine = seen.get(key);\n if (firstLine !== undefined) {\n errors.push({\n line: rule.lineNumber ?? 0,\n message: `Duplicate rule in group \"${rule.group}\" (first defined at line ${firstLine}): \"${rule.text}\"`,\n });\n } else {\n seen.set(key, rule.lineNumber ?? 0);\n }\n }\n\n return {\n rules,\n context: contextLines.join(\"\\n\"),\n errors,\n };\n}\n\n/**\n * ヘッダーテキストからグループ名とファイルパターンを分離する。\n * 例: \"Security (*.ts, *.js)\" → { name: \"Security\", patterns: [\"*.ts\", \"*.js\"] }\n */\nfunction parseHeaderWithPatterns(header: string): { name: string; patterns: string[] } {\n const match = header.match(/^(.+?)\\s*\\(([^)]+)\\)\\s*$/);\n if (match) {\n const name = match[1].trim();\n const patterns = match[2]\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n return { name, patterns };\n }\n\n return { name: header, patterns: [] };\n}\n\n/**\n * ルールテキストから重要度プレフィックスを抽出する。\n * `!error` / `!warn` が先頭にあればそれを使い、なければデフォルト error。\n */\nfunction parseSeverity(rawText: string): { severity: Severity; text: string } {\n const warnMatch = rawText.match(/^!warn\\s+(.+)$/i);\n if (warnMatch) {\n return { severity: \"warn\", text: warnMatch[1].trim() };\n }\n\n const errorMatch = rawText.match(/^!error\\s+(.+)$/i);\n if (errorMatch) {\n return { severity: \"error\", text: errorMatch[1].trim() };\n }\n\n return { severity: \"error\", text: rawText };\n}\n\n/**\n * ルール配列をLLMプロンプト用のフォーマット文字列に変換する。\n */\nexport function formatRulesForPrompt(rules: Rule[]): string {\n const grouped = new Map<string, Rule[]>();\n\n for (const rule of rules) {\n const group = grouped.get(rule.group) ?? [];\n group.push(rule);\n grouped.set(rule.group, group);\n }\n\n const sections: string[] = [];\n for (const [group, groupRules] of grouped) {\n const scope = groupRules[0]?.filePatterns.length > 0\n ? ` (applies to: ${groupRules[0].filePatterns.join(\", \")})`\n : \"\";\n sections.push(`### ${group}${scope}`);\n for (let i = 0; i < groupRules.length; i++) {\n const r = groupRules[i];\n const tag = r.severity === \"warn\" ? \"[WARNING]\" : \"[ERROR]\";\n sections.push(`${i + 1}. ${tag} ${r.text}`);\n }\n sections.push(\"\");\n }\n\n return sections.join(\"\\n\");\n}\n\n/**\n * 変更ファイル一覧に基づいてルールをフィルタリングする。\n * filePatterns が空のルールは全ファイルに適用される。\n */\nexport function filterRulesByFiles(rules: Rule[], changedFiles: string[]): Rule[] {\n return rules.filter((rule) => {\n // パターン未指定 → 全ファイル対象\n if (rule.filePatterns.length === 0) return true;\n\n // いずれかの変更ファイルがパターンにマッチすれば適用\n return changedFiles.some((file) =>\n rule.filePatterns.some((pattern) => matchGlob(file, pattern)),\n );\n });\n}\n\n/**\n * シンプルなglobマッチング。\n * *.ts, *.md, docs/*, **\\/*.test.ts 等の基本パターンに対応。\n */\nfunction matchGlob(filePath: string, pattern: string): boolean {\n // パターンを正規表現に変換\n const regexStr = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \"{{DOUBLE_STAR}}\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/{{DOUBLE_STAR}}/g, \".*\")\n .replace(/\\?/g, \"[^/]\");\n\n const regex = new RegExp(`(^|/)${regexStr}$`, \"i\");\n return regex.test(filePath);\n}\n\n// 後方互換: 旧APIを維持\nexport function parseRules(filePath: string): Rule[] {\n return parseRulesFile(filePath).rules;\n}\n","import { execSync } from \"node:child_process\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport interface StagedChanges {\n /** git diff --staged の出力 */\n diff: string;\n /** 変更されたファイルのパス一覧 */\n files: string[];\n}\n\nexport interface FileContext {\n /** ファイルパス(リポジトリルートからの相対パス) */\n path: string;\n /** ファイルの全文内容 */\n content: string;\n}\n\n/**\n * ステージされた変更のdiffとファイル一覧を取得する。\n */\nexport function getStagedChanges(): StagedChanges {\n const diff = exec(\"git diff --staged\");\n const filesRaw = exec(\"git diff --staged --name-only\");\n const files = filesRaw\n .split(\"\\n\")\n .map((f) => f.trim())\n .filter(Boolean);\n\n return { diff, files };\n}\n\n/**\n * 変更されたファイルの全文内容を取得する(LLMへのコンテキスト用)。\n * バイナリファイルはスキップする。\n */\nexport function getFileContexts(files: string[]): FileContext[] {\n const repoRoot = getRepoRoot();\n const contexts: FileContext[] = [];\n\n for (const file of files) {\n const absPath = resolve(repoRoot, file);\n if (!existsSync(absPath)) continue;\n\n // バイナリファイルの簡易判定\n if (isBinaryPath(file)) continue;\n\n try {\n const content = readFileSync(absPath, \"utf-8\");\n // 極端に大きいファイルはスキップ (100KB超)\n if (content.length > 100_000) continue;\n contexts.push({ path: file, content });\n } catch {\n // 読み取れないファイルはスキップ\n }\n }\n\n return contexts;\n}\n\n/**\n * Gitリポジトリのルートディレクトリを取得する。\n */\nexport function getRepoRoot(): string {\n return exec(\"git rev-parse --show-toplevel\").trim();\n}\n\n/**\n * ステージされた変更があるかチェック。\n */\nexport function hasStagedChanges(): boolean {\n const output = exec(\"git diff --staged --name-only\");\n return output.trim().length > 0;\n}\n\nfunction exec(command: string): string {\n try {\n return execSync(command, { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n throw new Error(`Git command failed: ${command}\\n${err.stderr ?? err.message}`);\n }\n}\n\nconst BINARY_EXTENSIONS = new Set([\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\", \".ico\", \".webp\", \".svg\",\n \".mp4\", \".webm\", \".mov\", \".avi\",\n \".mp3\", \".wav\", \".ogg\",\n \".zip\", \".tar\", \".gz\", \".rar\", \".7z\",\n \".pdf\", \".doc\", \".docx\", \".xls\", \".xlsx\",\n \".woff\", \".woff2\", \".ttf\", \".eot\", \".otf\",\n \".exe\", \".dll\", \".so\", \".dylib\",\n \".lock\",\n]);\n\nfunction isBinaryPath(filePath: string): boolean {\n const ext = filePath.slice(filePath.lastIndexOf(\".\")).toLowerCase();\n return BINARY_EXTENSIONS.has(ext);\n}\n","import OpenAI from \"openai\";\nimport type { LLMProvider, ReviewResult } from \"./types.js\";\n\n// ChatGPT OAuth時のベースURL(Codex CLIと同じ)\nconst CHATGPT_BASE_URL = \"https://chatgpt.com/backend-api/codex\";\n\nexport class OpenAIProvider implements LLMProvider {\n private apiKey: string;\n private model: string;\n private useOAuth: boolean;\n\n constructor(apiKey: string, model: string, useOAuth = false) {\n this.apiKey = apiKey;\n this.model = model;\n this.useOAuth = useOAuth;\n }\n\n async review(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n if (this.useOAuth) {\n return this.reviewWithCodexBackend(systemPrompt, userPrompt);\n }\n return this.reviewWithChatCompletions(systemPrompt, userPrompt);\n }\n\n private async reviewWithChatCompletions(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n const client = new OpenAI({ apiKey: this.apiKey });\n const response = await client.chat.completions.create({\n model: this.model,\n messages: [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userPrompt },\n ],\n response_format: { type: \"json_object\" },\n temperature: 0.1,\n });\n\n const content = response.choices[0]?.message?.content;\n if (!content) {\n throw new Error(\"OpenAI returned an empty response.\");\n }\n\n return parseReviewResponse(content);\n }\n\n private async reviewWithCodexBackend(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n const url = `${CHATGPT_BASE_URL}/responses`;\n const body = {\n model: this.model,\n instructions: systemPrompt,\n input: [\n { role: \"user\", content: userPrompt },\n ],\n store: false,\n stream: true,\n };\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const errorBody = await res.text();\n throw new Error(`${res.status} ${errorBody}`);\n }\n\n // SSEストリームからテキストを収集\n const content = await this.readSSEStream(res);\n\n return parseReviewResponse(content);\n }\n\n private async readSSEStream(res: Response): Promise<string> {\n const reader = res.body?.getReader();\n if (!reader) {\n throw new Error(\"No response body\");\n }\n\n const decoder = new TextDecoder();\n let content = \"\";\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const data = line.slice(6).trim();\n if (data === \"[DONE]\") continue;\n\n try {\n const event = JSON.parse(data);\n // response.output_text.delta イベントからテキストを収集\n if (event.type === \"response.output_text.delta\" && event.delta) {\n content += event.delta;\n }\n } catch {\n // JSON解析できないイベントはスキップ\n }\n }\n }\n\n if (!content) {\n throw new Error(\"OpenAI returned an empty response from stream.\");\n }\n\n return content;\n }\n}\n\nfunction parseReviewResponse(raw: string): ReviewResult {\n try {\n const parsed = JSON.parse(raw);\n\n if (!Array.isArray(parsed.results)) {\n throw new Error(\"Response missing 'results' array.\");\n }\n\n return {\n results: parsed.results.map((r: Record<string, unknown>) => ({\n rule: String(r.rule ?? \"\"),\n passed: Boolean(r.passed),\n reason: String(r.reason ?? \"\"),\n })),\n summary: String(parsed.summary ?? \"\"),\n };\n } catch (error) {\n throw new Error(`Failed to parse LLM response as JSON:\\n${raw}\\n\\n${error}`);\n }\n}\n","import Anthropic from \"@anthropic-ai/sdk\";\nimport type { LLMProvider, ReviewResult } from \"./types.js\";\n\nexport class AnthropicProvider implements LLMProvider {\n private client: Anthropic;\n private model: string;\n\n constructor(apiKey: string, model: string) {\n this.client = new Anthropic({ apiKey });\n this.model = model;\n }\n\n async review(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: 4096,\n system: systemPrompt,\n messages: [{ role: \"user\", content: userPrompt }],\n });\n\n const textBlock = response.content.find((b) => b.type === \"text\");\n if (!textBlock || textBlock.type !== \"text\") {\n throw new Error(\"Anthropic returned no text content.\");\n }\n\n return parseReviewResponse(textBlock.text);\n }\n}\n\nfunction parseReviewResponse(raw: string): ReviewResult {\n // Anthropicはコードブロック内にJSONを返すことがあるため、抽出を試みる\n const jsonMatch = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n const jsonStr = jsonMatch ? jsonMatch[1].trim() : raw.trim();\n\n try {\n const parsed = JSON.parse(jsonStr);\n\n if (!Array.isArray(parsed.results)) {\n throw new Error(\"Response missing 'results' array.\");\n }\n\n return {\n results: parsed.results.map((r: Record<string, unknown>) => ({\n rule: String(r.rule ?? \"\"),\n passed: Boolean(r.passed),\n reason: String(r.reason ?? \"\"),\n })),\n summary: String(parsed.summary ?? \"\"),\n };\n } catch (error) {\n throw new Error(`Failed to parse LLM response as JSON:\\n${raw}\\n\\n${error}`);\n }\n}\n","import { spawn } from \"node:child_process\";\nimport type { LLMProvider, ReviewResult } from \"./types.js\";\n\n/**\n * ローカルのClaude CLI (`claude -p`) をサブプロセスとして呼び出すプロバイダー。\n * OpenClawと同じ方式で、Claude CLIが認証・セッション管理を担当する。\n */\nexport class ClaudeCliProvider implements LLMProvider {\n async review(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n // systemとuserを1つのプロンプトに結合してstdinで渡す\n const combinedPrompt = `${systemPrompt}\\n\\n---\\n\\n${userPrompt}`;\n\n const output = await this.runClaudeCli(combinedPrompt);\n return parseReviewResponse(output);\n }\n\n private runClaudeCli(prompt: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // Node.js 20+ の Windows では .cmd/.bat を直接 spawn できない\n // (CVE-2024-27980 対応)。`shell:true` は DEP0190 を引き起こすため、\n // cmd.exe 自体を spawn し、その中で claude を起動する。\n // cmd.exe は .exe なので spawn の制限に該当しない。\n const isWin = process.platform === \"win32\";\n const child = isWin\n ? spawn(\"cmd.exe\", [\"/d\", \"/s\", \"/c\", \"claude -p\"], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n windowsVerbatimArguments: true,\n })\n : spawn(\"claude\", [\"-p\"], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString(\"utf-8\");\n });\n\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf-8\");\n });\n\n child.on(\"error\", (err) => {\n reject(new Error(`Failed to spawn claude CLI: ${err.message}`));\n });\n\n child.on(\"close\", (code) => {\n if (code !== 0) {\n reject(new Error(`claude CLI exited with code ${code}: ${stderr || stdout}`));\n return;\n }\n resolve(stdout);\n });\n\n // プロンプトをstdinに書き込んで閉じる\n child.stdin.write(prompt);\n child.stdin.end();\n });\n }\n}\n\nfunction parseReviewResponse(raw: string): ReviewResult {\n // Claude CLIはコードブロック内にJSONを返すことがあるため抽出を試みる\n const jsonMatch = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n const jsonStr = jsonMatch ? jsonMatch[1].trim() : raw.trim();\n\n try {\n const parsed = JSON.parse(jsonStr);\n\n if (!Array.isArray(parsed.results)) {\n throw new Error(\"Response missing 'results' array.\");\n }\n\n return {\n results: parsed.results.map((r: Record<string, unknown>) => ({\n rule: String(r.rule ?? \"\"),\n passed: Boolean(r.passed),\n reason: String(r.reason ?? \"\"),\n })),\n summary: String(parsed.summary ?? \"\"),\n };\n } catch (error) {\n throw new Error(`Failed to parse Claude CLI response as JSON:\\n${raw}\\n\\n${error}`);\n }\n}\n","import type { KanzakiConfig } from \"../config.js\";\nimport type { Rule } from \"./parser.js\";\nimport { formatRulesForPrompt } from \"./parser.js\";\nimport type { FileContext, StagedChanges } from \"./git.js\";\nimport type { LLMProvider, ReviewResult, Severity } from \"../llm/types.js\";\nimport { OpenAIProvider } from \"../llm/openai.js\";\nimport { AnthropicProvider } from \"../llm/anthropic.js\";\nimport { ClaudeCliProvider } from \"../llm/claude-cli.js\";\n\nconst SYSTEM_PROMPT = `You are a strict quality reviewer. Your job is to review changes (git diff) against a checklist of rules defined by the user.\n\nThe rules may cover ANY domain — code, documentation, research, writing, presentations, design, or any other type of output. Evaluate each rule based on its intent, not just literal text matching.\n\nFor each rule in the checklist, determine whether the staged changes comply with it.\n\nIMPORTANT:\n- Rules marked [ERROR] are critical. Be strict when evaluating them.\n- Rules marked [WARNING] are advisory. Be fair but flag clear violations.\n- Only evaluate rules that are RELEVANT to the changes. If a rule clearly does not apply to the content being changed, mark it as passed with reason \"Not applicable to these changes.\"\n- Use the context section (if provided) to understand the project's goals and constraints.\n- Look at both the diff AND the full file context to make accurate judgments.\n\nYou MUST respond with valid JSON in this exact format:\n{\n \"results\": [\n { \"rule\": \"<exact rule text>\", \"passed\": true, \"reason\": \"<brief explanation>\" },\n { \"rule\": \"<exact rule text>\", \"passed\": false, \"reason\": \"<specific explanation of the violation>\" }\n ],\n \"summary\": \"<one-line overall summary>\"\n}`;\n\n/**\n * ステージされた変更をルールに照らし合わせてLLMでレビューする。\n */\nexport async function review(\n config: KanzakiConfig,\n rules: Rule[],\n staged: StagedChanges,\n fileContexts: FileContext[],\n rulesContext?: string,\n): Promise<ReviewResult> {\n const provider = createProvider(config);\n const userPrompt = buildUserPrompt(rules, staged, fileContexts, rulesContext);\n\n const rawResult = await provider.review(SYSTEM_PROMPT, userPrompt);\n\n // ルール定義側のseverityを結果にマッピングする\n return mapSeverities(rawResult, rules);\n}\n\nfunction createProvider(config: KanzakiConfig): LLMProvider {\n switch (config.provider) {\n case \"openai\":\n return new OpenAIProvider(config.apiKey, config.model, config.useOAuth);\n case \"anthropic\":\n if (config.useClaudeCli) {\n return new ClaudeCliProvider();\n }\n return new AnthropicProvider(config.apiKey, config.model);\n default:\n throw new Error(`Unknown provider: ${config.provider}`);\n }\n}\n\nfunction buildUserPrompt(\n rules: Rule[],\n staged: StagedChanges,\n fileContexts: FileContext[],\n rulesContext?: string,\n): string {\n const parts: string[] = [];\n\n // ユーザー定義のコンテキスト(自由記述)\n if (rulesContext && rulesContext.trim().length > 0) {\n parts.push(\"## Project Context\\n\");\n parts.push(rulesContext);\n parts.push(\"\");\n }\n\n // ルール\n parts.push(\"## Checklist Rules\\n\");\n parts.push(formatRulesForPrompt(rules));\n\n // Diff\n parts.push(\"## Staged Changes (git diff)\\n\");\n parts.push(\"```diff\");\n parts.push(truncate(staged.diff, 50_000));\n parts.push(\"```\\n\");\n\n // ファイルコンテキスト\n if (fileContexts.length > 0) {\n parts.push(\"## Full File Context\\n\");\n parts.push(\"The following are the full contents of modified files for additional context:\\n\");\n for (const ctx of fileContexts) {\n const ext = ctx.path.split(\".\").pop() ?? \"\";\n parts.push(`### ${ctx.path}\\n`);\n parts.push(`\\`\\`\\`${ext}`);\n parts.push(truncate(ctx.content, 20_000));\n parts.push(\"```\\n\");\n }\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * LLMレスポンスのresultsにルール定義側のseverityを付与する。\n * ルールテキストのマッチングで紐付ける。\n */\nfunction mapSeverities(result: ReviewResult, rules: Rule[]): ReviewResult {\n const severityMap = new Map<string, Severity>();\n for (const rule of rules) {\n severityMap.set(rule.text.toLowerCase(), rule.severity);\n }\n\n return {\n ...result,\n results: result.results.map((r) => ({\n ...r,\n severity: severityMap.get(r.rule.toLowerCase()) ?? \"error\",\n })),\n };\n}\n\n/**\n * テキストを指定文字数で切り詰める。\n */\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength) + \"\\n\\n... (truncated)\";\n}\n","import chalk from \"chalk\";\nimport type { ReviewResult } from \"../llm/types.js\";\n\nexport interface ReportSummary {\n /** エラー(ブロック対象)の失敗数 */\n errorCount: number;\n /** 警告の失敗数 */\n warnCount: number;\n}\n\n/**\n * レビュー結果をターミナルに出力する。\n * @returns エラーと警告の失敗数\n */\nexport function report(result: ReviewResult, verbose: boolean): ReportSummary {\n const { results, summary } = result;\n\n console.log();\n console.log(chalk.bold.underline(\"Kanzaki Review Results\"));\n console.log();\n\n let errorCount = 0;\n let warnCount = 0;\n\n for (const r of results) {\n const isWarn = r.severity === \"warn\";\n const label = isWarn ? chalk.dim(\"[warn]\") : chalk.dim(\"[error]\");\n\n if (r.passed) {\n console.log(` ${chalk.green(\"✓\")} ${label} ${r.rule}`);\n if (verbose) {\n console.log(` ${chalk.dim(r.reason)}`);\n }\n } else {\n if (isWarn) {\n warnCount++;\n console.log(` ${chalk.yellow(\"⚠\")} ${label} ${r.rule}`);\n console.log(` ${chalk.yellow(\"→\")} ${r.reason}`);\n } else {\n errorCount++;\n console.log(` ${chalk.red(\"✗\")} ${label} ${r.rule}`);\n console.log(` ${chalk.red(\"→\")} ${r.reason}`);\n }\n }\n }\n\n // サマリー\n console.log();\n const total = results.length;\n const passedCount = total - errorCount - warnCount;\n\n if (errorCount === 0 && warnCount === 0) {\n console.log(chalk.green.bold(` All ${total} rules passed ✓`));\n } else {\n const parts: string[] = [];\n parts.push(`${passedCount}/${total} passed`);\n if (errorCount > 0) parts.push(chalk.red(`${errorCount} errors`));\n if (warnCount > 0) parts.push(chalk.yellow(`${warnCount} warnings`));\n console.log(` ${parts.join(\", \")}`);\n\n if (errorCount > 0) {\n console.log(chalk.red.bold(\"\\n Commit blocked due to errors.\"));\n } else {\n console.log(chalk.yellow(\"\\n Warnings found, but commit allowed.\"));\n }\n }\n\n if (summary) {\n console.log();\n console.log(chalk.dim(` ${summary}`));\n }\n\n console.log();\n\n return { errorCount, warnCount };\n}\n","import { mkdirSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ReviewResult, RuleResult } from \"../llm/types.js\";\nimport type { Rule } from \"./parser.js\";\nimport type { StagedChanges } from \"./git.js\";\n\n/**\n * レビュー結果からコーディングエージェント向けのフィードバックmarkdownを生成し、\n * `{outputDir}/{timestamp}.md` に書き出す。違反が1件もなければ何もしない。\n */\nexport function writeFeedbackFile(\n result: ReviewResult,\n rules: Rule[],\n staged: StagedChanges,\n outputDir: string,\n): string | null {\n const failures = result.results.filter((r) => !r.passed);\n if (failures.length === 0) return null;\n\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n const now = new Date();\n const filePath = join(outputDir, `${formatTimestamp(now)}.md`);\n const content = buildFeedbackMarkdown(failures, rules, staged, now, result.summary);\n writeFileSync(filePath, content, \"utf-8\");\n return filePath;\n}\n\nfunction buildFeedbackMarkdown(\n failures: RuleResult[],\n rules: Rule[],\n staged: StagedChanges,\n now: Date,\n summary: string,\n): string {\n const ruleMap = new Map<string, Rule>();\n for (const r of rules) {\n ruleMap.set(r.text.toLowerCase(), r);\n }\n\n const errorCount = failures.filter((f) => f.severity === \"error\").length;\n const warnCount = failures.filter((f) => f.severity === \"warn\").length;\n\n const lines: string[] = [];\n lines.push(`# Kanzaki Review Feedback`);\n lines.push(``);\n lines.push(`Generated: ${now.toISOString()}`);\n lines.push(``);\n lines.push(`## Summary`);\n lines.push(``);\n if (summary) {\n lines.push(summary);\n lines.push(``);\n }\n lines.push(`- エラー: ${errorCount} 件`);\n lines.push(`- 警告: ${warnCount} 件`);\n lines.push(`- 変更ファイル: ${staged.files.join(\", \")}`);\n lines.push(``);\n lines.push(`## Instructions for Coding Agents`);\n lines.push(``);\n lines.push(`以下は \\`kanzaki check\\` が検出したルール違反です。各違反について、指摘内容を読み、対象ファイルの該当箇所を修正してください。`);\n lines.push(`修正後は \\`git add\\` で再度ステージし、\\`kanzaki check\\` を実行して違反が解消されたことを確認してください。`);\n lines.push(`ルール定義そのものを変更することで違反を回避することは禁止されています(ルールの変更が必要な場合はユーザーに確認してください)。`);\n lines.push(``);\n lines.push(`## Violations`);\n lines.push(``);\n\n failures.forEach((f, idx) => {\n const rule = ruleMap.get(f.rule.toLowerCase());\n const tag = f.severity === \"warn\" ? \"[WARN]\" : \"[ERROR]\";\n lines.push(`### ${idx + 1}. ${tag} ${f.rule}`);\n lines.push(``);\n if (rule) {\n lines.push(`- **グループ**: ${rule.group}`);\n const scope = rule.filePatterns.length > 0 ? rule.filePatterns.join(\", \") : \"全ファイル\";\n lines.push(`- **適用スコープ**: ${scope}`);\n if (rule.lineNumber) {\n lines.push(`- **ルール定義**: rules.md:${rule.lineNumber}`);\n }\n }\n lines.push(`- **変更ファイル**: ${staged.files.join(\", \")}`);\n lines.push(``);\n lines.push(`**違反理由**:`);\n lines.push(``);\n const reason = f.reason.trim() || \"(no reason provided)\";\n for (const line of reason.split(\"\\n\")) {\n lines.push(`> ${line}`);\n }\n lines.push(``);\n });\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Windows互換のタイムスタンプ文字列を生成する(コロン不使用)。\n * 例: \"2026-04-16T14-30-45\"\n */\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number) => String(n).padStart(2, \"0\");\n const y = d.getFullYear();\n const mo = pad(d.getMonth() + 1);\n const da = pad(d.getDate());\n const h = pad(d.getHours());\n const mi = pad(d.getMinutes());\n const s = pad(d.getSeconds());\n return `${y}-${mo}-${da}T${h}-${mi}-${s}`;\n}\n","import { createCli } from \"./cli.js\";\n\nconst program = createCli();\nprogram.parse();\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,OAAOA,YAAW;AAClB,SAAS,cAAAC,aAAY,iBAAAC,gBAAe,gBAAAC,eAAc,aAAAC,kBAA4B;AAC9E,SAAS,WAAAC,UAAS,eAAe;AACjC,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,YAAAC,iBAAgB;;;ACNzB,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,WAAAC,gBAAe;AACxB,OAAO,YAAY;;;ACFnB,SAAS,YAAY,cAAc,eAAe,iBAAiB;AACnE,SAAS,eAAe;AACxB,SAAS,eAAe;AAuFxB,SAAS,oBAAoB;AAC7B,SAAS,aAAa,kBAAkB;AACxC,OAAO,UAAU;AAvFjB,IAAM,aAAa,QAAQ,QAAQ,GAAG,WAAW,SAAS;AAC1D,IAAM,mBAAmB,QAAQ,YAAY,kBAAkB;AAkBxD,SAAS,kBAA4C;AAC1D,MAAI,CAAC,WAAW,gBAAgB,EAAG,QAAO;AAE1C,MAAI;AACF,UAAM,MAAM,aAAa,kBAAkB,OAAO;AAClD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,gBAAgB,aAAsC;AACpE,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACA,gBAAc,kBAAkB,KAAK,UAAU,aAAa,MAAM,CAAC,GAAG,OAAO;AAC/E;AAKO,SAAS,mBAAyB;AACvC,MAAI,WAAW,gBAAgB,GAAG;AAChC,kBAAc,kBAAkB,MAAM,OAAO;AAAA,EAC/C;AACF;AAcO,SAAS,gBAAgB,OAAkC;AAEhE,MAAI,MAAM,cAAc;AACtB,WAAO;AAAA,EACT;AAGA,MAAI,MAAM,YAAY;AAEpB,QAAI,CAAC,MAAM,WAAW;AACpB,aAAO,MAAM;AAAA,IACf;AACA,UAAM,SAAS,IAAI,KAAK,MAAM,SAAS;AACvC,QAAI,SAAS,oBAAI,KAAK,GAAG;AACvB,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AAGA,SAAO,MAAM;AACf;AAQA,IAAM,kBAAkB;AACxB,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,eAAe;AAQrB,SAAS,eAAwD;AAC/D,QAAM,WAAW,YAAY,EAAE,EAAE,SAAS,WAAW;AACrD,QAAM,YAAY,WAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,WAAW;AAC1E,SAAO,EAAE,UAAU,UAAU;AAC/B;AAEA,eAAsB,qBAA6C;AACjE,QAAM,EAAE,UAAU,UAAU,IAAI,aAAa;AAC7C,QAAM,QAAQ,YAAY,EAAE,EAAE,SAAS,WAAW;AAElD,QAAM,QAAQ,mBAAmB,+EAA+E;AAChH,QAAM,UAAU,GAAG,eAAe,iCAAiC,gBAAgB,mBAAmB,SAAS,4CAA4C,mBAAmB,YAAY,CAAC,UAAU,KAAK,UAAU,KAAK;AAEzN,UAAQ,IAAI,uCAAuC;AACnD,UAAQ,IAAI,qEAAqE;AACjF,UAAQ,IAAI,OAAO;AAEnB,MAAI;AACF,UAAM,KAAK,OAAO;AAAA,EACpB,QAAQ;AAAA,EAER;AAEA,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,UAAM,SAAS,aAAa,OAAO,KAAK,QAAQ;AAC9C,UAAI;AACF,YAAI,CAAC,IAAI,KAAK,WAAW,gBAAgB,GAAG;AAC1C,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,WAAW;AACnB;AAAA,QACF;AAEA,cAAM,MAAM,IAAI,IAAI,IAAI,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AACzD,cAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAC1C,cAAM,YAAY,IAAI,aAAa,IAAI,mBAAmB;AAC1D,cAAM,OAAO,IAAI,aAAa,IAAI,MAAM;AACxC,cAAM,gBAAgB,IAAI,aAAa,IAAI,OAAO;AAElD,YAAI,OAAO;AACT,cAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,cAAI,IAAI,oCAAoC,KAAK,KAAK,aAAa,eAAe,MAAM;AACxF,iBAAO,MAAM;AACb,iBAAO,OAAO,IAAI,MAAM,gBAAgB,KAAK,MAAM,SAAS,EAAE,CAAC;AAAA,QACjE;AAEA,YAAI,CAAC,MAAM;AACT,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,4BAA4B;AACpC,iBAAO,MAAM;AACb,iBAAO,OAAO,IAAI,MAAM,gCAAgC,CAAC;AAAA,QAC3D;AAEA,YAAI,kBAAkB,OAAO;AAC1B,cAAI,UAAU,GAAG;AACjB,cAAI,IAAI,eAAe;AACvB,iBAAO,MAAM;AACb,iBAAO,OAAO,IAAI,MAAM,sBAAsB,CAAC;AAAA,QAClD;AAEA,YAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,YAAI,IAAI,+FAA+F;AAEvG,eAAO,MAAM;AAGb,cAAM,WAAW,MAAM,MAAM,kBAAkB;AAAA,UAC7C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,WAAW;AAAA,YACX,YAAY;AAAA,YACZ;AAAA,YACA,eAAe;AAAA,YACf,cAAc;AAAA,UAChB,CAAC;AAAA,QACH,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,iBAAO,OAAO,IAAI,MAAM,0BAA0B,SAAS,MAAM,IAAI,IAAI,EAAE,CAAC;AAAA,QAC9E;AAEA,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,QAAAA,SAAQ,SAAS;AAAA,MAEnB,SAAS,KAAK;AACX,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI,uBAAuB;AAC/B,eAAO,MAAM;AACb,eAAO,GAAG;AAAA,MACb;AAAA,IACF,CAAC;AAED,WAAO,OAAO,MAAM,WAAW;AAAA,EACjC,CAAC;AACH;;;ADrLA,IAAM,iBAAyC;AAAA,EAC7C,QAAQ;AAAA,EACR,WAAW;AACb;AAMO,SAAS,WAAW,YAAoC,CAAC,GAAkB;AAEhF,QAAM,UAAUC,SAAQ,QAAQ,IAAI,GAAG,MAAM;AAC7C,MAAIC,YAAW,OAAO,GAAG;AACvB,WAAO,OAAO,EAAE,MAAM,QAAQ,CAAC;AAAA,EACjC;AAGA,QAAM,SAAS,gBAAgB;AAE/B,QAAM,WACJ,UAAU,YACP,QAAQ,IAAI,oBACZ,QAAQ,YACR;AAIL,MAAI,SAAS,UAAU,UAAU,QAAQ,IAAI,mBAAmB;AAChE,MAAI,CAAC,UAAU,QAAQ;AACrB,aAAS,gBAAgB,MAAM;AAAA,EACjC;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,UAAU,SAAS,QAAQ,IAAI,iBAAiB,eAAe,QAAQ,KAAK;AAE1F,QAAM,YAAY,UAAU,aAAa,QAAQ,IAAI,sBAAsB;AAE3E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAWD,SAAQ,QAAQ,IAAI,GAAG,SAAS;AAAA,IAC3C,SAAS,UAAU,WAAW;AAAA,IAC9B,SAAS,UAAU,WAAW;AAAA,IAC9B,UAAU,CAAC,CAAE,QAAQ;AAAA,IACrB,cAAc,CAAC,CAAE,QAAQ;AAAA,EAC3B;AACF;;;AErEA,SAAS,gBAAAE,qBAAoB;AA+CtB,SAAS,eAAe,UAAmC;AAChE,QAAM,UAAUA,cAAa,UAAU,OAAO;AAC9C,SAAO,sBAAsB,OAAO;AACtC;AAEO,SAAS,sBAAsB,SAAkC;AACtE,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,QAAgB,CAAC;AACvB,QAAM,eAAyB,CAAC;AAChC,QAAM,SAAuB,CAAC;AAC9B,MAAI,eAAe;AACnB,MAAI,kBAA4B,CAAC;AAEjC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,aAAa,IAAI;AAGvB,UAAM,cAAc,QAAQ,MAAM,iBAAiB;AACnD,QAAI,aAAa;AACf,YAAM,aAAa,YAAY,CAAC,EAAE,KAAK;AACvC,YAAM,EAAE,MAAM,SAAS,IAAI,wBAAwB,UAAU;AAC7D,qBAAe;AACf,wBAAkB;AAGlB,UAAI,WAAW,SAAS,GAAG,KAAK,CAAC,WAAW,SAAS,GAAG,GAAG;AACzD,eAAO,KAAK,EAAE,MAAM,YAAY,SAAS,+CAA+C,UAAU,IAAI,CAAC;AAAA,MACzG;AAGA,YAAM,kBAAkB,WAAW,MAAM,SAAS;AAClD,UAAI,iBAAiB;AACnB,eAAO,KAAK,EAAE,MAAM,YAAY,SAAS,kCAAkC,UAAU,kDAAkD,CAAC;AAAA,MAC1I;AACA;AAAA,IACF;AAGA,UAAM,iBAAiB,QAAQ,MAAM,oBAAoB;AACzD,QAAI,gBAAgB;AAClB,aAAO,KAAK,EAAE,MAAM,YAAY,SAAS,0CAA0C,CAAC;AACpF;AAAA,IACF;AAGA,UAAM,YAAY,QAAQ,MAAM,wBAAwB;AACxD,QAAI,WAAW;AACb,YAAM,UAAU,UAAU,CAAC,EAAE,KAAK;AAGlC,YAAM,kBAAkB,QAAQ,MAAM,wCAAwC;AAC9E,UAAI,iBAAiB;AACnB,eAAO,KAAK,EAAE,MAAM,YAAY,SAAS,yBAAyB,gBAAgB,CAAC,CAAC,0BAA0B,CAAC;AAAA,MACjH;AAEA,YAAM,EAAE,UAAU,KAAK,IAAI,cAAc,OAAO;AAGhD,UAAI,KAAK,WAAW,GAAG;AACrB,eAAO,KAAK,EAAE,MAAM,YAAY,SAAS,0EAA0E,CAAC;AACpH;AAAA,MACF;AAEA,YAAM,KAAK;AAAA,QACT,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAGA,QAAI,QAAQ,SAAS,GAAG;AAEtB,UAAI,QAAQ,MAAM,oBAAoB,GAAG;AACvC,eAAO,KAAK,EAAE,MAAM,YAAY,SAAS,0DAA0D,OAAO,IAAI,CAAC;AAAA,MACjH;AAGA,mBAAa,KAAK,OAAO;AAAA,IAC3B;AAAA,EACF;AAGA,QAAM,OAAO,oBAAI,IAAoB;AACrC,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,GAAG,KAAK,KAAK,KAAS,KAAK,KAAK,YAAY,CAAC;AACzD,UAAM,YAAY,KAAK,IAAI,GAAG;AAC9B,QAAI,cAAc,QAAW;AAC3B,aAAO,KAAK;AAAA,QACV,MAAM,KAAK,cAAc;AAAA,QACzB,SAAS,4BAA4B,KAAK,KAAK,4BAA4B,SAAS,OAAO,KAAK,IAAI;AAAA,MACtG,CAAC;AAAA,IACH,OAAO;AACL,WAAK,IAAI,KAAK,KAAK,cAAc,CAAC;AAAA,IACpC;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS,aAAa,KAAK,IAAI;AAAA,IAC/B;AAAA,EACF;AACF;AAMA,SAAS,wBAAwB,QAAsD;AACrF,QAAM,QAAQ,OAAO,MAAM,0BAA0B;AACrD,MAAI,OAAO;AACT,UAAM,OAAO,MAAM,CAAC,EAAE,KAAK;AAC3B,UAAM,WAAW,MAAM,CAAC,EACrB,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AACjB,WAAO,EAAE,MAAM,SAAS;AAAA,EAC1B;AAEA,SAAO,EAAE,MAAM,QAAQ,UAAU,CAAC,EAAE;AACtC;AAMA,SAAS,cAAc,SAAuD;AAC5E,QAAM,YAAY,QAAQ,MAAM,iBAAiB;AACjD,MAAI,WAAW;AACb,WAAO,EAAE,UAAU,QAAQ,MAAM,UAAU,CAAC,EAAE,KAAK,EAAE;AAAA,EACvD;AAEA,QAAM,aAAa,QAAQ,MAAM,kBAAkB;AACnD,MAAI,YAAY;AACd,WAAO,EAAE,UAAU,SAAS,MAAM,WAAW,CAAC,EAAE,KAAK,EAAE;AAAA,EACzD;AAEA,SAAO,EAAE,UAAU,SAAS,MAAM,QAAQ;AAC5C;AAKO,SAAS,qBAAqB,OAAuB;AAC1D,QAAM,UAAU,oBAAI,IAAoB;AAExC,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,QAAQ,IAAI,KAAK,KAAK,KAAK,CAAC;AAC1C,UAAM,KAAK,IAAI;AACf,YAAQ,IAAI,KAAK,OAAO,KAAK;AAAA,EAC/B;AAEA,QAAM,WAAqB,CAAC;AAC5B,aAAW,CAAC,OAAO,UAAU,KAAK,SAAS;AACzC,UAAM,QAAQ,WAAW,CAAC,GAAG,aAAa,SAAS,IAC/C,iBAAiB,WAAW,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC,MACtD;AACJ,aAAS,KAAK,OAAO,KAAK,GAAG,KAAK,EAAE;AACpC,aAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,YAAM,IAAI,WAAW,CAAC;AACtB,YAAM,MAAM,EAAE,aAAa,SAAS,cAAc;AAClD,eAAS,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE;AAAA,IAC5C;AACA,aAAS,KAAK,EAAE;AAAA,EAClB;AAEA,SAAO,SAAS,KAAK,IAAI;AAC3B;AAMO,SAAS,mBAAmB,OAAe,cAAgC;AAChF,SAAO,MAAM,OAAO,CAAC,SAAS;AAE5B,QAAI,KAAK,aAAa,WAAW,EAAG,QAAO;AAG3C,WAAO,aAAa;AAAA,MAAK,CAAC,SACxB,KAAK,aAAa,KAAK,CAAC,YAAY,UAAU,MAAM,OAAO,CAAC;AAAA,IAC9D;AAAA,EACF,CAAC;AACH;AAMA,SAAS,UAAU,UAAkB,SAA0B;AAE7D,QAAM,WAAW,QACd,QAAQ,OAAO,KAAK,EACpB,QAAQ,SAAS,iBAAiB,EAClC,QAAQ,OAAO,OAAO,EACtB,QAAQ,oBAAoB,IAAI,EAChC,QAAQ,OAAO,MAAM;AAExB,QAAM,QAAQ,IAAI,OAAO,QAAQ,QAAQ,KAAK,GAAG;AACjD,SAAO,MAAM,KAAK,QAAQ;AAC5B;;;AC5PA,SAAS,gBAAgB;AACzB,SAAS,gBAAAC,eAAc,cAAAC,mBAAkB;AACzC,SAAS,WAAAC,gBAAe;AAmBjB,SAAS,mBAAkC;AAChD,QAAM,OAAO,KAAK,mBAAmB;AACrC,QAAM,WAAW,KAAK,+BAA+B;AACrD,QAAM,QAAQ,SACX,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AAEjB,SAAO,EAAE,MAAM,MAAM;AACvB;AAMO,SAAS,gBAAgB,OAAgC;AAC9D,QAAM,WAAW,YAAY;AAC7B,QAAM,WAA0B,CAAC;AAEjC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAUA,SAAQ,UAAU,IAAI;AACtC,QAAI,CAACD,YAAW,OAAO,EAAG;AAG1B,QAAI,aAAa,IAAI,EAAG;AAExB,QAAI;AACF,YAAM,UAAUD,cAAa,SAAS,OAAO;AAE7C,UAAI,QAAQ,SAAS,IAAS;AAC9B,eAAS,KAAK,EAAE,MAAM,MAAM,QAAQ,CAAC;AAAA,IACvC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,cAAsB;AACpC,SAAO,KAAK,+BAA+B,EAAE,KAAK;AACpD;AAKO,SAAS,mBAA4B;AAC1C,QAAM,SAAS,KAAK,+BAA+B;AACnD,SAAO,OAAO,KAAK,EAAE,SAAS;AAChC;AAEA,SAAS,KAAK,SAAyB;AACrC,MAAI;AACF,WAAO,SAAS,SAAS,EAAE,UAAU,SAAS,OAAO,CAAC,QAAQ,QAAQ,MAAM,EAAE,CAAC;AAAA,EACjF,SAAS,OAAO;AACd,UAAM,MAAM;AACZ,UAAM,IAAI,MAAM,uBAAuB,OAAO;AAAA,EAAK,IAAI,UAAU,IAAI,OAAO,EAAE;AAAA,EAChF;AACF;AAEA,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAC1D;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EACzB;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAChB;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAQ;AAAA,EAC/B;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EACjC;AAAA,EAAS;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAQ;AAAA,EACnC;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EACvB;AACF,CAAC;AAED,SAAS,aAAa,UAA2B;AAC/C,QAAM,MAAM,SAAS,MAAM,SAAS,YAAY,GAAG,CAAC,EAAE,YAAY;AAClE,SAAO,kBAAkB,IAAI,GAAG;AAClC;;;AClGA,OAAO,YAAY;AAInB,IAAM,mBAAmB;AAElB,IAAM,iBAAN,MAA4C;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAgB,OAAe,WAAW,OAAO;AAC3D,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,OAAO,cAAsB,YAA2C;AAC5E,QAAI,KAAK,UAAU;AACjB,aAAO,KAAK,uBAAuB,cAAc,UAAU;AAAA,IAC7D;AACA,WAAO,KAAK,0BAA0B,cAAc,UAAU;AAAA,EAChE;AAAA,EAEA,MAAc,0BAA0B,cAAsB,YAA2C;AACvG,UAAM,SAAS,IAAI,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;AACjD,UAAM,WAAW,MAAM,OAAO,KAAK,YAAY,OAAO;AAAA,MACpD,OAAO,KAAK;AAAA,MACZ,UAAU;AAAA,QACR,EAAE,MAAM,UAAU,SAAS,aAAa;AAAA,QACxC,EAAE,MAAM,QAAQ,SAAS,WAAW;AAAA,MACtC;AAAA,MACA,iBAAiB,EAAE,MAAM,cAAc;AAAA,MACvC,aAAa;AAAA,IACf,CAAC;AAED,UAAM,UAAU,SAAS,QAAQ,CAAC,GAAG,SAAS;AAC9C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AAEA,WAAO,oBAAoB,OAAO;AAAA,EACpC;AAAA,EAEA,MAAc,uBAAuB,cAAsB,YAA2C;AACpG,UAAM,MAAM,GAAG,gBAAgB;AAC/B,UAAM,OAAO;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,cAAc;AAAA,MACd,OAAO;AAAA,QACL,EAAE,MAAM,QAAQ,SAAS,WAAW;AAAA,MACtC;AAAA,MACA,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB,UAAU,KAAK,MAAM;AAAA,MACxC;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,YAAY,MAAM,IAAI,KAAK;AACjC,YAAM,IAAI,MAAM,GAAG,IAAI,MAAM,IAAI,SAAS,EAAE;AAAA,IAC9C;AAGA,UAAM,UAAU,MAAM,KAAK,cAAc,GAAG;AAE5C,WAAO,oBAAoB,OAAO;AAAA,EACpC;AAAA,EAEA,MAAc,cAAc,KAAgC;AAC1D,UAAM,SAAS,IAAI,MAAM,UAAU;AACnC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AAEA,UAAM,UAAU,IAAI,YAAY;AAChC,QAAI,UAAU;AACd,QAAI,SAAS;AAEb,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AAEV,gBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD,YAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,eAAS,MAAM,IAAI,KAAK;AAExB,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,WAAW,QAAQ,EAAG;AAChC,cAAM,OAAO,KAAK,MAAM,CAAC,EAAE,KAAK;AAChC,YAAI,SAAS,SAAU;AAEvB,YAAI;AACF,gBAAM,QAAQ,KAAK,MAAM,IAAI;AAE7B,cAAI,MAAM,SAAS,gCAAgC,MAAM,OAAO;AAC9D,uBAAW,MAAM;AAAA,UACnB;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,oBAAoB,KAA2B;AACtD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAE7B,QAAI,CAAC,MAAM,QAAQ,OAAO,OAAO,GAAG;AAClC,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,WAAO;AAAA,MACL,SAAS,OAAO,QAAQ,IAAI,CAAC,OAAgC;AAAA,QAC3D,MAAM,OAAO,EAAE,QAAQ,EAAE;AAAA,QACzB,QAAQ,QAAQ,EAAE,MAAM;AAAA,QACxB,QAAQ,OAAO,EAAE,UAAU,EAAE;AAAA,MAC/B,EAAE;AAAA,MACF,SAAS,OAAO,OAAO,WAAW,EAAE;AAAA,IACtC;AAAA,EACF,SAAS,OAAO;AACd,UAAM,IAAI,MAAM;AAAA,EAA0C,GAAG;AAAA;AAAA,EAAO,KAAK,EAAE;AAAA,EAC7E;AACF;;;AC1IA,OAAO,eAAe;AAGf,IAAM,oBAAN,MAA+C;AAAA,EAC5C;AAAA,EACA;AAAA,EAER,YAAY,QAAgB,OAAe;AACzC,SAAK,SAAS,IAAI,UAAU,EAAE,OAAO,CAAC;AACtC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAM,OAAO,cAAsB,YAA2C;AAC5E,UAAM,WAAW,MAAM,KAAK,OAAO,SAAS,OAAO;AAAA,MACjD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,WAAW,CAAC;AAAA,IAClD,CAAC;AAED,UAAM,YAAY,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAChE,QAAI,CAAC,aAAa,UAAU,SAAS,QAAQ;AAC3C,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAEA,WAAOG,qBAAoB,UAAU,IAAI;AAAA,EAC3C;AACF;AAEA,SAASA,qBAAoB,KAA2B;AAEtD,QAAM,YAAY,IAAI,MAAM,8BAA8B;AAC1D,QAAM,UAAU,YAAY,UAAU,CAAC,EAAE,KAAK,IAAI,IAAI,KAAK;AAE3D,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AAEjC,QAAI,CAAC,MAAM,QAAQ,OAAO,OAAO,GAAG;AAClC,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,WAAO;AAAA,MACL,SAAS,OAAO,QAAQ,IAAI,CAAC,OAAgC;AAAA,QAC3D,MAAM,OAAO,EAAE,QAAQ,EAAE;AAAA,QACzB,QAAQ,QAAQ,EAAE,MAAM;AAAA,QACxB,QAAQ,OAAO,EAAE,UAAU,EAAE;AAAA,MAC/B,EAAE;AAAA,MACF,SAAS,OAAO,OAAO,WAAW,EAAE;AAAA,IACtC;AAAA,EACF,SAAS,OAAO;AACd,UAAM,IAAI,MAAM;AAAA,EAA0C,GAAG;AAAA;AAAA,EAAO,KAAK,EAAE;AAAA,EAC7E;AACF;;;ACpDA,SAAS,aAAa;AAOf,IAAM,oBAAN,MAA+C;AAAA,EACpD,MAAM,OAAO,cAAsB,YAA2C;AAE5E,UAAM,iBAAiB,GAAG,YAAY;AAAA;AAAA;AAAA;AAAA,EAAc,UAAU;AAE9D,UAAM,SAAS,MAAM,KAAK,aAAa,cAAc;AACrD,WAAOC,qBAAoB,MAAM;AAAA,EACnC;AAAA,EAEQ,aAAa,QAAiC;AACpD,WAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AAKtC,YAAM,QAAQ,QAAQ,aAAa;AACnC,YAAM,QAAQ,QACV,MAAM,WAAW,CAAC,MAAM,MAAM,MAAM,WAAW,GAAG;AAAA,QAChD,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,QAC9B,0BAA0B;AAAA,MAC5B,CAAC,IACD,MAAM,UAAU,CAAC,IAAI,GAAG;AAAA,QACtB,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAChC,CAAC;AAEL,UAAI,SAAS;AACb,UAAI,SAAS;AAEb,YAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACzC,kBAAU,MAAM,SAAS,OAAO;AAAA,MAClC,CAAC;AAED,YAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACzC,kBAAU,MAAM,SAAS,OAAO;AAAA,MAClC,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,eAAO,IAAI,MAAM,+BAA+B,IAAI,OAAO,EAAE,CAAC;AAAA,MAChE,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,YAAI,SAAS,GAAG;AACd,iBAAO,IAAI,MAAM,+BAA+B,IAAI,KAAK,UAAU,MAAM,EAAE,CAAC;AAC5E;AAAA,QACF;AACA,QAAAA,SAAQ,MAAM;AAAA,MAChB,CAAC;AAGD,YAAM,MAAM,MAAM,MAAM;AACxB,YAAM,MAAM,IAAI;AAAA,IAClB,CAAC;AAAA,EACH;AACF;AAEA,SAASD,qBAAoB,KAA2B;AAEtD,QAAM,YAAY,IAAI,MAAM,8BAA8B;AAC1D,QAAM,UAAU,YAAY,UAAU,CAAC,EAAE,KAAK,IAAI,IAAI,KAAK;AAE3D,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AAEjC,QAAI,CAAC,MAAM,QAAQ,OAAO,OAAO,GAAG;AAClC,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,WAAO;AAAA,MACL,SAAS,OAAO,QAAQ,IAAI,CAAC,OAAgC;AAAA,QAC3D,MAAM,OAAO,EAAE,QAAQ,EAAE;AAAA,QACzB,QAAQ,QAAQ,EAAE,MAAM;AAAA,QACxB,QAAQ,OAAO,EAAE,UAAU,EAAE;AAAA,MAC/B,EAAE;AAAA,MACF,SAAS,OAAO,OAAO,WAAW,EAAE;AAAA,IACtC;AAAA,EACF,SAAS,OAAO;AACd,UAAM,IAAI,MAAM;AAAA,EAAiD,GAAG;AAAA;AAAA,EAAO,KAAK,EAAE;AAAA,EACpF;AACF;;;AC5EA,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBtB,eAAsB,OACpB,QACA,OACA,QACA,cACA,cACuB;AACvB,QAAM,WAAW,eAAe,MAAM;AACtC,QAAM,aAAa,gBAAgB,OAAO,QAAQ,cAAc,YAAY;AAE5E,QAAM,YAAY,MAAM,SAAS,OAAO,eAAe,UAAU;AAGjE,SAAO,cAAc,WAAW,KAAK;AACvC;AAEA,SAAS,eAAe,QAAoC;AAC1D,UAAQ,OAAO,UAAU;AAAA,IACvB,KAAK;AACH,aAAO,IAAI,eAAe,OAAO,QAAQ,OAAO,OAAO,OAAO,QAAQ;AAAA,IACxE,KAAK;AACH,UAAI,OAAO,cAAc;AACvB,eAAO,IAAI,kBAAkB;AAAA,MAC/B;AACA,aAAO,IAAI,kBAAkB,OAAO,QAAQ,OAAO,KAAK;AAAA,IAC1D;AACE,YAAM,IAAI,MAAM,qBAAqB,OAAO,QAAQ,EAAE;AAAA,EAC1D;AACF;AAEA,SAAS,gBACP,OACA,QACA,cACA,cACQ;AACR,QAAM,QAAkB,CAAC;AAGzB,MAAI,gBAAgB,aAAa,KAAK,EAAE,SAAS,GAAG;AAClD,UAAM,KAAK,sBAAsB;AACjC,UAAM,KAAK,YAAY;AACvB,UAAM,KAAK,EAAE;AAAA,EACf;AAGA,QAAM,KAAK,sBAAsB;AACjC,QAAM,KAAK,qBAAqB,KAAK,CAAC;AAGtC,QAAM,KAAK,gCAAgC;AAC3C,QAAM,KAAK,SAAS;AACpB,QAAM,KAAK,SAAS,OAAO,MAAM,GAAM,CAAC;AACxC,QAAM,KAAK,OAAO;AAGlB,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,KAAK,wBAAwB;AACnC,UAAM,KAAK,iFAAiF;AAC5F,eAAW,OAAO,cAAc;AAC9B,YAAM,MAAM,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AACzC,YAAM,KAAK,OAAO,IAAI,IAAI;AAAA,CAAI;AAC9B,YAAM,KAAK,SAAS,GAAG,EAAE;AACzB,YAAM,KAAK,SAAS,IAAI,SAAS,GAAM,CAAC;AACxC,YAAM,KAAK,OAAO;AAAA,IACpB;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAMA,SAAS,cAAc,QAAsB,OAA6B;AACxE,QAAM,cAAc,oBAAI,IAAsB;AAC9C,aAAW,QAAQ,OAAO;AACxB,gBAAY,IAAI,KAAK,KAAK,YAAY,GAAG,KAAK,QAAQ;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,OAAO,QAAQ,IAAI,CAAC,OAAO;AAAA,MAClC,GAAG;AAAA,MACH,UAAU,YAAY,IAAI,EAAE,KAAK,YAAY,CAAC,KAAK;AAAA,IACrD,EAAE;AAAA,EACJ;AACF;AAKA,SAAS,SAAS,MAAc,WAA2B;AACzD,MAAI,KAAK,UAAU,UAAW,QAAO;AACrC,SAAO,KAAK,MAAM,GAAG,SAAS,IAAI;AACpC;;;AClIA,OAAO,WAAW;AAcX,SAAS,OAAO,QAAsB,SAAiC;AAC5E,QAAM,EAAE,SAAS,QAAQ,IAAI;AAE7B,UAAQ,IAAI;AACZ,UAAQ,IAAI,MAAM,KAAK,UAAU,wBAAwB,CAAC;AAC1D,UAAQ,IAAI;AAEZ,MAAI,aAAa;AACjB,MAAI,YAAY;AAEhB,aAAW,KAAK,SAAS;AACvB,UAAM,SAAS,EAAE,aAAa;AAC9B,UAAM,QAAQ,SAAS,MAAM,IAAI,QAAQ,IAAI,MAAM,IAAI,SAAS;AAEhE,QAAI,EAAE,QAAQ;AACZ,cAAQ,IAAI,KAAK,MAAM,MAAM,QAAG,CAAC,IAAI,KAAK,IAAI,EAAE,IAAI,EAAE;AACtD,UAAI,SAAS;AACX,gBAAQ,IAAI,OAAO,MAAM,IAAI,EAAE,MAAM,CAAC,EAAE;AAAA,MAC1C;AAAA,IACF,OAAO;AACL,UAAI,QAAQ;AACV;AACA,gBAAQ,IAAI,KAAK,MAAM,OAAO,QAAG,CAAC,IAAI,KAAK,IAAI,EAAE,IAAI,EAAE;AACvD,gBAAQ,IAAI,OAAO,MAAM,OAAO,QAAG,CAAC,IAAI,EAAE,MAAM,EAAE;AAAA,MACpD,OAAO;AACL;AACA,gBAAQ,IAAI,KAAK,MAAM,IAAI,QAAG,CAAC,IAAI,KAAK,IAAI,EAAE,IAAI,EAAE;AACpD,gBAAQ,IAAI,OAAO,MAAM,IAAI,QAAG,CAAC,IAAI,EAAE,MAAM,EAAE;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,IAAI;AACZ,QAAM,QAAQ,QAAQ;AACtB,QAAM,cAAc,QAAQ,aAAa;AAEzC,MAAI,eAAe,KAAK,cAAc,GAAG;AACvC,YAAQ,IAAI,MAAM,MAAM,KAAK,SAAS,KAAK,sBAAiB,CAAC;AAAA,EAC/D,OAAO;AACL,UAAM,QAAkB,CAAC;AACzB,UAAM,KAAK,GAAG,WAAW,IAAI,KAAK,SAAS;AAC3C,QAAI,aAAa,EAAG,OAAM,KAAK,MAAM,IAAI,GAAG,UAAU,SAAS,CAAC;AAChE,QAAI,YAAY,EAAG,OAAM,KAAK,MAAM,OAAO,GAAG,SAAS,WAAW,CAAC;AACnE,YAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE;AAEnC,QAAI,aAAa,GAAG;AAClB,cAAQ,IAAI,MAAM,IAAI,KAAK,mCAAmC,CAAC;AAAA,IACjE,OAAO;AACL,cAAQ,IAAI,MAAM,OAAO,yCAAyC,CAAC;AAAA,IACrE;AAAA,EACF;AAEA,MAAI,SAAS;AACX,YAAQ,IAAI;AACZ,YAAQ,IAAI,MAAM,IAAI,KAAK,OAAO,EAAE,CAAC;AAAA,EACvC;AAEA,UAAQ,IAAI;AAEZ,SAAO,EAAE,YAAY,UAAU;AACjC;;;AC3EA,SAAS,aAAAE,YAAW,iBAAAC,gBAAe,cAAAC,mBAAkB;AACrD,SAAS,YAAY;AASd,SAAS,kBACd,QACA,OACA,QACA,WACe;AACf,QAAM,WAAW,OAAO,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM;AACvD,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,MAAI,CAACA,YAAW,SAAS,GAAG;AAC1B,IAAAF,WAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAC1C;AAEA,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,WAAW,KAAK,WAAW,GAAG,gBAAgB,GAAG,CAAC,KAAK;AAC7D,QAAM,UAAU,sBAAsB,UAAU,OAAO,QAAQ,KAAK,OAAO,OAAO;AAClF,EAAAC,eAAc,UAAU,SAAS,OAAO;AACxC,SAAO;AACT;AAEA,SAAS,sBACP,UACA,OACA,QACA,KACA,SACQ;AACR,QAAM,UAAU,oBAAI,IAAkB;AACtC,aAAW,KAAK,OAAO;AACrB,YAAQ,IAAI,EAAE,KAAK,YAAY,GAAG,CAAC;AAAA,EACrC;AAEA,QAAM,aAAa,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AAClE,QAAM,YAAY,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAEhE,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,2BAA2B;AACtC,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,cAAc,IAAI,YAAY,CAAC,EAAE;AAC5C,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,YAAY;AACvB,QAAM,KAAK,EAAE;AACb,MAAI,SAAS;AACX,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,EAAE;AAAA,EACf;AACA,QAAM,KAAK,yBAAU,UAAU,SAAI;AACnC,QAAM,KAAK,mBAAS,SAAS,SAAI;AACjC,QAAM,KAAK,2CAAa,OAAO,MAAM,KAAK,IAAI,CAAC,EAAE;AACjD,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,mCAAmC;AAC9C,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,mVAA0E;AACrF,QAAM,KAAK,qQAAuE;AAClF,QAAM,KAAK,kYAAkE;AAC7E,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,eAAe;AAC1B,QAAM,KAAK,EAAE;AAEb,WAAS,QAAQ,CAAC,GAAG,QAAQ;AAC3B,UAAM,OAAO,QAAQ,IAAI,EAAE,KAAK,YAAY,CAAC;AAC7C,UAAM,MAAM,EAAE,aAAa,SAAS,WAAW;AAC/C,UAAM,KAAK,OAAO,MAAM,CAAC,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE;AAC7C,UAAM,KAAK,EAAE;AACb,QAAI,MAAM;AACR,YAAM,KAAK,mCAAe,KAAK,KAAK,EAAE;AACtC,YAAM,QAAQ,KAAK,aAAa,SAAS,IAAI,KAAK,aAAa,KAAK,IAAI,IAAI;AAC5E,YAAM,KAAK,+CAAiB,KAAK,EAAE;AACnC,UAAI,KAAK,YAAY;AACnB,cAAM,KAAK,kDAAyB,KAAK,UAAU,EAAE;AAAA,MACvD;AAAA,IACF;AACA,UAAM,KAAK,+CAAiB,OAAO,MAAM,KAAK,IAAI,CAAC,EAAE;AACrD,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,+BAAW;AACtB,UAAM,KAAK,EAAE;AACb,UAAM,SAAS,EAAE,OAAO,KAAK,KAAK;AAClC,eAAW,QAAQ,OAAO,MAAM,IAAI,GAAG;AACrC,YAAM,KAAK,KAAK,IAAI,EAAE;AAAA,IACxB;AACA,UAAM,KAAK,EAAE;AAAA,EACf,CAAC;AAED,SAAO,MAAM,KAAK,IAAI;AACxB;AAMA,SAAS,gBAAgB,GAAiB;AACxC,QAAM,MAAM,CAAC,MAAc,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AACpD,QAAM,IAAI,EAAE,YAAY;AACxB,QAAM,KAAK,IAAI,EAAE,SAAS,IAAI,CAAC;AAC/B,QAAM,KAAK,IAAI,EAAE,QAAQ,CAAC;AAC1B,QAAM,IAAI,IAAI,EAAE,SAAS,CAAC;AAC1B,QAAM,KAAK,IAAI,EAAE,WAAW,CAAC;AAC7B,QAAM,IAAI,IAAI,EAAE,WAAW,CAAC;AAC5B,SAAO,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;AACzC;;;AVvFA,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AAE7B,SAAS,YAAqB;AACnC,QAAME,WAAU,IAAI,QAAQ;AAE5B,EAAAA,SACG,KAAK,SAAS,EACd,YAAY,wCAAwC,EACpD,QAAQ,OAAO;AAGlB,EAAAA,SACG,QAAQ,MAAM,EACd,YAAY,qCAAqC,EACjD,OAAO,YAAY;AAClB,UAAM,MAAM,QAAQ,IAAI;AAGxB,UAAM,YAAYC,SAAQ,KAAK,YAAY,UAAU;AACrD,UAAM,WAAW,QAAQ,SAAS;AAElC,QAAI,CAACC,YAAW,QAAQ,GAAG;AACzB,MAAAC,WAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,IACzC;AAEA,QAAID,YAAW,SAAS,GAAG;AACzB,cAAQ,IAAIE,OAAM,OAAO,oDAA+C,CAAC;AAAA,IAC3E,OAAO;AACL,YAAM,WAAW,aAAa;AAC9B,MAAAC,eAAc,WAAW,UAAU,OAAO;AAC1C,cAAQ,IAAID,OAAM,MAAM,kCAA6B,CAAC;AAAA,IACxD;AAGA,UAAM,mBAAmBH,SAAQ,UAAU,YAAY;AACvD,QAAI,CAACC,YAAW,gBAAgB,GAAG;AACjC,MAAAG,eAAc,kBAAkB,cAAc,OAAO;AACrD,cAAQ,IAAID,OAAM,MAAM,oCAA+B,CAAC;AAAA,IAC1D;AAEA,YAAQ,IAAI;AACZ,YAAQ,IAAIA,OAAM,IAAI,wDAAwD,CAAC;AAC/E,YAAQ,IAAIA,OAAM,IAAI,4EAA4E,CAAC;AACnG,YAAQ,IAAIA,OAAM,IAAI,sCAAsC,CAAC;AAAA,EAC/D,CAAC;AAGH,EAAAJ,SACG,QAAQ,OAAO,EACf,YAAY,qCAAqC,EACjD,OAAO,6BAA6B,mCAAmC,EACvE,OAAO,uBAAuB,YAAY,EAC1C,OAAO,sBAAsB,sBAAsB,mBAAmB,EACtE,OAAO,mBAAmB,0CAA0C,EACpE,OAAO,cAAc,+BAA+B,EACpD,OAAO,uBAAuB,kEAAkE,EAChG,OAAO,iBAAiB,gBAAgB,EACxC,OAAO,OAAO,SAAS;AACtB,QAAI;AAEF,UAAI,CAAC,iBAAiB,GAAG;AACvB,gBAAQ,IAAII,OAAM,OAAO,6CAA6C,CAAC;AACvE,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAGA,YAAM,SAAS,WAAW;AAAA,QACxB,UAAU,KAAK;AAAA,QACf,QAAQ,KAAK;AAAA,QACb,OAAO,KAAK;AAAA,QACZ,WAAW,KAAK;AAAA,QAChB,SAAS,KAAK,WAAW;AAAA,QACzB,SAAS,KAAK,YAAY;AAAA;AAAA,MAC5B,CAAC;AAGD,UAAI,CAACF,YAAW,OAAO,SAAS,GAAG;AACjC,gBAAQ,MAAME,OAAM,IAAI,yBAAyB,OAAO,SAAS,EAAE,CAAC;AACpE,gBAAQ,MAAMA,OAAM,IAAI,mCAAmC,CAAC;AAC5D,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAGA,YAAM,EAAE,OAAO,SAAS,cAAc,QAAQ,YAAY,IAAI,eAAe,OAAO,SAAS;AAE7F,UAAI,eAAe,YAAY,SAAS,GAAG;AACzC,gBAAQ,MAAMA,OAAM,IAAI,KAAK;AAAA,eAAa,YAAY,MAAM,2BAA2B,OAAO,SAAS,GAAG,CAAC;AAC3G,oBAAY,QAAQ,CAAC,QAAQ;AAC3B,kBAAQ,MAAMA,OAAM,OAAO,UAAU,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO;AAAA,QAClE,CAAC;AACD,gBAAQ,MAAMA,OAAM,IAAI,8CAA8C,CAAC;AACvE,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,UAAI,MAAM,WAAW,GAAG;AACtB,gBAAQ,IAAIA,OAAM,OAAO,gDAAgD,CAAC;AAC1E,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,YAAM,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,EAAE;AAC/D,YAAM,YAAY,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE;AAE7D,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAIA,OAAM,IAAI,aAAa,OAAO,QAAQ,KAAK,OAAO,KAAK,GAAG,CAAC;AACvE,gBAAQ,IAAIA,OAAM,IAAI,UAAU,UAAU,YAAY,SAAS,WAAW,CAAC;AAC3E,YAAI,cAAc;AAChB,kBAAQ,IAAIA,OAAM,IAAI,YAAY,aAAa,MAAM,8BAA8B,CAAC;AAAA,QACtF;AAAA,MACF;AAGA,YAAM,SAAS,iBAAiB;AAChC,YAAM,eAAe,gBAAgB,OAAO,KAAK;AAGjD,YAAM,kBAAkB,mBAAmB,OAAO,OAAO,KAAK;AAE9D,UAAI,gBAAgB,WAAW,GAAG;AAChC,gBAAQ,IAAIA,OAAM,OAAO,yDAAyD,CAAC;AACnF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAIA,OAAM,IAAI,kBAAkB,OAAO,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;AAClE,YAAI,gBAAgB,SAAS,MAAM,QAAQ;AACzC,kBAAQ,IAAIA,OAAM,IAAI,mBAAmB,gBAAgB,MAAM,IAAI,MAAM,MAAM,aAAa,CAAC;AAAA,QAC/F;AAAA,MACF;AAGA,cAAQ,IAAIA,OAAM,IAAI,+BAA+B,CAAC;AACtD,YAAM,SAAS,MAAM,OAAO,QAAQ,iBAAiB,QAAQ,cAAc,YAAY;AAGvF,YAAM,EAAE,WAAW,IAAI,OAAO,QAAQ,OAAO,OAAO;AAGpD,UAAI,KAAK,cAAc;AACrB,cAAM,WAAW,QAAQH,SAAQ,OAAO,SAAS,CAAC;AAClD,cAAM,aAAaA,SAAQ,UAAU,SAAS;AAC9C,cAAM,eAAe,kBAAkB,QAAQ,iBAAiB,QAAQ,UAAU;AAClF,YAAI,cAAc;AAChB,kBAAQ,IAAIG,OAAM,IAAI,8BAAyB,YAAY,EAAE,CAAC;AAAA,QAChE;AAAA,MACF;AAGA,UAAI,aAAa,KAAK,CAAC,OAAO,SAAS;AACrC,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAMA,OAAM,IAAI,UAAW,MAAgB,OAAO,EAAE,CAAC;AAC7D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAGH,QAAM,sBAAsB,CAAC,UAAU,WAAW;AAGlD,EAAAJ,SACG,QAAQ,OAAO,EACf,YAAY,4CAA4C,EACxD,OAAO,6BAA6B,oBAAoB,oBAAoB,KAAK,KAAK,CAAC,GAAG,EAC1F,OAAO,iBAAiB,mDAAmD,EAC3E,OAAO,gBAAgB,0CAA0C,EACjE,OAAO,OAAO,SAAS;AACtB,QAAI,KAAK,YAAY;AAInB,cAAQ,IAAII,OAAM,IAAI,2CAA2C,CAAC;AAClE,UAAI;AACF,cAAM,QAAQ,MAAM,mBAAmB;AAEvC,cAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,aAAa,GAAI,EAAE,YAAY;AAC7E,wBAAgB;AAAA,UACd,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,YAAY,MAAM;AAAA,UAClB,cAAc,MAAM;AAAA,UACpB;AAAA,QACF,CAAC;AAED,gBAAQ,IAAIA,OAAM,MAAM,kCAA6B,CAAC;AAAA,MACxD,SAAS,OAAO;AACd,gBAAQ,MAAMA,OAAM,IAAI,iBAAkB,MAAgB,OAAO,EAAE,CAAC;AACpE,gBAAQ,MAAMA,OAAM,IAAI,8CAA8C,CAAC;AACvE,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,WAAW,KAAK,WAAW;AAEzB,cAAQ,IAAIA,OAAM,IAAI,2CAA2C,CAAC;AAElE,UAAI;AACF,cAAM,UAAUE,UAAS,oBAAoB,EAAE,UAAU,SAAS,OAAO,CAAC,UAAU,QAAQ,MAAM,EAAE,CAAC,EAAE,KAAK;AAC5G,gBAAQ,IAAIF,OAAM,IAAI,UAAU,OAAO,EAAE,CAAC;AAAA,MAC5C,QAAQ;AACN,gBAAQ,MAAMA,OAAM,IAAI,uBAAuB,CAAC;AAChD,gBAAQ,MAAMA,OAAM,IAAI,2FAA2F,CAAC;AACpH,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,sBAAgB;AAAA,QACd,UAAU;AAAA,QACV,QAAQ;AAAA,QACR,cAAc;AAAA,MAChB,CAAC;AAED,cAAQ,IAAIA,OAAM,MAAM,6CAAwC,CAAC;AACjE,cAAQ,IAAIA,OAAM,IAAI,sFAAsF,CAAC;AAC7G,cAAQ,IAAIA,OAAM,IAAI,0DAA0D,CAAC;AAAA,IACnF,OAAO;AAEL,UAAI,CAAC,KAAK,UAAU;AAClB,gBAAQ,MAAMA,OAAM,IAAI,oCAAoC,CAAC;AAC7D,gBAAQ,MAAMA,OAAM,IAAI,aAAa,CAAC;AACtC,gBAAQ,MAAMA,OAAM,IAAI,+BAA+B,oBAAoB,KAAK,KAAK,CAAC,eAAe,CAAC;AACtG,gBAAQ,MAAMA,OAAM,IAAI,uEAAuE,CAAC;AAChG,gBAAQ,MAAMA,OAAM,IAAI,+EAA+E,CAAC;AACxG,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,UAAI,CAAC,oBAAoB,SAAS,KAAK,QAA6B,GAAG;AACrE,gBAAQ,MAAMA,OAAM,IAAI,qBAAqB,KAAK,QAAQ,EAAE,CAAC;AAC7D,gBAAQ,MAAMA,OAAM,IAAI,wBAAwB,oBAAoB,KAAK,IAAI,CAAC,EAAE,CAAC;AACjF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,YAAM,WAAW,KAAK;AACtB,YAAM,QAAQ,aAAa,WAAW,WAAW;AACjD,YAAM,MAAM,MAAM,aAAa,cAAc,KAAK,YAAY;AAE9D,UAAI,CAAC,KAAK;AACR,gBAAQ,MAAMA,OAAM,IAAI,sBAAsB,CAAC;AAC/C,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,sBAAgB,EAAE,UAAU,QAAQ,IAAI,CAAC;AACzC,cAAQ,IAAIA,OAAM,MAAM,gBAAW,QAAQ,cAAc,CAAC;AAC1D,cAAQ,IAAIA,OAAM,IAAI,0DAA0D,CAAC;AAAA,IACnF;AAAA,EACF,CAAC;AAGH,EAAAJ,SACG,QAAQ,QAAQ,EAChB,YAAY,0BAA0B,EACtC,OAAO,MAAM;AACZ,qBAAiB;AACjB,YAAQ,IAAII,OAAM,MAAM,4BAAuB,CAAC;AAAA,EAClD,CAAC;AAGH,EAAAJ,SACG,QAAQ,QAAQ,EAChB,YAAY,4BAA4B,EACxC,OAAO,MAAM;AACZ,UAAM,QAAQ,gBAAgB;AAC9B,QAAI,CAAC,SAAU,CAAC,MAAM,UAAU,CAAC,MAAM,cAAc,CAAC,MAAM,cAAe;AACzE,cAAQ,IAAII,OAAM,OAAO,oBAAoB,CAAC;AAC9C,cAAQ,IAAIA,OAAM,IAAI,sCAAsC,CAAC;AAC7D;AAAA,IACF;AAEA,YAAQ,IAAIA,OAAM,KAAK,gBAAgB,CAAC;AACxC,YAAQ,IAAI,eAAeA,OAAM,KAAK,MAAM,QAAQ,CAAC,EAAE;AAEvD,QAAI,MAAM,cAAc;AACtB,cAAQ,IAAI,WAAWA,OAAM,KAAK,uBAAuB,CAAC,IAAIA,OAAM,MAAM,UAAU,CAAC,EAAE;AAAA,IACzF,WAAW,MAAM,YAAY;AAC3B,YAAM,UAAU,MAAM,aAAa,IAAI,KAAK,MAAM,SAAS,IAAI,oBAAI,KAAK;AACxE,cAAQ,IAAI,WAAWA,OAAM,KAAK,OAAO,CAAC,GAAG,UAAUA,OAAM,IAAI,YAAY,IAAIA,OAAM,MAAM,WAAW,CAAC,EAAE;AAAA,IAC7G,OAAO;AACL,YAAM,SAAS,MAAM,OAAO,MAAM,GAAG,CAAC,IAAI,QAAQ,MAAM,OAAO,MAAM,EAAE;AACvE,cAAQ,IAAI,WAAWA,OAAM,KAAK,SAAS,CAAC,KAAK,MAAM,GAAG;AAAA,IAC5D;AAAA,EACF,CAAC;AAEH,SAAOJ;AACT;AAEA,SAAS,eAAuB;AAE9B,QAAM,eAAeC,SAAQ,WAAW,MAAM,aAAa,UAAU;AACrE,MAAIC,YAAW,YAAY,GAAG;AAC5B,WAAOK,cAAa,cAAc,OAAO;AAAA,EAC3C;AAGA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcT;AAKA,SAAS,aAAa,QAAiC;AACrD,SAAO,IAAI,QAAQ,CAACN,aAAY;AAC9B,UAAM,KAAK,gBAAgB;AAAA,MACzB,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAGD,YAAQ,OAAO,MAAM,MAAM;AAC3B,UAAM,QAAQ,QAAQ;AACtB,UAAM,SAAS,MAAM;AAErB,QAAI,MAAM,SAAS,MAAM,YAAY;AACnC,YAAM,WAAW,IAAI;AAAA,IACvB;AAEA,QAAI,QAAQ;AACZ,UAAM,SAAS,CAAC,SAAiB;AAC/B,YAAM,IAAI,KAAK,SAAS;AACxB,UAAI,MAAM,QAAQ,MAAM,MAAM;AAC5B,cAAM,eAAe,QAAQ,MAAM;AACnC,YAAI,MAAM,SAAS,MAAM,YAAY;AACnC,gBAAM,WAAW,UAAU,KAAK;AAAA,QAClC;AACA,gBAAQ,OAAO,MAAM,IAAI;AACzB,WAAG,MAAM;AACT,QAAAA,SAAQ,KAAK;AAAA,MACf,WAAW,MAAM,KAAQ;AAEvB,gBAAQ,KAAK,CAAC;AAAA,MAChB,WAAW,MAAM,UAAU,MAAM,MAAM;AAErC,gBAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,MAC3B,OAAO;AACL,iBAAS;AACT,gBAAQ,OAAO,MAAM,GAAG;AAAA,MAC1B;AAAA,IACF;AAEA,UAAM,GAAG,QAAQ,MAAM;AAAA,EACzB,CAAC;AACH;;;AWnXA,IAAM,UAAU,UAAU;AAC1B,QAAQ,MAAM;","names":["chalk","existsSync","writeFileSync","readFileSync","mkdirSync","resolve","execSync","existsSync","resolve","resolve","resolve","existsSync","readFileSync","readFileSync","existsSync","resolve","parseReviewResponse","parseReviewResponse","resolve","mkdirSync","writeFileSync","existsSync","program","resolve","existsSync","mkdirSync","chalk","writeFileSync","execSync","readFileSync"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/auth.ts","../src/core/parser.ts","../src/core/git.ts","../src/llm/openai.ts","../src/llm/anthropic.ts","../src/llm/claude-cli.ts","../src/core/reviewer.ts","../src/core/reporter.ts","../src/core/feedback.ts","../src/bin.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { existsSync, writeFileSync, readFileSync, mkdirSync, chmodSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createInterface } from \"node:readline\";\nimport { execSync, execFileSync } from \"node:child_process\";\n\nimport { loadConfig } from \"./config.js\";\nimport { parseRulesFile, filterRulesByFiles, type Rule } from \"./core/parser.js\";\nimport {\n getReviewSource,\n getFileContextsForSource,\n hasStagedChanges,\n type ReviewSourceKind,\n} from \"./core/git.js\";\nimport { review } from \"./core/reviewer.js\";\nimport { report } from \"./core/reporter.js\";\nimport { writeFeedbackFile } from \"./core/feedback.js\";\nimport {\n saveCredentials,\n clearCredentials,\n loadCredentials,\n hasCredentials,\n loginWithOAuthPKCE,\n} from \"./auth.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name(\"kanzaki\")\n .description(\"LLM-powered semantic pre-commit linter\")\n .version(\"0.3.0\");\n\n // ── init ──────────────────────────────────────────────\n program\n .command(\"init\")\n .description(\"Create .kanzaki/rules.md rules file\")\n .action(async () => {\n const cwd = process.cwd();\n\n // ルールファイル作成\n const rulesPath = resolve(cwd, \".kanzaki\", \"rules.md\");\n const rulesDir = dirname(rulesPath);\n \n if (!existsSync(rulesDir)) {\n mkdirSync(rulesDir, { recursive: true });\n }\n\n if (existsSync(rulesPath)) {\n console.log(chalk.yellow(\"⚠ .kanzaki/rules.md already exists, skipping.\"));\n } else {\n const template = loadTemplate();\n writeFileSync(rulesPath, template, \"utf-8\");\n console.log(chalk.green(\"✓ Created .kanzaki/rules.md\"));\n }\n\n // reviewsフォルダ(フィードバック出力先)はGit管理対象外にする\n const kanzakiGitignore = resolve(rulesDir, \".gitignore\");\n if (!existsSync(kanzakiGitignore)) {\n writeFileSync(kanzakiGitignore, \"reviews/\\n\", \"utf-8\");\n console.log(chalk.green(\"✓ Created .kanzaki/.gitignore\"));\n }\n\n console.log();\n console.log(chalk.dim(\"Edit .kanzaki/rules.md to customize your review rules.\"));\n console.log(chalk.dim(\"You can run 'kanzaki check' directly, or set it up with husky/lint-staged.\"));\n console.log(chalk.dim(\"Run 'kanzaki login' to authenticate.\"));\n });\n\n // ── check ─────────────────────────────────────────────\n program\n .command(\"check\")\n .description(\"Review changes against rules\")\n .option(\"-p, --provider <provider>\", \"LLM provider (openai / anthropic)\")\n .option(\"-m, --model <model>\", \"Model name\")\n .option(\"-r, --rules <path>\", \"Path to rules file\", \".kanzaki/rules.md\")\n .option(\"--api-key <key>\", \"API key (prefer KANZAKI_API_KEY env var)\")\n .option(\"--no-block\", \"Warn only, don't block commit\")\n .option(\"-o, --emit-feedback\", \"Write feedback markdown (for coding agents) to .kanzaki/reviews/\")\n .option(\"-v, --verbose\", \"Verbose output\")\n .option(\"--working-tree\", \"Review working tree changes against HEAD (staged + unstaged)\")\n .option(\"--range <range>\", \"Review diff for a revision range (e.g. main..HEAD)\")\n .option(\"--files <paths...>\", \"Review current state of the specified files (no diff)\")\n .action(async (opts) => {\n try {\n // 起点オプションの相互排他チェック\n const sourceFlagsUsed = [\n opts.workingTree ? \"--working-tree\" : null,\n opts.range ? \"--range\" : null,\n opts.files ? \"--files\" : null,\n ].filter((v): v is string => v !== null);\n\n if (sourceFlagsUsed.length > 1) {\n console.error(chalk.red(`Cannot combine ${sourceFlagsUsed.join(\" and \")}. Choose exactly one source.`));\n process.exit(1);\n }\n\n const sourceKind: ReviewSourceKind = opts.workingTree\n ? \"workingTree\"\n : opts.range\n ? \"range\"\n : opts.files\n ? \"files\"\n : \"staged\";\n\n // stagedモードのみ、早期終了チェック\n if (sourceKind === \"staged\" && !hasStagedChanges()) {\n console.log(chalk.yellow(\"No staged changes found. Nothing to review.\"));\n process.exit(0);\n }\n\n // 設定ロード\n const config = loadConfig({\n provider: opts.provider,\n apiKey: opts.apiKey,\n model: opts.model,\n rulesPath: opts.rules,\n verbose: opts.verbose ?? false,\n noBlock: opts.noBlock === false, // commander の --no-block は block=false にする\n });\n\n // ルールファイルの存在確認\n if (!existsSync(config.rulesPath)) {\n console.error(chalk.red(`Rules file not found: ${config.rulesPath}`));\n console.error(chalk.dim(\"Run 'kanzaki init' to create one.\"));\n process.exit(1);\n }\n\n // ルール解析\n const { rules, context: rulesContext, errors: parseErrors } = parseRulesFile(config.rulesPath);\n\n if (parseErrors && parseErrors.length > 0) {\n console.error(chalk.red.bold(`\\n❌ Found ${parseErrors.length} formatting error(s) in ${config.rulesPath}:`));\n parseErrors.forEach((err) => {\n console.error(chalk.yellow(` Line ${err.line}: `) + err.message);\n });\n console.error(chalk.dim(\"\\nPlease fix these errors before committing.\"));\n process.exit(1);\n }\n\n if (rules.length === 0) {\n console.log(chalk.yellow(\"No rules found in rules file. Skipping review.\"));\n process.exit(0);\n }\n\n const errorRules = rules.filter((r) => r.severity === \"error\").length;\n const warnRules = rules.filter((r) => r.severity === \"warn\").length;\n\n if (config.verbose) {\n console.log(chalk.dim(`Provider: ${config.provider} (${config.model})`));\n console.log(chalk.dim(`Rules: ${errorRules} errors, ${warnRules} warnings`));\n if (rulesContext) {\n console.log(chalk.dim(`Context: ${rulesContext.length} chars of additional context`));\n }\n }\n\n // 起点に応じてソース取得\n const source = getReviewSource({\n kind: sourceKind,\n range: opts.range,\n files: opts.files,\n });\n\n if (source.files.length === 0) {\n console.log(chalk.yellow(`No files to review (source: ${source.label}).`));\n process.exit(0);\n }\n\n // ファイルスコープでルールをフィルタリング\n const applicableRules = filterRulesByFiles(rules, source.files);\n\n if (applicableRules.length === 0) {\n console.log(chalk.yellow(\"No applicable rules for changed files. Skipping review.\"));\n process.exit(0);\n }\n\n // @state(globs) で指定された追加ファイルを収集\n const extraPaths = collectExtraStatePaths(applicableRules, source.files);\n const fileContexts = getFileContextsForSource(source, extraPaths);\n\n if (config.verbose) {\n console.log(chalk.dim(`Source: ${source.label}`));\n console.log(chalk.dim(`Files: ${source.files.join(\", \")}`));\n if (extraPaths.length > 0) {\n console.log(chalk.dim(`Extra state files: ${extraPaths.join(\", \")}`));\n }\n if (applicableRules.length < rules.length) {\n console.log(chalk.dim(`Rules filtered: ${applicableRules.length}/${rules.length} applicable`));\n }\n }\n\n // LLMレビュー\n console.log(chalk.dim(\"Reviewing changes with LLM...\"));\n const result = await review(config, applicableRules, source, fileContexts, rulesContext);\n\n // 結果表示\n const { errorCount } = report(result, config.verbose);\n\n // エージェント向けフィードバックの書き出し(オプトイン)\n if (opts.emitFeedback) {\n const rulesDir = dirname(resolve(config.rulesPath));\n const reviewsDir = resolve(rulesDir, \"reviews\");\n const feedbackPath = writeFeedbackFile(result, applicableRules, source, reviewsDir);\n if (feedbackPath) {\n console.log(chalk.dim(`→ Feedback written to ${feedbackPath}`));\n }\n }\n\n // errorのみブロック(warnはブロックしない)\n if (errorCount > 0 && !config.noBlock) {\n process.exit(1);\n }\n } catch (error) {\n console.error(chalk.red(`Error: ${(error as Error).message}`));\n process.exit(1);\n }\n });\n\n // ── login ─────────────────────────────────────────────\n const SUPPORTED_PROVIDERS = [\"openai\", \"anthropic\"] as const;\n type SupportedProvider = typeof SUPPORTED_PROVIDERS[number];\n\n program\n .command(\"login\")\n .description(\"Authenticate with a supported LLM provider\")\n .option(\"-p, --provider <provider>\", `Provider to use (${SUPPORTED_PROVIDERS.join(\" / \")})`)\n .option(\"--use-chatgpt\", \"Log in with ChatGPT Plus/Pro subscription (OAuth)\")\n .option(\"--use-claude\", \"Use the local Claude CLI as a subprocess\")\n .action(async (opts) => {\n if (opts.useChatgpt) {\n // OpenAI OAuth Flow\n\n\n console.log(chalk.dim(\"Starting OAuth Authorization Code Flow...\"));\n try {\n const token = await loginWithOAuthPKCE();\n\n const expiresAt = new Date(Date.now() + token.expires_in * 1000).toISOString();\n saveCredentials({\n provider: \"openai\",\n apiKey: \"\",\n oauthToken: token.access_token,\n refreshToken: token.refresh_token,\n expiresAt,\n });\n\n console.log(chalk.green(\"\\n✓ Authenticated via OAuth\"));\n } catch (error) {\n console.error(chalk.red(`OAuth failed: ${(error as Error).message}`));\n console.error(chalk.dim(\"Try 'kanzaki login' with an API key instead.\"));\n process.exit(1);\n }\n } else if (opts.useClaude) {\n // Claude CLI subprocess flow (OpenClaw style)\n console.log(chalk.dim(\"Checking local Claude CLI installation...\"));\n\n try {\n const version = execSync(\"claude --version\", { encoding: \"utf-8\", stdio: [\"ignore\", \"pipe\", \"pipe\"] }).trim();\n console.log(chalk.dim(`Found: ${version}`));\n } catch {\n console.error(chalk.red(\"Claude CLI not found.\"));\n console.error(chalk.dim(\"Install it from https://docs.claude.com/en/docs/claude-code and run 'claude login' first.\"));\n process.exit(1);\n }\n\n saveCredentials({\n provider: \"anthropic\",\n apiKey: \"\",\n useClaudeCli: true,\n });\n\n console.log(chalk.green(\"\\n✓ Configured to use local Claude CLI\"));\n console.log(chalk.dim(\"Kanzaki will invoke 'claude -p' for reviews, using your existing Claude CLI session.\"));\n console.log(chalk.dim(\"Credentials stored in ~/.config/kanzaki/credentials.json\"));\n } else {\n // API Key入力(--provider 必須)\n if (!opts.provider) {\n console.error(chalk.red(\"Authentication method is required.\"));\n console.error(chalk.dim(\"Use one of:\"));\n console.error(chalk.dim(` kanzaki login --provider <${SUPPORTED_PROVIDERS.join(\" | \")}> (API key)`));\n console.error(chalk.dim(\" kanzaki login --use-chatgpt (ChatGPT OAuth)\"));\n console.error(chalk.dim(\" kanzaki login --use-claude (Claude CLI subprocess)\"));\n process.exit(1);\n }\n\n if (!SUPPORTED_PROVIDERS.includes(opts.provider as SupportedProvider)) {\n console.error(chalk.red(`Unknown provider: ${opts.provider}`));\n console.error(chalk.dim(`Supported providers: ${SUPPORTED_PROVIDERS.join(\", \")}`));\n process.exit(1);\n }\n\n const provider = opts.provider as SupportedProvider;\n const label = provider === \"openai\" ? \"OpenAI\" : \"Anthropic\";\n const key = await promptSecret(`Enter your ${label} API key: `);\n\n if (!key) {\n console.error(chalk.red(\"No API key provided.\"));\n process.exit(1);\n }\n\n saveCredentials({ provider, apiKey: key });\n console.log(chalk.green(`✓ Saved ${provider} credentials`));\n console.log(chalk.dim(\"Credentials stored in ~/.config/kanzaki/credentials.json\"));\n }\n });\n\n // ── logout ────────────────────────────────────────────\n program\n .command(\"logout\")\n .description(\"Remove saved credentials\")\n .action(() => {\n clearCredentials();\n console.log(chalk.green(\"✓ Credentials removed\"));\n });\n\n // ── status ────────────────────────────────────────────\n program\n .command(\"status\")\n .description(\"Show authentication status\")\n .action(() => {\n const creds = loadCredentials();\n if (!creds || (!creds.apiKey && !creds.oauthToken && !creds.useClaudeCli)) {\n console.log(chalk.yellow(\"Not authenticated.\"));\n console.log(chalk.dim(\"Run 'kanzaki login' to authenticate.\"));\n return;\n }\n\n console.log(chalk.bold(\"Kanzaki Status\"));\n console.log(` Provider: ${chalk.cyan(creds.provider)}`);\n\n if (creds.useClaudeCli) {\n console.log(` Auth: ${chalk.cyan(\"Claude CLI subprocess\")} ${chalk.green(\"(active)\")}`);\n } else if (creds.oauthToken) {\n const expired = creds.expiresAt && new Date(creds.expiresAt) < new Date();\n console.log(` Auth: ${chalk.cyan(\"OAuth\")}${expired ? chalk.red(\" (expired)\") : chalk.green(\" (active)\")}`);\n } else {\n const masked = creds.apiKey.slice(0, 7) + \"...\" + creds.apiKey.slice(-4);\n console.log(` Auth: ${chalk.cyan(\"API Key\")} (${masked})`);\n }\n });\n\n return program;\n}\n\nfunction loadTemplate(): string {\n // まずパッケージ内のtemplateを試す\n const templatePath = resolve(__dirname, \"..\", \"templates\", \"rules.md\");\n if (existsSync(templatePath)) {\n return readFileSync(templatePath, \"utf-8\");\n }\n\n // フォールバック: デフォルトテンプレート\n return `# Kanzaki レビュールール\n\n## 品質\n- [ ] !error 変更内容がプロジェクトの既存のスタイルや規約と一貫していること\n- [ ] !error プレースホルダーやTODOが残っていないこと\n- [ ] !warn 内容が明確・簡潔で、不要な繰り返しがないこと\n\n## 正確性\n- [ ] !error 事実誤認や誤解を招く情報が含まれていないこと\n- [ ] !warn 適切な箇所に出典・参考文献が記載されていること\n\n## セキュリティ (*.ts, *.js, *.py)\n- [ ] !error ハードコードされたシークレット・APIキー・パスワードが含まれていないこと\n`;\n}\n\n/**\n * ターミナルでAPIキーを安全に入力させる(入力は非表示)。\n */\nfunction promptSecret(prompt: string): Promise<string> {\n return new Promise((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n // 入力を非表示にする\n process.stdout.write(prompt);\n const stdin = process.stdin;\n const wasRaw = stdin.isRaw;\n\n if (stdin.isTTY && stdin.setRawMode) {\n stdin.setRawMode(true);\n }\n\n let input = \"\";\n const onData = (char: Buffer) => {\n const c = char.toString();\n if (c === \"\\n\" || c === \"\\r\") {\n stdin.removeListener(\"data\", onData);\n if (stdin.isTTY && stdin.setRawMode) {\n stdin.setRawMode(wasRaw ?? false);\n }\n process.stdout.write(\"\\n\");\n rl.close();\n resolve(input);\n } else if (c === \"\\x03\") {\n // Ctrl+C\n process.exit(1);\n } else if (c === \"\\x7f\" || c === \"\\b\") {\n // Backspace\n input = input.slice(0, -1);\n } else {\n input += c;\n process.stdout.write(\"*\");\n }\n };\n\n stdin.on(\"data\", onData);\n });\n}\n\n/**\n * @state(globs) に指定されたglobにマッチするファイルを、\n * git ls-files から取得して返す(既に対象になっているファイルは除外)。\n */\nfunction collectExtraStatePaths(rules: Rule[], alreadyIncluded: string[]): string[] {\n const patterns = Array.from(\n new Set(rules.flatMap((r) => r.stateExtraPatterns)),\n );\n if (patterns.length === 0) return [];\n\n let trackedFiles: string[] = [];\n try {\n const raw = execFileSync(\"git\", [\"ls-files\"], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n trackedFiles = raw.split(\"\\n\").map((f) => f.trim()).filter(Boolean);\n } catch {\n return [];\n }\n\n const included = new Set(alreadyIncluded);\n const matched = new Set<string>();\n for (const file of trackedFiles) {\n if (included.has(file)) continue;\n if (patterns.some((p) => matchGlobSimple(file, p))) {\n matched.add(file);\n }\n }\n return Array.from(matched);\n}\n\nfunction matchGlobSimple(filePath: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \"{{DOUBLE_STAR}}\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/{{DOUBLE_STAR}}/g, \".*\")\n .replace(/\\?/g, \"[^/]\");\n const regex = new RegExp(`(^|/)${regexStr}$`, \"i\");\n return regex.test(filePath);\n}\n\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport dotenv from \"dotenv\";\nimport { loadCredentials, getActiveApiKey } from \"./auth.js\";\n\nexport interface KanzakiConfig {\n provider: \"openai\" | \"anthropic\";\n apiKey: string;\n model: string;\n rulesPath: string;\n verbose: boolean;\n noBlock: boolean;\n /** ChatGPT OAuth認証を使用しているか */\n useOAuth: boolean;\n /** Claude CLIをサブプロセスとして利用するか */\n useClaudeCli: boolean;\n}\n\nconst DEFAULT_MODELS: Record<string, string> = {\n openai: \"gpt-5.4\",\n anthropic: \"claude-sonnet-4-20250514\",\n};\n\n/**\n * CLI引数とenv変数からKanzaki設定をロードする。\n * 優先順位: CLIフラグ → env変数 → 保存済みクレデンシャル\n */\nexport function loadConfig(overrides: Partial<KanzakiConfig> = {}): KanzakiConfig {\n // .env ファイルがあれば読み込む\n const envPath = resolve(process.cwd(), \".env\");\n if (existsSync(envPath)) {\n dotenv.config({ path: envPath });\n }\n\n // 保存済みクレデンシャルを読み込む\n const stored = loadCredentials();\n\n const provider = (\n overrides.provider\n ?? process.env.KANZAKI_PROVIDER\n ?? stored?.provider\n ?? \"openai\"\n ) as KanzakiConfig[\"provider\"];\n\n // APIキー解決: CLIフラグ → env → 保存済み\n let apiKey = overrides.apiKey ?? process.env.KANZAKI_API_KEY ?? \"\";\n if (!apiKey && stored) {\n apiKey = getActiveApiKey(stored);\n }\n if (!apiKey) {\n throw new Error(\n \"API key is required. Run 'kanzaki login' or set KANZAKI_API_KEY environment variable.\",\n );\n }\n\n const model = overrides.model ?? process.env.KANZAKI_MODEL ?? DEFAULT_MODELS[provider] ?? \"gpt-5.4\";\n\n const rulesPath = overrides.rulesPath ?? process.env.KANZAKI_RULES_PATH ?? \".kanzaki/rules.md\";\n\n return {\n provider,\n apiKey,\n model,\n rulesPath: resolve(process.cwd(), rulesPath),\n verbose: overrides.verbose ?? false,\n noBlock: overrides.noBlock ?? false,\n useOAuth: !!(stored?.oauthToken),\n useClaudeCli: !!(stored?.useClaudeCli),\n };\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst CONFIG_DIR = resolve(homedir(), \".config\", \"kanzaki\");\nconst CREDENTIALS_PATH = resolve(CONFIG_DIR, \"credentials.json\");\n\nexport interface StoredCredentials {\n provider: \"openai\" | \"anthropic\";\n apiKey: string;\n /** OAuth token (if authenticated via OAuth) */\n oauthToken?: string;\n /** OAuth refresh token */\n refreshToken?: string;\n /** Token expiry (ISO string) */\n expiresAt?: string;\n /** Claude CLIをサブプロセスとして利用するフラグ */\n useClaudeCli?: boolean;\n}\n\n/**\n * 保存済み認証情報を読み込む。存在しない場合は null を返す。\n */\nexport function loadCredentials(): StoredCredentials | null {\n if (!existsSync(CREDENTIALS_PATH)) return null;\n\n try {\n const raw = readFileSync(CREDENTIALS_PATH, \"utf-8\");\n return JSON.parse(raw) as StoredCredentials;\n } catch {\n return null;\n }\n}\n\n/**\n * 認証情報を ~/.config/kanzaki/credentials.json に保存する。\n */\nexport function saveCredentials(credentials: StoredCredentials): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n writeFileSync(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), \"utf-8\");\n}\n\n/**\n * 保存済み認証情報を削除する。\n */\nexport function clearCredentials(): void {\n if (existsSync(CREDENTIALS_PATH)) {\n writeFileSync(CREDENTIALS_PATH, \"{}\", \"utf-8\");\n }\n}\n\n/**\n * 認証情報が保存されているか確認する。\n */\nexport function hasCredentials(): boolean {\n const creds = loadCredentials();\n return creds !== null && (!!creds.apiKey || !!creds.oauthToken || !!creds.useClaudeCli);\n}\n\n/**\n * 有効なAPIキーを取得する。\n * OAuth tokenがある場合はそちらを優先(有効期限チェック付き)。\n */\nexport function getActiveApiKey(creds: StoredCredentials): string {\n // Claude CLIを使う場合は、APIキーはsubprocess側で管理するのでダミー値を返す\n if (creds.useClaudeCli) {\n return \"claude-cli\";\n }\n\n // OAuthトークンが有効ならそれを使う\n if (creds.oauthToken) {\n // expiresAtが未設定(Claudeセッショントークン等)なら常に有効とみなす\n if (!creds.expiresAt) {\n return creds.oauthToken;\n }\n const expiry = new Date(creds.expiresAt);\n if (expiry > new Date()) {\n return creds.oauthToken;\n }\n }\n\n // フォールバック: 通常のAPIキー\n return creds.apiKey;\n}\n\n// ── OAuth Authorization Code Flow (PKCE) ─────────────────\n\nimport { createServer } from \"node:http\";\nimport { randomBytes, createHash } from \"node:crypto\";\nimport open from \"open\";\n\nconst OPENAI_AUTH_URL = \"https://auth.openai.com/oauth/authorize\";\nconst OPENAI_TOKEN_URL = \"https://auth.openai.com/oauth/token\";\nconst OPENAI_CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\"; // Correct client ID used by the platform\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\n\nexport interface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n}\n\nfunction generatePKCE(): { verifier: string; challenge: string } {\n const verifier = randomBytes(32).toString('base64url');\n const challenge = createHash('sha256').update(verifier).digest('base64url');\n return { verifier, challenge };\n}\n\nexport async function loginWithOAuthPKCE(): Promise<TokenResponse> {\n const { verifier, challenge } = generatePKCE();\n const state = randomBytes(16).toString('base64url');\n \n const scope = encodeURIComponent(\"openid profile email offline_access api.connectors.read api.connectors.invoke\");\n const authUrl = `${OPENAI_AUTH_URL}?response_type=code&client_id=${OPENAI_CLIENT_ID}&code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=${scope}&state=${state}&id_token_add_organizations=true&codex_cli_simplified_flow=true`;\n\n console.log(\"Opening browser for authentication...\");\n console.log(\"If your browser does not open automatically, please open this link:\");\n console.log(authUrl);\n \n try {\n await open(authUrl);\n } catch {\n // Ignore error if `open` fails (e.g. no default browser found)\n }\n\n return new Promise((resolve, reject) => {\n const server = createServer(async (req, res) => {\n try {\n if (!req.url?.startsWith('/auth/callback')) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const url = new URL(req.url, `http://${req.headers.host}`);\n const error = url.searchParams.get('error');\n const errorDesc = url.searchParams.get('error_description');\n const code = url.searchParams.get('code');\n const returnedState = url.searchParams.get('state');\n\n if (error) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(`<h1>Authentication Failed</h1><p>${error}: ${errorDesc || 'Unknown error'}</p>`);\n server.close();\n return reject(new Error(`OAuth error: ${error} - ${errorDesc}`));\n }\n\n if (!code) {\n res.writeHead(400);\n res.end(\"Missing authorization code\");\n server.close();\n return reject(new Error(\"No authorization code received\"));\n }\n\n if (returnedState !== state) {\n res.writeHead(400);\n res.end(\"Invalid state\");\n server.close();\n return reject(new Error(\"OAuth state mismatch\"));\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(\"<h1>Authentication Successful!</h1><p>You can close this tab and return to your terminal.</p>\");\n\n server.close();\n\n // Exchange code for token\n const tokenRes = await fetch(OPENAI_TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n client_id: OPENAI_CLIENT_ID,\n grant_type: 'authorization_code',\n code,\n code_verifier: verifier,\n redirect_uri: REDIRECT_URI\n })\n });\n\n if (!tokenRes.ok) {\n const body = await tokenRes.text();\n return reject(new Error(`Token exchange failed: ${tokenRes.status} ${body}`));\n }\n\n const tokenData = await tokenRes.json() as TokenResponse;\n resolve(tokenData);\n\n } catch (err) {\n res.writeHead(500);\n res.end(\"Internal Server Error\");\n server.close();\n reject(err);\n }\n });\n\n server.listen(1455, 'localhost');\n });\n}\n\n\n","import { readFileSync } from \"node:fs\";\n\nexport type Severity = \"error\" | \"warn\";\nexport type RuleScope = \"diff\" | \"state\";\n\nexport interface Rule {\n /** ルールが属するグループ (Markdownのヘッダー) */\n group: string;\n /** ルールのテキスト */\n text: string;\n /** 重要度: error = ブロック, warn = 警告のみ */\n severity: Severity;\n /** 対象ファイルのglobパターン(未指定=全ファイル対象) */\n filePatterns: string[];\n /** 判定スコープ: diff = 差分のみ, state = ファイル現状全体 */\n scope: RuleScope;\n /** scope=state のとき、追加で読み込む(変更されていない)ファイルのglobパターン */\n stateExtraPatterns: string[];\n /** このルールが定義された行番号 (1-indexed)、重複検出用 */\n lineNumber?: number;\n}\n\nexport interface ParseError {\n /** エラーが発生した行番号 (1-indexed) */\n line: number;\n /** エラーまたは警告のメッセージ */\n message: string;\n}\n\nexport interface ParsedRulesFile {\n /** パースされたルール一覧 */\n rules: Rule[];\n /** ルール以外の自由記述テキスト(LLMへのコンテキスト) */\n context: string;\n /** フォーマットエラー(あれば) */\n errors: ParseError[];\n}\n\n/**\n * .kanzaki.md ファイルを解析し、ルールとコンテキストを抽出する。\n *\n * 対応形式:\n * - `- [ ] ルールテキスト` → severity: error (デフォルト)\n * - `- [ ] !error ルールテキスト` → severity: error (明示)\n * - `- [ ] !warn ルールテキスト` → severity: warn\n *\n * ヘッダー(##)はグループ名として使用される。\n * ヘッダーに括弧でglobパターンを指定可能:\n * - `## Security (*.ts, *.js)` → このグループのルールは .ts/.js ファイルのみに適用\n *\n * チェックリスト項目でもヘッダーでもない行は「コンテキスト」として収集される。\n */\nexport function parseRulesFile(filePath: string): ParsedRulesFile {\n const content = readFileSync(filePath, \"utf-8\");\n return parseRulesFromContent(content);\n}\n\nexport function parseRulesFromContent(content: string): ParsedRulesFile {\n const lines = content.split(\"\\n\");\n const rules: Rule[] = [];\n const contextLines: string[] = [];\n const errors: ParseError[] = [];\n let currentGroup = \"General\";\n let currentPatterns: string[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const trimmed = line.trim();\n const lineNumber = i + 1;\n\n // ヘッダーを検出してグループ名とファイルパターンを更新\n const headerMatch = trimmed.match(/^#{1,6}\\s+(.+)$/);\n if (headerMatch) {\n const headerText = headerMatch[1].trim();\n const { name, patterns } = parseHeaderWithPatterns(headerText);\n currentGroup = name;\n currentPatterns = patterns;\n\n // 括弧の不一致を検出(閉じ括弧忘れ等)\n if (headerText.includes(\"(\") && !headerText.includes(\")\")) {\n errors.push({ line: lineNumber, message: `Missing closing parenthesis in file scope: \"${headerText}\"` });\n }\n\n // 空の括弧を検出\n const emptyParenMatch = headerText.match(/\\(\\s*\\)/);\n if (emptyParenMatch) {\n errors.push({ line: lineNumber, message: `Empty file scope parentheses: \"${headerText}\". Remove \"()\" or specify glob patterns inside.` });\n }\n continue;\n }\n\n // 空のチェックリスト項目を検出 (- [ ] の後にテキストなし)\n const emptyRuleMatch = trimmed.match(/^-\\s*\\[[\\s]?\\]\\s*$/);\n if (emptyRuleMatch) {\n errors.push({ line: lineNumber, message: `Empty rule. Checklist item has no text.` });\n continue;\n }\n\n // チェックリスト項目を検出 (- [ ])\n const ruleMatch = trimmed.match(/^-\\s*\\[[\\s]?\\]\\s+(.+)$/);\n if (ruleMatch) {\n const rawText = ruleMatch[1].trim();\n\n // 不正なseverityタグの検出(タイポ)\n const invalidTagMatch = rawText.match(/^!(err|warning|info|critical|block)\\b/i);\n if (invalidTagMatch) {\n errors.push({ line: lineNumber, message: `Unknown severity tag \"${invalidTagMatch[0]}\". Use !error or !warn.` });\n }\n\n // 不正な @state 構文(閉じ括弧忘れ)の検出\n const unclosedScopeMatch = rawText.match(/@state\\s*\\([^)]*$/i);\n if (unclosedScopeMatch) {\n errors.push({ line: lineNumber, message: `Missing closing parenthesis in @state(...): \"${rawText}\"` });\n }\n\n const parsed = parseRuleTags(rawText);\n\n // severityタグ直後に本文がない場合\n if (parsed.text.length === 0) {\n errors.push({ line: lineNumber, message: `Empty rule. Checklist item has only tag(s) with no description.` });\n continue;\n }\n\n // @state() のように空括弧\n if (parsed.scopeHasEmptyParens) {\n errors.push({ line: lineNumber, message: `Empty @state() parentheses. Use \"@state\" alone or \"@state(<glob>, ...)\".` });\n }\n\n rules.push({\n group: currentGroup,\n text: parsed.text,\n severity: parsed.severity,\n filePatterns: currentPatterns,\n scope: parsed.scope,\n stateExtraPatterns: parsed.stateExtraPatterns,\n lineNumber,\n });\n continue;\n }\n\n // それ以外の行の解析\n if (trimmed.length > 0) {\n // リスト項目の形式ミスの検出 (* [ ] や - [x] など)\n if (trimmed.match(/^[-*+]\\s*\\[(.*?)\\]/)) {\n errors.push({ line: lineNumber, message: `Invalid rule format. Checkbox must be '- [ ]'. Found: \"${trimmed}\"` });\n }\n\n // 通常のコンテキストとして収集\n contextLines.push(trimmed);\n }\n }\n\n // 同一グループ内での重複ルールを検出\n const seen = new Map<string, number>();\n for (const rule of rules) {\n const key = `${rule.group}\\u0000${rule.text.toLowerCase()}`;\n const firstLine = seen.get(key);\n if (firstLine !== undefined) {\n errors.push({\n line: rule.lineNumber ?? 0,\n message: `Duplicate rule in group \"${rule.group}\" (first defined at line ${firstLine}): \"${rule.text}\"`,\n });\n } else {\n seen.set(key, rule.lineNumber ?? 0);\n }\n }\n\n return {\n rules,\n context: contextLines.join(\"\\n\"),\n errors,\n };\n}\n\n/**\n * ヘッダーテキストからグループ名とファイルパターンを分離する。\n * 例: \"Security (*.ts, *.js)\" → { name: \"Security\", patterns: [\"*.ts\", \"*.js\"] }\n */\nfunction parseHeaderWithPatterns(header: string): { name: string; patterns: string[] } {\n const match = header.match(/^(.+?)\\s*\\(([^)]+)\\)\\s*$/);\n if (match) {\n const name = match[1].trim();\n const patterns = match[2]\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n return { name, patterns };\n }\n\n return { name: header, patterns: [] };\n}\n\n/**\n * ルールテキストから severity (`!error` / `!warn`) と scope (`@state[(globs)]`) を抽出する。\n * タグの順序はどちらでもよく、混在も可。\n */\ninterface ParsedRuleTags {\n severity: Severity;\n scope: RuleScope;\n stateExtraPatterns: string[];\n text: string;\n scopeHasEmptyParens: boolean;\n}\n\nfunction parseRuleTags(rawText: string): ParsedRuleTags {\n let severity: Severity = \"error\";\n let scope: RuleScope = \"diff\";\n let stateExtraPatterns: string[] = [];\n let scopeHasEmptyParens = false;\n let text = rawText;\n\n // 先頭のタグ列をループで剥がす(順序自由)\n const severityRegex = /^!(error|warn)\\b\\s*/i;\n const scopeRegex = /^@state(?:\\(([^)]*)\\))?\\s*/i;\n\n let progressed = true;\n while (progressed) {\n progressed = false;\n\n const sevMatch = text.match(severityRegex);\n if (sevMatch) {\n severity = sevMatch[1].toLowerCase() === \"warn\" ? \"warn\" : \"error\";\n text = text.slice(sevMatch[0].length);\n progressed = true;\n continue;\n }\n\n const scopeMatch = text.match(scopeRegex);\n if (scopeMatch) {\n scope = \"state\";\n const inside = scopeMatch[1];\n if (inside !== undefined) {\n const trimmedInside = inside.trim();\n if (trimmedInside.length === 0) {\n scopeHasEmptyParens = true;\n } else {\n stateExtraPatterns = trimmedInside\n .split(\",\")\n .map((p) => p.trim())\n .filter(Boolean);\n }\n }\n text = text.slice(scopeMatch[0].length);\n progressed = true;\n continue;\n }\n }\n\n return {\n severity,\n scope,\n stateExtraPatterns,\n text: text.trim(),\n scopeHasEmptyParens,\n };\n}\n\n/**\n * ルール配列をLLMプロンプト用のフォーマット文字列に変換する。\n */\nexport function formatRulesForPrompt(rules: Rule[]): string {\n const grouped = new Map<string, Rule[]>();\n\n for (const rule of rules) {\n const group = grouped.get(rule.group) ?? [];\n group.push(rule);\n grouped.set(rule.group, group);\n }\n\n const sections: string[] = [];\n for (const [group, groupRules] of grouped) {\n const fileScope = groupRules[0]?.filePatterns.length > 0\n ? ` (applies to: ${groupRules[0].filePatterns.join(\", \")})`\n : \"\";\n sections.push(`### ${group}${fileScope}`);\n for (let i = 0; i < groupRules.length; i++) {\n const r = groupRules[i];\n const severity = r.severity === \"warn\" ? \"WARNING\" : \"ERROR\";\n const meta = [`severity=${severity}`, `scope=${r.scope}`];\n if (r.scope === \"state\" && r.stateExtraPatterns.length > 0) {\n meta.push(`also_consult=${r.stateExtraPatterns.join(\"|\")}`);\n }\n sections.push(`- Rule #${i + 1} [${meta.join(\", \")}]`);\n sections.push(` text: ${r.text}`);\n }\n sections.push(\"\");\n }\n\n return sections.join(\"\\n\");\n}\n\n/**\n * 変更ファイル一覧に基づいてルールをフィルタリングする。\n * filePatterns が空のルールは全ファイルに適用される。\n */\nexport function filterRulesByFiles(rules: Rule[], changedFiles: string[]): Rule[] {\n return rules.filter((rule) => {\n // パターン未指定 → 全ファイル対象\n if (rule.filePatterns.length === 0) return true;\n\n // いずれかの変更ファイルがパターンにマッチすれば適用\n return changedFiles.some((file) =>\n rule.filePatterns.some((pattern) => matchGlob(file, pattern)),\n );\n });\n}\n\n/**\n * シンプルなglobマッチング。\n * *.ts, *.md, docs/*, **\\/*.test.ts 等の基本パターンに対応。\n */\nfunction matchGlob(filePath: string, pattern: string): boolean {\n // パターンを正規表現に変換\n const regexStr = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \"{{DOUBLE_STAR}}\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/{{DOUBLE_STAR}}/g, \".*\")\n .replace(/\\?/g, \"[^/]\");\n\n const regex = new RegExp(`(^|/)${regexStr}$`, \"i\");\n return regex.test(filePath);\n}\n\n// 後方互換: 旧APIを維持\nexport function parseRules(filePath: string): Rule[] {\n return parseRulesFile(filePath).rules;\n}\n","import { execFileSync } from \"node:child_process\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, isAbsolute, relative } from \"node:path\";\n\n/**\n * レビューの起点種別。\n * - staged: `git diff --staged`(デフォルト、pre-commit用途)\n * - workingTree: `git diff HEAD`(staged + unstaged)\n * - range: `git diff <a..b>`(任意のリビジョン間、CI/PRレビュー用途)\n * - files: 指定ファイルの現状を読むのみ(diffなし、git管理外もOK)\n */\nexport type ReviewSourceKind = \"staged\" | \"workingTree\" | \"range\" | \"files\";\n\nexport interface ReviewSourceOptions {\n kind: ReviewSourceKind;\n /** range: \"main..HEAD\" のようなリビジョン指定 */\n range?: string;\n /** files: レビュー対象のファイルパス */\n files?: string[];\n}\n\nexport interface ReviewSource {\n kind: ReviewSourceKind;\n /** 人間向けの起点ラベル(例: \"staged\", \"working tree\", \"range:main..HEAD\", \"files\") */\n label: string;\n /** 差分本文。files モードでは空文字 */\n diff: string;\n /** 対象ファイル一覧(リポジトリルートからの相対パス、または絶対パス) */\n files: string[];\n}\n\nexport interface FileContext {\n /** ファイルパス(表示用) */\n path: string;\n /** ファイルの全文内容 */\n content: string;\n}\n\n/**\n * 起点の種別に応じてdiffとファイル一覧を取得する。\n */\nexport function getReviewSource(opts: ReviewSourceOptions): ReviewSource {\n switch (opts.kind) {\n case \"staged\":\n return getStagedSource();\n case \"workingTree\":\n return getWorkingTreeSource();\n case \"range\":\n if (!opts.range) throw new Error(\"range option is required for range source\");\n return getRangeSource(opts.range);\n case \"files\":\n if (!opts.files || opts.files.length === 0) {\n throw new Error(\"files option requires at least one path\");\n }\n return getFilesSource(opts.files);\n }\n}\n\n/**\n * 指定起点に対応するファイルコンテキストを取得する。\n * range の場合は終端リビジョンでの内容、それ以外は作業ツリー(filesystem)の内容を読む。\n */\nexport function getFileContextsForSource(\n source: ReviewSource,\n extraPaths: string[] = [],\n): FileContext[] {\n if (source.kind === \"range\") {\n const endRef = extractEndRef(extractRangeFromLabel(source.label));\n const paths = dedupe([...source.files, ...extraPaths]);\n const contexts: FileContext[] = [];\n for (const p of paths) {\n if (isBinaryPath(p)) continue;\n const content = readFileAtRef(endRef, p);\n if (content === null) continue;\n if (content.length > 100_000) continue;\n contexts.push({ path: p, content });\n }\n return contexts;\n }\n\n const contexts: FileContext[] = [];\n const seen = new Set<string>();\n const repoRoot = safeRepoRoot();\n\n const collect = (path: string) => {\n if (seen.has(path)) return;\n seen.add(path);\n\n if (isBinaryPath(path)) return;\n\n const absPath = isAbsolute(path)\n ? path\n : repoRoot\n ? resolve(repoRoot, path)\n : resolve(process.cwd(), path);\n\n if (!existsSync(absPath)) return;\n try {\n const content = readFileSync(absPath, \"utf-8\");\n if (content.length > 100_000) return;\n contexts.push({ path, content });\n } catch {\n // 読み取れないファイルはスキップ\n }\n };\n\n for (const f of source.files) collect(f);\n for (const f of extraPaths) collect(f);\n return contexts;\n}\n\nfunction getStagedSource(): ReviewSource {\n const diff = execGit([\"diff\", \"--staged\"]);\n const files = splitFiles(execGit([\"diff\", \"--staged\", \"--name-only\"]));\n return { kind: \"staged\", label: \"staged\", diff, files };\n}\n\nfunction getWorkingTreeSource(): ReviewSource {\n const diff = execGit([\"diff\", \"HEAD\"]);\n const files = splitFiles(execGit([\"diff\", \"HEAD\", \"--name-only\"]));\n return { kind: \"workingTree\", label: \"working tree (vs HEAD)\", diff, files };\n}\n\nfunction getRangeSource(range: string): ReviewSource {\n const diff = execGit([\"diff\", range]);\n const files = splitFiles(execGit([\"diff\", range, \"--name-only\"]));\n return { kind: \"range\", label: `range:${range}`, diff, files };\n}\n\nfunction getFilesSource(paths: string[]): ReviewSource {\n const repoRoot = safeRepoRoot();\n const normalized = paths.map((p) => {\n if (!isAbsolute(p)) return p;\n if (repoRoot) {\n const rel = relative(repoRoot, p);\n if (!rel.startsWith(\"..\")) return rel.split(\"\\\\\").join(\"/\");\n }\n return p;\n });\n return { kind: \"files\", label: \"files\", diff: \"\", files: normalized };\n}\n\n/**\n * ステージされた変更があるかチェック。\n */\nexport function hasStagedChanges(): boolean {\n try {\n const output = execGit([\"diff\", \"--staged\", \"--name-only\"]);\n return output.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Gitリポジトリのルートディレクトリを取得する。\n */\nexport function getRepoRoot(): string {\n return execGit([\"rev-parse\", \"--show-toplevel\"]).trim();\n}\n\nfunction safeRepoRoot(): string | null {\n try {\n return getRepoRoot();\n } catch {\n return null;\n }\n}\n\nfunction execGit(args: string[]): string {\n try {\n return execFileSync(\"git\", args, { encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n throw new Error(`Git command failed: git ${args.join(\" \")}\\n${err.stderr ?? err.message}`);\n }\n}\n\nfunction readFileAtRef(ref: string, path: string): string | null {\n try {\n return execFileSync(\"git\", [\"show\", `${ref}:${path}`], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n } catch {\n return null;\n }\n}\n\nfunction splitFiles(raw: string): string[] {\n return raw\n .split(\"\\n\")\n .map((f) => f.trim())\n .filter(Boolean);\n}\n\nfunction dedupe(arr: string[]): string[] {\n return Array.from(new Set(arr));\n}\n\nfunction extractRangeFromLabel(label: string): string {\n return label.startsWith(\"range:\") ? label.slice(\"range:\".length) : label;\n}\n\n/**\n * \"a..b\" や \"a...b\" から終端リビジョンを取り出す。単一refの場合はそのまま返す。\n */\nfunction extractEndRef(range: string): string {\n const tripleIdx = range.indexOf(\"...\");\n if (tripleIdx >= 0) {\n const end = range.slice(tripleIdx + 3).trim();\n return end.length > 0 ? end : \"HEAD\";\n }\n const doubleIdx = range.indexOf(\"..\");\n if (doubleIdx >= 0) {\n const end = range.slice(doubleIdx + 2).trim();\n return end.length > 0 ? end : \"HEAD\";\n }\n return range;\n}\n\nconst BINARY_EXTENSIONS = new Set([\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\", \".ico\", \".webp\", \".svg\",\n \".mp4\", \".webm\", \".mov\", \".avi\",\n \".mp3\", \".wav\", \".ogg\",\n \".zip\", \".tar\", \".gz\", \".rar\", \".7z\",\n \".pdf\", \".doc\", \".docx\", \".xls\", \".xlsx\",\n \".woff\", \".woff2\", \".ttf\", \".eot\", \".otf\",\n \".exe\", \".dll\", \".so\", \".dylib\",\n \".lock\",\n]);\n\nfunction isBinaryPath(filePath: string): boolean {\n const dot = filePath.lastIndexOf(\".\");\n if (dot < 0) return false;\n return BINARY_EXTENSIONS.has(filePath.slice(dot).toLowerCase());\n}\n\n// ── 後方互換API ─────────────────────────────────────────\n\nexport interface StagedChanges {\n diff: string;\n files: string[];\n}\n\n/** @deprecated use getReviewSource({kind:\"staged\"}) instead */\nexport function getStagedChanges(): StagedChanges {\n const s = getStagedSource();\n return { diff: s.diff, files: s.files };\n}\n\n/** @deprecated use getFileContextsForSource instead */\nexport function getFileContexts(files: string[]): FileContext[] {\n const repoRoot = safeRepoRoot();\n const contexts: FileContext[] = [];\n for (const file of files) {\n if (isBinaryPath(file)) continue;\n const absPath = repoRoot ? resolve(repoRoot, file) : resolve(process.cwd(), file);\n if (!existsSync(absPath)) continue;\n try {\n const content = readFileSync(absPath, \"utf-8\");\n if (content.length > 100_000) continue;\n contexts.push({ path: file, content });\n } catch {\n // skip\n }\n }\n return contexts;\n}\n","import OpenAI from \"openai\";\nimport type { LLMProvider, ReviewResult } from \"./types.js\";\n\n// ChatGPT OAuth時のベースURL(Codex CLIと同じ)\nconst CHATGPT_BASE_URL = \"https://chatgpt.com/backend-api/codex\";\n\nexport class OpenAIProvider implements LLMProvider {\n private apiKey: string;\n private model: string;\n private useOAuth: boolean;\n\n constructor(apiKey: string, model: string, useOAuth = false) {\n this.apiKey = apiKey;\n this.model = model;\n this.useOAuth = useOAuth;\n }\n\n async review(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n if (this.useOAuth) {\n return this.reviewWithCodexBackend(systemPrompt, userPrompt);\n }\n return this.reviewWithChatCompletions(systemPrompt, userPrompt);\n }\n\n private async reviewWithChatCompletions(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n const client = new OpenAI({ apiKey: this.apiKey });\n const response = await client.chat.completions.create({\n model: this.model,\n messages: [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userPrompt },\n ],\n response_format: { type: \"json_object\" },\n temperature: 0.1,\n });\n\n const content = response.choices[0]?.message?.content;\n if (!content) {\n throw new Error(\"OpenAI returned an empty response.\");\n }\n\n return parseReviewResponse(content);\n }\n\n private async reviewWithCodexBackend(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n const url = `${CHATGPT_BASE_URL}/responses`;\n const body = {\n model: this.model,\n instructions: systemPrompt,\n input: [\n { role: \"user\", content: userPrompt },\n ],\n store: false,\n stream: true,\n };\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!res.ok) {\n const errorBody = await res.text();\n throw new Error(`${res.status} ${errorBody}`);\n }\n\n // SSEストリームからテキストを収集\n const content = await this.readSSEStream(res);\n\n return parseReviewResponse(content);\n }\n\n private async readSSEStream(res: Response): Promise<string> {\n const reader = res.body?.getReader();\n if (!reader) {\n throw new Error(\"No response body\");\n }\n\n const decoder = new TextDecoder();\n let content = \"\";\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const data = line.slice(6).trim();\n if (data === \"[DONE]\") continue;\n\n try {\n const event = JSON.parse(data);\n // response.output_text.delta イベントからテキストを収集\n if (event.type === \"response.output_text.delta\" && event.delta) {\n content += event.delta;\n }\n } catch {\n // JSON解析できないイベントはスキップ\n }\n }\n }\n\n if (!content) {\n throw new Error(\"OpenAI returned an empty response from stream.\");\n }\n\n return content;\n }\n}\n\nfunction parseReviewResponse(raw: string): ReviewResult {\n try {\n const parsed = JSON.parse(raw);\n\n if (!Array.isArray(parsed.results)) {\n throw new Error(\"Response missing 'results' array.\");\n }\n\n return {\n results: parsed.results.map((r: Record<string, unknown>) => ({\n rule: String(r.rule ?? \"\"),\n passed: Boolean(r.passed),\n reason: String(r.reason ?? \"\"),\n })),\n summary: String(parsed.summary ?? \"\"),\n };\n } catch (error) {\n throw new Error(`Failed to parse LLM response as JSON:\\n${raw}\\n\\n${error}`);\n }\n}\n","import Anthropic from \"@anthropic-ai/sdk\";\nimport type { LLMProvider, ReviewResult } from \"./types.js\";\n\nexport class AnthropicProvider implements LLMProvider {\n private client: Anthropic;\n private model: string;\n\n constructor(apiKey: string, model: string) {\n this.client = new Anthropic({ apiKey });\n this.model = model;\n }\n\n async review(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: 4096,\n system: systemPrompt,\n messages: [{ role: \"user\", content: userPrompt }],\n });\n\n const textBlock = response.content.find((b) => b.type === \"text\");\n if (!textBlock || textBlock.type !== \"text\") {\n throw new Error(\"Anthropic returned no text content.\");\n }\n\n return parseReviewResponse(textBlock.text);\n }\n}\n\nfunction parseReviewResponse(raw: string): ReviewResult {\n // Anthropicはコードブロック内にJSONを返すことがあるため、抽出を試みる\n const jsonMatch = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n const jsonStr = jsonMatch ? jsonMatch[1].trim() : raw.trim();\n\n try {\n const parsed = JSON.parse(jsonStr);\n\n if (!Array.isArray(parsed.results)) {\n throw new Error(\"Response missing 'results' array.\");\n }\n\n return {\n results: parsed.results.map((r: Record<string, unknown>) => ({\n rule: String(r.rule ?? \"\"),\n passed: Boolean(r.passed),\n reason: String(r.reason ?? \"\"),\n })),\n summary: String(parsed.summary ?? \"\"),\n };\n } catch (error) {\n throw new Error(`Failed to parse LLM response as JSON:\\n${raw}\\n\\n${error}`);\n }\n}\n","import { spawn } from \"node:child_process\";\nimport type { LLMProvider, ReviewResult } from \"./types.js\";\n\n/**\n * ローカルのClaude CLI (`claude -p`) をサブプロセスとして呼び出すプロバイダー。\n * OpenClawと同じ方式で、Claude CLIが認証・セッション管理を担当する。\n */\nexport class ClaudeCliProvider implements LLMProvider {\n async review(systemPrompt: string, userPrompt: string): Promise<ReviewResult> {\n // systemとuserを1つのプロンプトに結合してstdinで渡す\n const combinedPrompt = `${systemPrompt}\\n\\n---\\n\\n${userPrompt}`;\n\n const output = await this.runClaudeCli(combinedPrompt);\n return parseReviewResponse(output);\n }\n\n private runClaudeCli(prompt: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // Node.js 20+ の Windows では .cmd/.bat を直接 spawn できない\n // (CVE-2024-27980 対応)。`shell:true` は DEP0190 を引き起こすため、\n // cmd.exe 自体を spawn し、その中で claude を起動する。\n // cmd.exe は .exe なので spawn の制限に該当しない。\n const isWin = process.platform === \"win32\";\n const child = isWin\n ? spawn(\"cmd.exe\", [\"/d\", \"/s\", \"/c\", \"claude -p\"], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n windowsVerbatimArguments: true,\n })\n : spawn(\"claude\", [\"-p\"], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n stdout += chunk.toString(\"utf-8\");\n });\n\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf-8\");\n });\n\n child.on(\"error\", (err) => {\n reject(new Error(`Failed to spawn claude CLI: ${err.message}`));\n });\n\n child.on(\"close\", (code) => {\n if (code !== 0) {\n reject(new Error(`claude CLI exited with code ${code}: ${stderr || stdout}`));\n return;\n }\n resolve(stdout);\n });\n\n // プロンプトをstdinに書き込んで閉じる\n child.stdin.write(prompt);\n child.stdin.end();\n });\n }\n}\n\nfunction parseReviewResponse(raw: string): ReviewResult {\n // Claude CLIはコードブロック内にJSONを返すことがあるため抽出を試みる\n const jsonMatch = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n const jsonStr = jsonMatch ? jsonMatch[1].trim() : raw.trim();\n\n try {\n const parsed = JSON.parse(jsonStr);\n\n if (!Array.isArray(parsed.results)) {\n throw new Error(\"Response missing 'results' array.\");\n }\n\n return {\n results: parsed.results.map((r: Record<string, unknown>) => ({\n rule: String(r.rule ?? \"\"),\n passed: Boolean(r.passed),\n reason: String(r.reason ?? \"\"),\n })),\n summary: String(parsed.summary ?? \"\"),\n };\n } catch (error) {\n throw new Error(`Failed to parse Claude CLI response as JSON:\\n${raw}\\n\\n${error}`);\n }\n}\n","import type { KanzakiConfig } from \"../config.js\";\nimport type { Rule } from \"./parser.js\";\nimport { formatRulesForPrompt } from \"./parser.js\";\nimport type { FileContext, ReviewSource } from \"./git.js\";\nimport type { LLMProvider, ReviewResult, Severity } from \"../llm/types.js\";\nimport { OpenAIProvider } from \"../llm/openai.js\";\nimport { AnthropicProvider } from \"../llm/anthropic.js\";\nimport { ClaudeCliProvider } from \"../llm/claude-cli.js\";\n\nconst SYSTEM_PROMPT = `You are a strict quality reviewer. Your job is to review a set of changes (or a snapshot of files) against a checklist of rules defined by the user.\n\nThe rules may cover ANY domain — code, documentation, research, writing, presentations, design, or any other type of output. Evaluate each rule based on its intent, not just literal text matching.\n\nEach rule is printed as:\n\n - Rule #N [severity=..., scope=..., (optional) also_consult=...]\n text: <the rule text>\n\nThe \\`scope=\\` field controls evaluation mode:\n- scope=diff → Judge whether THIS CHANGE introduces a new violation. Pre-existing violations that the change does not touch should NOT cause the rule to fail.\n- scope=state → Judge whether the CURRENT STATE of the relevant files satisfies the rule, regardless of what was changed. Pre-existing violations SHOULD cause the rule to fail. Consult the full file contents, not just diff hunks.\n\nThe \\`also_consult=\\` field (pipe-separated globs) lists extra files included in the prompt for cross-file state checks (e.g. glossary, schema).\n\nWhen returning results, the \"rule\" field MUST contain ONLY the text shown on the \\`text:\\` line — no \"Rule #N\" prefix, no severity/scope tags, no brackets, no numbering.\n\nIMPORTANT:\n- Rules marked [ERROR] are critical. Be strict when evaluating them.\n- Rules marked [WARNING] are advisory. Be fair but flag clear violations.\n- Only evaluate rules that are RELEVANT to the files under review. If a rule clearly does not apply, mark it as passed with reason \"Not applicable.\"\n- Use the context section (if provided) to understand the project's goals and constraints.\n- If no diff is provided (files-only review), treat every rule as a state check against the provided files.\n\nYou MUST respond with valid JSON in this exact format:\n{\n \"results\": [\n { \"rule\": \"<exact rule text>\", \"passed\": true, \"reason\": \"<brief explanation>\" },\n { \"rule\": \"<exact rule text>\", \"passed\": false, \"reason\": \"<specific explanation of the violation>\" }\n ],\n \"summary\": \"<one-line overall summary>\"\n}`;\n\n/**\n * 指定されたソース(staged/range/files等)をルールに照らしてLLMレビューする。\n */\nexport async function review(\n config: KanzakiConfig,\n rules: Rule[],\n source: ReviewSource,\n fileContexts: FileContext[],\n rulesContext?: string,\n): Promise<ReviewResult> {\n const provider = createProvider(config);\n const userPrompt = buildUserPrompt(rules, source, fileContexts, rulesContext);\n\n const rawResult = await provider.review(SYSTEM_PROMPT, userPrompt);\n\n // ルール定義側のseverityを結果にマッピングする\n return mapSeverities(rawResult, rules);\n}\n\nfunction createProvider(config: KanzakiConfig): LLMProvider {\n switch (config.provider) {\n case \"openai\":\n return new OpenAIProvider(config.apiKey, config.model, config.useOAuth);\n case \"anthropic\":\n if (config.useClaudeCli) {\n return new ClaudeCliProvider();\n }\n return new AnthropicProvider(config.apiKey, config.model);\n default:\n throw new Error(`Unknown provider: ${config.provider}`);\n }\n}\n\nfunction buildUserPrompt(\n rules: Rule[],\n source: ReviewSource,\n fileContexts: FileContext[],\n rulesContext?: string,\n): string {\n const parts: string[] = [];\n\n // ユーザー定義のコンテキスト(自由記述)\n if (rulesContext && rulesContext.trim().length > 0) {\n parts.push(\"## Project Context\\n\");\n parts.push(rulesContext);\n parts.push(\"\");\n }\n\n // 起点情報\n parts.push(`## Review Source\\n`);\n parts.push(`Source: ${source.label}`);\n if (source.kind === \"files\") {\n parts.push(\"Mode: files-only (no diff). Evaluate each rule against the current state of the files below.\");\n }\n parts.push(\"\");\n\n // ルール\n parts.push(\"## Checklist Rules\\n\");\n parts.push(formatRulesForPrompt(rules));\n\n // Diff\n if (source.diff.trim().length > 0) {\n parts.push(\"## Changes (git diff)\\n\");\n parts.push(\"```diff\");\n parts.push(truncate(source.diff, 50_000));\n parts.push(\"```\\n\");\n }\n\n // ファイルコンテキスト\n if (fileContexts.length > 0) {\n parts.push(\"## Full File Context\\n\");\n parts.push(\"The following are the full contents of the files under review:\\n\");\n for (const ctx of fileContexts) {\n const ext = ctx.path.split(\".\").pop() ?? \"\";\n parts.push(`### ${ctx.path}\\n`);\n parts.push(`\\`\\`\\`${ext}`);\n parts.push(truncate(ctx.content, 20_000));\n parts.push(\"```\\n\");\n }\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * LLMレスポンスのresultsにルール定義側のseverityを付与する。\n * ルールテキストのマッチングで紐付ける。\n */\nfunction mapSeverities(result: ReviewResult, rules: Rule[]): ReviewResult {\n const severityMap = new Map<string, Severity>();\n for (const rule of rules) {\n severityMap.set(rule.text.toLowerCase(), rule.severity);\n }\n\n return {\n ...result,\n results: result.results.map((r) => ({\n ...r,\n severity: severityMap.get(r.rule.toLowerCase()) ?? \"error\",\n })),\n };\n}\n\n/**\n * テキストを指定文字数で切り詰める。\n */\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength) + \"\\n\\n... (truncated)\";\n}\n","import chalk from \"chalk\";\nimport type { ReviewResult } from \"../llm/types.js\";\n\nexport interface ReportSummary {\n /** エラー(ブロック対象)の失敗数 */\n errorCount: number;\n /** 警告の失敗数 */\n warnCount: number;\n}\n\n/**\n * レビュー結果をターミナルに出力する。\n * @returns エラーと警告の失敗数\n */\nexport function report(result: ReviewResult, verbose: boolean): ReportSummary {\n const { results, summary } = result;\n\n console.log();\n console.log(chalk.bold.underline(\"Kanzaki Review Results\"));\n console.log();\n\n let errorCount = 0;\n let warnCount = 0;\n\n for (const r of results) {\n const isWarn = r.severity === \"warn\";\n const label = isWarn ? chalk.dim(\"[warn]\") : chalk.dim(\"[error]\");\n\n if (r.passed) {\n console.log(` ${chalk.green(\"✓\")} ${label} ${r.rule}`);\n if (verbose) {\n console.log(` ${chalk.dim(r.reason)}`);\n }\n } else {\n if (isWarn) {\n warnCount++;\n console.log(` ${chalk.yellow(\"⚠\")} ${label} ${r.rule}`);\n console.log(` ${chalk.yellow(\"→\")} ${r.reason}`);\n } else {\n errorCount++;\n console.log(` ${chalk.red(\"✗\")} ${label} ${r.rule}`);\n console.log(` ${chalk.red(\"→\")} ${r.reason}`);\n }\n }\n }\n\n // サマリー\n console.log();\n const total = results.length;\n const passedCount = total - errorCount - warnCount;\n\n if (errorCount === 0 && warnCount === 0) {\n console.log(chalk.green.bold(` All ${total} rules passed ✓`));\n } else {\n const parts: string[] = [];\n parts.push(`${passedCount}/${total} passed`);\n if (errorCount > 0) parts.push(chalk.red(`${errorCount} errors`));\n if (warnCount > 0) parts.push(chalk.yellow(`${warnCount} warnings`));\n console.log(` ${parts.join(\", \")}`);\n\n if (errorCount > 0) {\n console.log(chalk.red.bold(\"\\n Commit blocked due to errors.\"));\n } else {\n console.log(chalk.yellow(\"\\n Warnings found, but commit allowed.\"));\n }\n }\n\n if (summary) {\n console.log();\n console.log(chalk.dim(` ${summary}`));\n }\n\n console.log();\n\n return { errorCount, warnCount };\n}\n","import { mkdirSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ReviewResult, RuleResult } from \"../llm/types.js\";\nimport type { Rule } from \"./parser.js\";\nimport type { ReviewSource } from \"./git.js\";\n\n/**\n * レビュー結果からコーディングエージェント向けのフィードバックmarkdownを生成し、\n * `{outputDir}/{timestamp}.md` に書き出す。違反が1件もなければ何もしない。\n */\nexport function writeFeedbackFile(\n result: ReviewResult,\n rules: Rule[],\n source: ReviewSource,\n outputDir: string,\n): string | null {\n const failures = result.results.filter((r) => !r.passed);\n if (failures.length === 0) return null;\n\n if (!existsSync(outputDir)) {\n mkdirSync(outputDir, { recursive: true });\n }\n\n const now = new Date();\n const filePath = join(outputDir, `${formatTimestamp(now)}.md`);\n const content = buildFeedbackMarkdown(failures, rules, source, now, result.summary);\n writeFileSync(filePath, content, \"utf-8\");\n return filePath;\n}\n\nfunction buildFeedbackMarkdown(\n failures: RuleResult[],\n rules: Rule[],\n source: ReviewSource,\n now: Date,\n summary: string,\n): string {\n const ruleMap = new Map<string, Rule>();\n for (const r of rules) {\n ruleMap.set(r.text.toLowerCase(), r);\n }\n\n const errorCount = failures.filter((f) => f.severity === \"error\").length;\n const warnCount = failures.filter((f) => f.severity === \"warn\").length;\n\n const lines: string[] = [];\n lines.push(`# Kanzaki Review Feedback`);\n lines.push(``);\n lines.push(`Generated: ${now.toISOString()}`);\n lines.push(``);\n lines.push(`## Summary`);\n lines.push(``);\n if (summary) {\n lines.push(summary);\n lines.push(``);\n }\n lines.push(`- レビュー起点: ${source.label}`);\n lines.push(`- エラー: ${errorCount} 件`);\n lines.push(`- 警告: ${warnCount} 件`);\n lines.push(`- 対象ファイル: ${source.files.join(\", \")}`);\n lines.push(``);\n lines.push(`## Instructions for Coding Agents`);\n lines.push(``);\n lines.push(`以下は \\`kanzaki check\\` が検出したルール違反です。各違反について、指摘内容を読み、対象ファイルの該当箇所を修正してください。`);\n if (source.kind === \"staged\") {\n lines.push(`修正後は \\`git add\\` で再度ステージし、\\`kanzaki check\\` を実行して違反が解消されたことを確認してください。`);\n } else {\n lines.push(`修正後は同じ起点で \\`kanzaki check\\` を再実行し、違反が解消されたことを確認してください。`);\n }\n lines.push(`ルール定義そのものを変更することで違反を回避することは禁止されています(ルールの変更が必要な場合はユーザーに確認してください)。`);\n lines.push(``);\n lines.push(`## Violations`);\n lines.push(``);\n\n failures.forEach((f, idx) => {\n const rule = ruleMap.get(f.rule.toLowerCase());\n const tag = f.severity === \"warn\" ? \"[WARN]\" : \"[ERROR]\";\n lines.push(`### ${idx + 1}. ${tag} ${f.rule}`);\n lines.push(``);\n if (rule) {\n lines.push(`- **グループ**: ${rule.group}`);\n const fileScope = rule.filePatterns.length > 0 ? rule.filePatterns.join(\", \") : \"全ファイル\";\n lines.push(`- **適用スコープ**: ${fileScope}`);\n lines.push(`- **判定スコープ**: ${rule.scope === \"state\" ? \"state(ファイル現状)\" : \"diff(差分)\"}`);\n if (rule.stateExtraPatterns.length > 0) {\n lines.push(`- **追加参照**: ${rule.stateExtraPatterns.join(\", \")}`);\n }\n if (rule.lineNumber) {\n lines.push(`- **ルール定義**: rules.md:${rule.lineNumber}`);\n }\n }\n lines.push(`- **対象ファイル**: ${source.files.join(\", \")}`);\n lines.push(``);\n lines.push(`**違反理由**:`);\n lines.push(``);\n const reason = f.reason.trim() || \"(no reason provided)\";\n for (const line of reason.split(\"\\n\")) {\n lines.push(`> ${line}`);\n }\n lines.push(``);\n });\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Windows互換のタイムスタンプ文字列を生成する(コロン不使用)。\n * 例: \"2026-04-16T14-30-45\"\n */\nfunction formatTimestamp(d: Date): string {\n const pad = (n: number) => String(n).padStart(2, \"0\");\n const y = d.getFullYear();\n const mo = pad(d.getMonth() + 1);\n const da = pad(d.getDate());\n const h = pad(d.getHours());\n const mi = pad(d.getMinutes());\n const s = pad(d.getSeconds());\n return `${y}-${mo}-${da}T${h}-${mi}-${s}`;\n}\n","import { createCli } from \"./cli.js\";\n\nconst program = createCli();\nprogram.parse();\n"],"mappings":"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","names":["chalk","existsSync","writeFileSync","readFileSync","mkdirSync","resolve","execFileSync","existsSync","resolve","resolve","resolve","existsSync","readFileSync","readFileSync","existsSync","resolve","contexts","parseReviewResponse","parseReviewResponse","resolve","mkdirSync","writeFileSync","existsSync","program","resolve","existsSync","mkdirSync","chalk","writeFileSync","readFileSync","execFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kanzaki",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "LLM-powered semantic pre-commit linter. Define rules in Markdown, review diffs with AI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,6 +3,10 @@
3
3
  このプロジェクトでは Kanzaki を使って、コミット前に変更内容の品質チェックを自動で行います。
4
4
  以下のルールをプロジェクトの基準に合わせてカスタマイズしてください。
5
5
 
6
+ タグ:
7
+ - 既定 `@diff`(差分のみ判定、既存の違反は無視)/`@state`(ファイル現状で判定、既存違反も拾う)
8
+ - `@state(<glob>, ...)` で、変更されていない参照ファイルも追加で読み込めます
9
+
6
10
  ## 品質
7
11
  - [ ] !error 変更内容がプロジェクトの既存のスタイルや規約と一貫していること
8
12
  - [ ] !error プレースホルダーや未解決の TODO が残っていないこと
@@ -13,8 +17,8 @@
13
17
  - [ ] !warn 適切な箇所に出典・参考文献が記載されていること
14
18
 
15
19
  ## セキュリティ (*.ts, *.js, *.py)
16
- - [ ] !error ハードコードされたシークレット・APIキー・パスワードが含まれていないこと
17
- - [ ] !error 機密性の高い個人情報が露出していないこと
20
+ - [ ] !error @state ハードコードされたシークレット・APIキー・パスワードが含まれていないこと
21
+ - [ ] !error @state 機密性の高い個人情報が露出していないこと
18
22
 
19
23
  ## 完全性
20
24
  - [ ] !warn 変更されたセクションが完成していること(中途半端な作業が残っていないこと)