ai-check-template 0.2.0-alpha.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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/README-ja.md +151 -0
  3. package/README.md +149 -0
  4. package/bin/ai-check-template.mjs +7 -0
  5. package/docs/cli.md +348 -0
  6. package/package-templates/.claude/README.md +83 -0
  7. package/package-templates/.claude/rules/test-rules.md +46 -0
  8. package/package-templates/.claude/settings.hook-fragment.json +25 -0
  9. package/package-templates/README.md +56 -0
  10. package/package-templates/ci-examples/README.md +134 -0
  11. package/package-templates/ci-examples/github-actions/ai-check-fast.yml +49 -0
  12. package/package-templates/ci-examples/github-actions/ai-check.yml +58 -0
  13. package/package-templates/ci-examples/github-actions/ai-quality-call.yml +26 -0
  14. package/package-templates/ci-examples/github-actions/ai-quality-reusable.yml +113 -0
  15. package/package-templates/docs/philosophy/formal-name-match.md +182 -0
  16. package/package-templates/docs/philosophy/given-when-then.md +206 -0
  17. package/package-templates/docs/philosophy/qa-techniques.md +235 -0
  18. package/package-templates/docs/philosophy/test-pyramid.md +171 -0
  19. package/package-templates/docs/test-design-template.md +173 -0
  20. package/package-templates/package.scripts.fragment.json +6 -0
  21. package/package-templates/profiles/README.md +89 -0
  22. package/package-templates/profiles/expo-rn/README.md +80 -0
  23. package/package-templates/profiles/node-cli/README.md +93 -0
  24. package/package-templates/profiles/react-nextjs/README.md +82 -0
  25. package/package-templates/profiles/react-vanilla/README.md +73 -0
  26. package/package-templates/profiles/supabase-rls/README.md +89 -0
  27. package/package-templates/prompts/README.md +94 -0
  28. package/package-templates/prompts/boundary-value.md +94 -0
  29. package/package-templates/prompts/decision-table.md +82 -0
  30. package/package-templates/prompts/diagnostic-repair.md +149 -0
  31. package/package-templates/prompts/plan-first.md +122 -0
  32. package/package-templates/prompts/rls-permission.md +94 -0
  33. package/package-templates/prompts/state-transition.md +81 -0
  34. package/package-templates/scripts/README.md +78 -0
  35. package/package-templates/scripts/ai-check-fast.sh +20 -0
  36. package/package-templates/scripts/ai-check.sh +22 -0
  37. package/package.json +47 -0
  38. package/src/cli/ci-workflows.mjs +104 -0
  39. package/src/cli/claude-hooks.mjs +94 -0
  40. package/src/cli/dependency-installer.mjs +164 -0
  41. package/src/cli/doctor.mjs +392 -0
  42. package/src/cli/index.mjs +80 -0
  43. package/src/cli/init.mjs +433 -0
  44. package/src/cli/install-state.mjs +242 -0
  45. package/src/cli/package-manager.mjs +78 -0
  46. package/src/cli/profile-diagnostics.mjs +160 -0
  47. package/src/cli/profile-docs.mjs +31 -0
  48. package/src/cli/profile-scripts.mjs +96 -0
  49. package/src/cli/profile.mjs +59 -0
  50. package/src/cli/update.mjs +537 -0
  51. package/src/cli/utils.mjs +75 -0
