Files
brain/tools/enforce-tdd-real-test-verifier.mjs
T

67 lines
2.4 KiB
JavaScript

/**
* PreToolUse(Edit|Write) wrapper for tools/tdd-real-test-verifier.mjs.
* Router-gate v4 spec §3.11.
*
* Blocks Edit/Write on a *.test.* / *.spec.* file when the proposed content
* lacks expect() / it() OR doesn't reference any of the prod files edited
* this session (the sentinel-gaming guard from spec §3.11).
*
* Fail-CLOSE: an internal error blocks (security-hook default).
*/
import { fileURLToPath } from 'url';
import { join } from 'path';
import { existsSync, readFileSync } from 'fs';
import {
readStdin,
parseEventJson,
exitDecision,
runtimeDir,
} from './enforce-hook-helpers.mjs';
import { verifyRealTestContent } from './tdd-real-test-verifier.mjs';
const TEST_FILE_RE = /\.(?:test|spec)\.[a-z0-9]+$/i;
function readEditedFiles(sessionId) {
try {
const p = join(runtimeDir(), `edited-files-${sessionId || 'unknown'}.json`);
if (!existsSync(p)) return [];
const j = JSON.parse(readFileSync(p, 'utf-8'));
return Array.isArray(j.files) ? j.files : [];
} catch { return []; }
}
export function decide({ filePath, content, editedFiles }) {
const fp = String(filePath || '').split('\\').join('/');
if (!TEST_FILE_RE.test(fp)) return { block: false, reason: null };
const r = verifyRealTestContent(content, editedFiles || []);
if (r.valid) return { block: false, reason: null };
return { block: true, reason: r.reason };
}
async function main() {
try {
const raw = await readStdin();
const event = parseEventJson(raw);
if (event.tool_name !== 'Edit' && event.tool_name !== 'Write') {
return exitDecision({ block: false });
}
const filePath = event.tool_input?.file_path || '';
const content = event.tool_input?.content || event.tool_input?.new_string || '';
const sessionId = event.session_id || 'unknown';
const editedFiles = readEditedFiles(sessionId);
const r = decide({ filePath, content, editedFiles });
if (r.block) {
return exitDecision({
block: true,
message: `[tdd-real-test-verifier] proposed test file fails real-test check: ${r.reason}. Write a test that asserts behaviour (expect + it/test) and references one of the edited prod files.`,
});
}
return exitDecision({ block: false });
} catch {
return exitDecision({ block: true, message: '[tdd-real-test-verifier] внутренняя ошибка — fail-CLOSE' });
}
}
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
if (isCli) main();