UNPKG

tdd-guard

Version:

Automated Test-Driven Development enforcement for Claude Code

124 lines (123 loc) 4.08 kB
"use strict"; 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, }; }