"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
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.__testing = exports.runSnapshots = exports.RunSnapshotRegistry = void 0;
exports.computeStrategyContentHash = computeStrategyContentHash;
exports.computePatchFingerprint = computePatchFingerprint;
exports.detectDrift = detectDrift;
/**
 * Strategy version fingerprinting + per-run snapshot freezing.
 *
 * Two kinds of fingerprint cooperate so PR9b's supersede markers survive
 * sloppy real-world edits:
 *
 *   1. `strategyContentHash` — sha256 of the entire strategy file. Cheap to
 *      compute, but too coarse to use alone: a typo fix in an unrelated
 *      paragraph would invalidate every supersede marker pinned to the file.
 *   2. `patchFingerprint` — a hash over the normalized form of a single
 *      phase_hints entry (id + sorted keywords + constraints + critical
 *      tools). Drift detection on the patch fingerprint tells us whether the
 *      *patched* hint is still in place even if the whole file changed.
 *
 * The §11.2 three-tier drift rule:
 *   - file hash changed, patch fingerprint still present → stay `active`
 *   - patch fingerprint changed → `drifted` (×0.5 injection weight)
 *   - phase_hints entry deleted entirely → `reverted` (restore to ×1.0)
 *
 * The `RunSnapshotRegistry` ensures an in-flight analysis sees a frozen
 * version of its scene's strategy + phase_hints — `invalidateStrategyCache()`
 * must never half-update an analysis mid-flight.
 *
 * See docs/architecture/self-improving-design.md §11 (Strategy Version Fingerprint).
 */
const crypto_1 = require("crypto");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const strategyLoader_1 = require("../strategyLoader");
const hintFingerprint_1 = require("./hintFingerprint");
const STRATEGIES_DIR = path.resolve(__dirname, '..', '..', '..', 'strategies');
/**
 * Resolve the strategy file path through the loader's registry rather than
 * naively joining `${scene}.strategy.md`. Compound scene ids use underscores
 * (`touch_tracking`) while their file basenames use hyphens
 * (`touch-tracking.strategy.md`); the naive form silently returned an empty
 * hash for those scenes. Falls back to the legacy join for unknown scenes
 * so misconfigured tests still get a deterministic-looking path.
 */
function strategyFilePath(scene) {
    return (0, strategyLoader_1.getStrategyFilePath)(scene) ?? path.join(STRATEGIES_DIR, `${scene}.strategy.md`);
}
/**
 * Stable sha256 of the strategy file content. Returns empty string if the
 * file is missing — callers should treat that as "no fingerprint" rather
 * than blow up the analysis path.
 */
function computeStrategyContentHash(scene) {
    const file = strategyFilePath(scene);
    if (!fs.existsSync(file))
        return '';
    const content = fs.readFileSync(file, 'utf-8');
    return (0, crypto_1.createHash)('sha256').update(content).digest('hex');
}
/**
 * Hash a phase_hints entry by its shared canonical form. Sorted keys +
 * lower-case strings + array sort keep the hash stable across cosmetic
 * reordering of the same content.
 *
 * Intentionally excludes `id` (it is derived from the fingerprint —
 * including it would be circular) so that a freshly-rendered auto-patch
 * by `phaseHintsRenderer` produces the *same* fingerprint that
 * `detectDrift` computes off the on-disk hint after the patch lands.
 */
function computePatchFingerprint(hint) {
    return (0, hintFingerprint_1.computeHintFingerprint)({
        keywords: hint.keywords || [],
        constraints: hint.constraints || '',
        criticalTools: hint.criticalTools || [],
        critical: hint.critical === true,
    });
}
/**
 * Compare a stored fingerprint against the current on-disk state and report
 * which drift tier we're in. Pure function — does not mutate anything.
 */
function detectDrift(input) {
    if (input.fingerprint.strategyContentHash === input.currentContentHash &&
        input.fingerprint.patchFingerprint === '' // scene-level fingerprint
    ) {
        return 'none';
    }
    if (input.fingerprint.patchFingerprint === '') {
        return input.fingerprint.strategyContentHash === input.currentContentHash ? 'none' : 'whole_file_only';
    }
    const pinnedId = input.fingerprint.phaseHintId;
    const pinnedHint = pinnedId
        ? input.currentHints.find(h => h.id === pinnedId)
        : input.currentHints.find(h => computePatchFingerprint(h) === input.fingerprint.patchFingerprint);
    if (!pinnedHint)
        return 'patch_deleted';
    const currentPatchHash = computePatchFingerprint(pinnedHint);
    if (currentPatchHash === input.fingerprint.patchFingerprint) {
        return input.fingerprint.strategyContentHash === input.currentContentHash ? 'none' : 'whole_file_only';
    }
    return 'patch_changed';
}
/**
 * Per-session snapshot store. Implemented as a class so a test can spin up
 * a fresh instance instead of leaning on a module-level singleton.
 *
 * Production code should use the exported `runSnapshots` instance.
 */
class RunSnapshotRegistry {
    constructor() {
        this.snapshots = new Map();
    }
    capture(sessionId, sceneType) {
        // Re-capturing for the same session is allowed (multi-turn) and simply
        // refreshes the snapshot — the new values reflect any hot-reloads that
        // happened between turns, which is the desired behaviour: the freeze
        // boundary is the per-turn analyze() call.
        const strategyContent = (0, strategyLoader_1.getStrategyContent)(sceneType);
        const phaseHints = (0, strategyLoader_1.getPhaseHints)(sceneType);
        const strategyContentHash = computeStrategyContentHash(sceneType);
        const fingerprint = {
            strategyFile: `${sceneType}.strategy.md`,
            strategyContentHash,
            patchFingerprint: '', // scene-level snapshot pins nothing in particular
            appliedAt: Date.now(),
        };
        const snapshot = {
            sessionId,
            sceneType,
            strategyContent,
            phaseHints,
            fingerprint,
        };
        this.snapshots.set(sessionId, snapshot);
        return snapshot;
    }
    release(sessionId) {
        this.snapshots.delete(sessionId);
    }
    get(sessionId) {
        return this.snapshots.get(sessionId);
    }
    /** Number of active snapshots — surfaced for the monitoring PR. */
    size() {
        return this.snapshots.size;
    }
}
exports.RunSnapshotRegistry = RunSnapshotRegistry;
/** Process-wide snapshot store. Tests should construct their own instance. */
exports.runSnapshots = new RunSnapshotRegistry();
exports.__testing = { strategyFilePath, STRATEGIES_DIR };
//# sourceMappingURL=strategyFingerprint.js.map