/**
 * assertions-runtime.ts — Runtime assertion functions for agent harness tasks.
 *
 * These run as file-based promptfoo JavaScript assertions, which execute
 * in a full Node.js context (unlike inline `type: javascript` assertions
 * which run in a restricted eval() sandbox without require()).
 *
 * Each export is a named function with the signature:
 *   (output: string, context: { vars, config, ... }) => GradingResult
 *
 * Referenced in promptfoo config as:
 *   value: file://dist/agent-harness/assertions-runtime.js:functionName
 *   config: { filePath: "...", ... }
 *
 * @see https://www.promptfoo.dev/docs/configuration/expected-outputs/javascript/
 */
import { existsSync, readFileSync } from "fs";
import { execSync } from "child_process";
import { resolve, sep } from "path";
function resolveWorkDir(context) {
    return resolve(context.vars.__workingDir || ".");
}
function guardTraversal(workDir, target, label) {
    if (!target.startsWith(workDir + sep) && target !== workDir) {
        return {
            pass: false,
            score: 0,
            reason: `Path traversal: ${label} escapes sandbox`,
        };
    }
    return null;
}
/**
 * Assert that a file exists in the sandbox working directory.
 * Config: { filePath: string }
 */
export function fileExists(_output, context) {
    const filePath = context.config?.filePath ?? "";
    const workDir = resolveWorkDir(context);
    const target = resolve(workDir, filePath);
    const traversal = guardTraversal(workDir, target, filePath);
    if (traversal)
        return traversal;
    const exists = existsSync(target);
    return {
        pass: exists,
        score: exists ? 1 : 0,
        reason: exists
            ? `File exists: ${filePath}`
            : `Expected file not found: ${filePath}`,
    };
}
/**
 * Assert that a file contains expected content.
 * Config: { filePath: string, content: string }
 */
export function fileContains(_output, context) {
    const filePath = context.config?.filePath ?? "";
    const expected = context.config?.content ?? "";
    const workDir = resolveWorkDir(context);
    const target = resolve(workDir, filePath);
    const traversal = guardTraversal(workDir, target, filePath);
    if (traversal)
        return traversal;
    if (!existsSync(target)) {
        return { pass: false, score: 0, reason: `File not found: ${filePath}` };
    }
    const content = readFileSync(target, "utf-8");
    const contains = content.includes(expected);
    return {
        pass: contains,
        score: contains ? 1 : 0,
        reason: contains
            ? "File contains expected content"
            : "File does not contain expected content",
    };
}
/**
 * Assert that a shell command succeeds (exit code 0) in the sandbox.
 * Config: { command: string, timeoutMs?: number }
 *
 * SECURITY: Commands come from developer-authored task definitions,
 * not from user input or LLM output.
 */
export function commandSucceeds(_output, context) {
    const command = context.config?.command ?? "";
    const timeoutMs = context.config?.timeoutMs ?? 30000;
    const workDir = context.vars.__workingDir || ".";
    try {
        execSync(command, { cwd: workDir, timeout: timeoutMs });
        return { pass: true, score: 1, reason: `Command succeeded: ${command}` };
    }
    catch (err) {
        const error = err;
        return {
            pass: false,
            score: 0,
            reason: `Command failed: ${error.message || String(err)}`,
        };
    }
}
/**
 * Assert that the git diff in the sandbox contains expected content.
 * Config: { expected?: string }
 */
export function diffMatches(_output, context) {
    const expected = context.config?.expected;
    const workDir = context.vars.__workingDir || ".";
    try {
        const diff = execSync("git diff", {
            cwd: workDir,
            encoding: "utf-8",
        });
        if (typeof expected === "string") {
            const contains = diff.includes(expected);
            return {
                pass: contains,
                score: contains ? 1 : 0,
                reason: contains
                    ? "Diff matches expected pattern"
                    : "Diff does not match",
            };
        }
        return {
            pass: diff.length > 0,
            score: diff.length > 0 ? 1 : 0,
            reason: "Diff exists",
        };
    }
    catch (err) {
        const error = err;
        return {
            pass: false,
            score: 0,
            reason: `Failed to get diff: ${error.message}`,
        };
    }
}
