@wooojin/forgen 0.1.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 (268) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/CHANGELOG.md +353 -0
  3. package/CONTRIBUTING.md +98 -0
  4. package/LICENSE +21 -0
  5. package/README.ja.md +469 -0
  6. package/README.ko.md +469 -0
  7. package/README.md +483 -0
  8. package/README.zh.md +469 -0
  9. package/agents/analyst.md +98 -0
  10. package/agents/architect.md +62 -0
  11. package/agents/code-reviewer.md +120 -0
  12. package/agents/code-simplifier.md +197 -0
  13. package/agents/critic.md +70 -0
  14. package/agents/debugger.md +117 -0
  15. package/agents/designer.md +131 -0
  16. package/agents/executor.md +54 -0
  17. package/agents/explore.md +145 -0
  18. package/agents/git-master.md +212 -0
  19. package/agents/performance-reviewer.md +172 -0
  20. package/agents/planner.md +29 -0
  21. package/agents/qa-tester.md +158 -0
  22. package/agents/refactoring-expert.md +168 -0
  23. package/agents/scientist.md +144 -0
  24. package/agents/security-reviewer.md +137 -0
  25. package/agents/test-engineer.md +153 -0
  26. package/agents/verifier.md +133 -0
  27. package/agents/writer.md +184 -0
  28. package/commands/api-design.md +268 -0
  29. package/commands/architecture-decision.md +314 -0
  30. package/commands/ci-cd.md +270 -0
  31. package/commands/code-review.md +233 -0
  32. package/commands/compound.md +117 -0
  33. package/commands/database.md +263 -0
  34. package/commands/debug-detective.md +99 -0
  35. package/commands/docker.md +274 -0
  36. package/commands/documentation.md +276 -0
  37. package/commands/ecomode.md +51 -0
  38. package/commands/frontend.md +271 -0
  39. package/commands/git-master.md +90 -0
  40. package/commands/incident-response.md +292 -0
  41. package/commands/migrate.md +101 -0
  42. package/commands/performance.md +288 -0
  43. package/commands/refactor.md +105 -0
  44. package/commands/security-review.md +288 -0
  45. package/commands/tdd.md +183 -0
  46. package/commands/testing-strategy.md +265 -0
  47. package/dist/cli.d.ts +2 -0
  48. package/dist/cli.js +295 -0
  49. package/dist/core/auto-compound-runner.d.ts +12 -0
  50. package/dist/core/auto-compound-runner.js +460 -0
  51. package/dist/core/config-hooks.d.ts +10 -0
  52. package/dist/core/config-hooks.js +112 -0
  53. package/dist/core/config-injector.d.ts +50 -0
  54. package/dist/core/config-injector.js +455 -0
  55. package/dist/core/doctor.d.ts +1 -0
  56. package/dist/core/doctor.js +163 -0
  57. package/dist/core/errors.d.ts +81 -0
  58. package/dist/core/errors.js +133 -0
  59. package/dist/core/global-config.d.ts +43 -0
  60. package/dist/core/global-config.js +25 -0
  61. package/dist/core/harness.d.ts +24 -0
  62. package/dist/core/harness.js +621 -0
  63. package/dist/core/init.d.ts +7 -0
  64. package/dist/core/init.js +37 -0
  65. package/dist/core/inspect-cli.d.ts +7 -0
  66. package/dist/core/inspect-cli.js +47 -0
  67. package/dist/core/legacy-detector.d.ts +33 -0
  68. package/dist/core/legacy-detector.js +66 -0
  69. package/dist/core/logger.d.ts +34 -0
  70. package/dist/core/logger.js +121 -0
  71. package/dist/core/mcp-config.d.ts +44 -0
  72. package/dist/core/mcp-config.js +177 -0
  73. package/dist/core/notepad.d.ts +31 -0
  74. package/dist/core/notepad.js +88 -0
  75. package/dist/core/paths.d.ts +85 -0
  76. package/dist/core/paths.js +101 -0
  77. package/dist/core/plugin-detector.d.ts +44 -0
  78. package/dist/core/plugin-detector.js +226 -0
  79. package/dist/core/runtime-detector.d.ts +8 -0
  80. package/dist/core/runtime-detector.js +49 -0
  81. package/dist/core/scope-resolver.d.ts +8 -0
  82. package/dist/core/scope-resolver.js +45 -0
  83. package/dist/core/session-logger.d.ts +6 -0
  84. package/dist/core/session-logger.js +111 -0
  85. package/dist/core/session-store.d.ts +28 -0
  86. package/dist/core/session-store.js +218 -0
  87. package/dist/core/settings-lock.d.ts +18 -0
  88. package/dist/core/settings-lock.js +125 -0
  89. package/dist/core/spawn.d.ts +3 -0
  90. package/dist/core/spawn.js +135 -0
  91. package/dist/core/types.d.ts +108 -0
  92. package/dist/core/types.js +1 -0
  93. package/dist/core/uninstall.d.ts +4 -0
  94. package/dist/core/uninstall.js +307 -0
  95. package/dist/core/v1-bootstrap.d.ts +26 -0
  96. package/dist/core/v1-bootstrap.js +155 -0
  97. package/dist/engine/compound-cli.d.ts +24 -0
  98. package/dist/engine/compound-cli.js +250 -0
  99. package/dist/engine/compound-extractor.d.ts +68 -0
  100. package/dist/engine/compound-extractor.js +860 -0
  101. package/dist/engine/compound-lifecycle.d.ts +32 -0
  102. package/dist/engine/compound-lifecycle.js +305 -0
  103. package/dist/engine/compound-loop.d.ts +32 -0
  104. package/dist/engine/compound-loop.js +511 -0
  105. package/dist/engine/match-eval-log.d.ts +139 -0
  106. package/dist/engine/match-eval-log.js +270 -0
  107. package/dist/engine/phrase-blocklist.d.ts +119 -0
  108. package/dist/engine/phrase-blocklist.js +208 -0
  109. package/dist/engine/skill-promoter.d.ts +20 -0
  110. package/dist/engine/skill-promoter.js +115 -0
  111. package/dist/engine/solution-format.d.ts +160 -0
  112. package/dist/engine/solution-format.js +432 -0
  113. package/dist/engine/solution-index.d.ts +13 -0
  114. package/dist/engine/solution-index.js +252 -0
  115. package/dist/engine/solution-matcher.d.ts +364 -0
  116. package/dist/engine/solution-matcher.js +656 -0
  117. package/dist/engine/solution-writer.d.ts +76 -0
  118. package/dist/engine/solution-writer.js +157 -0
  119. package/dist/engine/term-matcher.d.ts +81 -0
  120. package/dist/engine/term-matcher.js +268 -0
  121. package/dist/engine/term-normalizer.d.ts +116 -0
  122. package/dist/engine/term-normalizer.js +171 -0
  123. package/dist/fgx.d.ts +6 -0
  124. package/dist/fgx.js +42 -0
  125. package/dist/forge/cli.d.ts +11 -0
  126. package/dist/forge/cli.js +100 -0
  127. package/dist/forge/evidence-processor.d.ts +21 -0
  128. package/dist/forge/evidence-processor.js +87 -0
  129. package/dist/forge/mismatch-detector.d.ts +44 -0
  130. package/dist/forge/mismatch-detector.js +83 -0
  131. package/dist/forge/onboarding-cli.d.ts +6 -0
  132. package/dist/forge/onboarding-cli.js +89 -0
  133. package/dist/forge/onboarding.d.ts +25 -0
  134. package/dist/forge/onboarding.js +122 -0
  135. package/dist/hooks/compound-reflection.d.ts +45 -0
  136. package/dist/hooks/compound-reflection.js +82 -0
  137. package/dist/hooks/context-guard.d.ts +24 -0
  138. package/dist/hooks/context-guard.js +156 -0
  139. package/dist/hooks/dangerous-patterns.json +18 -0
  140. package/dist/hooks/db-guard.d.ts +17 -0
  141. package/dist/hooks/db-guard.js +105 -0
  142. package/dist/hooks/hook-config.d.ts +29 -0
  143. package/dist/hooks/hook-config.js +92 -0
  144. package/dist/hooks/hook-registry.d.ts +43 -0
  145. package/dist/hooks/hook-registry.js +31 -0
  146. package/dist/hooks/hooks-generator.d.ts +49 -0
  147. package/dist/hooks/hooks-generator.js +99 -0
  148. package/dist/hooks/intent-classifier.d.ts +12 -0
  149. package/dist/hooks/intent-classifier.js +62 -0
  150. package/dist/hooks/keyword-detector.d.ts +25 -0
  151. package/dist/hooks/keyword-detector.js +389 -0
  152. package/dist/hooks/notepad-injector.d.ts +18 -0
  153. package/dist/hooks/notepad-injector.js +51 -0
  154. package/dist/hooks/permission-handler.d.ts +14 -0
  155. package/dist/hooks/permission-handler.js +114 -0
  156. package/dist/hooks/post-tool-failure.d.ts +11 -0
  157. package/dist/hooks/post-tool-failure.js +118 -0
  158. package/dist/hooks/post-tool-handlers.d.ts +17 -0
  159. package/dist/hooks/post-tool-handlers.js +115 -0
  160. package/dist/hooks/post-tool-use.d.ts +29 -0
  161. package/dist/hooks/post-tool-use.js +151 -0
  162. package/dist/hooks/pre-compact.d.ts +10 -0
  163. package/dist/hooks/pre-compact.js +165 -0
  164. package/dist/hooks/pre-tool-use.d.ts +31 -0
  165. package/dist/hooks/pre-tool-use.js +325 -0
  166. package/dist/hooks/prompt-injection-filter.d.ts +56 -0
  167. package/dist/hooks/prompt-injection-filter.js +287 -0
  168. package/dist/hooks/rate-limiter.d.ts +21 -0
  169. package/dist/hooks/rate-limiter.js +86 -0
  170. package/dist/hooks/secret-filter.d.ts +14 -0
  171. package/dist/hooks/secret-filter.js +65 -0
  172. package/dist/hooks/session-recovery.d.ts +27 -0
  173. package/dist/hooks/session-recovery.js +406 -0
  174. package/dist/hooks/shared/atomic-write.d.ts +41 -0
  175. package/dist/hooks/shared/atomic-write.js +148 -0
  176. package/dist/hooks/shared/context-budget.d.ts +37 -0
  177. package/dist/hooks/shared/context-budget.js +45 -0
  178. package/dist/hooks/shared/file-lock.d.ts +56 -0
  179. package/dist/hooks/shared/file-lock.js +253 -0
  180. package/dist/hooks/shared/hook-response.d.ts +33 -0
  181. package/dist/hooks/shared/hook-response.js +62 -0
  182. package/dist/hooks/shared/injection-caps.d.ts +39 -0
  183. package/dist/hooks/shared/injection-caps.js +52 -0
  184. package/dist/hooks/shared/plugin-signal.d.ts +23 -0
  185. package/dist/hooks/shared/plugin-signal.js +104 -0
  186. package/dist/hooks/shared/read-stdin.d.ts +8 -0
  187. package/dist/hooks/shared/read-stdin.js +63 -0
  188. package/dist/hooks/shared/sanitize-id.d.ts +7 -0
  189. package/dist/hooks/shared/sanitize-id.js +9 -0
  190. package/dist/hooks/shared/sanitize.d.ts +7 -0
  191. package/dist/hooks/shared/sanitize.js +22 -0
  192. package/dist/hooks/skill-injector.d.ts +38 -0
  193. package/dist/hooks/skill-injector.js +285 -0
  194. package/dist/hooks/slop-detector.d.ts +18 -0
  195. package/dist/hooks/slop-detector.js +93 -0
  196. package/dist/hooks/solution-injector.d.ts +58 -0
  197. package/dist/hooks/solution-injector.js +436 -0
  198. package/dist/hooks/subagent-tracker.d.ts +10 -0
  199. package/dist/hooks/subagent-tracker.js +90 -0
  200. package/dist/i18n/index.d.ts +43 -0
  201. package/dist/i18n/index.js +224 -0
  202. package/dist/lib.d.ts +14 -0
  203. package/dist/lib.js +14 -0
  204. package/dist/mcp/server.d.ts +8 -0
  205. package/dist/mcp/server.js +40 -0
  206. package/dist/mcp/solution-reader.d.ts +90 -0
  207. package/dist/mcp/solution-reader.js +273 -0
  208. package/dist/mcp/tools.d.ts +16 -0
  209. package/dist/mcp/tools.js +302 -0
  210. package/dist/preset/facet-catalog.d.ts +17 -0
  211. package/dist/preset/facet-catalog.js +46 -0
  212. package/dist/preset/preset-manager.d.ts +31 -0
  213. package/dist/preset/preset-manager.js +111 -0
  214. package/dist/renderer/inspect-renderer.d.ts +11 -0
  215. package/dist/renderer/inspect-renderer.js +123 -0
  216. package/dist/renderer/rule-renderer.d.ts +18 -0
  217. package/dist/renderer/rule-renderer.js +159 -0
  218. package/dist/store/evidence-store.d.ts +23 -0
  219. package/dist/store/evidence-store.js +58 -0
  220. package/dist/store/profile-store.d.ts +12 -0
  221. package/dist/store/profile-store.js +53 -0
  222. package/dist/store/recommendation-store.d.ts +22 -0
  223. package/dist/store/recommendation-store.js +64 -0
  224. package/dist/store/rule-store.d.ts +22 -0
  225. package/dist/store/rule-store.js +62 -0
  226. package/dist/store/session-state-store.d.ts +11 -0
  227. package/dist/store/session-state-store.js +44 -0
  228. package/dist/store/types.d.ts +159 -0
  229. package/dist/store/types.js +7 -0
  230. package/hooks/hook-registry.json +21 -0
  231. package/hooks/hooks.json +185 -0
  232. package/package.json +89 -0
  233. package/plugin.json +20 -0
  234. package/scripts/postinstall.js +826 -0
  235. package/skills/api-design/SKILL.md +262 -0
  236. package/skills/architecture-decision/SKILL.md +309 -0
  237. package/skills/ci-cd/SKILL.md +264 -0
  238. package/skills/code-review/SKILL.md +228 -0
  239. package/skills/compound/SKILL.md +101 -0
  240. package/skills/database/SKILL.md +257 -0
  241. package/skills/debug-detective/SKILL.md +95 -0
  242. package/skills/docker/SKILL.md +268 -0
  243. package/skills/documentation/SKILL.md +270 -0
  244. package/skills/ecomode/SKILL.md +46 -0
  245. package/skills/frontend/SKILL.md +265 -0
  246. package/skills/git-master/SKILL.md +86 -0
  247. package/skills/incident-response/SKILL.md +286 -0
  248. package/skills/migrate/SKILL.md +96 -0
  249. package/skills/performance/SKILL.md +282 -0
  250. package/skills/refactor/SKILL.md +100 -0
  251. package/skills/security-review/SKILL.md +282 -0
  252. package/skills/tdd/SKILL.md +178 -0
  253. package/skills/testing-strategy/SKILL.md +260 -0
  254. package/starter-pack/solutions/starter-api-error-responses.md +37 -0
  255. package/starter-pack/solutions/starter-async-patterns.md +40 -0
  256. package/starter-pack/solutions/starter-caching-strategy.md +40 -0
  257. package/starter-pack/solutions/starter-code-review-checklist.md +39 -0
  258. package/starter-pack/solutions/starter-debugging-systematic.md +40 -0
  259. package/starter-pack/solutions/starter-dependency-injection.md +40 -0
  260. package/starter-pack/solutions/starter-error-handling-patterns.md +38 -0
  261. package/starter-pack/solutions/starter-git-atomic-commits.md +36 -0
  262. package/starter-pack/solutions/starter-input-validation.md +40 -0
  263. package/starter-pack/solutions/starter-n-plus-one-queries.md +37 -0
  264. package/starter-pack/solutions/starter-refactor-safely.md +38 -0
  265. package/starter-pack/solutions/starter-secret-management.md +37 -0
  266. package/starter-pack/solutions/starter-separation-of-concerns.md +36 -0
  267. package/starter-pack/solutions/starter-tdd-red-green-refactor.md +40 -0
  268. package/starter-pack/solutions/starter-typescript-strict-types.md +39 -0
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Forgen — PreToolUse: DB Guard Hook
4
+ *
5
+ * Bash 도구 실행 전 위험한 SQL 명령어를 감지하여 차단 또는 경고합니다.
6
+ */
7
+ export interface SqlPattern {
8
+ pattern: RegExp;
9
+ description: string;
10
+ severity: 'block' | 'warn';
11
+ }
12
+ export declare const DANGEROUS_SQL_PATTERNS: SqlPattern[];
13
+ /** SQL 명령어 위험도 검사 (순수 함수) */
14
+ export declare function checkDangerousSql(toolName: string, toolInput: Record<string, unknown> | string): {
15
+ action: 'block' | 'warn' | 'pass';
16
+ description?: string;
17
+ };
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Forgen — PreToolUse: DB Guard Hook
4
+ *
5
+ * Bash 도구 실행 전 위험한 SQL 명령어를 감지하여 차단 또는 경고합니다.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import { readStdinJSON } from './shared/read-stdin.js';
10
+ import { atomicWriteJSON } from './shared/atomic-write.js';
11
+ import { isHookEnabled } from './hook-config.js';
12
+ import { approve, approveWithWarning, deny, failOpen } from './shared/hook-response.js';
13
+ import { STATE_DIR } from '../core/paths.js';
14
+ const FAIL_COUNTER_PATH = path.join(STATE_DIR, 'db-guard-fail-counter.json');
15
+ const FAIL_CLOSE_THRESHOLD = 3;
16
+ export const DANGEROUS_SQL_PATTERNS = [
17
+ { pattern: /DROP\s+(TABLE|DATABASE|SCHEMA)/i, description: 'DROP TABLE/DATABASE/SCHEMA', severity: 'block' },
18
+ { pattern: /TRUNCATE\s+TABLE/i, description: 'TRUNCATE TABLE', severity: 'block' },
19
+ { pattern: /DELETE\s+FROM\s+\w+/i, description: 'DELETE FROM (WHERE clause required)', severity: 'block' },
20
+ { pattern: /ALTER\s+TABLE\s+\w+\s+DROP\s+COLUMN/i, description: 'ALTER TABLE DROP COLUMN', severity: 'warn' },
21
+ { pattern: /UPDATE\s+\w+\s+SET/i, description: 'UPDATE SET (WHERE clause required)', severity: 'warn' },
22
+ ];
23
+ /** SQL 명령어 위험도 검사 (순수 함수) */
24
+ export function checkDangerousSql(toolName, toolInput) {
25
+ if (toolName !== 'Bash')
26
+ return { action: 'pass' };
27
+ const command = typeof toolInput === 'string'
28
+ ? toolInput
29
+ : (toolInput.command ?? '');
30
+ // 주석 제거 후 SQL에 대해 패턴 매칭 (주석 안 키워드 오차단 방지)
31
+ const sqlWithoutComments = command
32
+ .replace(/--[^\n]*/g, '') // 라인 주석 제거
33
+ .replace(/\/\*[\s\S]*?\*\//g, ''); // 블록 주석 제거
34
+ for (const { pattern, description, severity } of DANGEROUS_SQL_PATTERNS) {
35
+ if (pattern.test(sqlWithoutComments)) {
36
+ // DELETE/UPDATE — SQL 본문에서 WHERE 절이 있으면 통과
37
+ if (/DELETE\s+FROM/i.test(sqlWithoutComments) && /\bWHERE\s+/i.test(sqlWithoutComments))
38
+ continue;
39
+ if (/UPDATE\s+\w+\s+SET/i.test(sqlWithoutComments) && /\bWHERE\s+/i.test(sqlWithoutComments))
40
+ continue;
41
+ return { action: severity, description };
42
+ }
43
+ }
44
+ return { action: 'pass' };
45
+ }
46
+ /** 연속 파싱 실패 카운터 */
47
+ function getAndIncrementFailCount() {
48
+ try {
49
+ let count = 0;
50
+ if (fs.existsSync(FAIL_COUNTER_PATH)) {
51
+ const data = JSON.parse(fs.readFileSync(FAIL_COUNTER_PATH, 'utf-8'));
52
+ count = (data.count ?? 0) + 1;
53
+ }
54
+ else {
55
+ count = 1;
56
+ }
57
+ atomicWriteJSON(FAIL_COUNTER_PATH, { count, updatedAt: new Date().toISOString() });
58
+ return count;
59
+ }
60
+ catch {
61
+ return 1;
62
+ }
63
+ }
64
+ function resetFailCount() {
65
+ try {
66
+ if (fs.existsSync(FAIL_COUNTER_PATH))
67
+ fs.unlinkSync(FAIL_COUNTER_PATH);
68
+ }
69
+ catch { /* fail counter reset failed — counter stays elevated but next parse success resets it */ }
70
+ }
71
+ async function main() {
72
+ const data = await readStdinJSON();
73
+ if (!data) {
74
+ const failCount = getAndIncrementFailCount();
75
+ if (failCount >= FAIL_CLOSE_THRESHOLD) {
76
+ console.log(deny(`[Forgen] DB Guard: stdin parse failed ${failCount} consecutive times — blocking for safety.`));
77
+ }
78
+ else {
79
+ process.stderr.write(`[ch-hook] db-guard stdin parse failed (${failCount}/${FAIL_CLOSE_THRESHOLD})\n`);
80
+ console.log(approve());
81
+ }
82
+ return;
83
+ }
84
+ resetFailCount();
85
+ if (!isHookEnabled('db-guard')) {
86
+ console.log(approve());
87
+ return;
88
+ }
89
+ const toolName = data.tool_name ?? data.toolName ?? '';
90
+ const toolInput = data.tool_input ?? data.toolInput ?? {};
91
+ const check = checkDangerousSql(toolName, toolInput);
92
+ if (check.action === 'block') {
93
+ console.log(deny(`[Forgen] Dangerous SQL blocked: ${check.description}`));
94
+ return;
95
+ }
96
+ if (check.action === 'warn') {
97
+ console.log(approveWithWarning(`<compound-sql-warning>\n[Forgen] ⚠ Dangerous SQL detected: ${check.description}\nProceed with caution.\n</compound-sql-warning>`));
98
+ return;
99
+ }
100
+ console.log(approve());
101
+ }
102
+ main().catch((e) => {
103
+ process.stderr.write(`[ch-hook] DB Guard error: ${e instanceof Error ? e.message : String(e)}\n`);
104
+ console.log(failOpen());
105
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Forgen — Hook Config Loader
3
+ *
4
+ * ~/.compound/hook-config.json 에서 훅별 설정을 읽어 반환합니다.
5
+ * 파일이 없거나 읽기에 실패하면 null 을 반환합니다 (failure-tolerant).
6
+ *
7
+ * 설정 형식 (hook-config.json):
8
+ * {
9
+ * "tiers": { "compound-core": { "enabled": true }, "safety": { "enabled": true }, "workflow": { "enabled": true } },
10
+ * "hooks": { "hookName": { "enabled": false, ...customConfig } },
11
+ * "hookName": { "enabled": false } // 레거시 호환 (hooks 키 없이 직접 지정)
12
+ * }
13
+ *
14
+ * 안전 보장:
15
+ * - compound-core 티어는 tiers 설정으로 비활성화 불가 (복리화 보호)
16
+ * - 개별 hooks.hookName.enabled: false 로만 비활성화 가능
17
+ */
18
+ /** 특정 훅의 설정을 반환합니다. 실패 시 null 반환. */
19
+ export declare function loadHookConfig(hookName: string): Record<string, unknown> | null;
20
+ /**
21
+ * 훅이 활성화되어 있는지 확인합니다.
22
+ *
23
+ * 우선순위:
24
+ * 1. hooks.hookName.enabled (개별 훅 설정)
25
+ * 2. tiers.tierName.enabled (티어 설정) — compound-core는 티어 비활성화 무시
26
+ * 3. hookName.enabled (레거시 형식)
27
+ * 4. 기본값 true (하위호환)
28
+ */
29
+ export declare function isHookEnabled(hookName: string): boolean;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Forgen — Hook Config Loader
3
+ *
4
+ * ~/.compound/hook-config.json 에서 훅별 설정을 읽어 반환합니다.
5
+ * 파일이 없거나 읽기에 실패하면 null 을 반환합니다 (failure-tolerant).
6
+ *
7
+ * 설정 형식 (hook-config.json):
8
+ * {
9
+ * "tiers": { "compound-core": { "enabled": true }, "safety": { "enabled": true }, "workflow": { "enabled": true } },
10
+ * "hooks": { "hookName": { "enabled": false, ...customConfig } },
11
+ * "hookName": { "enabled": false } // 레거시 호환 (hooks 키 없이 직접 지정)
12
+ * }
13
+ *
14
+ * 안전 보장:
15
+ * - compound-core 티어는 tiers 설정으로 비활성화 불가 (복리화 보호)
16
+ * - 개별 hooks.hookName.enabled: false 로만 비활성화 가능
17
+ */
18
+ import * as fs from 'node:fs';
19
+ import * as path from 'node:path';
20
+ import { HOOK_REGISTRY } from './hook-registry.js';
21
+ import { FORGEN_HOME } from '../core/paths.js';
22
+ const HOOK_CONFIG_PATH = path.join(FORGEN_HOME, 'hook-config.json');
23
+ /**
24
+ * 훅 → 티어 매핑 (hook-registry.ts에서 자동 파생).
25
+ * 이중 구현 방지: HOOK_REGISTRY가 단일 소스 오브 트루스.
26
+ */
27
+ const HOOK_TIER_MAP = Object.fromEntries(HOOK_REGISTRY.map(h => [h.name, h.tier]));
28
+ /** 프로세스 내 설정 캐시 (각 훅은 별도 프로세스이므로 수명 = 1회 실행) */
29
+ let _configCache;
30
+ /** 전체 설정 파일을 파싱합니다. 실패 시 null. 프로세스 내 캐싱. */
31
+ function loadFullConfig() {
32
+ if (_configCache !== undefined)
33
+ return _configCache;
34
+ try {
35
+ if (!fs.existsSync(HOOK_CONFIG_PATH)) {
36
+ _configCache = null;
37
+ return null;
38
+ }
39
+ _configCache = JSON.parse(fs.readFileSync(HOOK_CONFIG_PATH, 'utf-8'));
40
+ return _configCache;
41
+ }
42
+ catch {
43
+ _configCache = null;
44
+ return null;
45
+ }
46
+ }
47
+ /** 특정 훅의 설정을 반환합니다. 실패 시 null 반환. */
48
+ export function loadHookConfig(hookName) {
49
+ const all = loadFullConfig();
50
+ if (!all)
51
+ return null;
52
+ // v2 형식: hooks.hookName
53
+ const hooksSection = all.hooks;
54
+ if (hooksSection?.[hookName])
55
+ return hooksSection[hookName];
56
+ // 레거시 형식: 최상위에 hookName 직접 지정
57
+ const legacy = all[hookName];
58
+ return legacy ?? null;
59
+ }
60
+ /**
61
+ * 훅이 활성화되어 있는지 확인합니다.
62
+ *
63
+ * 우선순위:
64
+ * 1. hooks.hookName.enabled (개별 훅 설정)
65
+ * 2. tiers.tierName.enabled (티어 설정) — compound-core는 티어 비활성화 무시
66
+ * 3. hookName.enabled (레거시 형식)
67
+ * 4. 기본값 true (하위호환)
68
+ */
69
+ export function isHookEnabled(hookName) {
70
+ const all = loadFullConfig();
71
+ if (!all)
72
+ return true;
73
+ // 1) 개별 훅 설정 (v2: hooks 섹션)
74
+ const hooksSection = all.hooks;
75
+ if (hooksSection?.[hookName]?.enabled === false)
76
+ return false;
77
+ if (hooksSection?.[hookName]?.enabled === true)
78
+ return true;
79
+ // 2) 티어 설정 — compound-core는 절대 티어 비활성화로 끄지 않음
80
+ const tier = HOOK_TIER_MAP[hookName];
81
+ if (tier && tier !== 'compound-core') {
82
+ const tiers = all.tiers;
83
+ if (tiers?.[tier]?.enabled === false)
84
+ return false;
85
+ }
86
+ // 3) 레거시 형식 (최상위 hookName.enabled)
87
+ const legacy = all[hookName];
88
+ if (legacy?.enabled === false)
89
+ return false;
90
+ // 4) 기본값: 활성화
91
+ return true;
92
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Forgen — Hook Registry
3
+ *
4
+ * 모든 훅의 메타데이터를 중앙 관리합니다.
5
+ * 단일 소스 오브 트루스: hooks/hook-registry.json
6
+ * postinstall.js와 이 모듈이 동일한 JSON을 읽으므로 중복/불일치 방지.
7
+ *
8
+ * 3개 티어로 분류:
9
+ * - compound-core: 경험 축적 엔진 (항상 활성)
10
+ * - safety: 범용 안전 훅 (기본 활성, 개별 비활성 가능)
11
+ * - workflow: 워크플로우 스킬 훅 (다른 플러그인 감지 시 자동 비활성)
12
+ */
13
+ export type HookTier = 'compound-core' | 'safety' | 'workflow';
14
+ export type HookEventType = 'UserPromptSubmit' | 'SessionStart' | 'Stop' | 'PreToolUse' | 'PostToolUse' | 'PostToolUseFailure' | 'SubagentStart' | 'SubagentStop' | 'PreCompact' | 'PermissionRequest';
15
+ export interface HookEntry {
16
+ /** 고유 이름 (hook-config.json에서 사용) */
17
+ name: string;
18
+ /** 티어 분류 */
19
+ tier: HookTier;
20
+ /** Claude Code 훅 이벤트 */
21
+ event: HookEventType;
22
+ /** 도구명 매칭 패턴 (regex 또는 '*'). Best practice: 필요한 도구만 필터링. */
23
+ matcher: string;
24
+ /** 실행 스크립트 (dist/ 기준 상대 경로) */
25
+ script: string;
26
+ /** 타임아웃 (초) */
27
+ timeout: number;
28
+ /** compound 피드백 루프에 필수인 훅인지 */
29
+ compoundCritical: boolean;
30
+ }
31
+ /**
32
+ * 단일 소스 오브 트루스: hooks/hook-registry.json
33
+ *
34
+ * 순서가 중요함:
35
+ * - pre-tool-use는 db-guard/rate-limiter보다 앞에 위치
36
+ * (Code Reflection + permission hints 주입 타이밍)
37
+ * - 같은 이벤트 내 훅은 배열 순서대로 실행됨
38
+ */
39
+ export declare const HOOK_REGISTRY: HookEntry[];
40
+ /** 티어별 훅 목록 조회 */
41
+ export declare function getHooksByTier(tier: HookTier): HookEntry[];
42
+ /** compound-critical 훅만 조회 (이 훅들은 비활성화하면 복리화가 깨짐) */
43
+ export declare function getCompoundCriticalHooks(): HookEntry[];
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Forgen — Hook Registry
3
+ *
4
+ * 모든 훅의 메타데이터를 중앙 관리합니다.
5
+ * 단일 소스 오브 트루스: hooks/hook-registry.json
6
+ * postinstall.js와 이 모듈이 동일한 JSON을 읽으므로 중복/불일치 방지.
7
+ *
8
+ * 3개 티어로 분류:
9
+ * - compound-core: 경험 축적 엔진 (항상 활성)
10
+ * - safety: 범용 안전 훅 (기본 활성, 개별 비활성 가능)
11
+ * - workflow: 워크플로우 스킬 훅 (다른 플러그인 감지 시 자동 비활성)
12
+ */
13
+ import { createRequire } from 'node:module';
14
+ const require = createRequire(import.meta.url);
15
+ /**
16
+ * 단일 소스 오브 트루스: hooks/hook-registry.json
17
+ *
18
+ * 순서가 중요함:
19
+ * - pre-tool-use는 db-guard/rate-limiter보다 앞에 위치
20
+ * (Code Reflection + permission hints 주입 타이밍)
21
+ * - 같은 이벤트 내 훅은 배열 순서대로 실행됨
22
+ */
23
+ export const HOOK_REGISTRY = require('../../hooks/hook-registry.json');
24
+ /** 티어별 훅 목록 조회 */
25
+ export function getHooksByTier(tier) {
26
+ return HOOK_REGISTRY.filter(h => h.tier === tier);
27
+ }
28
+ /** compound-critical 훅만 조회 (이 훅들은 비활성화하면 복리화가 깨짐) */
29
+ export function getCompoundCriticalHooks() {
30
+ return HOOK_REGISTRY.filter(h => h.compoundCritical);
31
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Forgen — Dynamic hooks.json Generator
3
+ *
4
+ * hook-registry + hook-config + plugin-detector를 조합하여
5
+ * hooks/hooks.json을 동적으로 생성합니다.
6
+ *
7
+ * 생성 시점:
8
+ * - postinstall (npm install 후)
9
+ * - forgen config hooks (사용자 설정 변경 후)
10
+ * - forgen install (플러그인 설치 후)
11
+ */
12
+ interface HookCommand {
13
+ type: 'command';
14
+ command: string;
15
+ timeout: number;
16
+ }
17
+ interface HookMatcher {
18
+ matcher: string;
19
+ hooks: HookCommand[];
20
+ }
21
+ interface HooksJson {
22
+ description: string;
23
+ hooks: Record<string, HookMatcher[]>;
24
+ }
25
+ interface GenerateOptions {
26
+ /** 프로젝트 cwd (플러그인 감지에 사용) */
27
+ cwd?: string;
28
+ /** 훅 실행 스크립트의 루트 경로 */
29
+ pluginRoot?: string;
30
+ }
31
+ /**
32
+ * 활성 훅만 포함한 hooks.json 객체를 생성합니다.
33
+ *
34
+ * 동작:
35
+ * 1. 다른 플러그인 감지
36
+ * 2. 충돌 훅 식별
37
+ * 3. hook-config.json 설정 적용
38
+ * 4. 활성 훅만 hooks.json 구조로 변환
39
+ */
40
+ export declare function generateHooksJson(options?: GenerateOptions): HooksJson;
41
+ /**
42
+ * hooks.json 파일을 생성하여 저장합니다.
43
+ * @returns 생성된 훅 수와 비활성화된 훅 수
44
+ */
45
+ export declare function writeHooksJson(hooksDir: string, options?: GenerateOptions): {
46
+ active: number;
47
+ disabled: number;
48
+ };
49
+ export {};
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Forgen — Dynamic hooks.json Generator
3
+ *
4
+ * hook-registry + hook-config + plugin-detector를 조합하여
5
+ * hooks/hooks.json을 동적으로 생성합니다.
6
+ *
7
+ * 생성 시점:
8
+ * - postinstall (npm install 후)
9
+ * - forgen config hooks (사용자 설정 변경 후)
10
+ * - forgen install (플러그인 설치 후)
11
+ */
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import { HOOK_REGISTRY } from './hook-registry.js';
15
+ import { isHookEnabled } from './hook-config.js';
16
+ import { detectInstalledPlugins, getHookConflicts } from '../core/plugin-detector.js';
17
+ /**
18
+ * 활성 훅만 포함한 hooks.json 객체를 생성합니다.
19
+ *
20
+ * 동작:
21
+ * 1. 다른 플러그인 감지
22
+ * 2. 충돌 훅 식별
23
+ * 3. hook-config.json 설정 적용
24
+ * 4. 활성 훅만 hooks.json 구조로 변환
25
+ */
26
+ export function generateHooksJson(options) {
27
+ const cwd = options?.cwd;
28
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: CLAUDE_PLUGIN_ROOT is a Claude Code Plugin SDK variable resolved at runtime
29
+ const pluginRoot = options?.pluginRoot ?? '${CLAUDE_PLUGIN_ROOT}/dist';
30
+ // 다른 플러그인의 충돌 훅 감지
31
+ const hookConflicts = getHookConflicts(cwd);
32
+ const detectedPlugins = detectInstalledPlugins(cwd);
33
+ const hasOtherPlugins = detectedPlugins.length > 0;
34
+ // 활성 훅 필터링
35
+ const activeHooks = HOOK_REGISTRY.filter(hook => {
36
+ // 1) hook-config.json에서 명시적 비활성화
37
+ if (!isHookEnabled(hook.name))
38
+ return false;
39
+ // 2) 다른 플러그인과 충돌하는 workflow 훅은 자동 비활성
40
+ // (단, compound-critical 훅은 항상 유지)
41
+ if (hasOtherPlugins && hook.tier === 'workflow' && hookConflicts.has(hook.name) && !hook.compoundCritical) {
42
+ return false;
43
+ }
44
+ return true;
45
+ });
46
+ // 이벤트별로 그룹핑
47
+ const byEvent = new Map();
48
+ for (const hook of activeHooks) {
49
+ const list = byEvent.get(hook.event) ?? [];
50
+ list.push(hook);
51
+ byEvent.set(hook.event, list);
52
+ }
53
+ // hooks.json 구조 생성 — matcher별로 그룹핑 (best practice: 도구 필터링)
54
+ const hooks = {};
55
+ for (const [event, entries] of byEvent) {
56
+ // 같은 matcher끼리 그룹핑
57
+ const byMatcher = new Map();
58
+ for (const h of entries) {
59
+ const m = h.matcher ?? '*';
60
+ const group = byMatcher.get(m) ?? [];
61
+ group.push(h);
62
+ byMatcher.set(m, group);
63
+ }
64
+ hooks[event] = [...byMatcher.entries()].map(([matcher, matcherEntries]) => ({
65
+ matcher,
66
+ hooks: matcherEntries.map(h => {
67
+ // script에 인자가 포함된 경우 (예: "hooks/subagent-tracker.js start")
68
+ // 파일 경로와 인자를 분리해야 셸에서 ENOENT를 방지
69
+ const spaceIdx = h.script.indexOf(' ');
70
+ const command = spaceIdx === -1
71
+ ? `node "${pluginRoot}/${h.script}"`
72
+ : `node "${pluginRoot}/${h.script.slice(0, spaceIdx)}" ${h.script.slice(spaceIdx + 1)}`;
73
+ return { type: 'command', command, timeout: h.timeout };
74
+ }),
75
+ }));
76
+ }
77
+ return {
78
+ description: `Forgen harness hooks (auto-generated, ${activeHooks.length}/${HOOK_REGISTRY.length} active)`,
79
+ hooks,
80
+ };
81
+ }
82
+ /**
83
+ * hooks.json 파일을 생성하여 저장합니다.
84
+ * @returns 생성된 훅 수와 비활성화된 훅 수
85
+ */
86
+ export function writeHooksJson(hooksDir, options) {
87
+ const json = generateHooksJson(options);
88
+ // 활성 훅 수 계산
89
+ let active = 0;
90
+ for (const matchers of Object.values(json.hooks)) {
91
+ for (const m of matchers)
92
+ active += m.hooks.length;
93
+ }
94
+ const disabled = HOOK_REGISTRY.length - active;
95
+ const outputPath = path.join(hooksDir, 'hooks.json');
96
+ fs.mkdirSync(hooksDir, { recursive: true });
97
+ fs.writeFileSync(outputPath, `${JSON.stringify(json, null, 2)}\n`);
98
+ return { active, disabled };
99
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Forgen — Intent Classifier Hook
4
+ *
5
+ * Claude Code UserPromptSubmit 훅으로 등록.
6
+ * 사용자 프롬프트를 분석하여 의도를 분류하고, 의도별 가이드를 주입합니다.
7
+ *
8
+ * stdin: JSON { prompt: string, ... }
9
+ * stdout: JSON { result: "approve", message?: string }
10
+ */
11
+ export type Intent = 'implement' | 'debug' | 'refactor' | 'explain' | 'review' | 'explore' | 'design' | 'general';
12
+ export declare function classifyIntent(prompt: string): Intent;
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Forgen — Intent Classifier Hook
4
+ *
5
+ * Claude Code UserPromptSubmit 훅으로 등록.
6
+ * 사용자 프롬프트를 분석하여 의도를 분류하고, 의도별 가이드를 주입합니다.
7
+ *
8
+ * stdin: JSON { prompt: string, ... }
9
+ * stdout: JSON { result: "approve", message?: string }
10
+ */
11
+ import { readStdinJSON } from './shared/read-stdin.js';
12
+ import { isHookEnabled } from './hook-config.js';
13
+ import { approve, approveWithContext, failOpen } from './shared/hook-response.js';
14
+ const INTENT_RULES = [
15
+ { intent: 'implement', pattern: /(?:만들어|추가해|구현해|생성해|작성해|넣어|create|add|implement|build|write|make)\b/i },
16
+ { intent: 'debug', pattern: /(?:에러|버그|안돼|안\s*되|안\s*됨|왜|고쳐|수정해|fix|bug|error|debug|문제|실패|fail|crash|broken)/i },
17
+ { intent: 'refactor', pattern: /(?:리팩토링|리팩터|정리|개선|refactor|clean\s*up|improve|optimize|최적화)/i },
18
+ { intent: 'explain', pattern: /(?:설명|알려|뭐야|뭔가요|어떻게|explain|what\s+is|how\s+does|why\s+does|tell\s+me)/i },
19
+ { intent: 'review', pattern: /(?:리뷰|검토|review|check|audit|평가)/i },
20
+ { intent: 'explore', pattern: /(?:찾아|어디|검색|find|search|where|locate|grep|어디에|어디서)/i },
21
+ { intent: 'design', pattern: /(?:설계|아키텍처|구조|design|architect|structure|다이어그램|diagram)/i },
22
+ ];
23
+ const INTENT_HINTS = {
24
+ implement: 'Implementation task. Consider tests.',
25
+ debug: 'Debug mode. Approach: reproduce → isolate → fix.',
26
+ refactor: 'Refactoring task. Improve structure while preserving existing behavior.',
27
+ explain: 'Explanation request. Convey core concepts concisely.',
28
+ review: 'Code review. Classify feedback by severity.',
29
+ explore: 'Exploration task. Use Glob/Grep to find quickly.',
30
+ design: 'Design task. Specify trade-offs explicitly.',
31
+ general: 'General request.',
32
+ };
33
+ export function classifyIntent(prompt) {
34
+ for (const rule of INTENT_RULES) {
35
+ if (rule.pattern.test(prompt)) {
36
+ return rule.intent;
37
+ }
38
+ }
39
+ return 'general';
40
+ }
41
+ async function main() {
42
+ const input = await readStdinJSON();
43
+ if (!isHookEnabled('intent-classifier')) {
44
+ console.log(approve());
45
+ return;
46
+ }
47
+ if (!input?.prompt) {
48
+ console.log(approve());
49
+ return;
50
+ }
51
+ const intent = classifyIntent(input.prompt);
52
+ if (intent === 'general') {
53
+ console.log(approve());
54
+ return;
55
+ }
56
+ const hint = INTENT_HINTS[intent];
57
+ console.log(approveWithContext(`[intent: ${intent}] ${hint}`, 'UserPromptSubmit'));
58
+ }
59
+ main().catch((e) => {
60
+ process.stderr.write(`[ch-hook] ${e instanceof Error ? e.message : String(e)}\n`);
61
+ console.log(failOpen());
62
+ });
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Forgen — Keyword Detector Hook
4
+ *
5
+ * Claude Code UserPromptSubmit 훅으로 등록.
6
+ * 사용자 프롬프트에서 매직 키워드를 감지하여 해당 스킬을 주입합니다.
7
+ *
8
+ * stdin: JSON { prompt: string, ... }
9
+ * stdout: JSON { result: "block"|"approve", message?: string }
10
+ */
11
+ export interface KeywordMatch {
12
+ type: 'skill' | 'inject' | 'cancel';
13
+ keyword: string;
14
+ skill?: string;
15
+ prompt?: string;
16
+ message?: string;
17
+ }
18
+ export declare function shouldTrackWorkflowActivation(match: KeywordMatch): boolean;
19
+ export declare const KEYWORD_PATTERNS: Array<{
20
+ pattern: RegExp;
21
+ keyword: string;
22
+ type: 'skill' | 'inject' | 'cancel';
23
+ skill?: string;
24
+ }>;
25
+ export declare function detectKeyword(prompt: string): KeywordMatch | null;