Hooks
What lumin-repo-lens runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
> /plugin marketplace add annyeong844/lumin-repo-lens > /plugin install lumin-repo-lens@annyeong844-marketplace
Ships with lumin-repo-lens. Installing the plugin gets these hooks.
What fires, and when
UserPromptSubmit
Fires before Claude sees each prompt you send. A plugin can use it to inject context, so the same instruction reaches the model every turn instead of only at session start.
node "${CLAUDE_PLUGIN_ROOT}/hooks/user-prompt-submit.mjs"
PreToolUse
- Matches
*node "${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-use.mjs"
PostToolBatch
node "${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-batch.mjs"
Stop
node "${CLAUDE_PLUGIN_ROOT}/hooks/stop.mjs"
Where it lives
- hooks/_runner-utils.mjsGitHub
Read the script
import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const PLUGIN_ROOT = path.resolve(__dirname, '..'); export function readJsonFromStdin() { let raw = ''; try { raw = readFileSync(0, 'utf8'); } catch { return null; } if (raw.trim().length === 0) return null; try { return JSON.parse(raw); } catch { return null; } } export function emitHookOutput(output) { if (!output || typeof output !== 'object' || Array.isArray(output)) return; writeFileSync(1, `${JSON.stringify(output)}\n`); } export async function importEngineModule(name) { const candidates = [ path.join(PLUGIN_ROOT, '_lib', name), path.join(PLUGIN_ROOT, 'skills', 'lumin-repo-lens', '_engine', 'lib', name), ]; for (const file of candidates) { if (!existsSync(file)) continue; return import(pathToFileURL(file).href); } throw new Error(`engine module not found: ${name}`); } export async function runHookMain(fn) { try { await fn(); } catch { // Hooks are advisory. Unexpected failures must not block the host action. } finally { process.exitCode = 0; } } - hooks/post-tool-batch.mjsRunsGitHub
Read the script
#!/usr/bin/env node import { emitHookOutput, importEngineModule, readJsonFromStdin, runHookMain, } from './_runner-utils.mjs'; await runHookMain(async () => { const payload = readJsonFromStdin(); if (!payload) return; const { processPostWriteLite } = await importEngineModule('hook-post-write-lite.mjs'); const result = processPostWriteLite(payload); emitHookOutput(result.output); }); - hooks/pre-tool-use.mjsRunsGitHub
Read the script
#!/usr/bin/env node import { emitHookOutput, importEngineModule, readJsonFromStdin, runHookMain, } from './_runner-utils.mjs'; const MUTATING_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']); await runHookMain(async () => { const payload = readJsonFromStdin(); if (!payload) return; const cwd = typeof payload.cwd === 'string' ? payload.cwd : process.cwd(); const { getToolTargetPath, resolveAuditRoot, safeRepoPathForToolInput, } = await importEngineModule('hook-path-safety.mjs'); const { safeSessionId, safeToolUseId, } = await importEngineModule('hook-id-safety.mjs'); const { capturePreimage } = await importEngineModule('hook-preimage-store.mjs'); const { drainDueEventReminders } = await importEngineModule('hook-event-drain.mjs'); const auditRoot = resolveAuditRoot(cwd); if (!auditRoot) return; const sid = safeSessionId(payload); if (MUTATING_TOOLS.has(payload.tool_name)) { try { const targetPath = getToolTargetPath(payload.tool_name, payload.tool_input ?? {}); const safe = safeRepoPathForToolInput(cwd, targetPath); if (safe.ok) { const tid = safeToolUseId({ tool_use_id: payload.tool_use_id, tool_name: payload.tool_name, tool_input: payload.tool_input ?? {}, }); capturePreimage({ auditRoot, sid, tid, safe }); } } catch { // Continue to drain reminders even when preimage capture cannot proceed. } } const drain = drainDueEventReminders(auditRoot, sid, { hookEventName: 'PreToolUse', }); emitHookOutput(drain.output); }); - hooks/stop.mjsRunsGitHub
Read the script
#!/usr/bin/env node import { importEngineModule, readJsonFromStdin, runHookMain, } from './_runner-utils.mjs'; await runHookMain(async () => { const payload = readJsonFromStdin(); if (!payload) return; const cwd = typeof payload.cwd === 'string' ? payload.cwd : process.cwd(); const { resolveAuditRoot } = await importEngineModule('hook-path-safety.mjs'); const { safeSessionId } = await importEngineModule('hook-id-safety.mjs'); const { observeStopAcknowledgements } = await importEngineModule('hook-ack-observer.mjs'); const auditRoot = resolveAuditRoot(cwd); if (!auditRoot) return; observeStopAcknowledgements(auditRoot, safeSessionId(payload), payload); }); - hooks/user-prompt-submit.mjsRunsGitHub
Read the script
#!/usr/bin/env node import { emitHookOutput, importEngineModule, readJsonFromStdin, runHookMain, } from './_runner-utils.mjs'; await runHookMain(async () => { const payload = readJsonFromStdin(); if (!payload) return; const cwd = typeof payload.cwd === 'string' ? payload.cwd : process.cwd(); const { resolveAuditRoot } = await importEngineModule('hook-path-safety.mjs'); const { safeSessionId } = await importEngineModule('hook-id-safety.mjs'); const { drainDueEventReminders } = await importEngineModule('hook-event-drain.mjs'); const auditRoot = resolveAuditRoot(cwd); if (!auditRoot) return; const drain = drainDueEventReminders(auditRoot, safeSessionId(payload), { hookEventName: 'UserPromptSubmit', }); emitHookOutput(drain.output); });
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
🇰🇷 한국어로 읽으시려면 → README.ko.md · 🇬🇧 English continues below. The kind little buddy that says "this already exists" before you write it again. Your repo's companion for vibe-coding sessions.
Repo: annyeong844/lumin-repo-lens

