@shirokuma-library/mcp-knowledge-base
Version:
Shirokuma MCP Server for comprehensive knowledge management including issues, plans, documents, and work sessions. All stored data is structured for AI processing, not human readability.
230 lines (229 loc) • 9.07 kB
JavaScript
import { SessionMarkdownFormatter } from '../formatters/session-markdown-formatter.js';
import { UnifiedStorage, STORAGE_CONFIGS } from '../storage/unified-storage.js';
import { createLogger } from '../utils/logger.js';
export class SessionRepository {
dataDir;
db;
storage;
formatter;
logger = createLogger('SessionRepository');
constructor(dataDir, db) {
this.dataDir = dataDir;
this.db = db;
this.storage = new UnifiedStorage(dataDir);
this.formatter = new SessionMarkdownFormatter();
}
sessionToStorageItem(session) {
const metadata = {
title: session.title,
description: session.description || '',
tags: session.tags || [],
related_tasks: session.related_tasks || [],
related_documents: session.related_documents || [],
date: session.date,
start_time: session.startTime || '',
created_at: session.createdAt,
updated_at: session.updatedAt || session.createdAt
};
return {
id: session.id,
metadata,
content: session.content || ''
};
}
storageItemToSession(item, id, date) {
const metadata = item.metadata;
return {
id,
title: String(metadata.title || ''),
description: metadata.description ? String(metadata.description) : undefined,
content: item.content || undefined,
tags: Array.isArray(metadata.tags) ? metadata.tags.map(t => String(t)) : [],
related_tasks: Array.isArray(metadata.related_tasks) ? metadata.related_tasks.map(t => String(t)) : undefined,
related_documents: Array.isArray(metadata.related_documents) ? metadata.related_documents.map(t => String(t)) : undefined,
date,
startTime: metadata.start_time ? String(metadata.start_time) : undefined,
createdAt: String(metadata.created_at || new Date().toISOString()),
updatedAt: metadata.updated_at ? String(metadata.updated_at) : undefined
};
}
dailyToStorageItem(daily) {
const metadata = {
title: daily.title,
description: daily.description || '',
tags: daily.tags || [],
related_tasks: daily.related_tasks || [],
related_documents: daily.related_documents || [],
created_at: daily.createdAt,
updated_at: daily.updatedAt || daily.createdAt
};
return {
id: daily.date,
metadata,
content: daily.content
};
}
storageItemToDaily(item, date) {
const metadata = item.metadata;
return {
date,
title: String(metadata.title || ''),
description: metadata.description ? String(metadata.description) : undefined,
content: item.content,
tags: Array.isArray(metadata.tags) ? metadata.tags.map(t => String(t)) : [],
related_tasks: Array.isArray(metadata.related_tasks) ? metadata.related_tasks.map(t => String(t)) : [],
related_documents: Array.isArray(metadata.related_documents) ? metadata.related_documents.map(t => String(t)) : [],
createdAt: String(metadata.created_at || new Date().toISOString()),
updatedAt: metadata.updated_at ? String(metadata.updated_at) : undefined
};
}
async saveSession(session) {
const item = this.sessionToStorageItem(session);
await this.storage.save(STORAGE_CONFIGS.sessions, item);
if (session.tags && session.tags.length > 0) {
await this.db.getTags();
for (const tag of session.tags) {
try {
await this.db.createTag(tag);
}
catch {
}
}
}
}
async loadSession(sessionId, date) {
const item = await this.storage.load(STORAGE_CONFIGS.sessions, sessionId);
if (!item) {
return null;
}
return this.storageItemToSession(item, sessionId, date);
}
async getSessionsForDate(date) {
const ids = await this.storage.list(STORAGE_CONFIGS.sessions, date);
const sessions = [];
for (const id of ids) {
const item = await this.storage.load(STORAGE_CONFIGS.sessions, id);
if (item) {
sessions.push(this.storageItemToSession(item, id, date));
}
}
return sessions;
}
async getSessions(startDate, endDate) {
if (!startDate && !endDate) {
const today = new Date().toISOString().split('T')[0];
return this.getSessionsForDate(today);
}
const dateDirs = await this.storage.listDateDirs(STORAGE_CONFIGS.sessions);
const results = [];
for (const dateDir of dateDirs) {
if (startDate && dateDir < startDate) {
continue;
}
if (endDate && dateDir > endDate) {
continue;
}
const sessions = await this.getSessionsForDate(dateDir);
results.push(...sessions);
}
return results;
}
async getSessionDetail(sessionId) {
const item = await this.storage.load(STORAGE_CONFIGS.sessions, sessionId);
if (!item) {
return null;
}
const date = STORAGE_CONFIGS.sessions.dateExtractor(sessionId);
return this.storageItemToSession(item, sessionId, date);
}
async searchSessionsByTag(tag) {
return this.storage.search(STORAGE_CONFIGS.sessions, item => {
const tags = Array.isArray(item.metadata.tags) ? item.metadata.tags : [];
return tags.some(t => String(t) === tag);
}).then(items => items.map(item => {
const date = STORAGE_CONFIGS.sessions.dateExtractor(item.id);
return this.storageItemToSession(item, item.id, date);
}));
}
async searchSessionsFullText(query) {
const lowerQuery = query.toLowerCase();
return this.storage.search(STORAGE_CONFIGS.sessions, item => {
const searchable = [
item.metadata.title || '',
item.metadata.description || '',
item.content || '',
(Array.isArray(item.metadata.tags) ? item.metadata.tags.map(t => String(t)).join(' ') : '')
].join(' ').toLowerCase();
return searchable.includes(lowerQuery);
}).then(items => items.map(item => {
const date = STORAGE_CONFIGS.sessions.dateExtractor(item.id);
return this.storageItemToSession(item, item.id, date);
}));
}
async saveDaily(summary) {
const exists = await this.storage.exists(STORAGE_CONFIGS.dailies, summary.date);
if (exists) {
throw new Error(`Daily summary for ${summary.date} already exists. Use update instead.`);
}
const item = this.dailyToStorageItem(summary);
await this.storage.save(STORAGE_CONFIGS.dailies, item);
if (summary.tags && summary.tags.length > 0) {
await this.db.getTags();
for (const tag of summary.tags) {
try {
await this.db.createTag(tag);
}
catch {
}
}
}
}
async updateDaily(summary) {
const item = this.dailyToStorageItem(summary);
await this.storage.save(STORAGE_CONFIGS.dailies, item);
if (summary.tags && summary.tags.length > 0) {
await this.db.getTags();
for (const tag of summary.tags) {
try {
await this.db.createTag(tag);
}
catch {
}
}
}
}
async loadDaily(date) {
const item = await this.storage.load(STORAGE_CONFIGS.dailies, date);
if (!item) {
return null;
}
return this.storageItemToDaily(item, date);
}
async getDailySummaries(startDate, endDate) {
if (!startDate && !endDate) {
const today = new Date();
endDate = today.toISOString().split('T')[0];
const weekAgo = new Date(today);
weekAgo.setDate(today.getDate() - 6);
startDate = weekAgo.toISOString().split('T')[0];
}
const ids = await this.storage.list(STORAGE_CONFIGS.dailies);
const results = [];
for (const id of ids) {
if (startDate && id < startDate) {
continue;
}
if (endDate && id > endDate) {
continue;
}
const item = await this.storage.load(STORAGE_CONFIGS.dailies, id);
if (item) {
results.push(this.storageItemToDaily(item, id));
}
}
return results.sort((a, b) => a.date.localeCompare(b.date));
}
ensureDailyDirectory(date) {
return date;
}
}