Files
cortex/src/core/db.ts
omigamedev f1b59a2d1a Add freshness scoring, auto-decay, and USAGE.md
Add lastAccessedAt timestamp to nodes with schema migration and
backfill. Touch timestamp on read, apply exponential freshness decay
(~69-day half-life) to search scoring alongside BM25 and vector
weights. Add auto-decay that marks untouched nodes as stale after a
configurable threshold, with CLI command and server-side daily interval.
Include comprehensive USAGE.md documenting all CLI commands and REST API.
2026-02-02 22:07:06 +01:00

80 lines
2.2 KiB
TypeScript

import Database from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
const SCHEMA = `
CREATE TABLE IF NOT EXISTS nodes (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
status TEXT,
tags TEXT DEFAULT '[]',
metadata TEXT DEFAULT '{}',
embedding BLOB,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
is_stale INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS edges (
id TEXT PRIMARY KEY,
from_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
to_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
type TEXT NOT NULL,
metadata TEXT DEFAULT '{}',
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS node_tags (
node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
tag TEXT NOT NULL,
PRIMARY KEY (node_id, tag)
);
CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);
CREATE INDEX IF NOT EXISTS idx_nodes_created ON nodes(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_nodes_stale ON nodes(is_stale);
CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_id);
CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_id);
CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
CREATE INDEX IF NOT EXISTS idx_tags_tag ON node_tags(tag);
`;
let _db: Database.Database | null = null;
export function getMemoryDir(): string {
return path.join(process.cwd(), '.memory');
}
export function getDb(): Database.Database {
if (_db) return _db;
const dir = getMemoryDir();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
_db = new Database(path.join(dir, 'cortex.db'));
_db.pragma('journal_mode = WAL');
_db.pragma('foreign_keys = ON');
_db.exec(SCHEMA);
// Migration: add last_accessed_at column
const cols = _db.prepare("PRAGMA table_info(nodes)").all() as any[];
if (!cols.some((c: any) => c.name === 'last_accessed_at')) {
_db.exec('ALTER TABLE nodes ADD COLUMN last_accessed_at INTEGER');
_db.exec('UPDATE nodes SET last_accessed_at = updated_at WHERE last_accessed_at IS NULL');
}
return _db;
}
export function closeDb(): void {
if (_db) {
_db.close();
_db = null;
}
}