@su-record/vibe 2.9.13 → 2.9.15
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.ko.md +2 -29
- package/README.md +2 -29
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +3 -3
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/postinstall/constants.d.ts.map +1 -1
- package/dist/cli/postinstall/constants.js +2 -0
- package/dist/cli/postinstall/constants.js.map +1 -1
- package/dist/cli/postinstall/main.d.ts.map +1 -1
- package/dist/cli/postinstall/main.js +62 -52
- package/dist/cli/postinstall/main.js.map +1 -1
- package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
- package/dist/cli/setup/ProjectSetup.js +15 -0
- package/dist/cli/setup/ProjectSetup.js.map +1 -1
- package/dist/cli/utils/cli-detector.d.ts +14 -1
- package/dist/cli/utils/cli-detector.d.ts.map +1 -1
- package/dist/cli/utils/cli-detector.js +36 -1
- package/dist/cli/utils/cli-detector.js.map +1 -1
- package/dist/infra/lib/llm-availability.d.ts +5 -2
- package/dist/infra/lib/llm-availability.d.ts.map +1 -1
- package/dist/infra/lib/llm-availability.js +11 -4
- package/dist/infra/lib/llm-availability.js.map +1 -1
- package/dist/infra/orchestrator/LLMCluster.d.ts +11 -2
- package/dist/infra/orchestrator/LLMCluster.d.ts.map +1 -1
- package/dist/infra/orchestrator/LLMCluster.js +25 -5
- package/dist/infra/orchestrator/LLMCluster.js.map +1 -1
- package/hooks/hooks.json +10 -58
- package/hooks/scripts/__tests__/pre-tool-guard.test.js +4 -3
- package/hooks/scripts/__tests__/sentinel-guard.test.js +6 -8
- package/hooks/scripts/figma-guard.js +2 -3
- package/hooks/scripts/lib/dispatcher.js +83 -0
- package/hooks/scripts/llm-orchestrate.js +84 -11
- package/hooks/scripts/post-edit-dispatcher.js +24 -0
- package/hooks/scripts/pre-tool-dispatcher.js +31 -0
- package/hooks/scripts/pre-tool-guard.js +2 -3
- package/hooks/scripts/prompt-dispatcher.js +5 -0
- package/hooks/scripts/sentinel-guard.js +2 -3
- package/hooks/scripts/stop-dispatcher.js +27 -0
- package/package.json +1 -1
- package/skills/rob-pike/SKILL.md +64 -0
- package/skills/systematic-debugging/SKILL.md +140 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI CLI 감지 유틸리티
|
|
3
3
|
*
|
|
4
|
-
* Codex CLI, Gemini CLI 설치 여부를 자동 감지하여
|
|
4
|
+
* Claude Code, Codex CLI, Gemini CLI 설치 여부를 자동 감지하여
|
|
5
5
|
* 설치된 CLI에만 설정을 적용
|
|
6
6
|
*/
|
|
7
7
|
export interface AiCliStatus {
|
|
@@ -9,6 +9,19 @@ export interface AiCliStatus {
|
|
|
9
9
|
configDir: string;
|
|
10
10
|
pluginDir?: string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Claude Code 설치 여부 감지
|
|
14
|
+
* - `which claude` 실행 가능 여부
|
|
15
|
+
* - `~/.claude/` 디렉토리 존재 여부
|
|
16
|
+
* 둘 중 하나만 true면 installed: true
|
|
17
|
+
*/
|
|
18
|
+
export declare function detectClaudeCli(): AiCliStatus;
|
|
19
|
+
/**
|
|
20
|
+
* Coco 설치 여부 감지
|
|
21
|
+
* - `COCO_HOME` 환경변수
|
|
22
|
+
* - `~/.coco/` 디렉토리 존재 여부
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectCocoCli(): AiCliStatus;
|
|
12
25
|
/**
|
|
13
26
|
* Codex CLI 설치 여부 감지
|
|
14
27
|
* - `which codex` 실행 가능 여부
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-detector.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/cli-detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAiB5C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,WAAW,CAgB7C"}
|
|
1
|
+
{"version":3,"file":"cli-detector.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/cli-detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,WAAW,CAgB7C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,WAAW,CAQ3C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAiB5C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,WAAW,CAgB7C"}
|
|
@@ -1,13 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI CLI 감지 유틸리티
|
|
3
3
|
*
|
|
4
|
-
* Codex CLI, Gemini CLI 설치 여부를 자동 감지하여
|
|
4
|
+
* Claude Code, Codex CLI, Gemini CLI 설치 여부를 자동 감지하여
|
|
5
5
|
* 설치된 CLI에만 설정을 적용
|
|
6
6
|
*/
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import os from 'os';
|
|
11
|
+
/**
|
|
12
|
+
* Claude Code 설치 여부 감지
|
|
13
|
+
* - `which claude` 실행 가능 여부
|
|
14
|
+
* - `~/.claude/` 디렉토리 존재 여부
|
|
15
|
+
* 둘 중 하나만 true면 installed: true
|
|
16
|
+
*/
|
|
17
|
+
export function detectClaudeCli() {
|
|
18
|
+
const configDir = path.join(os.homedir(), '.claude');
|
|
19
|
+
const hasDir = fs.existsSync(configDir);
|
|
20
|
+
let hasBin = false;
|
|
21
|
+
try {
|
|
22
|
+
execSync('which claude', { stdio: 'ignore' });
|
|
23
|
+
hasBin = true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// not found
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
installed: hasBin || hasDir,
|
|
30
|
+
configDir,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Coco 설치 여부 감지
|
|
35
|
+
* - `COCO_HOME` 환경변수
|
|
36
|
+
* - `~/.coco/` 디렉토리 존재 여부
|
|
37
|
+
*/
|
|
38
|
+
export function detectCocoCli() {
|
|
39
|
+
const configDir = process.env.COCO_HOME || path.join(os.homedir(), '.coco');
|
|
40
|
+
const hasDir = fs.existsSync(configDir);
|
|
41
|
+
return {
|
|
42
|
+
installed: hasDir,
|
|
43
|
+
configDir,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
11
46
|
/**
|
|
12
47
|
* Codex CLI 설치 여부 감지
|
|
13
48
|
* - `which codex` 실행 가능 여부
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-detector.js","sourceRoot":"","sources":["../../../src/cli/utils/cli-detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AAQpB;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAExC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,IAAI,MAAM;QAC3B,SAAS;QACT,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC;KACnD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAExC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,QAAQ,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,IAAI,MAAM;QAC3B,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"cli-detector.js","sourceRoot":"","sources":["../../../src/cli/utils/cli-detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AAQpB;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAExC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,QAAQ,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,IAAI,MAAM;QAC3B,SAAS;KACV,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAExC,OAAO;QACL,SAAS,EAAE,MAAM;QACjB,SAAS;KACV,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAExC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,IAAI,MAAM;QAC3B,SAAS;QACT,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC;KACnD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAExC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,QAAQ,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9C,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,IAAI,MAAM;QAC3B,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LLM 가용성 감지 유틸
|
|
3
3
|
*
|
|
4
|
-
* Codex/Gemini CLI 활성화 여부를 런타임에 판단.
|
|
4
|
+
* Claude/Codex/Gemini CLI 활성화 여부를 런타임에 판단.
|
|
5
5
|
* 결과는 프로세스당 1회 캐시.
|
|
6
6
|
*/
|
|
7
7
|
export interface LlmAvailability {
|
|
8
|
+
claude: boolean;
|
|
8
9
|
codex: boolean;
|
|
9
10
|
gemini: boolean;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
|
-
* Codex/Gemini CLI 가용성 감지 (캐시)
|
|
13
|
+
* Claude/Codex/Gemini CLI 가용성 감지 (캐시)
|
|
13
14
|
*/
|
|
14
15
|
export declare function detectLlmAvailability(): LlmAvailability;
|
|
16
|
+
/** Claude CLI 활성화 여부 */
|
|
17
|
+
export declare function isClaudeAvailable(): boolean;
|
|
15
18
|
/** Codex CLI 활성화 여부 */
|
|
16
19
|
export declare function isCodexAvailable(): boolean;
|
|
17
20
|
/** Gemini CLI 활성화 여부 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm-availability.d.ts","sourceRoot":"","sources":["../../../src/infra/lib/llm-availability.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAsBD;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,
|
|
1
|
+
{"version":3,"file":"llm-availability.d.ts","sourceRoot":"","sources":["../../../src/infra/lib/llm-availability.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;CACjB;AAsBD;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAqBvD;AAED,wBAAwB;AACxB,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,uBAAuB;AACvB,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAwB;AACxB,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,oBAAoB;AACpB,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LLM 가용성 감지 유틸
|
|
3
3
|
*
|
|
4
|
-
* Codex/Gemini CLI 활성화 여부를 런타임에 판단.
|
|
4
|
+
* Claude/Codex/Gemini CLI 활성화 여부를 런타임에 판단.
|
|
5
5
|
* 결과는 프로세스당 1회 캐시.
|
|
6
6
|
*/
|
|
7
7
|
import { execSync } from 'child_process';
|
|
@@ -28,22 +28,29 @@ function checkAuthExists(configDir, authFileName) {
|
|
|
28
28
|
return fs.existsSync(authPath);
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Codex/Gemini CLI 가용성 감지 (캐시)
|
|
31
|
+
* Claude/Codex/Gemini CLI 가용성 감지 (캐시)
|
|
32
32
|
*/
|
|
33
33
|
export function detectLlmAvailability() {
|
|
34
34
|
if (cached)
|
|
35
35
|
return cached;
|
|
36
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
36
37
|
const codexDir = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
37
38
|
const geminiDir = path.join(os.homedir(), '.gemini');
|
|
39
|
+
const claudeInstalled = checkCliInstalled('claude', claudeDir);
|
|
38
40
|
const codexInstalled = checkCliInstalled('codex', codexDir);
|
|
39
41
|
const geminiInstalled = checkCliInstalled('gemini', geminiDir);
|
|
40
|
-
// 설치 + 인증
|
|
42
|
+
// 설치 + 인증 존재 시 활성화로 판단
|
|
43
|
+
const claude = claudeInstalled;
|
|
41
44
|
const codex = codexInstalled && checkAuthExists(codexDir, 'auth.json');
|
|
42
45
|
const gemini = geminiInstalled && (checkAuthExists(geminiDir, 'auth.json') ||
|
|
43
46
|
!!process.env.GEMINI_API_KEY);
|
|
44
|
-
cached = { codex, gemini };
|
|
47
|
+
cached = { claude, codex, gemini };
|
|
45
48
|
return cached;
|
|
46
49
|
}
|
|
50
|
+
/** Claude CLI 활성화 여부 */
|
|
51
|
+
export function isClaudeAvailable() {
|
|
52
|
+
return detectLlmAvailability().claude;
|
|
53
|
+
}
|
|
47
54
|
/** Codex CLI 활성화 여부 */
|
|
48
55
|
export function isCodexAvailable() {
|
|
49
56
|
return detectLlmAvailability().codex;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm-availability.js","sourceRoot":"","sources":["../../../src/infra/lib/llm-availability.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"llm-availability.js","sourceRoot":"","sources":["../../../src/infra/lib/llm-availability.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AAQpB,IAAI,MAAM,GAA2B,IAAI,CAAC;AAE1C,SAAS,iBAAiB,CAAC,OAAe,EAAE,SAAiB;IAC3D,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACxC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC;IAC7C,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,QAAQ,CAAC,SAAS,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAClD,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IACD,OAAO,MAAM,IAAI,MAAM,CAAC;AAC1B,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB,EAAE,YAAoB;IAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACpD,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAErD,MAAM,eAAe,GAAG,iBAAiB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC/D,MAAM,cAAc,GAAG,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5D,MAAM,eAAe,GAAG,iBAAiB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAE/D,uBAAuB;IACvB,MAAM,MAAM,GAAG,eAAe,CAAC;IAC/B,MAAM,KAAK,GAAG,cAAc,IAAI,eAAe,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,eAAe,IAAI,CAChC,eAAe,CAAC,SAAS,EAAE,WAAW,CAAC;QACvC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAC7B,CAAC;IAEF,MAAM,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACnC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,iBAAiB;IAC/B,OAAO,qBAAqB,EAAE,CAAC,MAAM,CAAC;AACxC,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,gBAAgB;IAC9B,OAAO,qBAAqB,EAAE,CAAC,KAAK,CAAC;AACvC,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,iBAAiB;IAC/B,OAAO,qBAAqB,EAAE,CAAC,MAAM,CAAC;AACxC,CAAC;AAED,oBAAoB;AACpB,MAAM,UAAU,yBAAyB;IACvC,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LLMCluster - Multi-LLM 병렬 쿼리 및 상태 관리
|
|
3
|
-
* GPT
|
|
3
|
+
* GPT, Gemini, Claude 지원
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Multi-LLM 쿼리 결과
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export interface MultiLlmQueryResult {
|
|
9
9
|
gpt?: string;
|
|
10
10
|
gemini?: string;
|
|
11
|
+
claude?: string;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
14
|
* LLM 상태 결과
|
|
@@ -19,6 +20,9 @@ export interface LlmStatusResult {
|
|
|
19
20
|
gemini: {
|
|
20
21
|
available: boolean;
|
|
21
22
|
};
|
|
23
|
+
claude: {
|
|
24
|
+
available: boolean;
|
|
25
|
+
};
|
|
22
26
|
}
|
|
23
27
|
/**
|
|
24
28
|
* LLMCluster 설정
|
|
@@ -27,11 +31,15 @@ export interface LLMClusterOptions {
|
|
|
27
31
|
defaultSystemPrompt?: string;
|
|
28
32
|
}
|
|
29
33
|
/**
|
|
30
|
-
* LLMCluster - GPT / Gemini 병렬 쿼리 및 상태 관리
|
|
34
|
+
* LLMCluster - GPT / Gemini / Claude 병렬 쿼리 및 상태 관리
|
|
31
35
|
*/
|
|
32
36
|
export declare class LLMCluster {
|
|
33
37
|
private defaultSystemPrompt;
|
|
34
38
|
constructor(options?: LLMClusterOptions);
|
|
39
|
+
/**
|
|
40
|
+
* Claude CLI 오케스트레이션 (claude --print)
|
|
41
|
+
*/
|
|
42
|
+
claudeOrchestrate(prompt: string, systemPrompt?: string): Promise<string>;
|
|
35
43
|
/**
|
|
36
44
|
* GPT 오케스트레이션
|
|
37
45
|
*/
|
|
@@ -59,6 +67,7 @@ export declare class LLMCluster {
|
|
|
59
67
|
multiQuery(prompt: string, options?: {
|
|
60
68
|
useGpt?: boolean;
|
|
61
69
|
useGemini?: boolean;
|
|
70
|
+
useClaude?: boolean;
|
|
62
71
|
}): Promise<MultiLlmQueryResult>;
|
|
63
72
|
/**
|
|
64
73
|
* LLM 상태 확인 (전체)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMCluster.d.ts","sourceRoot":"","sources":["../../../src/infra/orchestrator/LLMCluster.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"LLMCluster.d.ts","sourceRoot":"","sources":["../../../src/infra/orchestrator/LLMCluster.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5B,MAAM,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/B,MAAM,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,mBAAmB,CAAS;gBAExB,OAAO,GAAE,iBAAsB;IAI3C;;OAEG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC;IAUlB;;OAEG;IACG,cAAc,CAClB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,MAAM,CAAC;IAQlB;;OAEG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAC/B,OAAO,CAAC,MAAM,CAAC;IAQlB;;OAEG;IACG,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIrD;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIlD;;OAEG;IACG,UAAU,CACd,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GACvE,OAAO,CAAC,mBAAmB,CAAC;IAkC/B;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,eAAe,CAAC;CAyB9C;AAKD,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,UAAU,CAKrE"}
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LLMCluster - Multi-LLM 병렬 쿼리 및 상태 관리
|
|
3
|
-
* GPT
|
|
3
|
+
* GPT, Gemini, Claude 지원
|
|
4
4
|
*/
|
|
5
|
+
import { execSync } from 'child_process';
|
|
5
6
|
import * as gptApi from '../lib/gpt/index.js';
|
|
6
7
|
import * as geminiApi from '../lib/gemini/index.js';
|
|
7
8
|
import { warnLog } from '../lib/utils.js';
|
|
8
9
|
/**
|
|
9
|
-
* LLMCluster - GPT / Gemini 병렬 쿼리 및 상태 관리
|
|
10
|
+
* LLMCluster - GPT / Gemini / Claude 병렬 쿼리 및 상태 관리
|
|
10
11
|
*/
|
|
11
12
|
export class LLMCluster {
|
|
12
13
|
defaultSystemPrompt;
|
|
13
14
|
constructor(options = {}) {
|
|
14
15
|
this.defaultSystemPrompt = options.defaultSystemPrompt ?? 'You are a helpful assistant.';
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Claude CLI 오케스트레이션 (claude --print)
|
|
19
|
+
*/
|
|
20
|
+
async claudeOrchestrate(prompt, systemPrompt) {
|
|
21
|
+
const sys = systemPrompt ?? this.defaultSystemPrompt;
|
|
22
|
+
const fullPrompt = `[System]\n${sys}\n\n[User]\n${prompt}`;
|
|
23
|
+
const result = execSync(`claude --print --dangerously-skip-permissions`, { input: fullPrompt, timeout: 180000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
24
|
+
return result.trim();
|
|
25
|
+
}
|
|
16
26
|
/**
|
|
17
27
|
* GPT 오케스트레이션
|
|
18
28
|
*/
|
|
@@ -42,7 +52,7 @@ export class LLMCluster {
|
|
|
42
52
|
* 멀티 LLM 병렬 쿼리
|
|
43
53
|
*/
|
|
44
54
|
async multiQuery(prompt, options) {
|
|
45
|
-
const { useGpt = true, useGemini = true } = options || {};
|
|
55
|
+
const { useGpt = true, useGemini = true, useClaude = false } = options || {};
|
|
46
56
|
const results = {};
|
|
47
57
|
const promises = [];
|
|
48
58
|
if (useGpt) {
|
|
@@ -55,6 +65,11 @@ export class LLMCluster {
|
|
|
55
65
|
.then(r => { results.gemini = r; })
|
|
56
66
|
.catch(e => { warnLog('Gemini query failed in multiLlm', e); }));
|
|
57
67
|
}
|
|
68
|
+
if (useClaude) {
|
|
69
|
+
promises.push(this.claudeOrchestrate(prompt)
|
|
70
|
+
.then(r => { results.claude = r; })
|
|
71
|
+
.catch(e => { warnLog('Claude query failed in multiLlm', e); }));
|
|
72
|
+
}
|
|
58
73
|
await Promise.all(promises);
|
|
59
74
|
return results;
|
|
60
75
|
}
|
|
@@ -64,18 +79,23 @@ export class LLMCluster {
|
|
|
64
79
|
async checkStatus() {
|
|
65
80
|
let gptAvailable = false;
|
|
66
81
|
let geminiAvailable = false;
|
|
82
|
+
let claudeAvailable = false;
|
|
67
83
|
const promises = [
|
|
68
84
|
this.gptOrchestrate('ping', 'Reply with pong')
|
|
69
85
|
.then(() => { gptAvailable = true; })
|
|
70
86
|
.catch(e => { warnLog('GPT status check failed', e); }),
|
|
71
87
|
this.geminiOrchestrate('ping', 'Reply with pong')
|
|
72
88
|
.then(() => { geminiAvailable = true; })
|
|
73
|
-
.catch(e => { warnLog('Gemini status check failed', e); })
|
|
89
|
+
.catch(e => { warnLog('Gemini status check failed', e); }),
|
|
90
|
+
this.claudeOrchestrate('ping', 'Reply with pong')
|
|
91
|
+
.then(() => { claudeAvailable = true; })
|
|
92
|
+
.catch(e => { warnLog('Claude status check failed', e); }),
|
|
74
93
|
];
|
|
75
94
|
await Promise.all(promises);
|
|
76
95
|
return {
|
|
77
96
|
gpt: { available: gptAvailable },
|
|
78
|
-
gemini: { available: geminiAvailable }
|
|
97
|
+
gemini: { available: geminiAvailable },
|
|
98
|
+
claude: { available: claudeAvailable },
|
|
79
99
|
};
|
|
80
100
|
}
|
|
81
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMCluster.js","sourceRoot":"","sources":["../../../src/infra/orchestrator/LLMCluster.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAC;AAC9C,OAAO,KAAK,SAAS,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"LLMCluster.js","sourceRoot":"","sources":["../../../src/infra/orchestrator/LLMCluster.ts"],"names":[],"mappings":"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"}
|
package/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "
|
|
2
|
+
"_comment": "Dispatcher pattern — PreToolUse/PostToolUse/Stop는 단일 디스패처가 stdin을 한 번 읽어 순차 실행 (병렬 spawn 폭주 제거, cascade 격리, config.hooks.{name}.enabled 토글 지원). UserPromptSubmit은 VIBE_HOOK_DEPTH 재귀 가드 탑재된 prompt-dispatcher 사용.",
|
|
3
3
|
"permissions": {
|
|
4
4
|
"allow": [],
|
|
5
5
|
"deny": [],
|
|
@@ -16,91 +16,55 @@
|
|
|
16
16
|
]
|
|
17
17
|
}
|
|
18
18
|
],
|
|
19
|
-
"_comment_PreToolUse": "WHY sentinel-guard runs before pre-tool-guard: sentinel blocks catastrophic commands (rm -rf, etc.) early; pre-tool-guard handles project-specific restrictions. Order matters — fail fast on danger.",
|
|
20
19
|
"PreToolUse": [
|
|
21
20
|
{
|
|
22
21
|
"matcher": "Bash",
|
|
23
22
|
"hooks": [
|
|
24
23
|
{
|
|
25
24
|
"type": "command",
|
|
26
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"type": "command",
|
|
30
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/pre-tool-guard.js Bash"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"type": "command",
|
|
34
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/command-log.js"
|
|
25
|
+
"command": "node {{VIBE_PATH}}/hooks/scripts/pre-tool-dispatcher.js Bash"
|
|
35
26
|
}
|
|
36
27
|
]
|
|
37
28
|
},
|
|
38
29
|
{
|
|
39
|
-
"matcher": "
|
|
30
|
+
"matcher": "Edit",
|
|
40
31
|
"hooks": [
|
|
41
32
|
{
|
|
42
33
|
"type": "command",
|
|
43
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/
|
|
34
|
+
"command": "node {{VIBE_PATH}}/hooks/scripts/pre-tool-dispatcher.js Edit"
|
|
44
35
|
}
|
|
45
36
|
]
|
|
46
37
|
},
|
|
47
38
|
{
|
|
48
|
-
"matcher": "
|
|
39
|
+
"matcher": "Write",
|
|
49
40
|
"hooks": [
|
|
50
41
|
{
|
|
51
42
|
"type": "command",
|
|
52
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"type": "command",
|
|
56
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/pre-tool-guard.js Edit"
|
|
43
|
+
"command": "node {{VIBE_PATH}}/hooks/scripts/pre-tool-dispatcher.js Write"
|
|
57
44
|
}
|
|
58
45
|
]
|
|
59
46
|
},
|
|
60
47
|
{
|
|
61
|
-
"matcher": "
|
|
48
|
+
"matcher": "mcp__github__create_pull_request",
|
|
62
49
|
"hooks": [
|
|
63
50
|
{
|
|
64
51
|
"type": "command",
|
|
65
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"type": "command",
|
|
69
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/pre-tool-guard.js Write"
|
|
52
|
+
"command": "node {{VIBE_PATH}}/hooks/scripts/pr-test-gate.js"
|
|
70
53
|
}
|
|
71
54
|
]
|
|
72
55
|
}
|
|
73
56
|
],
|
|
74
|
-
"_comment_PostToolUse": "WHY ordering: format first (normalize style), then lint/check (catch issues on clean code), then test (verify behavior). This chain ensures review-ready code on every edit.",
|
|
75
57
|
"PostToolUse": [
|
|
76
58
|
{
|
|
77
59
|
"matcher": "Write|Edit",
|
|
78
60
|
"hooks": [
|
|
79
61
|
{
|
|
80
62
|
"type": "command",
|
|
81
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"type": "command",
|
|
85
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/code-check.js"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"type": "command",
|
|
89
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/auto-test.js"
|
|
90
|
-
}
|
|
91
|
-
]
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"matcher": "Edit",
|
|
95
|
-
"hooks": [
|
|
96
|
-
{
|
|
97
|
-
"type": "command",
|
|
98
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/post-edit.js"
|
|
63
|
+
"command": "node {{VIBE_PATH}}/hooks/scripts/post-edit-dispatcher.js"
|
|
99
64
|
}
|
|
100
65
|
]
|
|
101
66
|
}
|
|
102
67
|
],
|
|
103
|
-
"_comment_UserPromptSubmit": "WHY interrupt echo is first: It injects a cancellation signal into the context BEFORE the dispatcher processes the new prompt, preventing the LLM from resuming cancelled work.",
|
|
104
68
|
"UserPromptSubmit": [
|
|
105
69
|
{
|
|
106
70
|
"hooks": [
|
|
@@ -153,19 +117,7 @@
|
|
|
153
117
|
"hooks": [
|
|
154
118
|
{
|
|
155
119
|
"type": "command",
|
|
156
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
"type": "command",
|
|
160
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/stop-notify.js"
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
"type": "command",
|
|
164
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/auto-commit.js"
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
"type": "command",
|
|
168
|
-
"command": "node {{VIBE_PATH}}/hooks/scripts/devlog-gen.js"
|
|
120
|
+
"command": "node {{VIBE_PATH}}/hooks/scripts/stop-dispatcher.js"
|
|
169
121
|
}
|
|
170
122
|
]
|
|
171
123
|
}
|
|
@@ -23,13 +23,14 @@ function runGuard({ args = [] } = {}) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Run pre-tool-guard.js with stdin JSON payload
|
|
26
|
+
* Run pre-tool-guard.js with stdin JSON payload.
|
|
27
|
+
* 스크립트가 fs.readSync(0, ...)로 stdin을 읽으므로 execFileSync input 옵션이 동작.
|
|
27
28
|
*/
|
|
28
29
|
function runGuardWithStdin(payload) {
|
|
29
30
|
const json = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
30
|
-
const escaped = json.replace(/'/g, "'\\''");
|
|
31
31
|
try {
|
|
32
|
-
const stdout =
|
|
32
|
+
const stdout = execFileSync('node', [SCRIPT], {
|
|
33
|
+
input: json,
|
|
33
34
|
encoding: 'utf-8',
|
|
34
35
|
timeout: 5000,
|
|
35
36
|
});
|
|
@@ -23,16 +23,14 @@ function runGuard(args = []) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Run sentinel-guard.js with stdin JSON payload
|
|
27
|
-
*
|
|
28
|
-
* a real pipe — execFileSync input option does not work.
|
|
26
|
+
* Run sentinel-guard.js with stdin JSON payload.
|
|
27
|
+
* 스크립트가 fs.readSync(0, ...)로 stdin을 읽으므로 execFileSync input 옵션이 동작.
|
|
29
28
|
*/
|
|
30
29
|
function runGuardWithStdin(payload) {
|
|
31
30
|
const json = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
32
|
-
// Escape single quotes in JSON for shell safety
|
|
33
|
-
const escaped = json.replace(/'/g, "'\\''");
|
|
34
31
|
try {
|
|
35
|
-
const stdout =
|
|
32
|
+
const stdout = execFileSync('node', [SCRIPT], {
|
|
33
|
+
input: json,
|
|
36
34
|
encoding: 'utf-8',
|
|
37
35
|
timeout: 5000,
|
|
38
36
|
});
|
|
@@ -194,9 +192,9 @@ describe('sentinel-guard', () => {
|
|
|
194
192
|
tool_name: 'Write',
|
|
195
193
|
tool_input: { file_path: 'src/infra/lib/autonomy/x.ts' },
|
|
196
194
|
});
|
|
197
|
-
const escaped = payload.replace(/'/g, "'\\''");
|
|
198
195
|
try {
|
|
199
|
-
|
|
196
|
+
execFileSync('node', [SCRIPT, 'Read', '{}'], {
|
|
197
|
+
input: payload,
|
|
200
198
|
encoding: 'utf-8',
|
|
201
199
|
timeout: 5000,
|
|
202
200
|
});
|
|
@@ -26,10 +26,9 @@ import os from 'os';
|
|
|
26
26
|
function readStdinSync() {
|
|
27
27
|
try {
|
|
28
28
|
if (process.stdin.isTTY) return null;
|
|
29
|
-
|
|
29
|
+
// fd 0을 직접 사용 (Windows는 '/dev/stdin'이 없음)
|
|
30
30
|
const buf = Buffer.alloc(1024 * 1024); // 1MB
|
|
31
|
-
const bytesRead = fs.readSync(
|
|
32
|
-
fs.closeSync(fd);
|
|
31
|
+
const bytesRead = fs.readSync(0, buf, 0, buf.length, null);
|
|
33
32
|
if (bytesRead > 0) {
|
|
34
33
|
return JSON.parse(buf.toString('utf-8', 0, bytesRead));
|
|
35
34
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook dispatcher library — 여러 hook script를 단일 이벤트에서 직렬 실행.
|
|
3
|
+
*
|
|
4
|
+
* 목적:
|
|
5
|
+
* - 동일 이벤트에 등록된 N개 스크립트의 **병렬 spawn 폭주**를 순차화
|
|
6
|
+
* - stdin을 한 번만 읽어 각 자식에 그대로 pipe (중복 파싱/읽기 방지)
|
|
7
|
+
* - config.hooks[name].enabled 로 개별 토글
|
|
8
|
+
* - 한 스크립트 실패가 다음 실행을 막지 않도록 cascade 격리
|
|
9
|
+
* - PreToolUse 계열: 자식이 exit 2(deny)면 즉시 상위에 전파
|
|
10
|
+
*/
|
|
11
|
+
import { spawn } from 'child_process';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const SCRIPTS_DIR = path.resolve(__dirname, '..');
|
|
18
|
+
|
|
19
|
+
function loadHookConfig() {
|
|
20
|
+
try {
|
|
21
|
+
const configPath = path.join(
|
|
22
|
+
process.env.CLAUDE_PROJECT_DIR || process.cwd(),
|
|
23
|
+
'.claude', 'vibe', 'config.json'
|
|
24
|
+
);
|
|
25
|
+
if (!fs.existsSync(configPath)) return {};
|
|
26
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8')).hooks || {};
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isEnabled(hookConfig, name) {
|
|
33
|
+
const entry = hookConfig[name];
|
|
34
|
+
if (entry && typeof entry === 'object' && entry.enabled === false) return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function readStdin() {
|
|
39
|
+
if (process.stdin.isTTY) return '';
|
|
40
|
+
let data = '';
|
|
41
|
+
for await (const chunk of process.stdin) data += chunk;
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 단일 스크립트 실행. stdin을 통해 입력 전달, stdout은 메인 stdout으로 통과.
|
|
47
|
+
* @returns {Promise<number>} exit code
|
|
48
|
+
*/
|
|
49
|
+
function runScript(scriptName, args, stdinData, timeoutMs) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const scriptPath = path.join(SCRIPTS_DIR, scriptName);
|
|
52
|
+
const proc = spawn(process.execPath, [scriptPath, ...args], {
|
|
53
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
54
|
+
timeout: timeoutMs,
|
|
55
|
+
});
|
|
56
|
+
if (stdinData) proc.stdin.end(stdinData);
|
|
57
|
+
else proc.stdin.end();
|
|
58
|
+
proc.on('close', (code) => resolve(code ?? 0));
|
|
59
|
+
proc.on('error', () => resolve(1));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 디스패처 실행.
|
|
65
|
+
* @param {Array<{name: string, script: string, args?: string[], denyOnExit2?: boolean, timeoutMs?: number}>} steps
|
|
66
|
+
*/
|
|
67
|
+
export async function dispatch(steps) {
|
|
68
|
+
const stdinData = await readStdin();
|
|
69
|
+
const hookConfig = loadHookConfig();
|
|
70
|
+
|
|
71
|
+
for (const step of steps) {
|
|
72
|
+
if (!isEnabled(hookConfig, step.name)) continue;
|
|
73
|
+
const code = await runScript(
|
|
74
|
+
step.script,
|
|
75
|
+
step.args || [],
|
|
76
|
+
stdinData,
|
|
77
|
+
step.timeoutMs || 30000
|
|
78
|
+
);
|
|
79
|
+
if (step.denyOnExit2 && code === 2) {
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|