#!/usr/bin/env node const fs = require('fs'); const path = require('path'); function readStdin() { return fs.readFileSync(0, 'utf8'); } function isInside(parent, target) { const relative = path.relative(parent, target); return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)); } function stripQuotes(value) { return value.replace(/^["']|["']$/g, ''); } function collectPathStrings(value, key = '', output = []) { if (value == null) { return output; } if (typeof value === 'string') { if (/path|file/i.test(key)) { output.push(value); } return output; } if (Array.isArray(value)) { value.forEach((item) => collectPathStrings(item, key, output)); return output; } if (typeof value === 'object') { Object.entries(value).forEach(([childKey, childValue]) => { collectPathStrings(childValue, childKey, output); }); } return output; } function resolveCandidate(candidate, rootDir) { const cleaned = stripQuotes(String(candidate).trim()); if (!cleaned || /^(https?:|file:|data:)/i.test(cleaned)) { return null; } const resolved = path.isAbsolute(cleaned) ? path.resolve(cleaned) : path.resolve(rootDir, cleaned); return resolved; } function commandTouchesRaw(command, rawDir) { if (!command || typeof command !== 'string') { return false; } const normalizedRaw = rawDir.toLowerCase().replace(/\//g, path.sep); const lower = command.toLowerCase(); const mentionsRaw = lower.includes(normalizedRaw.toLowerCase()) || /(^|[\s"'`])raw([\\/]|$)/i.test(command); if (!mentionsRaw) { return false; } return /(remove-item|move-item|copy-item|set-content|add-content|out-file|new-item|\brm\b|\bdel\b|\berase\b|\bmv\b|\bmove\b|\bcp\b|\bcopy\b|>>|(?(?!\d)|sed\s+-i|perl\s+-pi|\btouch\b)/i.test(command); } function deny(reason) { process.stdout.write(JSON.stringify({ decision: 'deny', reason })); } try { const input = JSON.parse(readStdin() || '{}'); const projectDir = process.env.GEMINI_PROJECT_DIR || input.cwd || process.cwd(); const rawDir = path.resolve(projectDir, 'raw'); const toolName = input.tool_name || ''; const toolInput = input.tool_input || {}; if (toolName === 'run_shell_command') { const command = toolInput.command || toolInput.cmd || ''; if (commandTouchesRaw(command, rawDir)) { deny('raw/는 불변 원본이다. raw/를 수정하지 말고, 필요하면 wiki/ 또는 output/에 결과를 작성하라.'); process.exit(0); } process.exit(0); } const candidates = collectPathStrings(toolInput); const targets = candidates .map((candidate) => resolveCandidate(candidate, projectDir)) .filter(Boolean); const rawTargets = targets.filter((target) => isInside(rawDir, target)); if (rawTargets.length > 0) { deny('raw/는 불변 원본이다. 해당 경로를 수정하지 말고, 요약·해석·구조화는 wiki/에서 수행하라.'); } } catch (error) { process.stderr.write(String(error)); process.exit(1); }