"use strict";
// Identity layer.
//
// Three persistent files under `.harness/identity/`:
//   - SOUL.md        — long-term agent personality / values
//   - USER.md        — long-term notes about the user
//   - structured.json — queryable people / projects / preferences / facts
//
// All operations are best-effort and idempotent. Missing files yield empty
// strings or empty objects rather than errors so callers can render a
// "nothing here yet" state.
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.readIdentitySnapshot = readIdentitySnapshot;
exports.readIdentityFile = readIdentityFile;
exports.writeIdentityFile = writeIdentityFile;
exports.readStructuredStore = readStructuredStore;
exports.upsertStructuredEntry = upsertStructuredEntry;
exports.deleteStructuredEntry = deleteStructuredEntry;
exports.queryStructured = queryStructured;
exports.renderIdentityForPrompt = renderIdentityForPrompt;
exports.exportIdentity = exportIdentity;
exports.importIdentity = importIdentity;
exports.runIdentityGc = runIdentityGc;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const DEFAULT_SOUL = `# Soul

Long-term identity, voice, and values for the harness agent. Edit freely
— the agent reads this whenever it answers, so anything written here
shapes how it behaves over time.
`;
const DEFAULT_USER = `# User

Long-term notes about the user (preferences, working style, common
projects). The agent updates this file as it learns.
`;
function identityDir(projectDir) {
    return path.join(projectDir, '.harness', 'identity');
}
function identityFilePath(projectDir, fileName) {
    return path.join(identityDir(projectDir), fileName);
}
function structuredFilePath(projectDir) {
    return path.join(identityDir(projectDir), 'structured.json');
}
async function readIdentitySnapshot(projectDir) {
    const [soul, user, structured] = await Promise.all([
        readIdentityFile(projectDir, 'SOUL.md'),
        readIdentityFile(projectDir, 'USER.md'),
        readStructuredStore(projectDir),
    ]);
    return { soul, user, structured };
}
async function readIdentityFile(projectDir, fileName) {
    try {
        return await fs.readFile(identityFilePath(projectDir, fileName), 'utf-8');
    }
    catch {
        return fileName === 'SOUL.md' ? DEFAULT_SOUL : DEFAULT_USER;
    }
}
async function writeIdentityFile(projectDir, fileName, content) {
    const fp = identityFilePath(projectDir, fileName);
    await fs.mkdir(path.dirname(fp), { recursive: true });
    await fs.writeFile(fp, content, 'utf-8');
}
async function readStructuredStore(projectDir) {
    try {
        const raw = await fs.readFile(structuredFilePath(projectDir), 'utf-8');
        const parsed = JSON.parse(raw);
        return { version: 1, entries: Array.isArray(parsed.entries) ? parsed.entries.filter(isEntryLike) : [] };
    }
    catch {
        return { version: 1, entries: [] };
    }
}
async function upsertStructuredEntry(projectDir, input, now = new Date()) {
    const store = await readStructuredStore(projectDir);
    const id = (input.id ?? slugifyId(input.category, input.summary)).slice(0, 80);
    if (!id)
        throw new Error('Cannot derive an id from the input.');
    const existingIdx = store.entries.findIndex((entry) => entry.id === id);
    const isoNow = now.toISOString();
    const next = existingIdx === -1
        ? { id, category: input.category, summary: input.summary, metadata: input.metadata, createdAt: isoNow, updatedAt: isoNow }
        : { ...store.entries[existingIdx], category: input.category, summary: input.summary, metadata: input.metadata, updatedAt: isoNow };
    if (existingIdx === -1)
        store.entries.push(next);
    else
        store.entries[existingIdx] = next;
    await writeStructuredStore(projectDir, store);
    return next;
}
async function deleteStructuredEntry(projectDir, id) {
    const store = await readStructuredStore(projectDir);
    const idx = store.entries.findIndex((entry) => entry.id === id);
    if (idx === -1)
        return false;
    store.entries.splice(idx, 1);
    await writeStructuredStore(projectDir, store);
    return true;
}
async function queryStructured(projectDir, filter = {}) {
    const store = await readStructuredStore(projectDir);
    const q = filter.q ? filter.q.toLowerCase() : '';
    return store.entries.filter((entry) => {
        if (filter.category && entry.category !== filter.category)
            return false;
        if (q) {
            const haystack = (entry.summary + ' ' + entry.id + ' ' + entry.category).toLowerCase();
            if (!haystack.includes(q))
                return false;
        }
        return true;
    });
}
/**
 * Build a compact identity context block suitable for inclusion in a system
 * prompt. Returns an empty string when nothing is configured so it can be
 * concatenated unconditionally.
 */
