"use strict";
// Read-Before-Write Gate
//
// Tracks which file paths have been read in the current run. When a write
// tool (file_write / file_edit) targets a path that has NOT been read,
// the gate can block the write (mode: 'enforce') or warn (mode: 'warn').
//
// This prevents "imaginary repo syndrome" — where the agent edits files
// it has never inspected, inventing content or clobbering existing work.
//
// The canonical write tools are:
//   file_write  — full overwrite
//   file_edit   — targeted string replacement
//
// New file creation (file_write to a non-existent path) is allowlisted
// by default because the agent cannot read something that doesn't exist.
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.ReadBeforeWriteGate = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
// ─── Gate ────────────────────────────────────────────────────────────
/** Write tools whose path argument lives under `input.path`. */
const WRITE_TOOLS = new Set(['file_write', 'file_edit']);
/** Read tools whose path argument lives under `input.path`. */
const READ_TOOLS = new Set(['file_read', 'file_write' /* write also reads via edit */]);
class ReadBeforeWriteGate {
    mode;
    allowNewFiles;
    exemptPaths;
    /** Normalized absolute paths read during this session. */
    readLedger = new Set();
    /** Violations recorded during the session. */
    violations = [];
    constructor(options = {}) {
        this.mode = options.mode ?? 'warn';
        this.allowNewFiles = options.allowNewFiles ?? true;
        this.exemptPaths = new Set((options.exemptPaths ?? []).map((p) => path.resolve(p)));
    }
    // ─── Public API ───────────────────────────────────────────────────
    /**
     * Record a file path as having been read. Call this after every
     * successful file_read (or file_edit which reads before writing).
     */
    recordRead(filePath) {
        this.readLedger.add(path.resolve(filePath));
    }
    /**
     * Record reads for multiple paths at once (e.g. after a grep that
     * returned file paths).
     */
    recordReads(filePaths) {
        for (const fp of filePaths)
            this.recordRead(fp);
    }
    /**
     * Check whether a write to `filePath` is allowed under the current mode.
     * Does NOT mutate state — call `recordRead` separately when reads succeed.
     */
    checkWrite(filePath, toolName) {
        const resolved = path.resolve(filePath);
        // Explicit exemption
        if (this.exemptPaths.has(resolved)) {
            return { allowed: true, wasRead: false, isNewFile: false, isExempt: true };
        }
        // Was it already read?
        const wasRead = this.readLedger.has(resolved);
        if (wasRead) {
            return { allowed: true, wasRead: true, isNewFile: false, isExempt: false };
        }
        // New file check (only meaningful for file_write)
        if (this.allowNewFiles && toolName === 'file_write' && !fileExistsSync(resolved)) {
            return { allowed: true, wasRead: false, isNewFile: true, isExempt: false };
        }
        // Violation
        const reason = `file_read required before ${toolName}: '${filePath}' has not been read in this session.`;
        this.violations.push({
            path: filePath,
            tool: toolName,
            timestamp: new Date().toISOString(),
        });
        if (this.mode === 'enforce') {
            return { allowed: false, wasRead: false, isNewFile: false, isExempt: false, reason };
        }
        // warn mode — log to stderr and allow
        if (this.mode === 'warn') {
            process.stderr.write(`[ReadBeforeWriteGate] WARN: ${reason}\n`);
        }
        return { allowed: true, wasRead: false, isNewFile: false, isExempt: false };
    }
    /**
     * The hook for the ToolDispatcher. Call this from a pre-dispatch gate:
     *   - records reads for file_read calls
     *   - checks gate for file_write / file_edit calls
     *
     * Returns { allowed, reason } compatible with ToolDispatcher permissionCheck.
     */
    gateTool(toolName, input) {
        if (this.mode === 'off')
            return { allowed: true };
        const filePath = typeof input.path === 'string' ? input.path : null;
        if (!filePath)
            return { allowed: true };
        if (READ_TOOLS.has(toolName)) {
            // Return the path for the caller to confirm after successful execution.
            // Do NOT record yet — a failed read should not unlock writes.
            return { allowed: true, pendingReadPath: filePath };
        }
        if (WRITE_TOOLS.has(toolName)) {
            const check = this.checkWrite(filePath, toolName);
            if (!check.allowed) {
                return { allowed: false, reason: check.reason };
            }
        }
        return { allowed: true };
    }
    /** Confirm that a read tool executed successfully. Call after successful execution. */
    confirmRead(filePath) {
        this.recordRead(filePath);
    }
    /** How many distinct paths have been read. */
    get readCount() {
        return this.readLedger.size;
    }
    /** Snapshot of the read ledger (absolute paths). */
    get readPaths() {
        return Array.from(this.readLedger);
    }
    /** Clear state for a new run. */
    reset() {
        this.readLedger.clear();
        this.violations.length = 0;
    }
}
exports.ReadBeforeWriteGate = ReadBeforeWriteGate;
// ─── Helpers ─────────────────────────────────────────────────────────
function fileExistsSync(filePath) {
    try {
        fs.accessSync(filePath);
        return true;
    }
    catch {
        return false;
    }
}
//# sourceMappingURL=readBeforeWriteGate.js.map