@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.
147 lines (146 loc) • 6.3 kB
JavaScript
import { SessionRepository } from './repositories/session-repository.js';
import { SessionSearchService } from './services/session-search-service.js';
import { SessionMarkdownFormatter } from './formatters/session-markdown-formatter.js';
import { getConfig } from './config.js';
import * as path from 'path';
export class SessionManager {
db;
repository;
searchService;
formatter;
constructor(sessionsDir = getConfig().database.sessionsPath, db) {
this.db = db;
this.repository = new SessionRepository(sessionsDir, db);
this.searchService = new SessionSearchService(db, this.repository);
this.formatter = new SessionMarkdownFormatter();
}
generateSessionId(date) {
const now = date || new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
return `${year}-${month}-${day}-${hours}.${minutes}.${seconds}.${milliseconds}`;
}
async createSession(title, content, tags, id, datetime, related_tasks, related_documents, description) {
const sessionDate = datetime ? new Date(datetime) : new Date();
const date = sessionDate.toISOString().split('T')[0];
if (id) {
if (id.includes('..') || id.includes('/') || id.includes('\\') ||
id.includes('\0') || id.includes('%') || id === '.' ||
path.isAbsolute(id)) {
throw new Error(`Invalid session ID format: ${id}`);
}
if (!/^[a-zA-Z0-9\-_.]+$/.test(id)) {
throw new Error(`Invalid session ID format: ${id}`);
}
}
const sessionId = id || this.generateSessionId(sessionDate);
const session = {
id: sessionId,
title,
description,
content,
tags,
related_tasks,
related_documents,
date,
createdAt: sessionDate.toISOString()
};
await this.repository.saveSession(session);
return session;
}
async updateSession(id, title, content, tags, related_tasks, related_documents, description) {
const session = await this.repository.getSessionDetail(id);
if (!session) {
throw new Error(`Session ${id} not found`);
}
const updatedSession = {
...session,
title: title !== undefined ? title : session.title,
description: description !== undefined ? description : session.description,
content: content !== undefined ? content : session.content,
tags: tags !== undefined ? tags : session.tags,
related_tasks: related_tasks !== undefined ? related_tasks : session.related_tasks,
related_documents: related_documents !== undefined ? related_documents : session.related_documents,
updatedAt: new Date().toISOString()
};
await this.repository.saveSession(updatedSession);
return updatedSession;
}
async getSession(sessionId) {
return await this.repository.getSessionDetail(sessionId);
}
async getLatestSession() {
const today = new Date().toISOString().split('T')[0];
const sessions = await this.repository.getSessionsForDate(today);
if (sessions.length === 0) {
return null;
}
sessions.sort((a, b) => a.id.localeCompare(b.id));
return sessions[sessions.length - 1];
}
async createDaily(date, title, content, tags = [], related_tasks, related_documents, description) {
const now = new Date().toISOString();
const summary = {
date,
title,
description,
content,
tags,
related_tasks: related_tasks || [],
related_documents: related_documents || [],
createdAt: now
};
await this.repository.saveDaily(summary);
return summary;
}
async updateDaily(date, title, content, tags, related_tasks, related_documents, description) {
const existing = await this.repository.loadDaily(date);
if (!existing) {
throw new Error(`Daily summary for ${date} not found`);
}
const updated = {
...existing,
title: title !== undefined ? title : existing.title,
description: description !== undefined ? description : existing.description,
content: content !== undefined ? content : existing.content,
tags: tags !== undefined ? tags : existing.tags,
related_tasks: related_tasks !== undefined ? related_tasks : existing.related_tasks,
related_documents: related_documents !== undefined ? related_documents : existing.related_documents,
updatedAt: new Date().toISOString()
};
await this.repository.updateDaily(updated);
return updated;
}
async searchSessionsByTag(tag) {
return await this.searchService.searchSessionsByTagDetailed(tag);
}
async searchSessionsFast(query) {
return this.searchService.searchSessionsFast(query);
}
async searchSessionsByTagFast(tag) {
return this.searchService.searchSessionsByTagFast(tag);
}
async searchDailySummariesFast(query) {
return this.searchService.searchDailySummariesFast(query);
}
async searchSessionsDetailed(query) {
return await this.searchService.searchSessionsDetailed(query);
}
async getSessions(startDate, endDate) {
return await this.repository.getSessions(startDate, endDate);
}
async getSessionDetail(sessionId) {
return await this.repository.getSessionDetail(sessionId);
}
async getDailySummaries(startDate, endDate) {
return await this.repository.getDailySummaries(startDate, endDate);
}
async getDailyDetail(date) {
return await this.repository.loadDaily(date);
}
}