(cwd: string)
| 268 | * — into QodeX's flat `{ Event: [{ matcher, command }] }`. |
| 269 | */ |
| 270 | export async function loadClaudeCodePluginHooks(cwd: string): Promise<HooksConfig> { |
| 271 | if (disabled()) return {}; |
| 272 | const out: HooksConfig = {}; |
| 273 | let plugins: Array<{ name: string; path: string }> = []; |
| 274 | try { plugins = await relevantPluginPaths(cwd); } catch { return {}; } |
| 275 | for (const p of plugins) { |
| 276 | const manifest = await readJsonIfExists(path.join(p.path, '.claude-plugin', 'plugin.json')); |
| 277 | const hooksFile = await readJsonIfExists(path.join(p.path, 'hooks', 'hooks.json')); |
| 278 | const raw = manifest?.hooks && typeof manifest.hooks === 'object' ? manifest.hooks |
| 279 | : hooksFile?.hooks && typeof hooksFile.hooks === 'object' ? hooksFile.hooks |
| 280 | : hooksFile ?? null; |
| 281 | if (!raw || typeof raw !== 'object') continue; |
| 282 | for (const event of SUPPORTED_HOOK_EVENTS) { |
| 283 | const groups = (raw as any)[event]; |
| 284 | if (!Array.isArray(groups)) continue; |
| 285 | for (const group of groups) { |
| 286 | const matcher: string | undefined = typeof group?.matcher === 'string' ? group.matcher : undefined; |
| 287 | const hooksArr = Array.isArray(group?.hooks) ? group.hooks : [group]; |
| 288 | for (const h of hooksArr) { |
| 289 | if (!h || (h.type && h.type !== 'command') || typeof h.command !== 'string') continue; |
| 290 | const hook: HookConfig = { |
| 291 | command: substitutePluginRoot(h.command, p.path), |
| 292 | ...(matcher ? { matcher } : {}), |
| 293 | ...(typeof h.timeout === 'number' ? { timeout: h.timeout } : {}), |
| 294 | name: `${p.name}:${event}`, |
| 295 | // Imported hooks are informational by default — never silently veto the user's tools. |
| 296 | blocking: false, |
| 297 | }; |
| 298 | (out[event] ??= []).push(hook); |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | return out; |
| 304 | } |
| 305 | |
| 306 | /** Merge plugin MCP servers into config.mcp.servers (mutates). User config wins. Returns count added. */ |
| 307 | export async function mergeClaudeCodePluginMcp(config: QodexConfig, cwd: string): Promise<number> { |
no test coverage detected