UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

241 lines 8.05 kB
const HEADER_PATTERN = /^(#{1,6})\s+/; const LIST_PATTERN = /^(\s*)([-*+]|\d+\.)\s+/; const TABLE_SEPARATOR = /^\|?[\s-:|]+\|[\s-:|]*$/; const PIPE_COUNT = /\|/g; const UNORDERED_LIST = /^(\s*)([-*+])\s+/; const ORDERED_LIST = /^(\s*)(\d+)\.\s+/; const WHITESPACE_ONLY = /^\s*$/; const SENTENCE_END = /[.!?;:\]})"`']$/; const HEADER_LINE = /^#{1,6}\s+/; const UNORDERED_LIST_LINE = /^[-*+]\s+/; const ORDERED_LIST_LINE = /^\d+\.\s+/; const MARKDOWN_PATTERNS = [ /^#{1,6}\s+/m, /```/, /^\s*[-*+]\s+/m, /^\s*\d+\.\s+/m, /\*\*.*\*\*/, /\*.*\*/, /\[.*\]\(.*\)/, /^>\s+/m, ]; export function analyzeMarkdownStructure(content) { const issues = []; const lines = content.split("\n"); const fenceLanguages = []; const headers = []; let openFences = 0; let inFence = false; let listDepth = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.trimStart().startsWith("```")) { inFence = !inFence; if (inFence) { openFences++; const lang = line.trim().slice(3).trim(); if (lang) { fenceLanguages.push(lang); } } else { openFences--; } } if (!inFence) { const headerMatch = line.match(HEADER_PATTERN); if (headerMatch && headerMatch[1]) { headers.push(headerMatch[1].length); } const listMatch = line.match(LIST_PATTERN); if (listMatch && listMatch[1] !== undefined) { const indent = listMatch[1].length; const currentDepth = Math.floor(indent / 2) + 1; listDepth = Math.max(listDepth, currentDepth); } } } if (inFence || openFences !== 0) { issues.push(`Unbalanced code fences: ${Math.abs(openFences)} unclosed`); } return { openFences: Math.max(0, openFences), fenceLanguages, inFence, headers, listDepth, issues, }; } export function looksLikeMarkdown(content) { if (!content) return false; return MARKDOWN_PATTERNS.some((pattern) => pattern.test(content)); } export function validateMarkdownFences(context) { const { content, completed } = context; const violations = []; const structure = analyzeMarkdownStructure(content); if (completed && (structure.inFence || structure.openFences !== 0)) { violations.push({ rule: "markdown-fences", message: `Unclosed code fences: ${structure.openFences} fence(s) not closed`, severity: "error", recoverable: true, suggestion: "Retry generation to properly close code fences", }); } return violations; } export function validateMarkdownTables(context) { const { content, completed } = context; const violations = []; if (!completed) { return violations; } const lines = content.split("\n"); let inTable = false; let columnCount = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (TABLE_SEPARATOR.test(line)) { inTable = true; columnCount = (line.match(PIPE_COUNT) || []).length; continue; } if (inTable) { if (line.includes("|")) { const cols = (line.match(PIPE_COUNT) || []).length; if (cols !== columnCount) { violations.push({ rule: "markdown-tables", message: `Inconsistent table columns at line ${i + 1}: expected ${columnCount}, got ${cols}`, severity: "warning", recoverable: true, }); } } else if (line.trim().length > 0) { inTable = false; } } } return violations; } export function validateMarkdownLists(context) { const { content, completed } = context; const violations = []; if (!completed) { return violations; } const lines = content.split("\n"); let lastListType = null; let lastIndent = -1; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const unorderedMatch = line.match(UNORDERED_LIST); if (unorderedMatch && unorderedMatch[1] !== undefined) { const indent = unorderedMatch[1].length; if (lastListType === "ordered" && lastIndent === indent) { violations.push({ rule: "markdown-lists", message: `Mixed list types at line ${i + 1}: switching from ordered to unordered at same level`, severity: "warning", recoverable: true, }); } lastListType = "unordered"; lastIndent = indent; continue; } const orderedMatch = line.match(ORDERED_LIST); if (orderedMatch && orderedMatch[1] !== undefined) { const indent = orderedMatch[1].length; if (lastListType === "unordered" && lastIndent === indent) { violations.push({ rule: "markdown-lists", message: `Mixed list types at line ${i + 1}: switching from unordered to ordered at same level`, severity: "warning", recoverable: true, }); } lastListType = "ordered"; lastIndent = indent; continue; } if (line.trim().length > 0 && !WHITESPACE_ONLY.test(line)) { lastListType = null; lastIndent = -1; } } return violations; } export function validateMarkdownComplete(context) { const { content, completed } = context; const violations = []; if (!completed) { return violations; } const structure = analyzeMarkdownStructure(content); if (structure.inFence) { violations.push({ rule: "markdown-complete", message: "Content ends inside code fence", severity: "error", recoverable: true, suggestion: "Retry to complete the code fence", }); } const trimmed = content.trim(); const lines = trimmed.split("\n"); const lastLine = lines[lines.length - 1] ?? ""; if (!structure.inFence && lastLine.trim().length > 0 && !SENTENCE_END.test(lastLine) && !HEADER_LINE.test(lastLine) && !UNORDERED_LIST_LINE.test(lastLine) && !ORDERED_LIST_LINE.test(lastLine)) { violations.push({ rule: "markdown-complete", message: "Content appears to end abruptly mid-sentence", severity: "warning", recoverable: true, }); } return violations; } export function markdownRule() { return { name: "markdown-structure", description: "Validates Markdown fences, blocks, and structure", streaming: true, severity: "error", recoverable: true, check: (context) => { const violations = []; if (!looksLikeMarkdown(context.content) && context.content.length > 50) { return violations; } violations.push(...validateMarkdownFences(context)); if (context.completed) { violations.push(...validateMarkdownTables(context)); violations.push(...validateMarkdownLists(context)); violations.push(...validateMarkdownComplete(context)); } return violations; }, }; } export class MarkdownGuardrail { rule; constructor() { this.rule = markdownRule(); } check(context) { return this.rule.check(context); } get name() { return this.rule.name; } } //# sourceMappingURL=markdown.js.map