@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
241 lines • 8.05 kB
JavaScript
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