import { createHash } from "node:crypto";
import { basename, dirname, join, resolve } from "node:path";
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
import { BestAgentError } from "@best-agent/foundation";
const AUTO_MEMORY_ENTRY_STALE_MS = 30 * 24 * 60 * 60 * 1000;
const AUTO_MEMORY_ENTRY_PRUNE_MS = 90 * 24 * 60 * 60 * 1000;
const AUTO_MEMORY_PENDING_PRUNE_MS = 30 * 24 * 60 * 60 * 1000;
const MANUAL_MEMORY_PROMPT_MAX_BYTES = 2 * 1024;
const PROJECT_MEMORY_PROMPT_TRUNCATED_NOTICE = "[additional project memory omitted from prompt]";
const AUTO_MEMORY_FILE_VERSION = 1;
const AUTO_MEMORY_MAX_EVIDENCE = 4;
const AUTO_MEMORY_MAX_ENTRIES = 32;
const AUTO_MEMORY_MAX_PENDING = 64;
const PROJECT_MEMORY_SECRET_RULES = [
    {
        name: "private-key",
        pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/u,
    },
    {
        name: "openai-api-key",
        pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}\b/u,
    },
    {
        name: "anthropic-api-key",
        pattern: /\bsk-ant-[A-Za-z0-9_-]{10,}\b/u,
    },
    {
        name: "github-pat",
        pattern: /\bgh(?:p|o|u|s|r)_[A-Za-z0-9]{20,}\b/u,
    },
    {
        name: "aws-access-key",
        pattern: /\bAKIA[0-9A-Z]{16}\b/u,
    },
    {
        name: "gcp-private-key",
        pattern: /"private_key"\s*:/u,
    },
];
const USER_RULE_SIGNAL = /\b(always|never|prefer|avoid|keep|use|should|must|do not|don't)\b/iu;
const USER_RULE_TRANSIENT_PREFIX = /^(need to|check |look at |inspect |debug |investigate |fix |update |edit |read |open |run |summarize |what |why |how |can |could |would |please )/iu;
const USER_RULE_TRANSIENT_PHRASE = /\b(today|right now|currently|this session|this turn|temporary|for now)\b/iu;
const COMMAND_QUERY_TOPICS = [
    "build",
    "check",
    "command",
    "commands",
    "compile",
    "dev",
    "format",
    "install",
    "lint",
    "run",
    "script",
    "scripts",
    "serve",
    "start",
    "test",
];
const WORKSPACE_COMMAND_TOPICS = [
    "build",
    "command",
    "commands",
    "dev",
    "install",
    "lint",
    "run",
    "script",
    "scripts",
    "test",
];
export class LocalProjectMemoryStore {
    #autoCandidateDir;
    #autoPath;
    #cwd;
    #manualPath;
    #projectHash;
    constructor(options) {
        this.#cwd = resolve(options.cwd);
        this.#projectHash = createHash("sha1").update(this.#cwd).digest("hex");
        const projectDir = join(resolve(options.storageRoot), "host", "projects", this.#projectHash);
        this.#manualPath = join(projectDir, "memory.md");
        this.#autoPath = join(projectDir, "auto-memory.json");
        this.#autoCandidateDir = join(projectDir, "auto-candidates");
    }
    prepareForSession() {
        this.#consolidateAutoCandidates();
    }
    get() {
        const manual = readManualMemory(this.#manualPath);
        const auto = readAutoMemoryFile(this.#autoPath, this.#projectHash);
        const autoEntryCount = auto.entries.length;
        const autoExists = auto.entries.length > 0 || auto.pending.length > 0 || existsSync(this.#autoPath);
        return {
            auto: {
                entryCount: autoEntryCount,
                exists: autoExists,
                path: this.#autoPath,
                renderedContent: renderAutoMemoryForDisplay(auto),
                updatedAt: autoExists ? statSyncSafe(this.#autoPath)?.mtime.toISOString() : undefined,
            },
            content: manual.content,
            cwd: this.#cwd,
            exists: manual.exists,
            path: this.#manualPath,
            projectHash: this.#projectHash,
            renderedContent: renderProjectMemoryForDisplay(manual.content, auto),
            updatedAt: manual.updatedAt,
        };
    }
    set(content) {
        if (!content.trim()) {
            return this.clear();
        }
        const blockedRule = detectProjectMemorySecretRule(content);
        if (blockedRule) {
            throw new BestAgentError("PROJECT_MEMORY_SECRET_BLOCKED", `Project memory contains a blocked secret pattern (${blockedRule}).`);
        }
        mkdirSync(dirname(this.#manualPath), { recursive: true });
        writeFileSync(this.#manualPath, `${normalizeText(content).trimEnd()}\n`, "utf8");
        return this.get();
    }
    clear() {
        rmSync(this.#manualPath, { force: true });
        rmSync(this.#autoPath, { force: true });
        rmSync(this.#autoCandidateDir, { force: true, recursive: true });
        return this.get();
    }
    listSyncEntries() {
        const manual = readManualMemory(this.#manualPath);
        const content = manual.content.trim();
        if (!content) {
            return [];
        }
        return [
            {
                checksum: checksumProjectMemoryEntry(content),
                content,
                key: "manual",
                updatedAt: manual.updatedAt ?? new Date(0).toISOString(),
            },
        ];
    }
    mergeSyncEntries(entries) {
        const manual = readManualMemory(this.#manualPath);
        let nextContent = manual.content.trim();
        let changed = false;
        for (const entry of entries) {
            if (entry.key !== "manual" || detectProjectMemorySecretRule(entry.content)) {
                continue;
            }
            const candidate = normalizeText(entry.content).trim();
            if (!candidate) {
                continue;
            }
            if (!nextContent ||
                (manual.updatedAt ?? new Date(0).toISOString()).localeCompare(entry.updatedAt) < 0) {
                if (nextContent !== candidate) {
                    nextContent = candidate;
                    changed = true;
                }
            }
        }
        if (!changed) {
            return {
                changed: false,
                memory: this.get(),
            };
        }
        return {
            changed: true,
            memory: this.set(nextContent),
        };
    }
    captureCandidate(input) {
        const entries = extractAutoMemoryCandidates(input.items, input.sourceSessionId).filter((entry) => !detectProjectMemorySecretRule(entry.content));
        if (entries.length === 0) {
            return;
        }
        mkdirSync(this.#autoCandidateDir, { recursive: true });
        const extractedAt = new Date().toISOString();
        const candidateFile = {
            entries,
            extractedAt,
            projectHash: this.#projectHash,
            sourceSessionId: input.sourceSessionId,
            version: 1,
        };
        writeFileSync(join(this.#autoCandidateDir, `${sanitizeFileSegment(input.sourceSessionId)}.json`), `${JSON.stringify(candidateFile, null, 2)}\n`, "utf8");
    }
    buildPromptSelection(input) {
        const manualPrompt = buildManualPrompt(this.#manualPath);
        const auto = readAutoMemoryFile(this.#autoPath, this.#projectHash);
        const retrievalContext = buildAutoMemoryRetrievalContext(input.items);
        const selection = selectAutoMemoryEntries(auto.entries, retrievalContext);
        const autoPrompt = renderAutoMemoryPrompt(selection.entries);
        const content = [manualPrompt, autoPrompt]
            .filter((value) => Boolean(value))
            .join("\n\n")
            .trim();
        if (!content) {
            return {
                prompt: undefined,
                referencedEntryKeys: [],
            };
        }
        return {
            prompt: `Project memory (workspace-scoped):\n${truncateProjectMemoryPrompt(content)}`,
            referencedEntryKeys: selection.entries.map((entry) => entry.key),
        };
    }
    buildSystemPrompt() {
        const manualPrompt = buildManualPrompt(this.#manualPath);
        const auto = readAutoMemoryFile(this.#autoPath, this.#projectHash);
        const autoPrompt = renderAutoMemoryPrompt(auto.entries.filter((entry) => !isAutoEntryStale(entry)).sort(compareAutoMemoryEntries));
        const content = [manualPrompt, autoPrompt]
            .filter((value) => Boolean(value))
            .join("\n\n")
            .trim();
        return content
            ? `Project memory (workspace-scoped):\n${truncateProjectMemoryPrompt(content)}`
            : undefined;
    }
    markReferenced(entryKeys, referencedAt = new Date().toISOString()) {
        if (entryKeys.length === 0 || !existsSync(this.#autoPath)) {
            return;
        }
        const auto = readAutoMemoryFile(this.#autoPath, this.#projectHash);
        const keySet = new Set(entryKeys);
        let changed = false;
        const nextEntries = auto.entries.map((entry) => {
            if (!keySet.has(entry.key)) {
                return entry;
            }
            changed = true;
            return {
                ...entry,
                lastReferencedAt: referencedAt,
                referenceCount: entry.referenceCount + 1,
            };
        });
        if (!changed) {
            return;
        }
        writeAutoMemoryFile(this.#autoPath, {
            ...auto,
            entries: nextEntries,
        });
    }
    #consolidateAutoCandidates() {
        const candidatePaths = this.#listCandidatePaths();
        if (candidatePaths.length === 0 && !existsSync(this.#autoPath)) {
            return;
        }
        const current = readAutoMemoryFile(this.#autoPath, this.#projectHash);
        const entries = new Map(current.entries.map((entry) => [entry.key, entry]));
        const pending = new Map(current.pending
            .filter((entry) => !isAutoPendingPrunable(entry))
            .map((entry) => [entry.key, entry]));
        const candidates = candidatePaths
            .map((path) => readCandidateFile(path, this.#projectHash))
            .filter((candidate) => Boolean(candidate))
            .sort((left, right) => left.extractedAt.localeCompare(right.extractedAt));
        for (const candidateFile of candidates) {
            for (const candidate of candidateFile.entries) {
                if (detectProjectMemorySecretRule(candidate.content)) {
                    continue;
                }
                const existingEntry = entries.get(candidate.key);
                if (existingEntry) {
                    entries.set(candidate.key, mergeCandidateIntoEntry(existingEntry, candidate));
                    continue;
                }
                const pendingEntry = pending.get(candidate.key);
                const mergedPending = pendingEntry
                    ? mergeCandidateIntoPending(pendingEntry, candidate)
                    : createPendingEntry(candidate);
                if (mergedPending.observationCount >= mergedPending.promoteAfter) {
                    entries.set(candidate.key, promotePendingEntry(mergedPending));
                    pending.delete(candidate.key);
                    continue;
                }
                pending.set(candidate.key, mergedPending);
            }
        }
        const nextEntries = [...entries.values()]
            .filter((entry) => !isAutoEntryPrunable(entry))
            .sort((left, right) => autoEntrySortKey(right).localeCompare(autoEntrySortKey(left)))
            .slice(0, AUTO_MEMORY_MAX_ENTRIES);
        const nextPending = [...pending.values()]
            .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
            .slice(0, AUTO_MEMORY_MAX_PENDING);
        if (nextEntries.length > 0 || nextPending.length > 0) {
            writeAutoMemoryFile(this.#autoPath, {
                entries: nextEntries,
                lastConsolidatedAt: new Date().toISOString(),
                pending: nextPending,
                projectHash: this.#projectHash,
                version: AUTO_MEMORY_FILE_VERSION,
            });
        }
        else {
            rmSync(this.#autoPath, { force: true });
        }
        for (const path of candidatePaths) {
            unlinkSync(path);
        }
    }
    #listCandidatePaths() {
        if (!existsSync(this.#autoCandidateDir)) {
            return [];
        }
        return readdirSync(this.#autoCandidateDir)
            .filter((entry) => entry.endsWith(".json"))
            .map((entry) => join(this.#autoCandidateDir, entry));
    }
}
function buildManualPrompt(path) {
    const manual = readManualMemory(path);
    const content = manual.content.trim();
    if (!content) {
        return undefined;
    }
    return `Manual project memory (user-authored, always applicable):\n${content}`;
}
function buildAutoMemoryRetrievalContext(items) {
    const activePaths = extractActivePaths(items);
    const pathTokens = activePaths.flatMap((path) => tokenizeForRetrieval(basename(path)));
    return {
        activePaths,
        queryTokens: new Set([...tokenizeForRetrieval(findLatestUserText(items)), ...pathTokens]),
    };
}
function buildWorkspaceCommandCandidate(command, sourceSessionId, observedAt) {
    return {
        content: `Use \`${command}\` for workspace commands in this repository.`,
        evidence: [
            {
                kind: "shell-command",
                observedAt,
                sourceSessionId,
                text: command,
            },
        ],
        key: `workspace-command:${command}`,
        kind: "workspace-command",
        observedAt,
        observationCount: 1,
        promoteAfter: 2,
        topics: Array.from(new Set([...WORKSPACE_COMMAND_TOPICS, command])),
    };
}
function buildUserRuleCandidate(text, sourceSessionId, observedAt) {
    const content = normalizeText(text).trim();
    return {
        content,
        evidence: [
            {
                kind: "user-message",
                observedAt,
                sourceSessionId,
                text: content,
            },
        ],
        key: `user-rule:${checksumProjectMemoryEntry(content)}`,
        kind: "user-rule",
        observedAt,
        observationCount: 1,
        promoteAfter: 1,
        topics: Array.from(new Set(["preference", "rule", ...tokenizeForRetrieval(content)])),
    };
}
function checksumProjectMemoryEntry(content) {
    return createHash("sha256").update(content).digest("hex");
}
function createPendingEntry(candidate) {
    return {
        content: candidate.content,
        createdAt: candidate.observedAt,
        evidence: candidate.evidence.slice(0, AUTO_MEMORY_MAX_EVIDENCE),
        key: candidate.key,
        kind: candidate.kind,
        observationCount: candidate.observationCount,
        promoteAfter: candidate.promoteAfter,
        topics: candidate.topics,
        updatedAt: candidate.observedAt,
    };
}
function detectProjectMemorySecretRule(content) {
    for (const rule of PROJECT_MEMORY_SECRET_RULES) {
        if (rule.pattern.test(content)) {
            return rule.name;
        }
    }
    return undefined;
}
function extractActivePaths(items) {
    const paths = [];
    for (const item of items) {
        if (item.kind !== "tool-call") {
            continue;
        }
        paths.push(...extractPathsFromToolCall(item));
    }
    return Array.from(new Set(paths)).slice(-6);
}
function extractAutoMemoryCandidates(items, sourceSessionId) {
    const observedAt = new Date().toISOString();
    const entries = [];
    const seenUserRules = new Set();
    for (const item of items) {
        if (item.kind !== "message" || item.role !== "user") {
            continue;
        }
        const userRule = maybeDurableUserRule(item.text);
        if (!userRule || seenUserRules.has(userRule)) {
            continue;
        }
        seenUserRules.add(userRule);
        entries.push(buildUserRuleCandidate(userRule, sourceSessionId, observedAt));
    }
    const toolCalls = items.filter((item) => item.kind === "tool-call");
    const toolResults = items.filter((item) => item.kind === "tool-result");
    const resultCallIds = new Set(toolResults.map((item) => item.callId));
    const commands = new Set();
    for (const toolCall of toolCalls) {
        if (toolCall.toolName !== "shell" || !resultCallIds.has(toolCall.callId)) {
            continue;
        }
        const command = extractWorkspaceCommand(toolCall.input);
        if (command) {
            commands.add(command);
        }
    }
    for (const command of commands) {
        entries.push(buildWorkspaceCommandCandidate(command, sourceSessionId, observedAt));
    }
    return entries;
}
function extractPathsFromToolCall(item) {
    if (item.toolName === "read-scratch" ||
        item.toolName === "append-scratch" ||
        item.toolName === "read-file" ||
        item.toolName === "write-scratch" ||
        item.toolName === "write-workspace" ||
        item.toolName === "edit" ||
        item.toolName === "write-artifact") {
        const rawValue = item.input.split("|")[0]?.trim() ?? item.input.trim();
        const value = item.toolName === "read-file" ? stripReadFileLineRangeSuffix(rawValue) : rawValue;
        return value.length > 0 ? [value] : [];
    }
    if (item.toolName === "shell") {
        const shellPath = extractShellPath(item.input);
        return shellPath ? [shellPath] : [];
    }
    return [];
}
function stripReadFileLineRangeSuffix(value) {
    const match = value.match(/^(.*?):\d+-\d+$/u);
    return (match?.[1] ?? value).trim();
}
function extractShellPath(command) {
    const match = command.match(/(?:^|\s)([./~][^\s'"`]+(?:\/[^\s'"`]+)+)/u);
    return match?.[1];
}
function extractWorkspaceCommand(input) {
    const firstCommand = input.split(/&&|\|\||;|\n/u)[0]?.trim();
    if (!firstCommand) {
        return undefined;
    }
    const tokens = firstCommand
        .split(/\s+/u)
        .filter(Boolean)
        .filter((token) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(token));
    if (tokens.length === 0) {
        return undefined;
    }
    return normalizeWorkspaceCommand(tokens[0] ?? "");
}
function isAutoEntryPrunable(entry) {
    return (Date.now() - Date.parse(entry.lastReferencedAt ?? entry.updatedAt) > AUTO_MEMORY_ENTRY_PRUNE_MS);
}
function isAutoEntryStale(entry) {
    return (Date.now() - Date.parse(entry.lastReferencedAt ?? entry.updatedAt) > AUTO_MEMORY_ENTRY_STALE_MS);
}
function isAutoPendingPrunable(entry) {
    return Date.now() - Date.parse(entry.updatedAt) > AUTO_MEMORY_PENDING_PRUNE_MS;
}
function maybeDurableUserRule(value) {
    const normalized = normalizeText(value).trim();
    if (!normalized || normalized.length < 12 || normalized.length > 200) {
        return undefined;
    }
    if (USER_RULE_TRANSIENT_PREFIX.test(normalized) || USER_RULE_TRANSIENT_PHRASE.test(normalized)) {
        return undefined;
    }
    if (/[?？]$/.test(normalized) || normalized.includes("\n")) {
        return undefined;
    }
    return USER_RULE_SIGNAL.test(normalized) ? normalized : undefined;
}
function mergeCandidateIntoEntry(entry, candidate) {
    return {
        ...entry,
        content: candidate.content,
        evidence: mergeEvidence(entry.evidence, candidate.evidence),
        observationCount: entry.observationCount + candidate.observationCount,
        topics: Array.from(new Set([...entry.topics, ...candidate.topics])),
        updatedAt: candidate.observedAt,
    };
}
function mergeCandidateIntoPending(entry, candidate) {
    return {
        ...entry,
        content: candidate.content,
        evidence: mergeEvidence(entry.evidence, candidate.evidence),
        observationCount: entry.observationCount + candidate.observationCount,
        topics: Array.from(new Set([...entry.topics, ...candidate.topics])),
        updatedAt: candidate.observedAt,
    };
}
function mergeEvidence(existing, incoming) {
    const merged = new Map();
    for (const evidence of [...existing, ...incoming]) {
        merged.set(`${evidence.kind}:${evidence.text}:${evidence.sourceSessionId}`, evidence);
    }
    return [...merged.values()]
        .sort((left, right) => right.observedAt.localeCompare(left.observedAt))
        .slice(0, AUTO_MEMORY_MAX_EVIDENCE);
}
function normalizeText(value) {
    return (value ?? "").replace(/^\uFEFF/u, "").replace(/\r\n?/gu, "\n");
}
function findLatestUserText(items) {
    for (let index = items.length - 1; index >= 0; index -= 1) {
        const item = items[index];
        if (item?.kind === "message" && item.role === "user") {
            return item.text;
        }
    }
    return undefined;
}
function normalizeWorkspaceCommand(token) {
    const normalized = token
        .trim()
        .replace(/^["'`]+|["'`]+$/gu, "")
        .replace(/^\.\//u, "")
        .toLowerCase();
    switch (normalized) {
        case "bun":
        case "cargo":
        case "deno":
        case "go":
        case "gradle":
        case "just":
        case "make":
        case "mvn":
        case "node":
        case "npm":
        case "nx":
        case "pnpm":
        case "poetry":
        case "python":
        case "python3":
        case "pytest":
        case "ruby":
        case "bazel":
        case "cmake":
        case "dotnet":
        case "rspec":
        case "swift":
        case "turbo":
        case "uv":
        case "yarn":
            return normalized;
        case "gradlew":
            return "gradle";
        case "mvnw":
            return "mvn";
        default:
            return undefined;
    }
}
function promotePendingEntry(entry) {
    return {
        content: entry.content,
        createdAt: entry.createdAt,
        evidence: entry.evidence,
        key: entry.key,
        kind: entry.kind,
        lastReferencedAt: undefined,
        observationCount: entry.observationCount,
        referenceCount: 0,
        topics: entry.topics,
        updatedAt: entry.updatedAt,
    };
}
function readAutoMemoryFile(path, projectHash) {
    if (!existsSync(path)) {
        return {
            entries: [],
            pending: [],
            projectHash,
            version: AUTO_MEMORY_FILE_VERSION,
        };
    }
    try {
        const parsed = JSON.parse(readFileSync(path, "utf8"));
        if (!parsed ||
            parsed.version !== AUTO_MEMORY_FILE_VERSION ||
            parsed.projectHash !== projectHash ||
            !Array.isArray(parsed.entries) ||
            !Array.isArray(parsed.pending)) {
            return {
                entries: [],
                pending: [],
                projectHash,
                version: AUTO_MEMORY_FILE_VERSION,
            };
        }
        return {
            entries: parsed.entries.filter(isAutoMemoryEntry),
            lastConsolidatedAt: parsed.lastConsolidatedAt,
            pending: parsed.pending.filter(isAutoMemoryPendingEntry),
            projectHash,
            version: AUTO_MEMORY_FILE_VERSION,
        };
    }
    catch {
        return {
            entries: [],
            pending: [],
            projectHash,
            version: AUTO_MEMORY_FILE_VERSION,
        };
    }
}
function readCandidateFile(path, projectHash) {
    try {
        const parsed = JSON.parse(readFileSync(path, "utf8"));
        if (!parsed ||
            parsed.version !== 1 ||
            parsed.projectHash !== projectHash ||
            !Array.isArray(parsed.entries)) {
            return null;
        }
        return {
            ...parsed,
            entries: parsed.entries.filter(isAutoMemoryCandidateEntry),
        };
    }
    catch {
        return null;
    }
}
function readManualMemory(path) {
    if (!existsSync(path)) {
        return {
            content: "",
            exists: false,
        };
    }
    const stats = statSync(path);
    return {
        content: normalizeText(readFileSync(path, "utf8")),
        exists: true,
        updatedAt: stats.mtime.toISOString(),
    };
}
function renderAutoMemoryForDisplay(auto) {
    const lines = [];
    if (auto.entries.length > 0) {
        lines.push("Auto memory");
        for (const entry of auto.entries) {
            const prefix = entry.kind === "user-rule" ? "- User rule: " : "- Workspace command: ";
            lines.push(`${prefix}${entry.content}`);
        }
    }
    if (auto.pending.length > 0) {
        if (lines.length > 0) {
            lines.push("");
        }
        lines.push(`Pending auto-memory candidates: ${auto.pending.length}`);
    }
    return lines.join("\n").trim();
}
function renderAutoMemoryPrompt(entries) {
    if (entries.length === 0) {
        return undefined;
    }
    return [
        "Retrieved auto memory for this turn:",
        ...entries.map((entry) => entry.kind === "user-rule"
            ? `- User rule: ${entry.content}`
            : `- Workspace command: ${entry.content}`),
    ].join("\n");
}
function renderProjectMemoryForDisplay(manualContent, auto) {
    const manual = manualContent.trim();
    const autoContent = renderAutoMemoryForDisplay(auto);
    return [manual ? `Manual memory\n${manual}` : undefined, autoContent || undefined]
        .filter((value) => Boolean(value))
        .join("\n\n")
        .trim();
}
function sanitizeFileSegment(value) {
    const normalized = value
        .trim()
        .replace(/[^a-zA-Z0-9._-]+/gu, "-")
        .replace(/^\.+/u, "");
    return normalized || "candidate";
}
function selectAutoMemoryEntries(entries, context) {
    const activePathSet = new Set(context.activePaths);
    const commandTopicSet = new Set(COMMAND_QUERY_TOPICS);
    const scored = entries
        .filter((entry) => !isAutoEntryStale(entry))
        .map((entry) => {
        let score = 0;
        if (entry.kind === "user-rule") {
            score = 100;
        }
        else {
            for (const topic of entry.topics) {
                if (context.queryTokens.has(topic)) {
                    score += 4;
                }
                if (commandTopicSet.has(topic) && context.queryTokens.has(topic)) {
                    score += 2;
                }
            }
        }
        for (const evidence of entry.evidence) {
            const maybePath = evidence.kind === "shell-command" ? extractShellPath(evidence.text) : undefined;
            if (maybePath && activePathSet.has(maybePath)) {
                score += 4;
            }
        }
        return { entry, score };
    })
        .filter(({ entry, score }) => entry.kind === "user-rule" || score > 0)
        .sort((left, right) => right.score - left.score ||
        autoEntrySortKey(right.entry).localeCompare(autoEntrySortKey(left.entry)));
    return {
        entries: scored.slice(0, 4).map(({ entry }) => entry),
    };
}
function compareAutoMemoryEntries(left, right) {
    return autoEntrySortKey(right).localeCompare(autoEntrySortKey(left));
}
function statSyncSafe(path) {
    try {
        return statSync(path);
    }
    catch {
        return undefined;
    }
}
function tokenizeForRetrieval(value) {
    return normalizeText(value)
        .toLowerCase()
        .split(/[^a-z0-9._/-]+/u)
        .map((token) => token.trim())
        .filter((token) => token.length >= 2 && !/^\d+$/.test(token));
}
function truncateProjectMemoryPrompt(content) {
    if (Buffer.byteLength(content, "utf8") <= MANUAL_MEMORY_PROMPT_MAX_BYTES) {
        return content;
    }
    const maxContentBytes = Math.max(0, MANUAL_MEMORY_PROMPT_MAX_BYTES -
        Buffer.byteLength(`\n${PROJECT_MEMORY_PROMPT_TRUNCATED_NOTICE}`, "utf8"));
    let truncated = content;
    while (Buffer.byteLength(truncated, "utf8") > maxContentBytes && truncated.length > 0) {
        truncated = truncated.slice(0, -1);
    }
    const lastBoundary = Math.max(truncated.lastIndexOf("\n"), truncated.lastIndexOf(" "));
    const bounded = (lastBoundary > 0 ? truncated.slice(0, lastBoundary) : truncated).trimEnd();
    return `${bounded}\n${PROJECT_MEMORY_PROMPT_TRUNCATED_NOTICE}`;
}
function writeAutoMemoryFile(path, file) {
    mkdirSync(dirname(path), { recursive: true });
    writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`, "utf8");
}
function autoEntrySortKey(entry) {
    return entry.lastReferencedAt ?? entry.updatedAt;
}
function isAutoMemoryEntry(value) {
    return (Boolean(value) &&
        typeof value === "object" &&
        typeof value.content === "string" &&
        typeof value.createdAt === "string" &&
        Array.isArray(value.evidence) &&
        typeof value.key === "string" &&
        typeof value.kind === "string" &&
        typeof value.observationCount === "number" &&
        typeof value.referenceCount === "number" &&
        Array.isArray(value.topics) &&
        typeof value.updatedAt === "string");
}
function isAutoMemoryPendingEntry(value) {
    return (Boolean(value) &&
        typeof value === "object" &&
        typeof value.content === "string" &&
        typeof value.createdAt === "string" &&
        Array.isArray(value.evidence) &&
        typeof value.key === "string" &&
        typeof value.kind === "string" &&
        typeof value.observationCount === "number" &&
        typeof value.promoteAfter === "number" &&
        Array.isArray(value.topics) &&
        typeof value.updatedAt === "string");
}
function isAutoMemoryCandidateEntry(value) {
    return (Boolean(value) &&
        typeof value === "object" &&
        typeof value.content === "string" &&
        Array.isArray(value.evidence) &&
        typeof value.key === "string" &&
        typeof value.kind === "string" &&
        typeof value.observedAt === "string" &&
        typeof value.observationCount === "number" &&
        typeof value.promoteAfter === "number" &&
        Array.isArray(value.topics));
}
export { detectProjectMemorySecretRule };
//# sourceMappingURL=project-memory.js.map