async function renderIdentityForPrompt(projectDir, options = {}) {
    const snapshot = await readIdentitySnapshot(projectDir);
    const maxChars = options.maxChars ?? 4000;
    const parts = [];
    const soulTrim = stripDefaults(snapshot.soul, DEFAULT_SOUL);
    const userTrim = stripDefaults(snapshot.user, DEFAULT_USER);
    if (soulTrim)
        parts.push(`## Soul\n${soulTrim}`);
    if (userTrim)
        parts.push(`## User\n${userTrim}`);
    if (snapshot.structured.entries.length > 0) {
        const lines = snapshot.structured.entries
            .slice(0, 32)
            .map((entry) => `- [${entry.category}] ${entry.summary}`);
        parts.push(`## Structured facts\n${lines.join('\n')}`);
    }
    if (parts.length === 0)
        return '';
    let output = `# Identity\n${parts.join('\n\n')}`;
    if (output.length > maxChars)
        output = output.slice(0, maxChars) + `\n…[truncated to ${maxChars} chars]`;
    return output;
}
async function writeStructuredStore(projectDir, store) {
    const fp = structuredFilePath(projectDir);
    await fs.mkdir(path.dirname(fp), { recursive: true });
    await fs.writeFile(fp, JSON.stringify(store, null, 2), 'utf-8');
}
function isEntryLike(value) {
    if (!value || typeof value !== 'object')
        return false;
    const v = value;
    return typeof v.id === 'string' && typeof v.category === 'string' && typeof v.summary === 'string';
}
function stripDefaults(content, defaultContent) {
    const trimmed = content.trim();
    if (!trimmed)
        return '';
    if (trimmed === defaultContent.trim())
        return '';
    return trimmed;
}
function slugifyId(category, summary) {
    return (category + '-' + summary).toLowerCase().replace(/[^a-z0-9-_ ]+/g, '').replace(/\s+/g, '-').slice(0, 80);
}
async function exportIdentity(projectDir) {
    const snapshot = await readIdentitySnapshot(projectDir);
    return { version: 1, exportedAt: new Date().toISOString(), snapshot };
}
async function importIdentity(projectDir, payload, options = {}) {
    if (!payload || typeof payload !== 'object')
        throw new Error('payload must be an object');
    const value = payload;
    const snapshot = value.snapshot;
    if (!snapshot || typeof snapshot !== 'object')
        throw new Error('payload.snapshot is required');
    const summary = { importedSoul: false, importedUser: false, importedStructured: 0, skippedStructured: 0 };
    const overwriteFiles = options.overwriteFiles !== false;
    if (overwriteFiles && typeof snapshot.soul === 'string' && snapshot.soul.trim().length > 0) {
        await writeIdentityFile(projectDir, 'SOUL.md', snapshot.soul);
        summary.importedSoul = true;
    }
    if (overwriteFiles && typeof snapshot.user === 'string' && snapshot.user.trim().length > 0) {
        await writeIdentityFile(projectDir, 'USER.md', snapshot.user);
        summary.importedUser = true;
    }
    const incoming = snapshot.structured && Array.isArray(snapshot.structured.entries) ? snapshot.structured.entries.filter(isEntryLike) : [];
    if (options.mergeStructured === false) {
        // Replace mode: write the incoming list verbatim (after sanitising).
        const next = { version: 1, entries: incoming };
        await writeStructuredStore(projectDir, next);
        summary.importedStructured = incoming.length;
    }
    else {
        // Merge mode (default): upsert each entry; existing entries with the
        // same id are updated, new ids are added.
        for (const entry of incoming) {
            try {
                await upsertStructuredEntry(projectDir, { id: entry.id, category: entry.category, summary: entry.summary, metadata: entry.metadata });
                summary.importedStructured += 1;
            }
            catch {
                summary.skippedStructured += 1;
            }
        }
    }
    return summary;
}
/**
 * Drop structured entries that have not been updated in `maxAgeDays`. Entries
 * with `metadata.pinned === true` are always kept regardless of age.
 */
async function runIdentityGc(projectDir, options = {}, now = new Date()) {
    const maxAgeDays = options.maxAgeDays ?? 90;
    const thresholdMs = now.getTime() - maxAgeDays * 24 * 60 * 60 * 1000;
    const store = await readStructuredStore(projectDir);
    let removed = 0;
    let pinnedKept = 0;
    const kept = [];
    for (const entry of store.entries) {
        const isPinned = entry.metadata && entry.metadata.pinned === true;
        if (isPinned) {
            pinnedKept += 1;
            kept.push(entry);
            continue;
        }
        const updated = Date.parse(entry.updatedAt);
        if (Number.isFinite(updated) && updated < thresholdMs) {
            removed += 1;
            continue;
        }
        kept.push(entry);
    }
    if (removed > 0) {
        await writeStructuredStore(projectDir, { version: 1, entries: kept });
    }
    return { scanned: store.entries.length, removed, pinnedKept };
}
//# sourceMappingURL=identity.js.map