import { isEmbeddingAvailable } from './brain-embedding-Djw9ekXu.js';
import { a as addGraphEdge } from './graph-auto-populate2-DYsUNGSL.js';
import { s as searchSimilar } from './brain-similarity-B2HLXCge.js';
import { createHash } from 'node:crypto';

//#region ../core/dist/memory/extraction-gate.js
/**
* Extraction Gate — Verification layer before BRAIN memory storage.
*
* Wraps all memory writes with three ordered checks:
*   A. Content-hash deduplication (always runs — fast, no embedding needed)
*   B. Cosine similarity deduplication (runs when embedding is available; skipped for trusted sources)
*   C. Confidence threshold enforcement (always runs)
*
* Trusted sources (manual, owner) bypass Check B but still run A and C.
*
* Design goals:
* - NEVER block the primary write path on error — all checks wrapped in try/catch
* - Degrade gracefully to hash-only dedup when embedding is unavailable
* - Contradiction detection uses a polarity-flip heuristic (no LLM required)
*
* @task T549 Wave 2-A
* @epic T549
*/
/** Cosine distance below which two entries are considered exact duplicates. */
var DUPLICATE_THRESHOLD = .15;
/** Cosine distance below which two entries are considered closely related. */
var SIMILAR_THRESHOLD = .3;
/** Minimum confidence score required to store an entry. */
var MINIMUM_CONFIDENCE = .4;
/**
* Title prefixes that identify known-noise entries (task lifecycle chatter,
* session scaffolding, evidence bookkeeping, etc.).
*
* Check A0 in verifyCandidate rejects any candidate whose title starts with
* one of these prefixes before any expensive hash/similarity work runs.
*
* Ported from brain-purge.ts:207-248 janitor classifier so this becomes the
* single source of truth. brain-purge.ts should import this const if it needs
* the same list (follow-on cleanup tracked separately).
*
* @task T993
*/
var BRAIN_NOISE_PREFIXES = [
	"Task start:",
	"Session note:",
	"Started work on:",
	"Fix evidence:",
	"Verified:",
	"Completed:",
	"Auto-generated:"
];
/**
* Negation markers used in polarity-flip contradiction detection.
* A contradiction is signalled when exactly one of two related entries
* contains one of these markers near shared keyword overlap.
*/
var NEGATION_MARKERS = [
	"not",
	"never",
	"no longer",
	"deprecated",
	"removed",
	"replaced",
	"broken",
	"avoid",
	"do not",
	"don't",
	"invalid",
	"obsolete"
];
/**
* Compute a short SHA-256 prefix over the normalised text content.
* Used for exact-duplicate detection without embedding.
*/
function contentHashPrefix(text) {
	return createHash("sha256").update(text.trim().toLowerCase()).digest("hex").slice(0, 16);
}
/**
* Polarity-flip contradiction heuristic.
*
* Returns true when exactly one of the two texts carries a negation marker
* AND both texts share at least 3 meaningful keywords (>4 chars).
*
* This is intentionally approximate — false positives cause a supersede
* write (safe), false negatives allow a duplicate (harmless, caught later
* by cosine merge).
*/
function hasContradictingPolarity(existingText, newText) {
	const existingLower = existingText.toLowerCase();
	const newLower = newText.toLowerCase();
	if (NEGATION_MARKERS.some((m) => existingLower.includes(m)) === NEGATION_MARKERS.some((m) => newLower.includes(m))) return false;
	const existingWords = new Set(existingLower.split(/\s+/).filter((w) => w.length > 4));
	return [...new Set(newLower.split(/\s+/).filter((w) => w.length > 4))].filter((w) => existingWords.has(w)).length >= 3;
}
/**
* Mark an existing entry as invalid (superseded).
* Sets the invalid_at column to now on whichever typed table owns the entry.
*
* Determined by ID prefix conventions:
*   D...  → brain_decisions
*   P...  → brain_patterns
*   L...  → brain_learnings
*   O... / CM-... → brain_observations
*/
async function invalidateEntry(projectRoot, entryId) {
	const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
	try {
		const { getBrainNativeDb, getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
		await getBrainDb(projectRoot);
		const nativeDb = getBrainNativeDb();
		if (!nativeDb) return;
		if (entryId.startsWith("D-") || /^D\d/.test(entryId)) nativeDb.prepare("UPDATE brain_decisions SET invalid_at = ? WHERE id = ? AND invalid_at IS NULL").run(now, entryId);
		else if (entryId.startsWith("P-") || /^P\d/.test(entryId)) nativeDb.prepare("UPDATE brain_patterns SET invalid_at = ? WHERE id = ? AND invalid_at IS NULL").run(now, entryId);
		else if (entryId.startsWith("L-") || /^L\d/.test(entryId)) nativeDb.prepare("UPDATE brain_learnings SET invalid_at = ? WHERE id = ? AND invalid_at IS NULL").run(now, entryId);
		else nativeDb.prepare("UPDATE brain_observations SET invalid_at = ? WHERE id = ? AND invalid_at IS NULL").run(now, entryId);
	} catch {}
}
/**
* Increment the citation_count of an existing entry by 1.
* Routes by the same ID-prefix convention as invalidateEntry.
*/
async function incrementCitationCount(projectRoot, entryId) {
	try {
		const { getBrainNativeDb, getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
		await getBrainDb(projectRoot);
		const nativeDb = getBrainNativeDb();
		if (!nativeDb) return;
		if (entryId.startsWith("D-") || /^D\d/.test(entryId)) nativeDb.prepare("UPDATE brain_decisions SET citation_count = citation_count + 1 WHERE id = ?").run(entryId);
		else if (entryId.startsWith("P-") || /^P\d/.test(entryId)) nativeDb.prepare("UPDATE brain_patterns SET citation_count = citation_count + 1 WHERE id = ?").run(entryId);
		else if (entryId.startsWith("L-") || /^L\d/.test(entryId)) nativeDb.prepare("UPDATE brain_learnings SET citation_count = citation_count + 1 WHERE id = ?").run(entryId);
		else nativeDb.prepare("UPDATE brain_observations SET citation_count = citation_count + 1 WHERE id = ?").run(entryId);
	} catch {}
}
/**
* Check A: SHA-256 content-hash deduplication for any of the four brain tables.
*
* Queries the specified table for a row with a matching content_hash that is
* still valid (invalid_at IS NULL). When a match is found, returns 'merged'
* so the caller avoids a duplicate insert and instead increments citation_count
* on the existing row.
*
* T737: Extended from observations-only to all four typed tables. Each table
* now has the content_hash column (migration 20260416000005_t726-dedup-tier-columns).
*
* @param projectRoot - Project root for brain.db access
* @param table - Which typed table to search
* @param text - Candidate content text (hashed internally)
*/
async function hashDedupCheck(projectRoot, text, table = "brain_observations") {
	try {
		const { getBrainNativeDb, getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
		await getBrainDb(projectRoot);
		const nativeDb = getBrainNativeDb();
		if (!nativeDb) return { matched: false };
		const hash = contentHashPrefix(text);
		const rows = nativeDb.prepare(`SELECT id FROM ${table} WHERE content_hash = ? AND invalid_at IS NULL LIMIT 1`).all(hash);
		if (rows.length > 0) return {
			matched: true,
			id: rows[0].id
		};
	} catch {}
	return { matched: false };
}
/**
* Determine whether the candidate is from a trusted source that bypasses
* the similarity/contradiction checks (Checks B).
*
* Trusted: explicit candidate.trusted flag, source='manual', or
* sourceConfidence='owner' or 'task-outcome'.
*/
function isTrustedSource(candidate) {
	if (candidate.trusted === true) return true;
	if (candidate.source === "manual") return true;
	if (candidate.sourceConfidence === "owner" || candidate.sourceConfidence === "task-outcome") return true;
	return false;
}
/**
* Public SHA-256 content-hash deduplication check.
*
* Exported for use by write paths (llm-extraction.ts, decisions.ts, patterns.ts)
* that need to run hash dedup before an INSERT without going through the full
* verifyCandidate gate (T736, T737).
*
* @param projectRoot - Project root for brain.db access
* @param text - Candidate content text (hashed internally)
* @param table - Which typed table to probe
*/
async function checkHashDedup(projectRoot, text, table) {
	return hashDedupCheck(projectRoot, text, table);
}
/**
* Run a MemoryCandidate through the verification gate.
*
* Checks (in order):
*   A0. Title-prefix blocklist (T993) — rejects known-noise titles before any DB work
*   A. Content-hash deduplication (always; degrades to observations-only when DB unavailable)
*   B. Cosine similarity deduplication + contradiction detection (skipped for trusted sources)
*   C. Confidence threshold >= 0.40 (always)
*
* The gate NEVER stores the entry itself — it only decides whether the caller
* should store, merge into an existing entry, queue as pending, or reject.
*
* To store after receiving action='stored', call the appropriate storage
* function (observeBrain, storeLearning, storePattern, storeDecision).
*
* @param projectRoot - Project root for brain.db access
* @param candidate - Candidate to verify
* @returns GateResult describing what should happen
*/
async function verifyCandidate(projectRoot, candidate) {
	try {
		const candidateTitle = candidate.title ?? "";
		if (BRAIN_NOISE_PREFIXES.some((prefix) => candidateTitle.startsWith(prefix))) return {
			action: "rejected",
			id: null,
			reason: "noise-prefix"
		};
		const dedupTable = candidate.memoryType === "semantic" ? "brain_learnings" : candidate.memoryType === "procedural" ? "brain_patterns" : "brain_observations";
		const hashCheck = await hashDedupCheck(projectRoot, candidate.text, dedupTable);
		if (hashCheck.matched) {
			incrementCitationCount(projectRoot, hashCheck.id).catch(() => void 0);
			return {
				action: "merged",
				id: hashCheck.id,
				reason: `exact-duplicate (hash match) of ${hashCheck.id}`
			};
		}
		if (!isTrustedSource(candidate) && isEmbeddingAvailable()) try {
			const similar = await searchSimilar(candidate.text, projectRoot, 5);
			if (similar.length > 0) {
				const nearest = similar[0];
				if (nearest.distance < DUPLICATE_THRESHOLD) {
					incrementCitationCount(projectRoot, nearest.id).catch(() => void 0);
					return {
						action: "merged",
						id: nearest.id,
						reason: `near-duplicate of ${nearest.id} (cosine distance=${nearest.distance.toFixed(3)})`,
						similarity: nearest.distance
					};
				}
				if (nearest.distance < SIMILAR_THRESHOLD) {
					if (hasContradictingPolarity(nearest.text, candidate.text)) {
						await invalidateEntry(projectRoot, nearest.id);
						return {
							action: "stored",
							id: null,
							reason: `contradiction-supersedes ${nearest.id}`,
							similarity: nearest.distance
						};
					}
					return {
						action: "stored",
						id: null,
						reason: `similar-but-distinct to ${nearest.id} (distance=${nearest.distance.toFixed(3)})`,
						similarity: nearest.distance
					};
				}
			}
		} catch {}
		if (candidate.confidence < MINIMUM_CONFIDENCE) return {
			action: "pending",
			id: null,
			reason: `confidence ${candidate.confidence.toFixed(2)} below minimum ${MINIMUM_CONFIDENCE}`
		};
		return {
			action: "stored",
			id: null,
			reason: "verified-new"
		};
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		console.warn(`[extraction-gate] verifyCandidate error (allowing store): ${message}`);
		return {
			action: "stored",
			id: null,
			reason: `gate-error-passthrough: ${message}`
		};
	}
}
/**
* Store a verified candidate in the appropriate BRAIN table based on memoryType.
*
* Routes:
*   semantic    → brain_learnings  (storeLearning)
*   episodic    → brain_observations (observeBrain)
*   procedural  → brain_patterns   (storePattern)
*
* Called only after verifyCandidate returns action='stored'.
*
* @param projectRoot - Project root for brain.db access
* @param candidate - Verified candidate to store
* @returns ID of the newly created entry
*/
async function storeVerifiedCandidate(projectRoot, candidate) {
	const title = candidate.title ?? candidate.text.slice(0, 120);
	switch (candidate.memoryType) {
		case "semantic": {
			const { storeLearning } = await import('./learnings-WTVKqfNE.js');
			return (await storeLearning(projectRoot, {
				insight: candidate.text,
				source: candidate.source,
				confidence: candidate.confidence,
				actionable: candidate.source === "task-completion",
				_skipGate: true
			})).id;
		}
		case "episodic": {
			const { observeBrain } = await import('./brain-retrieval2-CM6hsYog.js');
			return (await observeBrain(projectRoot, {
				text: candidate.text,
				title,
				sourceSessionId: candidate.sourceSessionId,
				sourceType: candidate.source === "manual" ? "manual" : "agent",
				_skipGate: true
			})).id;
		}
		case "procedural": {
			const { storePattern } = await import('./patterns-C696SQfJ.js');
			return (await storePattern(projectRoot, {
				type: "workflow",
				pattern: candidate.text,
				context: title,
				_skipGate: true
			})).id;
		}
		default: {
			const { observeBrain } = await import('./brain-retrieval2-CM6hsYog.js');
			return (await observeBrain(projectRoot, {
				text: candidate.text,
				title,
				sourceSessionId: candidate.sourceSessionId,
				sourceType: candidate.source === "manual" ? "manual" : "agent",
				_skipGate: true
			})).id;
		}
	}
}
/**
* Full gate-and-store pipeline for a single candidate.
*
* Runs verifyCandidate then calls storeVerifiedCandidate when action='stored'.
* For 'merged', increments the existing entry's citation_count (already done inside verifyCandidate).
* For 'pending' and 'rejected', returns the GateResult with no storage.
*
* After storage, adds a supersedes edge when the gate returned a contradiction reason.
*
* @param projectRoot - Project root for brain.db access
* @param candidate - Candidate to verify and store
* @returns Final GateResult with the stored/merged entry ID populated
*/
async function verifyAndStore(projectRoot, candidate) {
	const gateResult = await verifyCandidate(projectRoot, candidate);
	if (gateResult.action !== "stored") return gateResult;
	try {
		const newId = await storeVerifiedCandidate(projectRoot, candidate);
		if (gateResult.reason.startsWith("contradiction-supersedes ")) {
			const oldId = gateResult.reason.replace("contradiction-supersedes ", "");
			addGraphEdge(projectRoot, `observation:${newId}`, `observation:${oldId}`, "supersedes", 1, "extraction-gate").catch(() => void 0);
		}
		return {
			...gateResult,
			id: newId
		};
	} catch (err) {
		return {
			action: "rejected",
			id: null,
			reason: `storage-error: ${err instanceof Error ? err.message : String(err)}`
		};
	}
}

export { verifyAndStore as a, checkHashDedup as c, verifyCandidate as v };
//# sourceMappingURL=extraction-gate2-DEM3Y6aw.js.map