@@ -0,0 +1,94 @@
1
+ # rls-permission プロンプト
2
+
3
+ > **ステータス**: Draft v0.1(Phase 1 dogfooding 後に改訂予定)
4
+
5
+ ## 目的
6
+
7
+ Row Level Security(RLS)や権限制御で、「見えるべき / 見えてはいけない」「書けるべき / 書けてはいけない」の両方をテストする。フロントの表示制御だけで満足せず、サーバー側 API / DB level で境界が破られないことを確認する。
8
+
9
+ 形名参同の「名」に権限境界の拒否ケースを含めるための観点設計プロンプト。
10
+
11
+ ## プロンプト本文
12
+
13
+ ```
14
+ 以下の権限制御仕様について、デシジョンテーブルで権限境界を整理し、許可される操作と拒否される操作の両方をテストしてください。
15
+
16
+ ## 仕様
17
+ (例: 「ユーザーは自身が作成した記事を更新・削除できる。他人の記事は閲覧のみ可。管理者は全記事を更新・削除できる」)
18
+
19
+ ## 手順
20
+
21
+ 1. role を列挙(例: anonymous / user / owner / admin)
22
+ 2. 対象リソースの状態を列挙(例: 自分のリソース / 他人のリソース / 公開 / 非公開)
23
+ 3. 操作を列挙(read / create / update / delete)
24
+ 4. role × resource state × 操作 のマトリクスで「許可 / 拒否」を明示
25
+ 5. 各セルに対応するテストケースを生成
26
+ 6. **拒否ケースは API level でテスト**(フロントの非表示だけでは不十分)
27
+
28
+ ## 出力形式
29
+
30
+ ### Step 1: 権限マトリクス
31
+
32
+ | role | resource | read | create | update | delete |
33
+ |---|---|---|---|---|---|
34
+ | anonymous | any | OK / NG | NG | NG | NG |
35
+ | user | self | OK | OK | OK | OK |
36
+ | user | others | OK / NG | NG | NG | NG |
37
+ | admin | any | OK | OK | OK | OK |
38
+
39
+ ### Step 2: 拒否ケースのテスト(最重要)
40
+
41
+ 各 NG セルについて:
42
+ - 該当 role / resource を使い、該当操作を試みる
43
+ - 401 / 403 / 404 等の拒否応答を確認
44
+ - DB / RLS 等のサーバー側 level で実際に拒否されることを確認(mock や middleware で擬似的に通さない)
45
+
46
+ ### Step 3: 許可ケースのテスト
47
+
48
+ 各 OK セルについて:
49
+ - 該当 role / resource で該当操作が成功する
50
+
51
+ ## 制約
52
+
53
+ - 拒否は **API 直叩き** でテストする(curl / supertest / API client)。UI 経由だけでは網羅性が不足
54
+ - フロントの「ボタンを非表示」は UX 改善であって、セキュリティ対策ではない
55
+ - RLS(Row Level Security)を使う場合、ポリシー単位で許可・拒否両方をテストする
56
+ ```
57
+
58
+ ## 利用例
59
+
60
+ ### 入力仕様
61
+ > 記事は user_id で所有される。自分の記事は更新・削除可。他人の記事は閲覧のみ。admin は全記事を更新可能。
62
+
63
+ ### 期待される AI 出力
64
+
65
+ ```
66
+ | role | 記事所有 | read | update | delete |
67
+ |---|---|---|---|---|
68
+ | anonymous | - | OK(公開のみ) | NG | NG |
69
+ | user | self | OK | OK | OK |
70
+ | user | others | OK(公開のみ) | NG | NG |
71
+ | admin | any | OK | OK | NG(admin は削除権限なし) |
72
+ ```
73
+
74
+ 各セルのテスト:
75
+ - `user (others) update 記事` → 403 / 404 を確認
76
+ - DB-RLS test: 別 user_id でログインし、他人の記事を直接 UPDATE SQL → 0 rows affected または error
77
+
78
+ ## 隣接する思想
79
+
80
+ - [`../docs/philosophy/qa-techniques.md`](../docs/philosophy/qa-techniques.md) §3. デシジョンテーブル — 権限マトリクスは特殊な DT
81
+ - [`../docs/philosophy/test-pyramid.md`](../docs/philosophy/test-pyramid.md) §5. DB-RLS test 層
82
+ - [`../docs/philosophy/given-when-then.md`](../docs/philosophy/given-when-then.md) — 拒否ケースを GWT で記述
83
+ - [`../docs/philosophy/formal-name-match.md`](../docs/philosophy/formal-name-match.md) — 権限境界の「名」を機械検証可能化
84
+
85
+ ## カスタマイズ
86
+
87
+ - **multi-tenant**: organization_id / tenant_id の境界もマトリクスに加える
88
+ - **行レベル + 列レベル権限**: ある列だけ管理者にも見せないケースを別行で扱う
89
+ - **時間依存権限**: 「公開予約日時前は所有者のみ閲覧」等は state-transition プロンプトと組み合わせる
90
+
91
+ ## 出典
92
+
93
+ - Notion ページ: `35b68c677f4380bfa1ffeab248264e92` — テストフロー再設計(参照日 2026-05-13)の Supabase / RLS 観点
94
+ - 補強: `7c531b165bab4b7ea2dce1782469ac52` — Supabase Testing 戦略(pgTAP, service_role を使わない検証)
@@ -0,0 +1,81 @@
1
+ # state-transition プロンプト
2
+
3
+ > **ステータス**: Draft v0.1(Phase 1 dogfooding 後に改訂予定)
4
+
5
+ ## 目的
6
+
7
+ 状態を持つ機能(注文ステータス・記事ライフサイクル・予約ワークフロー等)で、許可される遷移と**禁止される遷移**の両方をテストする。AI は許可遷移だけテストしがちなので、不正遷移の拒否確認を明示的に依頼する。
8
+
9
+ 形名参同の「名」に「不正遷移の拒否」を含めるための観点設計プロンプト。
10
+
11
+ ## プロンプト本文
12
+
13
+ ```
14
+ 以下のステータス仕様について、状態遷移表を作成し、許可遷移と禁止遷移の両方をテストしてください。
15
+
16
+ ## 仕様
17
+ (例: 「注文ステータスは pending / confirmed / shipped / delivered / cancelled。pending → confirmed → shipped → delivered の順で進む。pending または confirmed からのみ cancelled へ遷移できる」)
18
+
19
+ ## 手順
20
+
21
+ 1. 状態リストを列挙
22
+ 2. 遷移表を作成(行: 遷移元、列: 遷移先、セル: 許可 / 禁止)
23
+ 3. 許可遷移のテストケースを生成
24
+ 4. 禁止遷移のテストケースを生成(**こちらが重要**)
25
+ 5. 各禁止遷移について、サーバー側(API / DB)で拒否されることを確認
26
+
27
+ ## 出力形式
28
+
29
+ ### Step 1: 状態遷移表
30
+
31
+ | from \\ to | pending | confirmed | shipped | delivered | cancelled |
32
+ |---|---|---|---|---|---|
33
+ | pending | - | OK | NG | NG | OK |
34
+ | confirmed | NG | - | OK | NG | OK |
35
+ ...
36
+
37
+ ### Step 2: テストコード
38
+
39
+ - 許可遷移: 「pending → confirmed が成功する」「confirmed → shipped が成功する」...
40
+ - 禁止遷移: 「pending → shipped が API で拒否される(4xx 応答)」「delivered → pending が拒否される」...
41
+
42
+ ## 制約
43
+
44
+ - 全 N×N 通りの遷移を表に明示すること(自己遷移含む)
45
+ - 禁止遷移は**サーバー側 API / DB level** で拒否されることを確認(フロントの非表示だけでは不十分)
46
+ - 1 遷移 1 テストケース
47
+ ```
48
+
49
+ ## 利用例
50
+
51
+ ### 入力仕様
52
+ > 記事ステータスは draft / review / published / archived。draft → review → published の順。published → archived は許可、archived → published は禁止。
53
+
54
+ ### 期待される AI 出力
55
+
56
+ ```
57
+ | from \\ to | draft | review | published | archived |
58
+ |---|---|---|---|---|
59
+ | draft | - | OK | NG | NG |
60
+ | review | OK | - | OK | NG |
61
+ | published | NG | NG | - | OK |
62
+ | archived | NG | NG | NG | - |
63
+ ```
64
+
65
+ 各セルに対応するテストケース(許可遷移 OK ケース + 禁止遷移の拒否ケース)。
66
+
67
+ ## 隣接する思想
68
+
69
+ - [`../docs/philosophy/qa-techniques.md`](../docs/philosophy/qa-techniques.md) §4. 状態遷移テスト — 技法の理論
70
+ - [`../docs/philosophy/given-when-then.md`](../docs/philosophy/given-when-then.md) — 遷移を GWT で記述する
71
+ - [`../docs/philosophy/test-pyramid.md`](../docs/philosophy/test-pyramid.md) — DB-RLS 層と Integration 層の境界
72
+
73
+ ## カスタマイズ
74
+
75
+ - **状態数が多い(10 以上)**: 表が肥大化。条件付き遷移(例: role 別の許可遷移)に切り出すと整理しやすい
76
+ - **時間依存遷移**: 「予約日時を過ぎたら自動的に cancelled」等は別プロンプト(バッチ処理テスト)と組み合わせる
77
+
78
+ ## 出典
79
+
80
+ - Notion ページ: `dc8774cd03c8490688b066c2b0179cac` — AI 駆動開発時代に押さえる QA 技法(参照日 2026-05-13)の「4. 状態遷移テスト」節
81
+ - 一次資料: ISTQB Foundation Level Syllabus
@@ -0,0 +1,78 @@
1
+ # scripts/
2
+
3
+ `ai:check` 系の薄い entry point スクリプト。配布される example。
4
+
5
+ > **ステータス**: Draft v0.1(Phase 1 dogfooding 後に改訂予定)
6
+
7
+ ## 提供物
8
+
9
+ ```
10
+ scripts/
11
+ ├── ai-check.sh # full check(Static + Unit + Integration + Diagnostic + E2E)
12
+ └── ai-check-fast.sh # fast check(Static + Unit のみ、AI 内部ループ用)
13
+ ```
14
+
15
+ ## 思想
16
+
17
+ 形名参同([`../docs/philosophy/formal-name-match.md`](../docs/philosophy/formal-name-match.md))の「形」を取得する実体。
18
+
19
+ - 「名」(成功基準)は `package.json` の `ai:check` / `ai:check:fast` スクリプトに定義
20
+ - 「形」(実測値)はそれらを実行した出力
21
+ - 本スクリプトは PM 抽象化と最小ロギングのみ担当し、ロジックは npm scripts に委譲する
22
+
23
+ ## 使い方
24
+
25
+ ### 直接実行
26
+ ```bash
27
+ bash scripts/ai-check.sh
28
+ bash scripts/ai-check-fast.sh
29
+ ```
30
+
31
+ ### PM 切り替え
32
+ デフォルトは `pnpm`。`PM` 環境変数で上書き可:
33
+ ```bash
34
+ PM=npm bash scripts/ai-check.sh
35
+ PM=yarn bash scripts/ai-check.sh
36
+ PM=bun bash scripts/ai-check.sh
37
+ ```
38
+
39
+ ### 直接 npm script を呼ぶ場合
40
+ シェルスクリプトを使わず `pnpm` 直接呼びでも同等:
41
+ ```bash
42
+ pnpm ai:check
43
+ pnpm ai:check:fast
44
+ ```
45
+
46
+ シェルスクリプトの利点:
47
+ - PM 非依存(環境変数で切り替え)
48
+ - 非 Node プロジェクトでも entry point として配置可能
49
+ - 統一 logging プレフィックス(`[ai-check]` / `[ai-check-fast]`)
50
+
51
+ ## package.scripts.fragment.json との関係
52
+
53
+ `ai:check` / `ai:check:fast` の中身は `../package.scripts.fragment.json` に定義される。
54
+ 利用者は自プロジェクトの `package.json` に scripts エントリをマージする。
55
+
56
+ ```
57
+ scripts/ai-check.sh → ${PM} ai:check → package.json scripts.ai:check
58
+ (typecheck → lint → test → e2e:smoke 等を連鎖)
59
+ ```
60
+
61
+ ## CI 統合との関係
62
+
63
+ [`../ci-examples/github-actions/ai-check.yml`](../ci-examples/github-actions/ai-check.yml) でも同じ `${PM} ai:check` を呼ぶ。
64
+ ローカル(シェル)と CI(GitHub Actions)で**同じコマンド**が走る設計。
65
+
66
+ ## Claude Code Hook との関係
67
+
68
+ [`../.claude/settings.hook-fragment.json`](../.claude/settings.hook-fragment.json) の Edit hook が `pnpm ai:check:fast` を呼び、Stop hook が `pnpm ai:check` を呼ぶ。
69
+ シェル経由でも npm script 直接でも、最終的に同じスクリプトが走る。
70
+
71
+ ## カスタマイズ
72
+
73
+ - timeout: シェルラッパーには timeout 設定なし。CI 側で `timeout-minutes` を設定する
74
+ - 失敗時の自動修正ループ: 本スクリプトはスコープ外。AI への修正指示は上位の運用プロンプトで行う
75
+
76
+ ## 出典
77
+
78
+ - Notion Doc #2(`c3e549660ca44005a20c4f6fdb54c8d5`、参照日 2026-05-13)の「## package.jsonに入れる推奨script」節
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # ai-check-fast.sh — fast AI check entry point (Static + Unit のみ)
3
+ #
4
+ # 配布される example。AI 内部ループ(Claude Code の Edit hook)から呼ばれる軽量版。
5
+ # Static check + Unit test までに限定し、E2E や Diagnostic(React Doctor 等)は含めない。
6
+ # timeout 10 分以内で完走する想定。
7
+ #
8
+ # 詳細思想:
9
+ # - package-templates/docs/philosophy/formal-name-match.md §段階的導入 Phase A/B
10
+ # - Edit hook(fast)と PR Gate(full)のハイブリッド構成の片方
11
+ #
12
+ # PM 切り替え:
13
+ # ai-check.sh と同じ。PM 環境変数で上書き可。
14
+
15
+ set -euo pipefail
16
+
17
+ PM="${PM:-pnpm}"
18
+
19
+ echo "[ai-check-fast] Running: ${PM} ai:check:fast"
20
+ "${PM}" ai:check:fast
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ # ai-check.sh — full AI check pipeline entry point
3
+ #
4
+ # 配布される example。利用者がコピーして自プロジェクトの scripts/ または bin/ に配置し、
5
+ # `bash scripts/ai-check.sh` で呼ぶ薄いラッパー。
6
+ #
7
+ # 形名参同(package-templates/docs/philosophy/formal-name-match.md):
8
+ # 事前宣言した「名」(成功基準)と実測「形」(コマンド出力)を一括照合する。
9
+ # 本スクリプトは「形」の取得を担当し、npm scripts の ai:check に委譲する。
10
+ #
11
+ # PM 切り替え:
12
+ # デフォルト pnpm。npm/yarn/bun を使う場合は PM 環境変数で上書き:
13
+ # PM=npm bash ai-check.sh
14
+ # PM=yarn bash ai-check.sh
15
+ # PM=bun bash ai-check.sh
16
+
17
+ set -euo pipefail
18
+
19
+ PM="${PM:-pnpm}"
20
+
21
+ echo "[ai-check] Running: ${PM} ai:check"
22
+ "${PM}" ai:check
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "ai-check-template",
3
+ "version": "0.2.0-alpha.0",
4
+ "description": "Templates and CLI for verifying, repairing, and safely merging AI-generated code.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "keywords": [
8
+ "ai",
9
+ "testing",
10
+ "verification",
11
+ "cli",
12
+ "templates",
13
+ "github-actions",
14
+ "claude-code"
15
+ ],
16
+ "homepage": "https://github.com/heidayo/ai-check-template#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/heidayo/ai-check-template/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/heidayo/ai-check-template.git"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "bin": {
28
+ "ai-check-template": "bin/ai-check-template.mjs"
29
+ },
30
+ "scripts": {
31
+ "cli:help": "node bin/ai-check-template.mjs --help",
32
+ "test": "node --test tests/cli/*.test.mjs",
33
+ "test:cli": "node --test tests/cli/*.test.mjs"
34
+ },
35
+ "files": [
36
+ "bin/",
37
+ "src/",
38
+ "package-templates/",
39
+ "docs/cli.md",
40
+ "README.md",
41
+ "README-ja.md",
42
+ "LICENSE"
43
+ ],
44
+ "engines": {
45
+ "node": ">=20"
46
+ }
47
+ }
@@ -0,0 +1,104 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { scriptCommand, validatePackageManager } from "./package-manager.mjs";
4
+ import { fromTemplates } from "./utils.mjs";
5
+
6
+ export const DIRECT_CI_FILES = ["ai-check.yml", "ai-check-fast.yml"];
7
+ export const REUSABLE_CI_FILES = ["ai-quality-reusable.yml", "ai-quality-call.yml"];
8
+
9
+ const PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
10
+
11
+ const PNPM_SETUP_BLOCK = ` - uses: pnpm/action-setup@v4
12
+ with:
13
+ version: 10
14
+
15
+ - uses: actions/setup-node@v5
16
+ with:
17
+ node-version: 22
18
+ cache: pnpm
19
+
20
+ - name: Install dependencies
21
+ run: pnpm install --frozen-lockfile`;
22
+
23
+ const SETUP_BLOCKS = {
24
+ pnpm: PNPM_SETUP_BLOCK,
25
+ npm: ` - uses: actions/setup-node@v5
26
+ with:
27
+ node-version: 22
28
+ cache: npm
29
+
30
+ - name: Install dependencies
31
+ run: npm ci`,
32
+ yarn: ` - uses: actions/setup-node@v5
33
+ with:
34
+ node-version: 22
35
+ cache: yarn
36
+
37
+ - name: Enable Corepack
38
+ run: corepack enable
39
+
40
+ - name: Install dependencies
41
+ run: yarn install --immutable`,
42
+ bun: ` - uses: oven-sh/setup-bun@v2
43
+
44
+ - name: Install dependencies
45
+ run: bun install --frozen-lockfile`,
46
+ };
47
+
48
+ export function ciWorkflowFiles(ciMode) {
49
+ return ciMode === "direct"
50
+ ? DIRECT_CI_FILES
51
+ : ciMode === "reusable"
52
+ ? REUSABLE_CI_FILES
53
+ : [];
54
+ }
55
+
56
+ export function inactiveCiWorkflowFiles(ciMode) {
57
+ return ciMode === "direct"
58
+ ? REUSABLE_CI_FILES
59
+ : ciMode === "reusable"
60
+ ? DIRECT_CI_FILES
61
+ : [...DIRECT_CI_FILES, ...REUSABLE_CI_FILES];
62
+ }
63
+
64
+ export function ciWorkflowRelativePath(fileName) {
65
+ return path.join(".github", "workflows", fileName);
66
+ }
67
+
68
+ export async function renderedCiWorkflow(fileName, packageManager) {
69
+ const validatedPackageManager = validatePackageManager(packageManager);
70
+ const source = await fs.readFile(fromTemplates("ci-examples", "github-actions", fileName), "utf8");
71
+
72
+ if (DIRECT_CI_FILES.includes(fileName)) {
73
+ return renderDirectWorkflow(source, fileName, validatedPackageManager);
74
+ }
75
+
76
+ if (fileName === "ai-quality-call.yml") {
77
+ return renderReusableCaller(source, validatedPackageManager);
78
+ }
79
+
80
+ return source;
81
+ }
82
+
83
+ export async function isManagedCiWorkflowContent(fileName, content) {
84
+ const variants = await Promise.all(
85
+ PACKAGE_MANAGERS.map((packageManager) => renderedCiWorkflow(fileName, packageManager)),
86
+ );
87
+
88
+ return variants.includes(content);
89
+ }
90
+
91
+ function renderDirectWorkflow(source, fileName, packageManager) {
92
+ const scriptName = fileName === "ai-check-fast.yml" ? "ai:check:fast" : "ai:check";
93
+ const checkCommand = scriptCommand(packageManager, scriptName);
94
+
95
+ return source
96
+ .replace(PNPM_SETUP_BLOCK, SETUP_BLOCKS[packageManager])
97
+ .replaceAll(`pnpm ${scriptName}`, checkCommand);
98
+ }
99
+
100
+ function renderReusableCaller(source, packageManager) {
101
+ return source
102
+ .replace("package-manager: pnpm", `package-manager: ${packageManager}`)
103
+ .replace("check-command: pnpm ai:check", `check-command: ${scriptCommand(packageManager, "ai:check")}`);
104
+ }
@@ -0,0 +1,94 @@
1
+ import { scriptCommand, validatePackageManager } from "./package-manager.mjs";
2
+
3
+ const MANAGED_COMMANDS = new Map([
4
+ ["pnpm ai:check:fast", "ai:check:fast"],
5
+ ["pnpm ai:check", "ai:check"],
6
+ ]);
7
+
8
+ const MANAGED_SCRIPT_COMMANDS = new Set(
9
+ ["pnpm", "npm", "yarn", "bun"].flatMap((packageManager) => [
10
+ scriptCommand(packageManager, "ai:check:fast"),
11
+ scriptCommand(packageManager, "ai:check"),
12
+ ]),
13
+ );
14
+
15
+ export function renderClaudeHookSettings(fragment, packageManager) {
16
+ const validatedPackageManager = validatePackageManager(packageManager);
17
+
18
+ return {
19
+ ...fragment,
20
+ hooks: Object.fromEntries(
21
+ Object.entries(fragment.hooks ?? {}).map(([name, entries]) => [
22
+ name,
23
+ renderHookEntries(entries, validatedPackageManager),
24
+ ]),
25
+ ),
26
+ };
27
+ }
28
+
29
+ export function mergeRenderedClaudeHookEntries(currentEntries, expectedEntries) {
30
+ if (!Array.isArray(currentEntries)) {
31
+ return expectedEntries;
32
+ }
33
+
34
+ const customEntries = currentEntries
35
+ .map((entry) => customHookEntry(entry))
36
+ .filter(Boolean);
37
+
38
+ return [...expectedEntries, ...customEntries];
39
+ }
40
+
41
+ function renderHookEntries(entries, packageManager) {
42
+ if (!Array.isArray(entries)) {
43
+ return entries;
44
+ }
45
+
46
+ return entries.map((entry) => ({
47
+ ...entry,
48
+ hooks: Array.isArray(entry.hooks)
49
+ ? entry.hooks.map((hook) => renderHookCommand(hook, packageManager))
50
+ : entry.hooks,
51
+ }));
52
+ }
53
+
54
+ function renderHookCommand(hook, packageManager) {
55
+ if (!hook || typeof hook !== "object" || hook.type !== "command") {
56
+ return hook;
57
+ }
58
+
59
+ const scriptName = MANAGED_COMMANDS.get(hook.command);
60
+ if (!scriptName) {
61
+ return { ...hook };
62
+ }
63
+
64
+ return {
65
+ ...hook,
66
+ command: scriptCommand(packageManager, scriptName),
67
+ };
68
+ }
69
+
70
+ function customHookEntry(entry) {
71
+ if (!entry || typeof entry !== "object" || !Array.isArray(entry.hooks)) {
72
+ return null;
73
+ }
74
+
75
+ const hooks = entry.hooks.filter((hook) => !isManagedCommandHook(hook));
76
+ if (hooks.length === 0) {
77
+ return null;
78
+ }
79
+
80
+ return {
81
+ ...entry,
82
+ hooks,
83
+ };
84
+ }
85
+
86
+ function isManagedCommandHook(hook) {
87
+ return (
88
+ hook &&
89
+ typeof hook === "object" &&
90
+ hook.type === "command" &&
91
+ typeof hook.command === "string" &&
92
+ MANAGED_SCRIPT_COMMANDS.has(hook.command)
93
+ );
94
+ }