tdd-guard
Version:
Automated Test-Driven Development enforcement for Claude Code
124 lines (123 loc) • 4.08 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.validator = validator;
const ClaudeCli_1 = require("./models/ClaudeCli");
const context_1 = require("./context/context");
async function validator(context, modelClient = new ClaudeCli_1.ClaudeCli()) {
try {
const prompt = (0, context_1.generateDynamicContext)(context);
const response = await modelClient.ask(prompt);
return parseModelResponse(response);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
const reason = errorMessage === 'No response from model'
? 'No response from model, try again'
: `Error during validation: ${errorMessage}`;
return {
decision: 'block',
reason,
};
}
}
function parseModelResponse(response) {
const jsonString = extractJsonString(response);
const parsed = JSON.parse(jsonString);
return normalizeValidationResult(parsed);
}
function extractJsonString(response) {
// Handle undefined/null responses
if (!response) {
throw new Error('No response from model');
}
const jsonFromCodeBlock = extractFromJsonCodeBlock(response);
if (jsonFromCodeBlock) {
return jsonFromCodeBlock;
}
const jsonFromGenericBlock = extractFromGenericCodeBlock(response);
if (jsonFromGenericBlock) {
return jsonFromGenericBlock;
}
// Try to extract plain JSON from text
const plainJson = extractPlainJson(response);
if (plainJson) {
return plainJson;
}
return response;
}
function extractFromJsonCodeBlock(response) {
// Find all json code blocks
const startPattern = '```json';
const endPattern = '```';
const blocks = [];
let startIndex = 0;
let blockStart = response.indexOf(startPattern, startIndex);
while (blockStart !== -1) {
const contentStart = blockStart + startPattern.length;
const blockEnd = response.indexOf(endPattern, contentStart);
if (blockEnd === -1)
break;
const content = response.substring(contentStart, blockEnd).trim();
blocks.push(content);
startIndex = blockEnd + endPattern.length;
blockStart = response.indexOf(startPattern, startIndex);
}
if (blocks.length > 0) {
return blocks[blocks.length - 1];
}
return null;
}
function extractPlainJson(response) {
// Simple regex to find JSON objects containing both "decision" and "reason" (in any order)
const pattern = /\{[^{}]*"decision"[^{}]*"reason"[^{}]*}|\{[^{}]*"reason"[^{}]*"decision"[^{}]*}/g;
const matches = response.match(pattern);
if (!matches)
return null;
// Return the last match (most likely the final decision)
const lastMatch = matches[matches.length - 1];
// Validate it's proper JSON
if (isValidJson(lastMatch)) {
return lastMatch;
}
return null;
}
function extractFromGenericCodeBlock(response) {
const codeBlock = findCodeBlock(response);
if (!codeBlock)
return null;
const content = codeBlock.trim();
return isValidJson(content) ? content : null;
}
function findCodeBlock(response) {
const startPattern = '```';
const blockStart = response.indexOf(startPattern);
if (blockStart === -1)
return null;
const contentStart = skipWhitespace(response, blockStart + startPattern.length);
const blockEnd = response.indexOf(startPattern, contentStart);
if (blockEnd === -1)
return null;
return response.substring(contentStart, blockEnd);
}
function skipWhitespace(text, startIndex) {
let index = startIndex;
while (index < text.length && /\s/.test(text[index])) {
index++;
}
return index;
}
function isValidJson(str) {
try {
JSON.parse(str);
return true;
}
catch {
return false;
}
}
function normalizeValidationResult(parsed) {
return {
decision: parsed.decision ?? undefined,
reason: parsed.reason,
};
}
;