import { d as brainDecisions, e as brainPatterns, f as brainLearnings, c as brainObservations, g as brainMemoryLinks, s as stickyTags, h as brainStickyNotes, i as brainAttention, j as jsonbText, b as brainPageNodes, a as brainPageEdges } from './memory-schema-Bi7cChwo.js';
import { e as getBrainDb } from './sqlite-Dsbe0wec.js';
import { s as sql } from './utils-BvHPJOVk.js';
import { e as eq, n as ne, d as desc, a as and, g as gte, f as asc, i as inArray, o as or, h as gt, c as isNull, j as lt } from './logger-ChCwlV5k.js';

//#region ../core/dist/store/memory-accessor.js
/**
* Data accessor for brain.db — CRUD operations for decisions, patterns,
* learnings, and memory links.
*
* Wraps drizzle ORM queries over the brain.db singleton. All methods are
* async (sqlite-proxy) and return typed rows from memory-schema.ts.
*
* @epic T5149
* @task T5128
*/
var BrainDataAccessor = class BrainDataAccessor {
	db;
	constructor(db) {
		this.db = db;
	}
	async addDecision(row) {
		await this.db.insert(brainDecisions).values(row);
		return (await this.db.select().from(brainDecisions).where(eq(brainDecisions.id, row.id)))[0];
	}
	/**
	* Insert a decision while allocating the next sequential `Dnnn` id
	* **atomically inside the INSERT statement** (T11552).
	*
	* The `id` value is computed by an in-SQL subquery
	* (`MAX(numeric suffix) + 1`) evaluated as part of the same INSERT, so the
	* id read and the row write are a single indivisible operation. Two
	* concurrent callers therefore can never read the same `MAX(id)` and collide
	* on the primary key — unlike a read-then-write in application code, which
	* yields the event loop between the `SELECT MAX(id)` and the `INSERT` and so
	* races (the original defect).
	*
	* Properties:
	* - **Race-free under concurrency** — node:sqlite executes the statement
	*   synchronously and atomically; no JS `await` boundary splits read from write.
	* - **Numeric ordering** — `CAST(substr(id,2) AS INTEGER)` so the sequence
	*   advances correctly past `D999 → D1000` (lexical ordering would regress).
	* - **Legacy-id tolerant** — `GLOB 'D[0-9]*'` restricts the max scan to
	*   canonical `Dnnn` ids, ignoring foreign shapes like `D-abcd`.
	* - **Never silently drops** — uses a real INSERT (not `INSERT OR IGNORE`)
	*   and returns the persisted row.
	*
	* The caller MUST NOT pre-set a meaningful `row.id`; any value present is
	* overridden by the computed id.
	*
	* @param row - The decision row to insert (its `id` field is ignored).
	* @returns The persisted decision row, including the allocated `id`.
	*
	* @task T11552
	*/
	async addDecisionWithSequentialId(row) {
		const nextId = sql`'D' || printf('%03d', COALESCE((SELECT MAX(CAST(substr(${brainDecisions.id}, 2) AS INTEGER)) FROM ${brainDecisions} WHERE ${brainDecisions.id} GLOB 'D[0-9]*'), 0) + 1)`;
		return (await this.db.insert(brainDecisions).values({
			...row,
			id: nextId
		}).returning())[0];
	}
	async getDecision(id) {
		return (await this.db.select().from(brainDecisions).where(eq(brainDecisions.id, id)))[0] ?? null;
	}
	async findDecisions(params = {}) {
		const conditions = [];
		if (params.type) conditions.push(eq(brainDecisions.type, params.type));
		if (params.confidence) conditions.push(eq(brainDecisions.confidence, params.confidence));
		if (params.outcome) conditions.push(eq(brainDecisions.outcome, params.outcome));
		if (params.contextTaskId) conditions.push(eq(brainDecisions.contextTaskId, params.contextTaskId));
		if (!params.includeAgentDispatch) conditions.push(ne(brainDecisions.decisionCategory, "agent_dispatch"));
		let query = this.db.select().from(brainDecisions).orderBy(desc(brainDecisions.createdAt));
		if (conditions.length > 0) query = query.where(and(...conditions));
		if (params.limit) query = query.limit(params.limit);
		return query;
	}
	async updateDecision(id, updates) {
		await this.db.update(brainDecisions).set({
			...updates,
			updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
		}).where(eq(brainDecisions.id, id));
	}
	async addPattern(row) {
		await this.db.insert(brainPatterns).values(row);
		return (await this.db.select().from(brainPatterns).where(eq(brainPatterns.id, row.id)))[0];
	}
	async getPattern(id) {
		return (await this.db.select().from(brainPatterns).where(eq(brainPatterns.id, id)))[0] ?? null;
	}
	async findPatterns(params = {}) {
		const conditions = [];
		if (params.type) conditions.push(eq(brainPatterns.type, params.type));
		if (params.impact) conditions.push(eq(brainPatterns.impact, params.impact));
		if (params.minFrequency !== void 0) conditions.push(gte(brainPatterns.frequency, params.minFrequency));
		let query = this.db.select().from(brainPatterns).orderBy(desc(brainPatterns.frequency));
		if (conditions.length > 0) query = query.where(and(...conditions));
		if (params.limit) query = query.limit(params.limit);
		return query;
	}
	async updatePattern(id, updates) {
		await this.db.update(brainPatterns).set({
			...updates,
			updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
		}).where(eq(brainPatterns.id, id));
	}
	async addLearning(row) {
		await this.db.insert(brainLearnings).values(row);
		return (await this.db.select().from(brainLearnings).where(eq(brainLearnings.id, row.id)))[0];
	}
	async getLearning(id) {
		return (await this.db.select().from(brainLearnings).where(eq(brainLearnings.id, id)))[0] ?? null;
	}
	async findLearnings(params = {}) {
		const conditions = [];
		if (params.minConfidence !== void 0) conditions.push(gte(brainLearnings.confidence, params.minConfidence));
		if (params.actionable !== void 0) conditions.push(eq(brainLearnings.actionable, params.actionable));
		let query = this.db.select().from(brainLearnings).orderBy(desc(brainLearnings.confidence));
		if (conditions.length > 0) query = query.where(and(...conditions));
		if (params.limit) query = query.limit(params.limit);
		return query;
	}
	async updateLearning(id, updates) {
		await this.db.update(brainLearnings).set({
			...updates,
			updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
		}).where(eq(brainLearnings.id, id));
	}
	async addObservation(row) {
		await this.db.insert(brainObservations).values(row);
		return (await this.db.select().from(brainObservations).where(eq(brainObservations.id, row.id)))[0];
	}
	async getObservation(id) {
		return (await this.db.select().from(brainObservations).where(eq(brainObservations.id, id)))[0] ?? null;
	}
	async findObservations(params = {}) {
		const conditions = [];
		if (params.type) conditions.push(eq(brainObservations.type, params.type));
		if (params.project) conditions.push(eq(brainObservations.project, params.project));
		if (params.sourceType) conditions.push(eq(brainObservations.sourceType, params.sourceType));
		if (params.sourceSessionId) conditions.push(eq(brainObservations.sourceSessionId, params.sourceSessionId));
		if (params.agent) conditions.push(eq(brainObservations.agent, params.agent));
		let query = this.db.select().from(brainObservations).orderBy(desc(brainObservations.createdAt));
		if (conditions.length > 0) query = query.where(and(...conditions));
		if (params.limit) query = query.limit(params.limit);
		return query;
	}
	async updateObservation(id, updates) {
		await this.db.update(brainObservations).set({
			...updates,
			updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
		}).where(eq(brainObservations.id, id));
	}
	async addLink(row) {
		await this.db.insert(brainMemoryLinks).values(row);
	}
	async getLinksForMemory(memoryType, memoryId) {
		return this.db.select().from(brainMemoryLinks).where(and(eq(brainMemoryLinks.memoryType, memoryType), eq(brainMemoryLinks.memoryId, memoryId))).orderBy(asc(brainMemoryLinks.createdAt));
	}
	async getLinksForTask(taskId) {
		return this.db.select().from(brainMemoryLinks).where(eq(brainMemoryLinks.taskId, taskId)).orderBy(asc(brainMemoryLinks.createdAt));
	}
	async removeLink(memoryType, memoryId, taskId, linkType) {
		await this.db.delete(brainMemoryLinks).where(and(eq(brainMemoryLinks.memoryType, memoryType), eq(brainMemoryLinks.memoryId, memoryId), eq(brainMemoryLinks.taskId, taskId), eq(brainMemoryLinks.linkType, linkType)));
	}
	/**
	* Parse a `tags_json` text column into a deduplicated string-array.
	*
	* Used to keep the {@link brainSchema.stickyTags} junction in sync with the
	* legacy whole-array column. Invalid / non-array JSON yields an empty list.
	*/
	static parseStickyTags(tagsJson) {
		if (!tagsJson) return [];
		try {
			const parsed = JSON.parse(tagsJson);
			if (!Array.isArray(parsed)) return [];
			const seen = /* @__PURE__ */ new Set();
			for (const t of parsed) if (typeof t === "string" && t.length > 0) seen.add(t);
			return [...seen];
		} catch {
			return [];
		}
	}
	/**
	* Replace the junction rows for one sticky so they exactly mirror its tags.
	*
	* Delete-then-insert keeps `sticky_tags` authoritative without RMW races:
	* the prior tag set is dropped and the supplied set re-inserted. Called on
	* every sticky create/update (T11355).
	*
	* @param stickyId - The owning sticky note id.
	* @param tags - The full tag set the junction should reflect.
	*/
	async syncStickyTags(stickyId, tags) {
		await this.db.delete(stickyTags).where(eq(stickyTags.stickyId, stickyId));
		if (tags.length === 0) return;
		await this.db.insert(stickyTags).values(tags.map((tag) => ({
			stickyId,
			tag
		})));
	}
	async addStickyNote(row) {
		await this.db.insert(brainStickyNotes).values(row);
		await this.syncStickyTags(row.id, BrainDataAccessor.parseStickyTags(row.tagsJson));
		return (await this.db.select().from(brainStickyNotes).where(eq(brainStickyNotes.id, row.id)))[0];
	}
	async getStickyNote(id) {
		return (await this.db.select().from(brainStickyNotes).where(eq(brainStickyNotes.id, id)))[0] ?? null;
	}
	/**
	* Find sticky notes with optional column filters and an index-backed,
	* SQL-side tag filter via the {@link brainSchema.stickyTags} junction.
	*
	* When `tags` is supplied the query keeps only notes that contain ALL of the
	* requested tags (membership runs through a junction subquery, never a
	* load-all-then-JS-filter). The `limit` is applied at the SQL layer in every
	* case — including with a tag filter — so callers never over-fetch.
	*
	* @param params - Status/color/priority equality filters, an all-of `tags`
	*   membership filter, and an optional row `limit`.
	* @returns Matching sticky-note rows, newest first.
	*/
	async findStickyNotes(params = {}) {
		const conditions = [];
		if (params.status) conditions.push(eq(brainStickyNotes.status, params.status));
		if (params.color) conditions.push(eq(brainStickyNotes.color, params.color));
		if (params.priority) conditions.push(eq(brainStickyNotes.priority, params.priority));
		if (params.tags && params.tags.length > 0) {
			const wantedTags = [...new Set(params.tags)];
			const matchingIds = this.db.select({ stickyId: stickyTags.stickyId }).from(stickyTags).where(inArray(stickyTags.tag, wantedTags)).groupBy(stickyTags.stickyId).having(sql`count(distinct ${stickyTags.tag}) = ${wantedTags.length}`);
			conditions.push(inArray(brainStickyNotes.id, matchingIds));
		}
		let query = this.db.select().from(brainStickyNotes).orderBy(desc(brainStickyNotes.createdAt));
		if (conditions.length > 0) query = query.where(and(...conditions));
		if (params.limit) query = query.limit(params.limit);
		return query;
	}
	async updateStickyNote(id, updates) {
		await this.db.update(brainStickyNotes).set({
			...updates,
			updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
		}).where(eq(brainStickyNotes.id, id));
		if (updates.tagsJson !== void 0) await this.syncStickyTags(id, BrainDataAccessor.parseStickyTags(updates.tagsJson));
	}
	async deleteStickyNote(id) {
		await this.db.delete(stickyTags).where(eq(stickyTags.stickyId, id));
		await this.db.delete(brainStickyNotes).where(eq(brainStickyNotes.id, id));
	}
	/**
	* Insert one attention (jot) row.
	*
	* Row-per-item: each jot is its own row so it decays and scopes
	* independently. `tags` is a JSONB BLOB written via the {@link jsonb} helper
	* (`toDriver` wraps in `jsonb()`), never serialized TEXT. Reads MUST use
	* `json_each` / `json(col)` — never the raw BLOB.
	*
	* @param row - New attention row (scope already resolved by the caller).
	* @returns The persisted row (tags read back via `json(col)` to satisfy the
	*   JSONB read rule).
	* @task T11372
	*/
	async addAttention(row) {
		await this.db.insert(brainAttention).values(row);
		return this.getAttention(row.id);
	}
	/**
	* Fetch one attention row by id, reading `tags` whole-value via `json(col)`.
	*
	* A plain `select()` of the JSONB column would hit the {@link jsonb}
	* `fromDriver` guard and throw, so `tags` is projected with `jsonbText`.
	*
	* @param id - Attention item id.
	* @returns The row, or `null` when absent.
	* @task T11372
	*/
	async getAttention(id) {
		return (await this.selectAttention(eq(brainAttention.id, id)))[0] ?? null;
	}
	/**
	* List attention items for a scope set, applying live-item + tag filters
	* entirely in SQL (never load-all-then-JS-filter).
	*
	* Scope filtering is by exact `(scope_kind, scope_id)` pairs — the visible
	* scope chain the caller resolved (narrowest → broader ancestors). Because
	* visibility is the scope key, items outside the chain are never read, so
	* cross-agent leakage is impossible by construction.
	*
	* Tag filtering uses `json_each(tags)` membership ("contains ALL" requested
	* tags) — a GROUP BY + HAVING COUNT(DISTINCT) subquery, exactly mirroring the
	* index-backed sticky-tags pattern but over the JSONB column directly.
	*
	* The default (`openOnly`, the common path) excludes items that are
	* `consolidated`/`discarded`, past `expires_at`, or below `decayThreshold`.
	*
	* @param params - Scope chain, optional tag filter, liveness controls, limit.
	* @returns Matching rows, newest first, `tags` read via `json(col)`.
	* @task T11372
	*/
	async findAttention(params) {
		const conditions = [];
		const t = brainAttention;
		if (params.scopes && params.scopes.length > 0) {
			const scopeClauses = params.scopes.map((s) => and(eq(t.scopeKind, s.kind), eq(t.scopeId, s.id)));
			const combined = scopeClauses.length === 1 ? scopeClauses[0] : or(...scopeClauses);
			if (combined) conditions.push(combined);
		}
		if (params.openOnly !== false) {
			conditions.push(eq(t.status, "open"));
			const nowMs = params.now ?? Date.now();
			const ttl = or(isNull(t.expiresAt), gt(t.expiresAt, nowMs));
			if (ttl) conditions.push(ttl);
			if (typeof params.decayThreshold === "number") {
				const decay = or(isNull(t.decayScore), gte(t.decayScore, params.decayThreshold));
				if (decay) conditions.push(decay);
			}
		}
		if (params.tags && params.tags.length > 0) {
			const wanted = [...new Set(params.tags)];
			const matchingIds = this.db.select({ id: sql`a.id` }).from(sql`${t} AS a, json_each(a.tags) AS je`).where(inArray(sql`je.value`, wanted)).groupBy(sql`a.id`).having(sql`count(distinct je.value) = ${wanted.length}`);
			conditions.push(inArray(t.id, matchingIds));
		}
		const where = conditions.length > 0 ? and(...conditions) : void 0;
		return this.selectAttention(where, params.limit);
	}
	/**
	* Sweep expired / decayed open items to `status = 'discarded'`.
	*
	* Idempotent: only flips rows that are currently `open` AND past
	* `expires_at` (or, when `decayThreshold` is given, below the floor). Returns
	* the number of rows discarded so callers can log the sweep.
	*
	* @param params - Reference time + optional decay floor.
	* @returns Count of rows transitioned to `discarded`.
	* @task T11372
	*/
	async expireAttention(params = {}) {
		const t = brainAttention;
		const nowMs = params.now ?? Date.now();
		const expiredByTtl = and(eq(t.status, "open"), lt(t.expiresAt, nowMs));
		const clauses = [];
		if (expiredByTtl) clauses.push(expiredByTtl);
		if (typeof params.decayThreshold === "number") {
			const decayed = and(eq(t.status, "open"), lt(t.decayScore, params.decayThreshold));
			if (decayed) clauses.push(decayed);
		}
		const where = clauses.length === 1 ? clauses[0] : or(...clauses);
		const before = await this.db.select({ id: t.id }).from(t).where(where ?? sql`0`);
		if (before.length === 0) return 0;
		await this.db.update(t).set({ status: "discarded" }).where(where ?? sql`0`);
		return before.length;
	}
	/**
	* Set the lifecycle status of one attention item (e.g. `consolidated`).
	*
	* @param id - Attention item id.
	* @param status - New status.
	* @task T11372
	*/
	async setAttentionStatus(id, status) {
		await this.db.update(brainAttention).set({ status }).where(eq(brainAttention.id, id));
	}
	/**
	* Shared SELECT that projects `tags` whole-value via `json(col)` so the
	* JSONB `fromDriver` guard is never hit. Newest-first ordering.
	*
	* @internal
	*/
	async selectAttention(where, limit) {
		const t = brainAttention;
		let query = this.db.select({
			id: t.id,
			content: t.content,
			sessionId: t.sessionId,
			agentId: t.agentId,
			scopeKind: t.scopeKind,
			scopeId: t.scopeId,
			tags: jsonbText(sql`${t.tags}`),
			createdAt: t.createdAt,
			expiresAt: t.expiresAt,
			decayScore: t.decayScore,
			status: t.status
		}).from(t).orderBy(desc(t.createdAt));
		if (where) query = query.where(where);
		if (limit) query = query.limit(limit);
		return (await query).map((r) => ({
			id: r.id,
			content: r.content,
			sessionId: r.sessionId,
			agentId: r.agentId,
			scopeKind: r.scopeKind,
			scopeId: r.scopeId,
			tags: r.tags,
			createdAt: r.createdAt,
			expiresAt: r.expiresAt,
			decayScore: r.decayScore,
			status: r.status
		}));
	}
	async addPageNode(node) {
		await this.db.insert(brainPageNodes).values(node);
		return (await this.db.select().from(brainPageNodes).where(eq(brainPageNodes.id, node.id)))[0];
	}
	async getPageNode(id) {
		return (await this.db.select().from(brainPageNodes).where(eq(brainPageNodes.id, id)))[0] ?? null;
	}
	async findPageNodes(params = {}) {
		const conditions = [];
		if (params.nodeType) conditions.push(eq(brainPageNodes.nodeType, params.nodeType));
		if (params.minQualityScore !== void 0) conditions.push(gte(brainPageNodes.qualityScore, params.minQualityScore));
		let query = this.db.select().from(brainPageNodes).orderBy(desc(brainPageNodes.lastActivityAt));
		if (conditions.length > 0) query = query.where(and(...conditions));
		if (params.limit) query = query.limit(params.limit);
		return query;
	}
	async updatePageNode(id, updates) {
		await this.db.update(brainPageNodes).set({
			...updates,
			updatedAt: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
		}).where(eq(brainPageNodes.id, id));
	}
	async removePageNode(id) {
		await this.db.delete(brainPageEdges).where(or(eq(brainPageEdges.fromId, id), eq(brainPageEdges.toId, id)));
		await this.db.delete(brainPageNodes).where(eq(brainPageNodes.id, id));
	}
	async addPageEdge(edge) {
		await this.db.insert(brainPageEdges).values(edge);
		return (await this.db.select().from(brainPageEdges).where(and(eq(brainPageEdges.fromId, edge.fromId), eq(brainPageEdges.toId, edge.toId), eq(brainPageEdges.edgeType, edge.edgeType))))[0];
	}
	async findPageEdges(params = {}) {
		const conditions = [];
		if (params.edgeType) conditions.push(eq(brainPageEdges.edgeType, params.edgeType));
		if (params.provenance) conditions.push(eq(brainPageEdges.provenance, params.provenance));
		let query = this.db.select().from(brainPageEdges).orderBy(desc(brainPageEdges.createdAt));
		if (conditions.length > 0) query = query.where(and(...conditions));
		if (params.limit) query = query.limit(params.limit);
		return query;
	}
	async getPageEdges(nodeId, direction = "both") {
		if (direction === "out") return this.db.select().from(brainPageEdges).where(eq(brainPageEdges.fromId, nodeId)).orderBy(asc(brainPageEdges.createdAt));
		if (direction === "in") return this.db.select().from(brainPageEdges).where(eq(brainPageEdges.toId, nodeId)).orderBy(asc(brainPageEdges.createdAt));
		return this.db.select().from(brainPageEdges).where(or(eq(brainPageEdges.fromId, nodeId), eq(brainPageEdges.toId, nodeId))).orderBy(asc(brainPageEdges.createdAt));
	}
	async getNeighbors(nodeId, edgeType) {
		const conditions = [eq(brainPageEdges.fromId, nodeId)];
		if (edgeType) conditions.push(eq(brainPageEdges.edgeType, edgeType));
		const edges = await this.db.select().from(brainPageEdges).where(and(...conditions));
		if (edges.length === 0) return [];
		const neighborIds = edges.map((e) => e.toId);
		const nodes = [];
		for (const nid of neighborIds) {
			const node = await this.getPageNode(nid);
			if (node) nodes.push(node);
		}
		return nodes;
	}
	async removePageEdge(fromId, toId, edgeType) {
		await this.db.delete(brainPageEdges).where(and(eq(brainPageEdges.fromId, fromId), eq(brainPageEdges.toId, toId), eq(brainPageEdges.edgeType, edgeType)));
	}
};
/**
* Factory: get a BrainDataAccessor backed by the brain.db singleton.
*/
async function getBrainAccessor(cwd) {
	return new BrainDataAccessor(await getBrainDb(cwd));
}

export { getBrainAccessor as g };
//# sourceMappingURL=memory-accessor2-BzlgmY-Z.js.map
