mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-03 15:43:11 +08:00
feat(memorycore): add tags system, session summaries, hook injection, tag filtering, and solidify compress mode
Implement 5 interconnected memorycore enhancements: 1. Tags backend: add tags TEXT column to memories table with migration, JSON array storage, full CRUD support via upsertMemory/getMemory/getMemories 2. LLM auto-tag extraction: extend extraction prompt to produce tags, parse and validate in pipeline, create CMEM from extraction results 3. Session summary API: expose rollout_summary via new REST endpoints GET /api/core-memory/sessions/summaries and sessions/:id/summary 4. Hook injection: increase SESSION_START_LIMIT to 1500, add Component 5 (Recent Sessions) to UnifiedContextBuilder with 300-char budget 5. Tag filtering: add getMemoriesByTags() with json_each() for safe SQL matching, wire through MCP tool, CLI --tags flag, REST ?tags= param 6. Solidify compress mode: add --type compress to solidify.md with getRecentMemories(), archiveMemories(), buildCompressionMetadata() Security fixes: safeParseTags() for corrupt DB data, json_each() instead of LIKE injection, ESCAPE clause for searchSessionsByKeyword, singleton store in unified-context-builder.
This commit is contained in:
@@ -38,10 +38,21 @@ export async function handleCoreMemoryRoutes(ctx: RouteContext): Promise<boolean
|
||||
const archived = archivedParam === null ? undefined : archivedParam === 'true';
|
||||
const limit = parseInt(url.searchParams.get('limit') || '100', 10);
|
||||
const offset = parseInt(url.searchParams.get('offset') || '0', 10);
|
||||
const tagsParam = url.searchParams.get('tags');
|
||||
|
||||
try {
|
||||
const store = getCoreMemoryStore(projectPath);
|
||||
const memories = store.getMemories({ archived, limit, offset });
|
||||
|
||||
// Use tag filter if tags query parameter is provided
|
||||
let memories;
|
||||
if (tagsParam) {
|
||||
const tags = tagsParam.split(',').map(t => t.trim()).filter(Boolean);
|
||||
memories = tags.length > 0
|
||||
? store.getMemoriesByTags(tags, { archived, limit, offset })
|
||||
: store.getMemories({ archived, limit, offset });
|
||||
} else {
|
||||
memories = store.getMemories({ archived, limit, offset });
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: true, memories }));
|
||||
@@ -78,7 +89,7 @@ export async function handleCoreMemoryRoutes(ctx: RouteContext): Promise<boolean
|
||||
// API: Core Memory - Create or update memory
|
||||
if (pathname === '/api/core-memory/memories' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
const { content, summary, raw_output, id, archived, metadata, path: projectPath } = body;
|
||||
const { content, summary, raw_output, id, archived, metadata, tags, path: projectPath } = body;
|
||||
|
||||
if (!content) {
|
||||
return { error: 'content is required', status: 400 };
|
||||
@@ -94,7 +105,8 @@ export async function handleCoreMemoryRoutes(ctx: RouteContext): Promise<boolean
|
||||
summary,
|
||||
raw_output,
|
||||
archived,
|
||||
metadata: metadata ? JSON.stringify(metadata) : undefined
|
||||
metadata: metadata ? JSON.stringify(metadata) : undefined,
|
||||
tags
|
||||
});
|
||||
|
||||
// Broadcast update event
|
||||
@@ -828,5 +840,48 @@ export async function handleCoreMemoryRoutes(ctx: RouteContext): Promise<boolean
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Get session summaries (list)
|
||||
if (pathname === '/api/core-memory/sessions/summaries' && req.method === 'GET') {
|
||||
const projectPath = url.searchParams.get('path') || initialPath;
|
||||
const limit = parseInt(url.searchParams.get('limit') || '20', 10);
|
||||
|
||||
try {
|
||||
const store = getCoreMemoryStore(projectPath);
|
||||
const summaries = store.getSessionSummaries(limit);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: true, summaries }));
|
||||
} catch (error: unknown) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: (error as Error).message }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Get single session summary by thread ID
|
||||
if (pathname.match(/^\/api\/core-memory\/sessions\/[^\/]+\/summary$/) && req.method === 'GET') {
|
||||
const parts = pathname.split('/');
|
||||
const threadId = parts[4]; // /api/core-memory/sessions/:id/summary
|
||||
const projectPath = url.searchParams.get('path') || initialPath;
|
||||
|
||||
try {
|
||||
const store = getCoreMemoryStore(projectPath);
|
||||
const summary = store.getSessionSummary(threadId);
|
||||
|
||||
if (!summary) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Session summary not found' }));
|
||||
return true;
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: true, ...summary }));
|
||||
} catch (error: unknown) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: (error as Error).message }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -91,10 +91,21 @@ export async function handleMemoryRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
// API: Memory Module - Get all memories (core memory list)
|
||||
if (pathname === '/api/memory' && req.method === 'GET') {
|
||||
const projectPath = url.searchParams.get('path') || initialPath;
|
||||
const tagsParam = url.searchParams.get('tags');
|
||||
|
||||
try {
|
||||
const store = getCoreMemoryStore(projectPath);
|
||||
const memories = store.getMemories({ archived: false, limit: 100 });
|
||||
|
||||
// Use tag filter if tags query parameter is provided
|
||||
let memories;
|
||||
if (tagsParam) {
|
||||
const tags = tagsParam.split(',').map(t => t.trim()).filter(Boolean);
|
||||
memories = tags.length > 0
|
||||
? store.getMemoriesByTags(tags, { archived: false, limit: 100 })
|
||||
: store.getMemories({ archived: false, limit: 100 });
|
||||
} else {
|
||||
memories = store.getMemories({ archived: false, limit: 100 });
|
||||
}
|
||||
|
||||
// Calculate total size
|
||||
const totalSize = memories.reduce((sum, m) => sum + (m.content?.length || 0), 0);
|
||||
@@ -109,7 +120,7 @@ export async function handleMemoryRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
createdAt: m.created_at,
|
||||
updatedAt: m.updated_at,
|
||||
source: m.metadata || undefined,
|
||||
tags: [], // TODO: Extract tags from metadata if available
|
||||
tags: m.tags || [],
|
||||
size: m.content?.length || 0
|
||||
}));
|
||||
|
||||
@@ -139,7 +150,7 @@ export async function handleMemoryRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
|
||||
try {
|
||||
const store = getCoreMemoryStore(basePath);
|
||||
const memory = store.upsertMemory({ content });
|
||||
const memory = store.upsertMemory({ content, tags });
|
||||
|
||||
// Broadcast update event
|
||||
broadcastToClients({
|
||||
@@ -156,7 +167,7 @@ export async function handleMemoryRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
createdAt: memory.created_at,
|
||||
updatedAt: memory.updated_at,
|
||||
source: memory.metadata || undefined,
|
||||
tags: tags || [],
|
||||
tags: memory.tags || [],
|
||||
size: memory.content?.length || 0
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
@@ -175,7 +186,7 @@ export async function handleMemoryRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
|
||||
try {
|
||||
const store = getCoreMemoryStore(basePath);
|
||||
const memory = store.upsertMemory({ id: memoryId, content });
|
||||
const memory = store.upsertMemory({ id: memoryId, content, tags });
|
||||
|
||||
// Broadcast update event
|
||||
broadcastToClients({
|
||||
@@ -192,7 +203,7 @@ export async function handleMemoryRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
createdAt: memory.created_at,
|
||||
updatedAt: memory.updated_at,
|
||||
source: memory.metadata || undefined,
|
||||
tags: tags || [],
|
||||
tags: memory.tags || [],
|
||||
size: memory.content?.length || 0
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
|
||||
Reference in New Issue
Block a user