@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
237 lines (234 loc) • 6.89 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { v4 as uuidv4 } from "uuid";
import { logger } from "../monitoring/logger.js";
class RetrievalAuditStore {
db;
projectId;
initialized = false;
constructor(db, projectId) {
this.db = db;
this.projectId = projectId;
this.initSchema();
}
/**
* Initialize the audit table schema
*/
initSchema() {
if (this.initialized) return;
try {
this.db.exec(`
CREATE TABLE IF NOT EXISTS retrieval_audit (
id TEXT PRIMARY KEY,
timestamp INTEGER NOT NULL,
project_id TEXT NOT NULL,
query TEXT NOT NULL,
reasoning TEXT NOT NULL,
frames_retrieved TEXT NOT NULL,
confidence_score REAL NOT NULL,
provider TEXT NOT NULL,
tokens_used INTEGER NOT NULL,
token_budget INTEGER NOT NULL,
analysis_time_ms INTEGER NOT NULL,
query_complexity TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_retrieval_audit_project_time
ON retrieval_audit(project_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_retrieval_audit_query
ON retrieval_audit(project_id, query);
`);
this.initialized = true;
logger.debug("Retrieval audit schema initialized");
} catch (error) {
logger.warn("Failed to initialize retrieval audit schema", {
error: error instanceof Error ? error.message : String(error)
});
}
}
/**
* Record a retrieval decision
*/
record(query, analysis, options) {
const id = uuidv4();
const timestamp = Date.now();
try {
const stmt = this.db.prepare(`
INSERT INTO retrieval_audit (
id, timestamp, project_id, query, reasoning, frames_retrieved,
confidence_score, provider, tokens_used, token_budget,
analysis_time_ms, query_complexity
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(
id,
timestamp,
this.projectId,
query,
analysis.reasoning,
JSON.stringify(analysis.framesToRetrieve.map((f) => f.frameId)),
analysis.confidenceScore,
options.provider,
options.tokensUsed,
options.tokenBudget,
analysis.metadata.analysisTimeMs,
analysis.metadata.queryComplexity
);
logger.debug("Recorded retrieval audit entry", {
id,
query: query.slice(0, 50)
});
return id;
} catch (error) {
logger.warn("Failed to record retrieval audit", {
error: error instanceof Error ? error.message : String(error)
});
return id;
}
}
/**
* Get recent retrieval audit entries
*/
getRecent(limit = 10) {
try {
const stmt = this.db.prepare(`
SELECT * FROM retrieval_audit
WHERE project_id = ?
ORDER BY timestamp DESC
LIMIT ?
`);
const rows = stmt.all(this.projectId, limit);
return rows.map(this.rowToEntry);
} catch (error) {
logger.warn("Failed to get recent audit entries", {
error: error instanceof Error ? error.message : String(error)
});
return [];
}
}
/**
* Get audit entry by ID
*/
getById(id) {
try {
const stmt = this.db.prepare(`
SELECT * FROM retrieval_audit WHERE id = ?
`);
const row = stmt.get(id);
return row ? this.rowToEntry(row) : null;
} catch (error) {
logger.warn("Failed to get audit entry", {
error: error instanceof Error ? error.message : String(error),
id
});
return null;
}
}
/**
* Search audit entries by query text
*/
searchByQuery(searchTerm, limit = 10) {
try {
const stmt = this.db.prepare(`
SELECT * FROM retrieval_audit
WHERE project_id = ? AND query LIKE ?
ORDER BY timestamp DESC
LIMIT ?
`);
const rows = stmt.all(this.projectId, `%${searchTerm}%`, limit);
return rows.map(this.rowToEntry);
} catch (error) {
logger.warn("Failed to search audit entries", {
error: error instanceof Error ? error.message : String(error)
});
return [];
}
}
/**
* Get statistics about retrieval patterns
*/
getStats() {
try {
const statsStmt = this.db.prepare(`
SELECT
COUNT(*) as total,
AVG(confidence_score) as avg_confidence,
AVG(tokens_used) as avg_tokens,
AVG(analysis_time_ms) as avg_time
FROM retrieval_audit
WHERE project_id = ?
`);
const providerStmt = this.db.prepare(`
SELECT provider, COUNT(*) as count
FROM retrieval_audit
WHERE project_id = ?
GROUP BY provider
`);
const stats = statsStmt.get(this.projectId);
const providers = providerStmt.all(this.projectId);
const providerBreakdown = {};
for (const p of providers) {
providerBreakdown[p.provider] = p.count;
}
return {
totalRetrievals: stats?.total || 0,
avgConfidence: stats?.avg_confidence || 0,
providerBreakdown,
avgTokensUsed: stats?.avg_tokens || 0,
avgAnalysisTime: stats?.avg_time || 0
};
} catch (error) {
logger.warn("Failed to get audit stats", {
error: error instanceof Error ? error.message : String(error)
});
return {
totalRetrievals: 0,
avgConfidence: 0,
providerBreakdown: {},
avgTokensUsed: 0,
avgAnalysisTime: 0
};
}
}
/**
* Clean up old audit entries
*/
cleanup(maxAgeMs = 7 * 24 * 60 * 60 * 1e3) {
try {
const cutoff = Date.now() - maxAgeMs;
const stmt = this.db.prepare(`
DELETE FROM retrieval_audit
WHERE project_id = ? AND timestamp < ?
`);
const result = stmt.run(this.projectId, cutoff);
logger.info("Cleaned up old audit entries", { deleted: result.changes });
return result.changes;
} catch (error) {
logger.warn("Failed to cleanup audit entries", {
error: error instanceof Error ? error.message : String(error)
});
return 0;
}
}
rowToEntry(row) {
return {
id: row.id,
timestamp: row.timestamp,
projectId: row.project_id,
query: row.query,
reasoning: row.reasoning,
framesRetrieved: JSON.parse(row.frames_retrieved),
confidenceScore: row.confidence_score,
provider: row.provider,
tokensUsed: row.tokens_used,
tokenBudget: row.token_budget,
analysisTimeMs: row.analysis_time_ms,
queryComplexity: row.query_complexity
};
}
}
export {
RetrievalAuditStore
};
//# sourceMappingURL=retrieval-audit.js.map