import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { parseFrontmatter } from "../../markdown/frontmatter.js";
import { hasBinary, init_installer } from "./installer.js";
import { parseSkillMetadata } from "./parse-skill-metadata.js";
import { scanSkillDirectory } from "./scanner.js";
import { basename, join, relative } from "path";
import { existsSync, readFileSync, readdirSync } from "fs";
//#region src/agent/skills/test-framework.ts
/**
* Skill Test Framework
* 
* Comprehensive testing framework for skills.
* Validates SKILL.md format, dependencies, security, and examples.
*/
init_logger();
init_installer();
const log = createLogger("SkillTestFramework");
var SkillTestFramework = class {
	options;
	constructor(options = {}) {
		this.options = {
			skipSecurity: false,
			skipDeps: false,
			skipExamples: false,
			strict: false,
			exampleTimeout: 1e4,
			cwd: process.cwd(),
			...options
		};
	}
	/**
	* Run all tests for a skill
	*/
	async testSkill(skillDir) {
		const results = [];
		log.info({ skillDir }, "Testing skill");
		if (!existsSync(skillDir)) return this.createFailureReport(skillDir, "Skill directory not found");
		const skillMdPath = join(skillDir, "SKILL.md");
		if (!existsSync(skillMdPath)) return this.createFailureReport(skillDir, "SKILL.md not found");
		const parseResult = this.testSkillMdFormat(skillMdPath);
		results.push(parseResult);
		if (parseResult.status === "fail") return this.createReport(skillDir, results);
		const { frontmatter } = parseFrontmatter(readFileSync(skillMdPath, "utf-8"));
		const metadata = parseSkillMetadata(frontmatter);
		if (!this.options.skipDeps) {
			const depsResult = await this.testDependencies(metadata);
			results.push(depsResult);
		}
		if (!this.options.skipSecurity) {
			const securityResult = await this.testSecurity(skillDir);
			results.push(securityResult);
		}
		const metadataResult = this.testMetadata(metadata, skillDir);
		results.push(metadataResult);
		if (!this.options.skipExamples) {
			const content = readFileSync(skillMdPath, "utf-8");
			const examplesResult = await this.testExamples(content, skillDir);
			results.push(examplesResult);
		}
		return this.createReport(skillDir, results);
	}
	/**
	* Test SKILL.md format
	*/
	testSkillMdFormat(filePath) {
		const startTime = Date.now();
		const details = [];
		try {
			const content = readFileSync(filePath, "utf-8");
			if (!content.startsWith("---")) return {
				name: "SKILL.md format",
				status: "fail",
				message: "Missing YAML frontmatter",
				duration: Date.now() - startTime
			};
			const { frontmatter } = parseFrontmatter(content);
			for (const field of ["name", "description"]) if (!frontmatter[field]) details.push(`Missing required field: ${field}`);
			if (details.length > 0) return {
				name: "SKILL.md format",
				status: "fail",
				message: "Missing required frontmatter fields",
				details,
				duration: Date.now() - startTime
			};
			if (typeof frontmatter.name !== "string") return {
				name: "SKILL.md format",
				status: "fail",
				message: "Field \"name\" must be a string",
				duration: Date.now() - startTime
			};
			if (typeof frontmatter.description !== "string") return {
				name: "SKILL.md format",
				status: "fail",
				message: "Field \"description\" must be a string",
				duration: Date.now() - startTime
			};
			const { content: bodyContent } = parseFrontmatter(content);
			if (!bodyContent || bodyContent.length < 10) return {
				name: "SKILL.md format",
				status: "warn",
				message: "SKILL.md body is too short",
				details: ["Consider adding more detailed documentation"],
				duration: Date.now() - startTime
			};
			return {
				name: "SKILL.md format",
				status: "pass",
				message: "Valid SKILL.md format",
				duration: Date.now() - startTime
			};
		} catch (err) {
			return {
				name: "SKILL.md format",
				status: "fail",
				message: err instanceof Error ? err.message : "Failed to parse SKILL.md",
				duration: Date.now() - startTime
			};
		}
	}
	/**
	* Test dependencies
	*/
	async testDependencies(metadata) {
		const startTime = Date.now();
		const details = [];
		let hasWarnings = false;
		let hasFailures = false;
		const requires = metadata.xopc?.requires;
		if (!requires) return {
			name: "Dependencies",
			status: "skip",
			message: "No dependencies declared",
			duration: Date.now() - startTime
		};
		if (requires.bins) for (const bin of requires.bins) if (!hasBinary(bin)) {
			details.push(`Missing required binary: ${bin}`);
			hasFailures = true;
		} else details.push(`Found binary: ${bin}`);
		if (requires.anyBins) if (!requires.anyBins.some((bin) => hasBinary(bin))) {
			details.push(`Missing binaries (need one of): ${requires.anyBins.join(", ")}`);
			hasFailures = true;
		} else details.push(`Found at least one binary from: ${requires.anyBins.join(", ")}`);
		const installSpecs = metadata.xopc?.install;
		if (installSpecs && installSpecs.length > 0) {
			details.push(`Available installers: ${installSpecs.map((s) => s.kind).join(", ")}`);
			for (const spec of installSpecs) {
				const installCheck = this.validateInstallSpec(spec);
				if (installCheck.status === "fail") {
					details.push(installCheck.message);
					hasWarnings = true;
				}
			}
		}
		return {
			name: "Dependencies",
			status: hasFailures ? "fail" : hasWarnings ? "warn" : "pass",
			message: hasFailures ? "Missing dependencies" : hasWarnings ? "Dependency warnings" : "All dependencies satisfied",
			details,
			duration: Date.now() - startTime
		};
	}
	/**
	* Test security
	*/
	async testSecurity(skillDir) {
		const startTime = Date.now();
		try {
			const summary = await scanSkillDirectory(skillDir);
			const details = [
				`Critical: ${summary.critical}`,
				`Warnings: ${summary.warn}`,
				`Info: ${summary.info}`
			];
			if (summary.critical > 0) return {
				name: "Security",
				status: "fail",
				message: "Critical security issues found",
				details: [...details, ...summary.findings.filter((f) => f.severity === "critical").map((f) => `${f.message} at ${relative(skillDir, f.file)}:${f.line}`)],
				duration: Date.now() - startTime
			};
			if (summary.warn > 0) return {
				name: "Security",
				status: "warn",
				message: "Security warnings found",
				details: [...details, ...summary.findings.filter((f) => f.severity === "warning").map((f) => `${f.message} at ${relative(skillDir, f.file)}:${f.line}`)],
				duration: Date.now() - startTime
			};
			return {
				name: "Security",
				status: "pass",
				message: "No security issues",
				details,
				duration: Date.now() - startTime
			};
		} catch (err) {
			return {
				name: "Security",
				status: "fail",
				message: err instanceof Error ? err.message : "Security scan failed",
				duration: Date.now() - startTime
			};
		}
	}
	/**
	* Test metadata
	*/
	testMetadata(metadata, _skillDir) {
		const startTime = Date.now();
		const details = [];
		let hasWarnings = false;
		const emoji = metadata.xopc?.emoji;
		if (emoji) details.push(`Emoji: ${emoji}`);
		else {
			details.push("No emoji defined (recommended)");
			hasWarnings = true;
		}
		if (metadata.homepage) details.push(`Homepage: ${metadata.homepage}`);
		else {
			details.push("No homepage defined (recommended)");
			hasWarnings = true;
		}
		const os = metadata.xopc?.os;
		if (os) details.push(`Supported OS: ${os.join(", ")}`);
		const installSpecs = metadata.xopc?.install;
		if (installSpecs && installSpecs.length > 0) details.push(`Installers: ${installSpecs.length} defined`);
		return {
			name: "Metadata",
			status: hasWarnings ? "warn" : "pass",
			message: hasWarnings ? "Metadata could be improved" : "Complete metadata",
			details,
			duration: Date.now() - startTime
		};
	}
	/**
	* Test code examples
	*/
	async testExamples(content, _skillDir) {
		const startTime = Date.now();
		const details = [];
		let hasFailures = false;
		const codeBlocks = this.extractCodeBlocks(content);
		if (codeBlocks.length === 0) return {
			name: "Examples",
			status: "warn",
			message: "No code examples found",
			details: ["Consider adding usage examples"],
			duration: Date.now() - startTime
		};
		details.push(`Found ${codeBlocks.length} code block(s)`);
		const shellCommands = codeBlocks.filter((block) => {
			const lang = block.language?.toLowerCase();
			return lang === "bash" || lang === "sh" || lang === "shell";
		});
		if (shellCommands.length > 0) {
			details.push(`Shell examples: ${shellCommands.length}`);
			for (const cmd of shellCommands) {
				const syntaxCheck = this.validateShellSyntax(cmd.code);
				if (!syntaxCheck.valid) {
					details.push(`Invalid syntax: ${syntaxCheck.error}`);
					hasFailures = true;
				}
			}
		}
		return {
			name: "Examples",
			status: hasFailures ? "fail" : "pass",
			message: hasFailures ? "Invalid examples found" : "Examples validated",
			details,
			duration: Date.now() - startTime
		};
	}
	validateInstallSpec(spec) {
		switch (spec.kind) {
			case "brew":
				if (!spec.formula) return {
					name: "Install spec",
					status: "fail",
					message: "Brew installer missing \"formula\" field"
				};
				break;
			case "pnpm":
			case "npm":
			case "yarn":
			case "bun":
				if (!spec.package) return {
					name: "Install spec",
					status: "fail",
					message: "Node installer missing \"package\" field"
				};
				break;
			case "go":
				if (!spec.module) return {
					name: "Install spec",
					status: "fail",
					message: "Go installer missing \"module\" field"
				};
				break;
			case "uv":
				if (!spec.package) return {
					name: "Install spec",
					status: "fail",
					message: "UV installer missing \"package\" field"
				};
				break;
		}
		return {
			name: "Install spec",
			status: "pass"
		};
	}
	extractCodeBlocks(content) {
		const blocks = [];
		const regex = /```(\w+)?\n([\s\S]*?)```/g;
		let match;
		while ((match = regex.exec(content)) !== null) blocks.push({
			language: match[1] || "text",
			code: match[2].trim()
		});
		return blocks;
	}
	validateShellSyntax(code) {
		const lines = code.split("\n").filter((line) => line.trim() && !line.trim().startsWith("#"));
		for (const line of lines) {
			const singleQuotes = (line.match(/'/g) || []).length;
			const doubleQuotes = (line.match(/"/g) || []).length;
			if (singleQuotes % 2 !== 0) return {
				valid: false,
				error: "Unclosed single quote"
			};
			if (doubleQuotes % 2 !== 0) return {
				valid: false,
				error: "Unclosed double quote"
			};
			if ((line.match(/\(/g) || []).length !== (line.match(/\)/g) || []).length) return {
				valid: false,
				error: "Mismatched parentheses"
			};
		}
		return { valid: true };
	}
	createReport(skillDir, results) {
		const summary = {
			total: results.length,
			passed: results.filter((r) => r.status === "pass").length,
			failed: results.filter((r) => r.status === "fail").length,
			warnings: results.filter((r) => r.status === "warn").length,
			skipped: results.filter((r) => r.status === "skip").length
		};
		const passed = summary.failed === 0 && (this.options.strict ? summary.warnings === 0 : true);
		return {
			skillName: basename(skillDir),
			skillPath: skillDir,
			timestamp: Date.now(),
			results,
			summary,
			passed
		};
	}
	createFailureReport(skillDir, message) {
		return {
			skillName: basename(skillDir),
			skillPath: skillDir,
			timestamp: Date.now(),
			results: [{
				name: "Validation",
				status: "fail",
				message
			}],
			summary: {
				total: 1,
				passed: 0,
				failed: 1,
				warnings: 0,
				skipped: 0
			},
			passed: false
		};
	}
};
var SkillTestRunner = class {
	options;
	framework;
	constructor(options) {
		this.options = options;
		this.framework = new SkillTestFramework(options);
	}
	async run() {
		const reports = [];
		const skillsDir = this.options.skillsDir;
		log.info({ skillsDir }, "Running skill tests");
		const skillDirs = this.findSkillDirectories(skillsDir);
		for (const skillDir of skillDirs) {
			const report = await this.framework.testSkill(skillDir);
			reports.push(report);
		}
		return {
			reports,
			passed: reports.every((r) => r.passed)
		};
	}
	findSkillDirectories(skillsDir) {
		const dirs = [];
		try {
			const entries = readdirSync(skillsDir, { withFileTypes: true });
			for (const entry of entries) if (entry.isDirectory() && !entry.name.startsWith(".")) {
				const skillDir = join(skillsDir, entry.name);
				if (existsSync(join(skillDir, "SKILL.md"))) dirs.push(skillDir);
			}
		} catch (err) {
			log.warn({ error: err }, "Failed to scan skills directory");
		}
		return dirs;
	}
};
/**
* Format test results as text
*/
function formatTestResults(reports, verbose = false) {
	const lines = [];
	for (const report of reports) {
		const icon = report.passed ? "✅" : "❌";
		lines.push(`\n${icon} ${report.skillName}`);
		lines.push("─".repeat(50));
		for (const result of report.results) {
			const statusIcon = result.status === "pass" ? "✓" : result.status === "fail" ? "✗" : result.status === "warn" ? "⚠" : "○";
			lines.push(`  ${statusIcon} ${result.name}: ${result.message || ""}`);
			if (verbose && result.details) for (const detail of result.details) lines.push(`     ${detail}`);
		}
		lines.push(`  Summary: ${report.summary.passed}/${report.summary.total} passed`);
		if (report.summary.failed > 0) lines.push(`  Failed: ${report.summary.failed}`);
		if (report.summary.warnings > 0) lines.push(`  Warnings: ${report.summary.warnings}`);
	}
	const totalTests = reports.reduce((sum, r) => sum + r.summary.total, 0);
	const totalPassed = reports.reduce((sum, r) => sum + r.summary.passed, 0);
	const totalFailed = reports.reduce((sum, r) => sum + r.summary.failed, 0);
	const totalWarnings = reports.reduce((sum, r) => sum + r.summary.warnings, 0);
	lines.push("\n" + "=".repeat(50));
	lines.push(`Total: ${totalTests} tests, ${totalPassed} passed, ${totalFailed} failed, ${totalWarnings} warnings`);
	const allPassed = reports.every((r) => r.passed);
	lines.push(`Result: ${allPassed ? "✅ PASSED" : "❌ FAILED"}`);
	return lines.join("\n");
}
/**
* Format test results as JSON
*/
function formatTestResultsJson(reports) {
	return JSON.stringify({
		timestamp: (/* @__PURE__ */ new Date()).toISOString(),
		totalSkills: reports.length,
		passedSkills: reports.filter((r) => r.passed).length,
		reports
	}, null, 2);
}
/**
* Format test results as TAP (Test Anything Protocol)
*/
function formatTestResultsTap(reports) {
	const lines = [];
	let testNum = 1;
	lines.push(`TAP version 13`);
	lines.push(`1..${reports.reduce((sum, r) => sum + r.results.length, 0)}`);
	for (const report of reports) for (const result of report.results) {
		const status = result.status === "pass" ? "ok" : "not ok";
		const testId = testNum++;
		lines.push(`${status} ${testId} - ${report.skillName}: ${result.name}`);
		if (result.status === "fail" && result.message) {
			lines.push(`  ---`);
			lines.push(`  message: ${result.message}`);
			lines.push(`  ...`);
		}
	}
	return lines.join("\n");
}
//#endregion
export { SkillTestFramework, SkillTestRunner, formatTestResults, formatTestResultsJson, formatTestResultsTap };

//# sourceMappingURL=test-framework.js.map