import { j as TaxonomyRegistry, k as TaxonomyError, m as CANONICAL_TYPE_TAGS, D as DecisionValidatorFailedError } from './paths-2M_UxD_H.js';
import { a as getDb } from './sqlite-Dsbe0wec.js';
import { g as getBrainAccessor } from './memory-accessor2-BzlgmY-Z.js';
import { t as taskExistsInTasksDb } from './cross-db-cleanup-C_FLyd-6.js';
import { u as upsertGraphNode, a as addGraphEdge } from './graph-auto-populate2-DYsUNGSL.js';
import { b as computeDecisionQuality } from './quality-scoring-CCPuRQmJ.js';
import { d as detectSupersession, s as supersedeMemory } from './temporal-supersession-BZ46j30w.js';
import { createHash } from 'node:crypto';

//#region ../core/dist/memory/decision-cross-link.js
/**
* Decision cross-link module for CLEO BRAIN.
*
* Extracts file paths and symbol names referenced in a decision's text and
* rationale, then creates `affects` edges from the decision graph node to
* matching `file` / `symbol` nodes in brain_page_nodes.  This implements
* the cross-substrate edge described in
* docs/plans/brain-synaptic-visualization-research.md §3.2.
*
* All database operations are best-effort — they never throw or block the
* caller.  Nodes for referenced files / symbols are upserted on demand so
* the graph remains consistent even when the target has not yet been
* independently indexed.
*
* @task T626
* @epic T626
*/
/**
* Regex patterns used to locate file paths and symbol names inside text.
*
* File-path pattern — matches:
*   - Relative paths:   `src/store/memory-schema.ts`
*   - Absolute paths:   `/mnt/projects/cleocode/packages/core/src/…`
*   - Extension-gated:  only `.ts`, `.tsx`, `.js`, `.jsx`, `.rs`, `.json`
*
* Symbol pattern — matches:
*   - PascalCase class/interface names: `BrainPageNodes`
*   - camelCase function names at word boundaries: `upsertGraphNode`
*   - snake_case identifiers: `brain_page_edges`
*
* Overlapping matches are deduplicated by nodeId before edge creation.
*/
var FILE_PATH_RE = /(?:^|[\s`"'([\]{,])((\/[\w.\-/]+|[\w.-]+(?:\/[\w.-]+)+)\.(ts|tsx|js|jsx|rs|json))(?=$|[\s`"')[\]{,])/gm;
var SYMBOL_RE = /(?<![`"'/\w.])(?:[A-Z][a-zA-Z0-9]{2,}|[a-z][a-zA-Z0-9]*(?:[A-Z][a-zA-Z0-9]*)+|[a-z][a-z0-9]*(?:_[a-z][a-z0-9]*){2,})(?![`"'/\w])/g;
/**
* Extract file-path and symbol references from free-form text.
*
* Symbols shorter than 4 characters or matching common English stop-words
* are filtered to reduce noise.
*
* @param text - Decision text and/or rationale to scan.
* @returns Deduplicated array of extracted references.
*/
function extractReferencedSymbols(text) {
	const seen = /* @__PURE__ */ new Set();
	const refs = [];
	for (const match of text.matchAll(FILE_PATH_RE)) {
		const raw = match[1];
		if (!raw) continue;
		const nodeId = `file:${raw}`;
		if (seen.has(nodeId)) continue;
		seen.add(nodeId);
		refs.push({
			raw,
			nodeId,
			nodeType: "file",
			label: raw
		});
	}
	for (const match of text.matchAll(SYMBOL_RE)) {
		const raw = match[0];
		if (!raw || raw.length < 4) continue;
		if (SYMBOL_STOP_WORDS.has(raw.toLowerCase())) continue;
		const nodeId = `symbol:${raw}`;
		if (seen.has(nodeId)) continue;
		seen.add(nodeId);
		refs.push({
			raw,
			nodeId,
			nodeType: "symbol",
			label: raw
		});
	}
	return refs;
}
/**
* Common English / technical words that look like camelCase or PascalCase
* symbols but carry no meaningful code reference.  Filtered out to keep the
* extracted reference set signal-rich.
*/
var SYMBOL_STOP_WORDS = new Set([
	"this",
	"that",
	"with",
	"from",
	"into",
	"when",
	"then",
	"also",
	"both",
	"each",
	"such",
	"over",
	"after",
	"before",
	"always",
	"never",
	"should",
	"must",
	"will",
	"would",
	"could",
	"have",
	"been",
	"there",
	"their",
	"they",
	"them",
	"these",
	"those",
	"some",
	"only",
	"just",
	"more",
	"most",
	"many",
	"much",
	"well",
	"very",
	"here",
	"where",
	"which",
	"what",
	"why",
	"how",
	"the",
	"and",
	"but",
	"for",
	"not",
	"are",
	"was",
	"were",
	"has",
	"had",
	"its",
	"the",
	"data",
	"true",
	"false",
	"null",
	"none",
	"type",
	"test",
	"spec",
	"todo",
	"fixme",
	"note",
	"example",
	"index",
	"config",
	"error",
	"value",
	"input",
	"output",
	"result",
	"return",
	"default",
	"source",
	"target",
	"import",
	"export",
	"class",
	"interface",
	"function",
	"const",
	"async",
	"await"
]);
/**
* Create `affects` edges from a decision graph node to every referenced
* file / symbol node.
*
* For each reference:
*  1. Upsert the target node (file or symbol) so the graph stays consistent.
*  2. Insert an `applies_to` edge from `decision:<id>` to the target node.
*
* All writes are best-effort via {@link upsertGraphNode} and
* {@link addGraphEdge} — failures are swallowed internally.
*
* @param projectRoot - Absolute path to the project root directory.
* @param decisionId  - The decision ID (e.g. `D001`).
* @param refs        - Extracted references returned by {@link extractReferencedSymbols}.
*/
async function linkDecisionToTargets(projectRoot, decisionId, refs) {
	const fromId = `decision:${decisionId}`;
	const writes = refs.map(async (ref) => {
		await upsertGraphNode(projectRoot, ref.nodeId, ref.nodeType, ref.label, .5, ref.raw);
		await addGraphEdge(projectRoot, fromId, ref.nodeId, "applies_to", 1, "auto:decision-cross-link");
	});
	await Promise.allSettled(writes);
}
/**
* Extract file/symbol references from a decision and create `applies_to`
* edges in the brain graph.  Combines {@link extractReferencedSymbols} and
* {@link linkDecisionToTargets} in one call.
*
* This is the function wired into {@link storeDecision} after a new decision
* is saved.  It is always fire-and-forget: the caller should NOT await it
* when used inside the decision write path.
*
* @param projectRoot  - Absolute path to the project root directory.
* @param decisionId   - The saved decision ID (e.g. `D001`).
* @param decisionText - Full decision text.
* @param rationale    - Full rationale text.
*/
async function autoCrossLinkDecision(projectRoot, decisionId, decisionText, rationale) {
	try {
		const refs = extractReferencedSymbols(`${decisionText} ${rationale}`);
		if (refs.length === 0) return;
		await linkDecisionToTargets(projectRoot, decisionId, refs);
	} catch {}
}
//#endregion
//#region ../core/dist/memory/decisions.js
/**
* Decision Memory module for CLEO BRAIN.
* Uses brain.db via BrainDataAccessor for persistent storage.
*
* Provides functions to store, recall, search, and update decisions
* with sequential ID generation (D001, D002, ...).
*
* @task T5155
* @epic T5149
*/
/** Default confidence threshold for the ADR decision validator. */
var DEFAULT_VALIDATOR_CONFIDENCE_THRESHOLD = .7;
/**
* Read the configured confidence threshold for ADR decision validation.
*
* Checks `.cleo/config.json` key `decisions.validatorConfidenceThreshold`.
* Falls back to {@link DEFAULT_VALIDATOR_CONFIDENCE_THRESHOLD} (0.7) when the
* key is absent or the file is unreadable.
*
* @param projectRoot - Absolute project root directory.
* @returns Configured threshold in [0.0, 1.0].
*
* @task T1828
*/
async function resolveValidatorThreshold(projectRoot) {
	try {
		const { getRawConfigValue } = await import('./config2-CdsX487B.js');
		const raw = await getRawConfigValue("decisions.validatorConfidenceThreshold", projectRoot);
		if (typeof raw === "number" && raw >= 0 && raw <= 1) return raw;
	} catch {}
	return DEFAULT_VALIDATOR_CONFIDENCE_THRESHOLD;
}
/**
* Validate a candidate decision for collision, contradiction, and supersession-
* graph integrity using the dialectic LLM evaluator.
*
* ## Scope
*
* Only runs for ADR-typed writes (where `adrPath` is provided on the params).
* Non-ADR writes skip validation entirely.
*
* ## Env skip
*
* When `process.env.CLEO_ENV === 'test'`, returns a synthetic passing result
* (`confidence: 1.0`, empty violation arrays) immediately so that unit test
* suites that do not want to make real LLM calls are not affected.
*
* ## LLM call
*
* Uses `evaluateDialectic()` from `dialectic-evaluator.ts` (cold tier,
* `claude-sonnet-4-6`).  The "user message" describes the candidate decision;
* the "system response" summarises existing decisions to provide contradiction
* context.  The LLM is prompted to identify conflicts and assign a confidence
* score.
*
* When no LLM backend is available, the function returns `confidence: 1.0` so
* that writes are never silently blocked due to infrastructure absence.
*
* ## Rejection
*
* If `confidence < threshold` (default 0.7, configurable via
* `decisions.validatorConfidenceThreshold` in `.cleo/config.json`),
* the caller MUST throw {@link DecisionValidatorFailedError}.
*
* @param params        - The store params for the candidate decision.
* @param existingDecisions - Snapshot of existing decisions for conflict checking.
* @returns Validation result with conflict lists and overall confidence.
*
* @task T1828
*/
async function validateDecisionConflicts(params, existingDecisions) {
	const PASS = {
		collisions: [],
		contradictions: [],
		supersession_graph_violations: [],
		confidence: 1
	};
	if (process.env["CLEO_ENV"] === "test") return PASS;
	if (!params.adrPath) return PASS;
	const collisions = [];
	const contradictions = [];
	const supersessionViolations = [];
	const candidateLower = (params.decision.trim() + " " + params.rationale.trim()).toLowerCase();
	for (const existing of existingDecisions) {
		const existingLower = (existing.decision.trim() + " " + existing.rationale.trim()).toLowerCase();
		const cTokens = new Set(candidateLower.match(/\b\w{4,}\b/g) ?? []);
		const eTokens = new Set(existingLower.match(/\b\w{4,}\b/g) ?? []);
		const intersection = [...cTokens].filter((t) => eTokens.has(t)).length;
		const union = new Set([...cTokens, ...eTokens]).size;
		if ((union > 0 ? intersection / union : 0) >= .65) collisions.push(existing.id);
	}
	if (params.supersedes) {
		const target = existingDecisions.find((d) => d.id === params.supersedes);
		if (!target) supersessionViolations.push(`supersedes:${params.supersedes}:not-found`);
		else if (target.supersedes) supersessionViolations.push(`supersedes:${params.supersedes}:already-superseded-by:${target.supersedes}`);
	}
	let llmConfidence = 1;
	try {
		const { evaluateDialectic } = await import('./dialectic-evaluator2-C2Xb87y1.js');
		const existingSummary = existingDecisions.length === 0 ? "No existing decisions in the database." : existingDecisions.slice(0, 20).map((d) => `[${d.id}] ${d.decision}: ${d.rationale}`).join("\n");
		const contradictionInsights = (await evaluateDialectic({
			userMessage: `Candidate ADR decision for conflict checking:\nType: ${params.type}\nDecision: ${params.decision}\nRationale: ${params.rationale}\n` + (params.adrPath ? `ADR path: ${params.adrPath}\n` : "") + (collisions.length > 0 ? `\nPossible near-duplicates detected: ${collisions.join(", ")}\n` : ""),
			systemResponse: `Existing architectural decisions in the system:\n${existingSummary}\n\nTask: Identify whether the candidate decision contradicts any existing decisions. Assign a confidence score where 1.0 = no conflicts and 0.0 = severe contradiction.`,
			activePeerId: "decision-validator",
			sessionId: `validate:${createHash("sha256").update(params.decision).digest("hex").slice(0, 8)}`
		})).peerInsights.filter((i) => i.key.includes("contradict") || i.key.includes("conflict") || i.key.includes("collision"));
		if (contradictionInsights.length > 0) for (const insight of contradictionInsights) {
			const ids = insight.value.match(/\bD\d{3,}\b/g) ?? [];
			for (const id of ids) if (!contradictions.includes(id)) contradictions.push(id);
			llmConfidence = Math.min(llmConfidence, insight.confidence);
		}
		if (contradictionInsights.length === 0) llmConfidence = Math.max(0, 1 - collisions.length * .15);
	} catch {
		llmConfidence = 1;
	}
	const supersessionPenalty = supersessionViolations.length * .3;
	return {
		collisions,
		contradictions,
		supersession_graph_violations: supersessionViolations,
		confidence: Math.max(0, llmConfidence - supersessionPenalty)
	};
}
/**
* Maximum number of `INSERT` re-attempts when a sequential decision ID
* collides with a row written by a concurrent agent (T11552).
*
* The bound is deliberately generous: a real collision is resolved on the
* first retry (the loser re-reads the now-higher `MAX(id)` and advances), and
* the only way to exhaust this budget is dozens of agents racing the same
* insert in the same instant — at which point surfacing the error is correct
* (we never silently drop the decision).
*/
var DECISION_ID_INSERT_MAX_RETRIES = 16;
/**
* Detect whether an error is a SQLite UNIQUE / PRIMARY-KEY constraint failure
* on `brain_decisions.id` (T11552).
*
* node:sqlite raises a generic `Error` (`code: 'ERR_SQLITE_ERROR'`,
* `errcode: 1555` = `SQLITE_CONSTRAINT_PRIMARYKEY`) whose message is
* `"UNIQUE constraint failed: brain_decisions.id"`. We match on both the
* numeric errcode and the message so the detector survives driver phrasing
* changes.
*
* @param err - The thrown error to classify.
* @returns `true` when the error is an id-uniqueness collision.
*
* @task T11552
*/
function isDecisionIdCollision(err) {
	if (!(err instanceof Error)) return false;
	const errcode = err.errcode;
	if (errcode === 1555 || errcode === 2067) return /brain_decisions\.id/.test(err.message);
	return /UNIQUE constraint failed/i.test(err.message) && /brain_decisions\.id/.test(err.message);
}
/**
* Store a new decision or update an existing one if a duplicate is found.
* Duplicate detection: same decision text (case-insensitive).
*
* @task T5155
*/
async function storeDecision(projectRoot, params) {
	if (!params.decision?.trim()) throw new Error("Decision text is required");
	if (!params.rationale?.trim()) throw new Error("Rationale is required");
	if (params.type) {
		const invalid = TaxonomyRegistry.default.validate([params.type]);
		if (invalid.length > 0) throw new TaxonomyError(`Invalid decision type '${params.type}'. Valid types: ${CANONICAL_TYPE_TAGS.join(", ")}. Use 'cleo taxonomy list --axis type' to see all valid type tags.`, invalid);
	}
	if (!params._skipGate && params.adrPath) {
		const existing = await (await getBrainAccessor(projectRoot)).findDecisions({});
		const validationResult = await validateDecisionConflicts({
			decision: params.decision,
			rationale: params.rationale,
			type: params.type,
			adrPath: params.adrPath,
			supersedes: params.supersedes
		}, existing.map((d) => ({
			id: d.id,
			decision: d.decision,
			rationale: d.rationale,
			supersedes: d.supersedes
		})));
		const threshold = await resolveValidatorThreshold(projectRoot);
		if (validationResult.confidence < threshold) {
			const violations = [
				...validationResult.collisions.map((id) => `collision:${id}`),
				...validationResult.contradictions.map((id) => `contradiction:${id}`),
				...validationResult.supersession_graph_violations
			];
			throw new DecisionValidatorFailedError(params.decision.trim().slice(0, 120), validationResult.confidence, violations);
		}
	}
	if (!params._skipGate) {
		const { verifyCandidate } = await import('./extraction-gate-Ctn6QtD_.js');
		const numericConf = params.confidence === "high" ? .85 : params.confidence === "medium" ? .65 : .45;
		const gateResult = await verifyCandidate(projectRoot, {
			text: (params.decision.trim() + "\n" + params.rationale.trim()).toLowerCase(),
			title: params.decision.trim().slice(0, 120),
			memoryType: "semantic",
			tier: "medium",
			confidence: numericConf,
			source: "manual",
			sourceConfidence: "owner",
			trusted: true
		});
		if (gateResult.action !== "stored") {
			const existing = gateResult.id ? await (await getBrainAccessor(projectRoot)).getDecision(gateResult.id).catch(() => null) : null;
			if (existing) return existing;
		}
	}
	const accessor = await getBrainAccessor(projectRoot);
	const duplicate = (await accessor.findDecisions({ type: params.type })).find((d) => d.decision.toLowerCase() === params.decision.toLowerCase());
	if (duplicate) {
		const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
		await accessor.updateDecision(duplicate.id, {
			rationale: params.rationale.trim(),
			confidence: params.confidence,
			outcome: params.outcome ?? duplicate.outcome,
			alternativesJson: params.alternatives ? JSON.stringify(params.alternatives) : duplicate.alternativesJson,
			updatedAt: now
		});
		const updated = await accessor.getDecision(duplicate.id);
		const updatedQuality = computeDecisionQuality({
			confidence: params.confidence,
			rationale: params.rationale.trim(),
			contextTaskId: params.contextTaskId ?? null
		});
		upsertGraphNode(projectRoot, `decision:${duplicate.id}`, "decision", params.decision.trim().substring(0, 200), updatedQuality, params.decision.trim() + params.rationale.trim(), {
			type: params.type,
			confidence: params.confidence
		}).catch(() => {});
		return updated;
	}
	let validEpicId = params.contextEpicId;
	let validTaskId = params.contextTaskId;
	if (validEpicId || validTaskId) {
		const tasksDb = await getDb(projectRoot);
		if (validEpicId && !await taskExistsInTasksDb(validEpicId, tasksDb)) validEpicId = void 0;
		if (validTaskId && !await taskExistsInTasksDb(validTaskId, tasksDb)) validTaskId = void 0;
	}
	const memoryTier = "medium";
	const memoryType = "semantic";
	const sourceConfidence = "owner";
	const verified = true;
	const qualityScore = computeDecisionQuality({
		confidence: params.confidence,
		rationale: params.rationale.trim(),
		contextTaskId: validTaskId ?? null,
		sourceConfidence,
		memoryTier
	});
	const contentHashValue = createHash("sha256").update((params.decision.trim() + "\n" + params.rationale.trim()).toLowerCase()).digest("hex").slice(0, 16);
	const row = {
		id: "D000",
		type: params.type,
		decision: params.decision.trim(),
		rationale: params.rationale.trim(),
		confidence: params.confidence,
		outcome: params.outcome,
		alternativesJson: params.alternatives ? JSON.stringify(params.alternatives) : void 0,
		contextEpicId: validEpicId,
		contextTaskId: validTaskId,
		contextPhase: params.contextPhase,
		qualityScore,
		memoryTier,
		memoryType,
		sourceConfidence,
		verified,
		contentHash: contentHashValue,
		adrPath: params.adrPath,
		supersedes: params.supersedes,
		confirmationState: params.confirmationState,
		decidedBy: params.decidedBy
	};
	let saved;
	let lastCollision;
	for (let attempt = 0; attempt < DECISION_ID_INSERT_MAX_RETRIES; attempt++) try {
		saved = await accessor.addDecisionWithSequentialId(row);
		break;
	} catch (err) {
		if (isDecisionIdCollision(err)) {
			lastCollision = err;
			continue;
		}
		throw err;
	}
	if (!saved) throw new Error(`Failed to allocate a unique brain_decisions.id after ${DECISION_ID_INSERT_MAX_RETRIES} attempts under concurrent writes` + (lastCollision instanceof Error ? `: ${lastCollision.message}` : ""));
	try {
		await upsertGraphNode(projectRoot, `decision:${saved.id}`, "decision", saved.decision.substring(0, 200), qualityScore, saved.decision + saved.rationale, {
			type: saved.type,
			confidence: saved.confidence
		});
		if (validTaskId) {
			await upsertGraphNode(projectRoot, `task:${validTaskId}`, "task", validTaskId, 1, "");
			await addGraphEdge(projectRoot, `decision:${saved.id}`, `task:${validTaskId}`, "applies_to", 1, "auto:store-decision");
		}
		if (validEpicId) {
			await upsertGraphNode(projectRoot, `epic:${validEpicId}`, "epic", validEpicId, 1, "");
			await addGraphEdge(projectRoot, `decision:${saved.id}`, `epic:${validEpicId}`, "applies_to", 1, "auto:store-decision");
		}
		autoCrossLinkDecision(projectRoot, saved.id, saved.decision, saved.rationale).catch(() => {});
	} catch {}
	detectSupersession(projectRoot, {
		id: saved.id,
		text: saved.decision + " " + saved.rationale,
		createdAt: saved.createdAt ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
	}).then((candidates) => {
		for (const candidate of candidates) supersedeMemory(projectRoot, candidate.existingId, saved.id, "auto:decision-supersedes — high overlap detected at store time").catch(() => {});
	}).catch(() => {});
	return saved;
}
/**
* Search decisions by type, confidence, outcome, and/or free-text query.
* Query searches across decision + rationale fields using LIKE.
*
* @task T5155
*/
async function searchDecisions(projectRoot, params = {}) {
	let results = await (await getBrainAccessor(projectRoot)).findDecisions({
		type: params.type,
		confidence: params.confidence,
		outcome: params.outcome ?? void 0,
		limit: params.query ? void 0 : params.limit
	});
	if (params.query) {
		const q = params.query.toLowerCase();
		results = results.filter((d) => d.decision.toLowerCase().includes(q) || d.rationale.toLowerCase().includes(q));
	}
	if (params.limit && params.limit > 0) results = results.slice(0, params.limit);
	return results;
}
/**
* List decisions with pagination.
*
* @task T5155
*/
async function listDecisions(projectRoot, params = {}) {
	const all = await (await getBrainAccessor(projectRoot)).findDecisions({});
	const total = all.length;
	const offset = params.offset ?? 0;
	const limit = params.limit ?? 50;
	return {
		decisions: all.slice(offset, offset + limit),
		total
	};
}

export { searchDecisions as a, listDecisions as l, storeDecision as s };
//# sourceMappingURL=decisions-CuNeBqZW.js.map
