"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GrepTool = void 0;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const pathResolution_1 = require("./pathResolution");
const MAX_SEARCH_FILE_BYTES = 1_000_000;
const MAX_MATCHES = 200;
// Directories grep skips by default. Scratch surfaces (`agent-outputs/`,
// `.harness/uploads/`) accumulate stale model output and uploaded
// attachments that nearly always pollute later searches with off-topic
// matches — observed on a HybridTurtle session where a 2-day-old VW
// car-finder report was returned for a "trading" query. Callers can
// opt back in with `include_scratch: true`.
const ALWAYS_EXCLUDED_DIRS = new Set(['node_modules', 'dist']);
const SCRATCH_DIRS = new Set(['agent-outputs']);
const SCRATCH_PATH_FRAGMENTS = ['.harness/uploads', '.harness\\uploads'];
exports.GrepTool = {
    name: 'grep',
    description: 'Search for a pattern in files. Returns matching lines with file paths and line numbers. Skips agent-outputs/ and .harness/uploads/ unless include_scratch is true.',
    parameters: {
        type: 'object',
        properties: {
            pattern: { type: 'string', description: 'Text or regex pattern to search for' },
            path: { type: 'string', description: 'File or directory to search in' },
            include: { type: 'string', description: 'Glob pattern for files to include (e.g. "*.ts")' },
            include_scratch: { type: 'boolean', description: 'When true, do NOT skip agent-outputs/ and .harness/uploads/' },
        },
        required: ['pattern', 'path'],
    },
    isReadOnly: true,
    async execute(input) {
        const pattern = input.pattern;
        const searchPath = (0, pathResolution_1.resolveProjectPath)(input.path);
        const include = input.include;
        const includeScratch = input.include_scratch === true;
        if (!searchPath) {
            return { success: false, output: 'Path is outside the project directory', error: 'path outside project' };
        }
        try {
            const results = [];
            await searchDir(searchPath, pattern, include, results, 0, includeScratch);
            if (results.length === 0) {
                return { success: true, output: `No matches found for "${pattern}"` };
            }
            const output = results.slice(0, MAX_MATCHES).join('\n');
            const suffix = results.length > MAX_MATCHES ? `\n...(${results.length - MAX_MATCHES} more matches)` : '';
            return { success: true, output: output + suffix };
        }
        catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return { success: false, output: `Grep failed: ${msg}`, error: msg };
        }
    },
};
async function searchDir(dirPath, pattern, include, results, depth, includeScratch) {
    if (depth > 10 || results.length > MAX_MATCHES)
        return;
    if (!includeScratch) {
        const normalized = dirPath.replace(/\\/g, '/');
        for (const fragment of SCRATCH_PATH_FRAGMENTS) {
            const fragNormal = fragment.replace(/\\/g, '/');
            if (normalized.includes(fragNormal))
                return;
        }
    }
    const stat = await fs.stat(dirPath);
    if (stat.isFile()) {
        await searchFile(dirPath, pattern, results);
        return;
    }
    const entries = await fs.readdir(dirPath, { withFileTypes: true });
    for (const entry of entries) {
        if (entry.name.startsWith('.') || ALWAYS_EXCLUDED_DIRS.has(entry.name))
            continue;
        if (!includeScratch && SCRATCH_DIRS.has(entry.name))
            continue;
        const fullPath = path.join(dirPath, entry.name);
        if (entry.isDirectory()) {
            await searchDir(fullPath, pattern, include, results, depth + 1, includeScratch);
        }
        else if (entry.isFile()) {
            if (include && !matchGlob(entry.name, include))
                continue;
            await searchFile(fullPath, pattern, results);
        }
    }
}
async function searchFile(filePath, pattern, results) {
    try {
        const stat = await fs.stat(filePath);
        if (stat.size > MAX_SEARCH_FILE_BYTES) {
            return;
        }
        const content = await fs.readFile(filePath, 'utf-8');
        const lines = content.split('\n');
        let regex;
        try {
            regex = new RegExp(pattern, 'i');
        }
        catch {
            regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
        }
        for (let i = 0; i < lines.length && results.length < MAX_MATCHES; i++) {
            if (regex.test(lines[i])) {
                results.push(`${filePath}:${i + 1}: ${lines[i].trimEnd()}`);
            }
        }
    }
    catch {
        // Binary file or unreadable — skip
    }
}
function matchGlob(name, glob) {
    const regex = glob
        .replace(/\./g, '\\.')
        .replace(/\*/g, '.*')
        .replace(/\?/g, '.');
    return new RegExp(`^${regex}$`, 'i').test(name);
}
//# sourceMappingURL=grepTool.js.map