// Planu — CLAUDE.md auditor for SPEC-092 / SPEC-255
import { readFile, access } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
const DOCS_REF = 'https://code.claude.com/docs/en/memory';
const MAX_LINES = 200;
const CRITICAL_LINES = 400;
const SECTION_THRESHOLD = 20;
// SPEC-255: tighter thresholds
const V2_WARN_LINES = 150;
const V2_CRITICAL_LINES = 250;
const REDUNDANT_PATTERN = /\b(write clean|be helpful|use best practices|make sure to|always be|follow good|ensure good)\b/i;
const ACTIONABLE_VERBS = /\b(run|use|avoid|never|always|add|remove|prefer|require|must|do not|don't|check|enforce|ensure|execute|install|create|delete|update)\b/i;
async function readFileLines(filePath) {
    try {
        const content = await readFile(filePath, 'utf-8');
        return content.split('\n');
    }
    catch {
        return null;
    }
}
async function pathExists(p) {
    try {
        await access(p);
        return true;
    }
    catch {
        return false;
    }
}
function auditLineCount(lineCount, label) {
    if (lineCount > V2_CRITICAL_LINES) {
        return {
            area: 'CLAUDE.md',
            severity: 'critical',
            code: 'LINE_COUNT_CRITICAL',
            message: `${label} has ${String(lineCount)} lines (limit: ${String(V2_CRITICAL_LINES)})`,
            suggestion: 'Split into .claude/rules/ files with paths: frontmatter for scoped loading',
            docsRef: DOCS_REF,
        };
    }
    if (lineCount > V2_WARN_LINES) {
        return {
            area: 'CLAUDE.md',
            severity: 'warn',
            code: 'LINE_COUNT_WARN',
            message: `${label} has ${String(lineCount)} lines (recommended: <${String(V2_WARN_LINES)})`,
            suggestion: 'Move detailed sections to .claude/rules/ files to reduce context overhead',
            docsRef: DOCS_REF,
        };
    }
    /* v8 ignore start -- superseded by V2_CRITICAL_LINES check above */
    if (lineCount > CRITICAL_LINES) {
        return {
            area: 'CLAUDE.md',
            severity: 'critical',
            message: `${label} has ${String(lineCount)} lines (limit: ${String(CRITICAL_LINES)})`,
            suggestion: 'Split into .claude/rules/ files with paths: frontmatter for scoped loading',
            docsRef: DOCS_REF,
        };
    }
    /* v8 ignore stop */
    /* v8 ignore start -- superseded by V2_WARN_LINES check above */
    if (lineCount > MAX_LINES) {
        return {
            area: 'CLAUDE.md',
            severity: 'warn',
            message: `${label} has ${String(lineCount)} lines (recommended: <${String(MAX_LINES)})`,
            suggestion: 'Move detailed sections to .claude/rules/ files to reduce context overhead',
            docsRef: DOCS_REF,
        };
    }
    /* v8 ignore stop */
    return null;
}
function auditInstructionQuality(lines) {
    const findings = [];
    for (const line of lines) {
        if (REDUNDANT_PATTERN.test(line)) {
            findings.push({
                area: 'CLAUDE.md',
                severity: 'info',
                code: 'REDUNDANT_INSTRUCTION',
                message: `Redundant instruction detected: "${line.trim().slice(0, 80)}"`,
                suggestion: 'Replace generic phrases with concrete, measurable instructions (e.g., "run pnpm lint" instead of "write clean code")',
                docsRef: DOCS_REF,
            });
        }
    }
    for (let i = 0; i < lines.length; i++) {
        const trimmed = (lines[i] ?? '').trim();
        if (trimmed === '' ||
            trimmed.startsWith('#') ||
            trimmed.startsWith('```') ||
            trimmed.startsWith('-') ||
            trimmed.startsWith('*') ||
            trimmed.startsWith('|')) {
            continue;
        }
        if (trimmed.split(/\s+/).length > 10 && !ACTIONABLE_VERBS.test(trimmed)) {
            findings.push({
                area: 'CLAUDE.md',
                severity: 'info',
                code: 'AMBIGUOUS_INSTRUCTION',
                message: `Ambiguous instruction on line ${String(i + 1)}: "${trimmed.slice(0, 80)}"`,
                suggestion: 'Rewrite with a concrete action verb: run, use, avoid, never, always, add, remove, prefer, require',
                docsRef: DOCS_REF,
            });
        }
    }
    return findings;
}
function auditSections(lines) {
    const findings = [];
    let currentSectionStart = -1;
    let currentSectionLines = 0;
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if (line?.startsWith('## ')) {
            if (currentSectionLines > SECTION_THRESHOLD && currentSectionStart >= 0) {
                const sectionName = lines[currentSectionStart]?.replace('## ', '') ?? 'unknown';
                findings.push({
                    area: 'CLAUDE.md',
                    severity: 'info',
                    message: `Section "${sectionName}" is ${String(currentSectionLines)} lines — could be a rule file`,
                    suggestion: `Move to .claude/rules/${sectionName.toLowerCase().replace(/\s+/g, '-')}.md`,
                    docsRef: DOCS_REF,
                });
            }
            currentSectionStart = i;
            currentSectionLines = 0;
        }
        else {
            currentSectionLines++;
        }
    }
    if (currentSectionLines > SECTION_THRESHOLD && currentSectionStart >= 0) {
        const sectionName = lines[currentSectionStart]?.replace('## ', '') ?? 'unknown';
        findings.push({
            area: 'CLAUDE.md',
            severity: 'info',
            message: `Section "${sectionName}" is ${String(currentSectionLines)} lines — could be a rule file`,
            suggestion: `Move to .claude/rules/${sectionName.toLowerCase().replace(/\s+/g, '-')}.md`,
            docsRef: DOCS_REF,
        });
    }
    return findings;
}
function auditSingleClaudeMd(lines, label) {
    const lineCountFinding = auditLineCount(lines.length, label);
    return [
        ...(lineCountFinding ? [lineCountFinding] : []),
        ...auditInstructionQuality(lines),
        ...auditSections(lines),
    ];
}
async function auditProjectClaudeMds(projectPath) {
    const findings = [];
    const projectPaths = [join(projectPath, 'CLAUDE.md'), join(projectPath, '.claude', 'CLAUDE.md')];
    let foundProject = false;
    for (const p of projectPaths) {
        const lines = await readFileLines(p);
        if (lines) {
            foundProject = true;
            findings.push(...auditSingleClaudeMd(lines, `Project CLAUDE.md (${p})`));
            if (!lines.some((l) => l.startsWith('@'))) {
                findings.push({
                    area: 'CLAUDE.md',
                    severity: 'info',
                    message: 'CLAUDE.md does not use @path imports (e.g. @README.md, @package.json)',
                    suggestion: 'Use @path syntax to import key files so Claude always has fresh context',
                    docsRef: DOCS_REF,
                });
            }
        }
    }
    if (!foundProject) {
        findings.push({
            area: 'CLAUDE.md',
            severity: 'warn',
            message: 'No CLAUDE.md found in project root or .claude/ directory',
            suggestion: 'Create a CLAUDE.md with project conventions, commands, and architecture overview',
            docsRef: DOCS_REF,
        });
    }
    if (!(await readFileLines(join(projectPath, 'CLAUDE.local.md')))) {
        findings.push({
            area: 'CLAUDE.md',
            severity: 'info',
            message: 'No CLAUDE.local.md found — personal preferences go here (gitignored)',
            suggestion: 'Create CLAUDE.local.md for personal project-specific preferences not tracked in git',
            docsRef: DOCS_REF,
        });
    }
    return findings;
}
async function auditAgentsMdImport(projectPath) {
    const projectPaths = [join(projectPath, 'CLAUDE.md'), join(projectPath, '.claude', 'CLAUDE.md')];
    const agentsMdExists = await pathExists(join(projectPath, 'AGENTS.md'));
    const cursorRulesExists = await pathExists(join(projectPath, '.cursor', 'rules'));
    if (!agentsMdExists && !cursorRulesExists) {
        return [];
    }
    const allLines = [];
    for (const p of projectPaths) {
        const lines = await readFileLines(p);
        if (lines) {
            allLines.push(...lines);
        }
    }
    if (allLines.some((l) => l.includes('@AGENTS.md'))) {
        return [];
    }
    const detected = agentsMdExists ? 'AGENTS.md' : '.cursor/rules';
    return [
        {
            area: 'CLAUDE.md',
            severity: 'warn',
            code: 'AGENTS_MD_NOT_IMPORTED',
            message: `Project has ${detected} but CLAUDE.md does not import @AGENTS.md`,
            suggestion: 'Add "@AGENTS.md" to CLAUDE.md so Claude Code loads cross-tool conventions',
            docsRef: DOCS_REF,
        },
    ];
}
export async function auditClaudeMd(projectPath, includeGlobal) {
    const [projectFindings, agentsFindings] = await Promise.all([
        auditProjectClaudeMds(projectPath),
        auditAgentsMdImport(projectPath),
    ]);
    const findings = [...projectFindings, ...agentsFindings];
    if (includeGlobal) {
        const globalLines = await readFileLines(join(homedir(), '.claude', 'CLAUDE.md'));
        if (globalLines) {
            findings.push(...auditSingleClaudeMd(globalLines, 'Global ~/.claude/CLAUDE.md'));
        }
    }
    return findings;
}
//# sourceMappingURL=claudemd-auditor.js.map