/** * @claude-flow/codex - Validators * * Comprehensive validation functions for AGENTS.md, SKILL.md, and config.toml * Provides detailed error messages and suggestions for fixes. */ /** * Secret patterns to detect */ const SECRET_PATTERNS = [ { pattern: /sk-[a-zA-Z0-9]{32,}/, name: 'OpenAI API key' }, { pattern: /sk-ant-[a-zA-Z0-9-]{32,}/, name: 'Anthropic API key' }, { pattern: /ghp_[a-zA-Z0-9]{36}/, name: 'GitHub personal access token' }, { pattern: /gho_[a-zA-Z0-9]{36}/, name: 'GitHub OAuth token' }, { pattern: /github_pat_[a-zA-Z0-9_]{22,}/, name: 'GitHub fine-grained token' }, { pattern: /xox[baprs]-[a-zA-Z0-9-]{10,}/, name: 'Slack token' }, { pattern: /AKIA[A-Z0-9]{16}/, name: 'AWS access key' }, { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*["']?[a-zA-Z0-9_-]{20,}["']?/i, name: 'Generic API key' }, { pattern: /(?:password|passwd|pwd)\s*[:=]\s*["'][^"']{8,}["']/i, name: 'Hardcoded password' }, { pattern: /(?:secret|token)\s*[:=]\s*["'][a-zA-Z0-9_/-]{16,}["']/i, name: 'Hardcoded secret/token' }, { pattern: /Bearer\s+[a-zA-Z0-9_.-]{20,}/, name: 'Bearer token' }, { pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/, name: 'Private key' }, ]; /** * Required sections for AGENTS.md */ const AGENTS_MD_REQUIRED_SECTIONS = ['Setup', 'Code Standards', 'Security']; /** * Recommended sections for AGENTS.md */ const AGENTS_MD_RECOMMENDED_SECTIONS = [ 'Project Overview', 'Skills', 'Agent Types', 'Memory System', 'Links', ]; /** * Valid approval policies */ const VALID_APPROVAL_POLICIES = ['untrusted', 'on-failure', 'on-request', 'never']; /** * Valid sandbox modes */ const VALID_SANDBOX_MODES = ['read-only', 'workspace-write', 'danger-full-access']; /** * Valid web search modes */ const VALID_WEB_SEARCH_MODES = ['disabled', 'cached', 'live']; /** * Required config.toml fields */ const CONFIG_TOML_REQUIRED_FIELDS = ['model', 'approval_policy', 'sandbox_mode']; /** * Validate an AGENTS.md file */ export async function validateAgentsMd(content) { const errors = []; const warnings = []; const lines = content.split('\n'); // Check for title (H1 heading) if (!content.startsWith('# ')) { const firstHeadingMatch = content.match(/^(#{1,6})\s+/m); if (firstHeadingMatch && firstHeadingMatch[1]) { if (firstHeadingMatch[1].length > 1) { errors.push({ path: 'AGENTS.md', message: 'AGENTS.md should start with a level-1 heading (# Title)', line: 1, }); } } else { errors.push({ path: 'AGENTS.md', message: 'AGENTS.md must start with a title heading', line: 1, }); } } // Check for empty content if (content.trim().length < 50) { errors.push({ path: 'AGENTS.md', message: 'AGENTS.md content is too short - add meaningful instructions', line: 1, }); } // Extract sections const sections = extractSections(content); const sectionTitles = sections.map((s) => s.title.toLowerCase()); // Check for required sections for (const required of AGENTS_MD_REQUIRED_SECTIONS) { const found = sectionTitles.some((t) => t.includes(required.toLowerCase()) || t === required.toLowerCase()); if (!found) { warnings.push({ path: 'AGENTS.md', message: `Missing recommended section: ## ${required}`, suggestion: `Add a "## ${required}" section for better agent guidance`, }); } } // Check for recommended sections for (const recommended of AGENTS_MD_RECOMMENDED_SECTIONS) { const found = sectionTitles.some((t) => t.includes(recommended.toLowerCase()) || t === recommended.toLowerCase()); if (!found) { warnings.push({ path: 'AGENTS.md', message: `Consider adding section: ## ${recommended}`, suggestion: `A "${recommended}" section would improve agent understanding`, }); } } // Check for hardcoded secrets for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (const { pattern, name } of SECRET_PATTERNS) { if (pattern.test(line)) { errors.push({ path: 'AGENTS.md', message: `Potential ${name} detected - never commit secrets`, line: i + 1, }); } } } // Check for skill references const dollarSkillPattern = /\$([a-z][a-z0-9-]+)/g; const slashSkillPattern = /\/([a-z][a-z0-9-]+)/g; const dollarSkills = content.match(dollarSkillPattern) || []; const slashSkills = content.match(slashSkillPattern) || []; if (dollarSkills.length === 0 && slashSkills.length === 0) { warnings.push({ path: 'AGENTS.md', message: 'No skill references found', suggestion: 'Add skill references using $skill-name syntax (Codex) or /skill-name (Claude Code)', }); } // Warn about slash syntax (Claude Code style) if (slashSkills.length > 0 && dollarSkills.length === 0) { warnings.push({ path: 'AGENTS.md', message: 'Using Claude Code skill syntax (/skill-name)', suggestion: 'Codex uses $skill-name syntax. Consider migrating for full compatibility.', }); } // Check for code blocks const codeBlockCount = (content.match(/```/g) || []).length / 2; if (codeBlockCount < 1) { warnings.push({ path: 'AGENTS.md', message: 'No code examples found', suggestion: 'Add code examples in fenced code blocks (```) to guide agent behavior', }); } // Check for common issues checkCommonIssues(content, lines, errors, warnings); // Check structure validateMarkdownStructure(content, lines, errors, warnings); return { valid: errors.length === 0, errors, warnings, }; } /** * Validate a SKILL.md file */ export async function validateSkillMd(content) { const errors = []; const warnings = []; const lines = content.split('\n'); // Check for YAML frontmatter if (!content.startsWith('---')) { errors.push({ path: 'SKILL.md', message: 'SKILL.md must start with YAML frontmatter (---)', line: 1, }); return { valid: false, errors, warnings }; } // Parse YAML frontmatter const frontmatterResult = parseYamlFrontmatter(content); if (!frontmatterResult.valid) { for (const err of frontmatterResult.errors) { errors.push({ path: 'SKILL.md', message: err.message, line: err.line, }); } return { valid: false, errors, warnings }; } const frontmatter = frontmatterResult.data; // Check required frontmatter fields const requiredFields = ['name', 'description']; for (const field of requiredFields) { if (!(field in frontmatter)) { errors.push({ path: 'SKILL.md', message: `Missing required frontmatter field: ${field}`, line: 2, }); } else if (typeof frontmatter[field] !== 'string' || frontmatter[field].trim() === '') { errors.push({ path: 'SKILL.md', message: `Field "${field}" must be a non-empty string`, line: 2, }); } } // Validate name format if (frontmatter.name && typeof frontmatter.name === 'string') { const name = frontmatter.name; if (!/^[a-z][a-z0-9-]*$/.test(name)) { errors.push({ path: 'SKILL.md', message: `Skill name "${name}" must be lowercase with hyphens only (e.g., my-skill)`, line: 2, }); } if (name.length > 50) { warnings.push({ path: 'SKILL.md', message: 'Skill name is very long', suggestion: 'Keep skill names under 50 characters for readability', }); } } // Check optional but recommended fields const recommendedFields = ['version', 'author', 'tags']; for (const field of recommendedFields) { if (!(field in frontmatter)) { warnings.push({ path: 'SKILL.md', message: `Consider adding field: ${field}`, suggestion: `Adding "${field}" improves skill discoverability`, }); } } // Check for model field (should specify min requirements) if (frontmatter.model) { warnings.push({ path: 'SKILL.md', message: 'Model specification found in frontmatter', suggestion: 'Model requirements are informational - skills work with any capable model', }); } // Get body content (after frontmatter) const bodyStartLine = frontmatterResult.endLine + 1; const body = lines.slice(bodyStartLine).join('\n'); // Check for Purpose section if (!body.includes('## Purpose') && !body.includes('## Overview')) { warnings.push({ path: 'SKILL.md', message: 'Missing Purpose or Overview section', suggestion: 'Add a "## Purpose" section to describe what the skill does', }); } // Check for trigger conditions const hasTriggers = body.includes('When to Trigger') || body.includes('When to Use') || body.includes('Triggers') || (frontmatter.triggers && Array.isArray(frontmatter.triggers)); if (!hasTriggers) { warnings.push({ path: 'SKILL.md', message: 'Missing trigger conditions', suggestion: 'Add a section or frontmatter field describing when to trigger this skill', }); } // Check for skip conditions const hasSkipWhen = body.includes('Skip When') || body.includes('When to Skip') || (frontmatter.skip_when && Array.isArray(frontmatter.skip_when)); if (!hasSkipWhen) { warnings.push({ path: 'SKILL.md', message: 'No skip conditions defined', suggestion: 'Consider adding skip conditions to prevent unnecessary skill invocation', }); } // Check for examples const hasExamples = body.includes('## Example') || body.includes('```'); if (!hasExamples) { warnings.push({ path: 'SKILL.md', message: 'No examples provided', suggestion: 'Add usage examples to help agents understand skill application', }); } // Check for secrets in content for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (const { pattern, name } of SECRET_PATTERNS) { if (pattern.test(line)) { errors.push({ path: 'SKILL.md', message: `Potential ${name} detected - never commit secrets`, line: i + 1, }); } } } return { valid: errors.length === 0, errors, warnings, }; } /** * Validate a config.toml file */ export async function validateConfigToml(content) { const errors = []; const warnings = []; const lines = content.split('\n'); // Parse TOML const parseResult = parseToml(content); if (!parseResult.valid) { for (const err of parseResult.errors) { errors.push({ path: 'config.toml', message: err.message, line: err.line, }); } return { valid: false, errors, warnings }; } const config = parseResult.data; // Check for required fields for (const field of CONFIG_TOML_REQUIRED_FIELDS) { const fieldLine = findFieldLine(lines, field); if (!content.includes(`${field} =`) && !content.includes(`${field}=`)) { errors.push({ path: 'config.toml', message: `Missing required field: ${field}`, line: fieldLine, }); } } // Validate model field if (config.model) { const model = config.model; if (typeof model !== 'string') { errors.push({ path: 'config.toml', message: 'model must be a string', line: findFieldLine(lines, 'model'), }); } } // Validate approval_policy value const approvalMatch = content.match(/approval_policy\s*=\s*"([^"]+)"/); if (approvalMatch) { const policy = approvalMatch[1]; if (!VALID_APPROVAL_POLICIES.includes(policy)) { errors.push({ path: 'config.toml', message: `Invalid approval_policy: "${policy}". Valid values: ${VALID_APPROVAL_POLICIES.join(', ')}`, line: findFieldLine(lines, 'approval_policy'), }); } } // Validate sandbox_mode value const sandboxMatch = content.match(/sandbox_mode\s*=\s*"([^"]+)"/); if (sandboxMatch) { const mode = sandboxMatch[1]; if (!VALID_SANDBOX_MODES.includes(mode)) { errors.push({ path: 'config.toml', message: `Invalid sandbox_mode: "${mode}". Valid values: ${VALID_SANDBOX_MODES.join(', ')}`, line: findFieldLine(lines, 'sandbox_mode'), }); } } // Validate web_search value const webSearchMatch = content.match(/web_search\s*=\s*"([^"]+)"/); if (webSearchMatch) { const mode = webSearchMatch[1]; if (!VALID_WEB_SEARCH_MODES.includes(mode)) { errors.push({ path: 'config.toml', message: `Invalid web_search: "${mode}". Valid values: ${VALID_WEB_SEARCH_MODES.join(', ')}`, line: findFieldLine(lines, 'web_search'), }); } } // Check for MCP servers section if (!content.includes('[mcp_servers')) { warnings.push({ path: 'config.toml', message: 'No MCP servers configured', suggestion: 'Add [mcp_servers.claude-flow] for Claude Flow integration', }); } else { // Validate MCP server configurations validateMcpServers(content, lines, errors, warnings); } // Check for features section if (!content.includes('[features]')) { warnings.push({ path: 'config.toml', message: 'No [features] section found', suggestion: 'Add [features] section to configure Codex behavior', }); } // Security warnings for dangerous settings if (content.includes('approval_policy = "never"')) { if (!content.includes('[profiles.')) { warnings.push({ path: 'config.toml', message: 'Using "never" approval policy globally', suggestion: 'Consider restricting to dev profile: [profiles.dev] approval_policy = "never"', }); } } if (content.includes('sandbox_mode = "danger-full-access"')) { warnings.push({ path: 'config.toml', message: 'Using "danger-full-access" sandbox mode', suggestion: 'This gives unrestricted file system access. Use only in trusted environments.', }); } // Check for secrets for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Skip comment lines if (line.trim().startsWith('#')) continue; for (const { pattern, name } of SECRET_PATTERNS) { if (pattern.test(line)) { errors.push({ path: 'config.toml', message: `Potential ${name} detected - use environment variables instead`, line: i + 1, }); } } // Check for inline secrets in env sections if (line.includes('_KEY =') || line.includes('_SECRET =') || line.includes('_TOKEN =')) { const valueMatch = line.match(/=\s*"([^"]+)"/); if (valueMatch && valueMatch[1] && !valueMatch[1].startsWith('$')) { warnings.push({ path: 'config.toml', message: 'Hardcoded credential detected', suggestion: `Use environment variable reference: $ENV_VAR_NAME instead of "${valueMatch[1]}"`, }); } } } // Validate project_doc_max_bytes if present const maxBytesMatch = content.match(/project_doc_max_bytes\s*=\s*(\d+)/); if (maxBytesMatch) { const bytes = parseInt(maxBytesMatch[1], 10); if (bytes < 1024) { warnings.push({ path: 'config.toml', message: `project_doc_max_bytes is very low (${bytes} bytes)`, suggestion: 'Consider increasing to at least 65536 for reasonable AGENTS.md support', }); } else if (bytes > 1048576) { warnings.push({ path: 'config.toml', message: `project_doc_max_bytes is very high (${bytes} bytes = ${(bytes / 1024 / 1024).toFixed(1)} MB)`, suggestion: 'Large values may impact performance. Default is 65536.', }); } } // Check profiles validateProfiles(content, lines, errors, warnings); return { valid: errors.length === 0, errors, warnings, }; } /** * Validate all files in a project */ export async function validateProject(files) { const results = {}; let totalErrors = 0; let totalWarnings = 0; if (files.agentsMd) { results['AGENTS.md'] = await validateAgentsMd(files.agentsMd); totalErrors += results['AGENTS.md'].errors.length; totalWarnings += results['AGENTS.md'].warnings.length; } if (files.skillMds) { for (const skill of files.skillMds) { const key = `skills/${skill.name}`; results[key] = await validateSkillMd(skill.content); totalErrors += results[key].errors.length; totalWarnings += results[key].warnings.length; } } if (files.configToml) { results['config.toml'] = await validateConfigToml(files.configToml); totalErrors += results['config.toml'].errors.length; totalWarnings += results['config.toml'].warnings.length; } return { valid: totalErrors === 0, results, summary: { errors: totalErrors, warnings: totalWarnings }, }; } /** * Generate a validation report */ export function generateValidationReport(results) { const lines = []; lines.push('# Validation Report'); lines.push(''); let totalErrors = 0; let totalWarnings = 0; for (const [file, result] of Object.entries(results)) { totalErrors += result.errors.length; totalWarnings += result.warnings.length; lines.push(`## ${file}`); lines.push(''); lines.push(`**Status**: ${result.valid ? 'Valid' : 'Invalid'}`); lines.push(''); if (result.errors.length > 0) { lines.push('### Errors'); lines.push(''); for (const error of result.errors) { const lineInfo = error.line ? ` (line ${error.line})` : ''; lines.push(`- ${error.message}${lineInfo}`); } lines.push(''); } if (result.warnings.length > 0) { lines.push('### Warnings'); lines.push(''); for (const warning of result.warnings) { lines.push(`- ${warning.message}`); if (warning.suggestion) { lines.push(` - Suggestion: ${warning.suggestion}`); } } lines.push(''); } if (result.errors.length === 0 && result.warnings.length === 0) { lines.push('No issues found.'); lines.push(''); } } lines.push('## Summary'); lines.push(''); lines.push(`- Total Errors: ${totalErrors}`); lines.push(`- Total Warnings: ${totalWarnings}`); lines.push(`- Overall Status: ${totalErrors === 0 ? 'PASS' : 'FAIL'}`); lines.push(''); return lines.join('\n'); } // ============================================================================ // Helper Functions // ============================================================================ /** * Extract sections from markdown content */ function extractSections(content) { const sections = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (!line) continue; const match = line.match(/^(#{1,6})\s+(.+)$/); if (match && match[1] && match[2]) { sections.push({ level: match[1].length, title: match[2].trim(), line: i + 1, }); } } return sections; } /** * Parse YAML frontmatter */ function parseYamlFrontmatter(content) { const result = { valid: false, errors: [], data: {}, endLine: 0, }; if (!content.startsWith('---')) { result.errors.push({ line: 1, message: 'Missing opening ---' }); return result; } const lines = content.split('\n'); let endLineIndex = -1; // Find closing --- for (let i = 1; i < lines.length; i++) { if (lines[i].trim() === '---') { endLineIndex = i; break; } } if (endLineIndex === -1) { result.errors.push({ line: 1, message: 'YAML frontmatter not properly closed (missing closing ---)' }); return result; } result.endLine = endLineIndex; // Parse YAML content (simple key: value parsing) const yamlLines = lines.slice(1, endLineIndex); for (let i = 0; i < yamlLines.length; i++) { const line = yamlLines[i].trim(); if (line === '' || line.startsWith('#')) continue; // Simple key: value parsing const colonIndex = line.indexOf(':'); if (colonIndex === -1) { // Could be a list item or continuation continue; } const key = line.substring(0, colonIndex).trim(); let value = line.substring(colonIndex + 1).trim(); // Parse value type if (value === '') { value = null; } else if (value === 'true') { value = true; } else if (value === 'false') { value = false; } else if (/^-?\d+$/.test(value)) { value = parseInt(value, 10); } else if (/^-?\d+\.\d+$/.test(value)) { value = parseFloat(value); } else if (value.startsWith('"') && value.endsWith('"')) { value = value.slice(1, -1); } else if (value.startsWith("'") && value.endsWith("'")) { value = value.slice(1, -1); } else if (value.startsWith('[') && value.endsWith(']')) { // Simple inline array try { value = JSON.parse(value.replace(/'/g, '"')); } catch { // Keep as string if not valid JSON } } if (key) { result.data[key] = value; } } result.valid = true; return result; } /** * Parse TOML content (simplified parser) */ function parseToml(content) { const result = { valid: true, errors: [], data: {}, }; const lines = content.split('\n'); let currentSection = ''; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Skip empty lines and comments if (line === '' || line.startsWith('#')) continue; // Section header if (line.startsWith('[')) { if (!line.endsWith(']')) { result.errors.push({ line: i + 1, message: `Invalid section header: ${line} (missing closing bracket)`, }); result.valid = false; continue; } currentSection = line.slice(1, -1); continue; } // Key = value const eqIndex = line.indexOf('='); if (eqIndex === -1) { // Could be array continuation or error if (!line.startsWith('"') && !line.startsWith("'") && !line.startsWith(']')) { result.errors.push({ line: i + 1, message: `Invalid line: ${line} (expected key = value)`, }); result.valid = false; } continue; } const key = line.substring(0, eqIndex).trim(); const valueStr = line.substring(eqIndex + 1).trim(); // Validate key format if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { result.errors.push({ line: i + 1, message: `Invalid key format: ${key}`, }); result.valid = false; continue; } // Parse value let value = valueStr; if (valueStr.startsWith('"') && valueStr.endsWith('"')) { value = valueStr.slice(1, -1); } else if (valueStr.startsWith("'") && valueStr.endsWith("'")) { value = valueStr.slice(1, -1); } else if (valueStr === 'true') { value = true; } else if (valueStr === 'false') { value = false; } else if (/^-?\d+$/.test(valueStr)) { value = parseInt(valueStr, 10); } else if (/^-?\d+\.\d+$/.test(valueStr)) { value = parseFloat(valueStr); } else if (valueStr.startsWith('[')) { // Array - simplified handling value = valueStr; } // Store in nested structure if (currentSection) { if (!result.data[currentSection]) { result.data[currentSection] = {}; } result.data[currentSection][key] = value; } else { result.data[key] = value; } } return result; } /** * Find the line number for a field */ function findFieldLine(lines, field) { for (let i = 0; i < lines.length; i++) { if (lines[i].includes(`${field} =`) || lines[i].includes(`${field}=`)) { return i + 1; } } return 1; } /** * Check for common issues in content */ function checkCommonIssues(content, lines, errors, warnings) { // Check for broken links const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g; let match; while ((match = linkPattern.exec(content)) !== null) { const url = match[2]; if (url.startsWith('http') && !url.startsWith('https://')) { const line = findLineNumber(content, match.index); warnings.push({ path: 'AGENTS.md', message: `Non-HTTPS URL found: ${url}`, suggestion: 'Use HTTPS URLs for security', }); } } // Check for TODO/FIXME comments for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (/\b(TODO|FIXME|XXX|HACK)\b/i.test(line)) { warnings.push({ path: 'AGENTS.md', message: `Incomplete item found: ${line.trim().substring(0, 50)}...`, suggestion: 'Complete or remove TODO/FIXME items before deployment', }); } } // Check for placeholder content const placeholderPatterns = [ /\[your[- ].*\]/i, /\[insert[- ].*\]/i, /\[add[- ].*\]/i, /\{your[- ].*\}/i, //i, ]; for (const pattern of placeholderPatterns) { if (pattern.test(content)) { warnings.push({ path: 'AGENTS.md', message: 'Placeholder content detected', suggestion: 'Replace placeholder text with actual content', }); break; } } } /** * Validate markdown structure */ function validateMarkdownStructure(content, lines, errors, warnings) { // Check heading hierarchy const headings = extractSections(content); let prevLevel = 0; for (const heading of headings) { if (heading.level > prevLevel + 1 && prevLevel > 0) { warnings.push({ path: 'AGENTS.md', message: `Heading level jumps from H${prevLevel} to H${heading.level}`, suggestion: `Use H${prevLevel + 1} instead of H${heading.level} for proper hierarchy`, }); } prevLevel = heading.level; } // Check for unclosed code blocks // Count all triple backticks - they should come in pairs const tripleBackticks = (content.match(/```/g) || []).length; if (tripleBackticks % 2 !== 0) { errors.push({ path: 'AGENTS.md', message: 'Unclosed code block detected (odd number of ``` markers)', line: 1, }); } // Check for very long lines for (let i = 0; i < lines.length; i++) { if (lines[i].length > 500) { warnings.push({ path: 'AGENTS.md', message: `Very long line (${lines[i].length} chars) at line ${i + 1}`, suggestion: 'Consider breaking into multiple lines for readability', }); } } } /** * Find line number for a character index */ function findLineNumber(content, index) { return content.substring(0, index).split('\n').length; } /** * Validate MCP server configurations */ function validateMcpServers(content, lines, errors, warnings) { // Find all MCP server sections const serverRegex = /\[mcp_servers\.([^\]]+)\]/g; const servers = []; let match; while ((match = serverRegex.exec(content)) !== null) { servers.push(match[1]); } for (const serverName of servers) { // Check if server has command const serverSection = content.match(new RegExp(`\\[mcp_servers\\.${serverName.replace('.', '\\.')}\\][\\s\\S]*?(?=\\[|$)`)); if (serverSection) { const section = serverSection[0]; if (!section.includes('command =')) { errors.push({ path: 'config.toml', message: `MCP server "${serverName}" missing required "command" field`, line: findFieldLine(lines, `[mcp_servers.${serverName}]`), }); } // Check for enabled = false (info) if (section.includes('enabled = false')) { warnings.push({ path: 'config.toml', message: `MCP server "${serverName}" is disabled`, suggestion: 'Set enabled = true to activate this server', }); } } } } /** * Validate profiles */ function validateProfiles(content, lines, errors, warnings) { const profileRegex = /\[profiles\.([^\]]+)\]/g; const profiles = []; let match; while ((match = profileRegex.exec(content)) !== null) { profiles.push(match[1]); } // Suggest common profiles if missing const recommendedProfiles = ['dev', 'safe', 'ci']; for (const profile of recommendedProfiles) { if (!profiles.includes(profile)) { warnings.push({ path: 'config.toml', message: `Consider adding "${profile}" profile`, suggestion: `Add [profiles.${profile}] for ${profile === 'dev' ? 'development' : profile === 'safe' ? 'restricted' : 'CI/CD'} environment`, }); } } // Check profile settings for (const profile of profiles) { const profileSection = content.match(new RegExp(`\\[profiles\\.${profile}\\][\\s\\S]*?(?=\\[profiles|$)`)); if (profileSection) { const section = profileSection[0]; // Check if profile has any settings if (!section.includes('=')) { warnings.push({ path: 'config.toml', message: `Profile "${profile}" has no settings`, suggestion: 'Add approval_policy, sandbox_mode, or web_search settings', }); } } } } //# sourceMappingURL=index.js.map