add template

This commit is contained in:
김경종
2026-04-17 10:35:09 +09:00
parent 5c308e14b2
commit e8f6208df1
41 changed files with 1133 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
#!/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)>(?!\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);
}