claude-memory-layer 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude/settings.local.json +15 -1
  2. package/dist/cli/index.js +156 -972
  3. package/dist/cli/index.js.map +4 -4
  4. package/dist/core/index.js +33 -67
  5. package/dist/core/index.js.map +3 -3
  6. package/dist/hooks/post-tool-use.js +183 -968
  7. package/dist/hooks/post-tool-use.js.map +4 -4
  8. package/dist/hooks/semantic-daemon.js +150 -966
  9. package/dist/hooks/semantic-daemon.js.map +4 -4
  10. package/dist/hooks/session-end.js +150 -966
  11. package/dist/hooks/session-end.js.map +4 -4
  12. package/dist/hooks/session-start.js +152 -966
  13. package/dist/hooks/session-start.js.map +4 -4
  14. package/dist/hooks/stop.js +158 -966
  15. package/dist/hooks/stop.js.map +4 -4
  16. package/dist/hooks/user-prompt-submit.js +152 -968
  17. package/dist/hooks/user-prompt-submit.js.map +4 -4
  18. package/dist/server/api/index.js +151 -967
  19. package/dist/server/api/index.js.map +4 -4
  20. package/dist/server/index.js +151 -967
  21. package/dist/server/index.js.map +4 -4
  22. package/dist/services/memory-service.js +150 -966
  23. package/dist/services/memory-service.js.map +4 -4
  24. package/memory/_index.md +2 -0
  25. package/memory/agent_response/uncategorized/2026-03-04.md +276 -1
  26. package/memory/agent_response/uncategorized/2026-03-05.md +48 -0
  27. package/memory/session_summary/uncategorized/2026-03-04.md +20 -1
  28. package/memory/tool_observation/uncategorized/2026-03-04.md +245 -1
  29. package/memory/tool_observation/uncategorized/2026-03-05.md +29 -0
  30. package/memory/user_prompt/uncategorized/2026-03-04.md +193 -1
  31. package/package.json +1 -2
  32. package/specs/memory-utilization-improvements/context.md +145 -0
  33. package/specs/memory-utilization-improvements/plan.md +361 -0
  34. package/specs/memory-utilization-improvements/spec.md +361 -0
  35. package/specs/optional-duckdb/context.md +77 -0
  36. package/specs/optional-duckdb/plan.md +142 -0
  37. package/specs/optional-duckdb/spec.md +35 -0
  38. package/src/core/db-wrapper.ts +18 -73
  39. package/src/core/sqlite-event-store.ts +32 -4
  40. package/src/hooks/post-tool-use.ts +25 -0
  41. package/src/hooks/session-start.ts +4 -0
  42. package/src/hooks/stop.ts +14 -0
  43. package/src/server/api/utils.ts +1 -1
  44. package/src/services/memory-service.ts +62 -58
@@ -0,0 +1,29 @@
1
+
2
+ ## 2026-03-04T15:00:17.516Z | ad4e29ab-d52e-440a-98df-40a3ca077562
3
+ - type: tool_observation
4
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
5
+ {"toolName":"Edit","toolInput":{"file_path":"/home/walter/workspace/opensource/claude-memory-layer/src/server/api/utils.ts","old_string":"export function getServiceFromQuery(c: Context): MemoryService {\n const project = c.req.query('project');","new_string":"export function getServiceFromQuery(c: Context): MemoryService {\n const project = c.req.query('project') || c.req.query('projectId');","replace_all":false},"toolOutput":"{\"filePath\":\"/home/walter/workspace/opensource/claude-memory-layer/src/server/api/utils.ts\",\"oldString\":\"export function getServiceFromQuery(c: Context): MemoryService {\\n const project = c.req.query('project');\",\"newString\":\"export function getServiceFromQuery(c: Context): MemoryService {\\n const project = c.req.query('project') || c.req.query('projectId');\",\"originalFile\":\"/**\\n * API Utilities\\n * Shared helpers for API endpoints\\n */\\n\\nimport type { Context } from 'hono';\\nimport * as path from 'path';\\nimport * as os from 'os';\\nimport { getReadOnlyMemoryService } from '../../services/memory-service.js';\\nimport { MemoryService } from '../../services/memory-service.js';\\n\\n/**\\n * Get the appropriate MemoryService based on the ?project= query parameter.\\n * - If ?project=<hash> is set (8 hex chars), resolves directly to project storage\\n * - If ?project=<path> is set, computes hash from path\\n * - Otherwise, returns the global read-only service\\n *\\n * Always creates read-only services for the dashboard API to avoid\\n * VectorWorker lifecycle issues with per-request services.\\n */\\nexport function getServiceFromQuery(c: Context): MemoryService {\\n const project = c.req.query('project');\\n if (project) {\\n // Check if it's a hash (8 hex chars) or a path\\n const isHash = /^[a-f0-9]{8}$/.test(project);\\n let storagePath: string;\\n\\n if (isHash) {\\n storagePath = path.join(os.homedir(), '.claude-code', 'memory', 'projects', project);\\n } else {\\n // Import hashProjectPath dynamically to compute the hash from path\\n const crypto = require('crypto');\\n const normalized = project.replace(/\\\\/+$/, '') || '/';\\n const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 8);\\n storagePath = path.join(os.homedir(), '.claude-code', 'memory', 'projects', hash);\\n }\\n\\n return new MemoryService({\\n storagePath,\\n readOnly: true,\\n analyticsEnabled: false,\\n sharedStoreConfig: { enabled: false }\\n });\\n }\\n return getReadOnlyMemoryService();\\n}\\n\",\"structuredPatch\":[{\"oldStart\":19,\"oldLines\":7,\"newStart\":19,\"newLines\":7,\"lines\":[\" * VectorWorker lifecycle issues with per-request services.\",\" */\",\" export function getServiceFromQuery(c: Context): MemoryService {\",\"- const project = c.req.query('project');\",\"+ const project = c.req.query('project') || c.req.query('projectId');\",\" if (project) {\",\" // Check if it's a hash (8 hex chars) or a path\",\" const isHash = /^[a-f0-9]{8}$/.test(project);\"]}],\"userModified\":false,\"replaceAll\":false}","durationMs":0,"success":true,"metadata":{"filePath":"/home/walter/workspace/opensource/claude-memory-layer/src/server/api/utils.ts","fileType":"typescript","turnId":"e42caf45-de79-4d73-a7bc-59954990b764"}}
6
+ ## 2026-03-04T15:00:30.869Z | 3c248d45-27b2-48fe-af1f-448078eeb52d
7
+ - type: tool_observation
8
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
9
+ {"toolName":"Edit","toolInput":{"file_path":"/home/walter/workspace/opensource/claude-memory-layer/src/core/sqlite-event-store.ts","old_string":" // Calculate helpfulness score\n const retrievalScore = retrieval.retrieval_score as number || 0;\n const helpfulnessScore = (\n 0.30 * Math.min(retrievalScore, 1.0) +\n 0.25 * (sessionContinued ? 1.0 : 0.0) +\n 0.25 * toolSuccessRatio +\n 0.20 * (wasReasked ? 0.0 : 1.0)\n );","new_string":" // Calculate helpfulness score\n // Weights tuned for shopping-assistant-like corpora where sessions\n // continue on the same topic (was_reasked was over-penalising normal conversation flow)\n const retrievalScore = retrieval.retrieval_score as number || 0;\n // More prompts after retrieval = memory was actually useful to the conversation\n const promptNorm = Math.min(promptCountAfter / 2, 1.0);\n const helpfulnessScore = (\n 0.40 * Math.min(retrievalScore, 1.0) +\n 0.30 * promptNorm +\n 0.20 * toolSuccessRatio +\n 0.10 * (sessionContinued ? 1.0 : 0.0)\n );","replace_all":false},"toolOutput":"{\"filePath\":\"/home/walter/workspace/opensource/claude-memory-layer/src/core/sqlite-event-store.ts\",\"oldString\":\" // Calculate helpfulness score\\n const retrievalScore = retrieval.retrieval_score as number || 0;\\n const helpfulnessScore = (\\n 0.30 * Math.min(retrievalScore, 1.0) +\\n 0.25 * (sessionContinued ? 1.0 : 0.0) +\\n 0.25 * toolSuccessRatio +\\n 0.20 * (wasReasked ? 0.0 : 1.0)\\n );\",\"newString\":\" // Calculate helpfulness score\\n // Weights tuned for shopping-assistant-like corpora where sessions\\n // continue on the same topic (was_reasked was over-penalising normal conversation flow)\\n const retrievalScore = retrieval.retrieval_score as number || 0;\\n // More prompts after retrieval = memory was actually useful to the conversation\\n const promptNorm = Math.min(promptCountAfter / 2, 1.0);\\n const helpfulnessScore = (\\n 0.40 * Math.min(retrievalScore, 1.0) +\\n 0.30 * promptNorm +\\n 0.20 * toolSuccessRatio +\\n 0.10 * (sessionContinued ? 1.0 : 0.0)\\n );\",\"originalFile\":\"/**\\n * SQLite-based EventStore implementation\\n * Primary store for hooks - WAL mode enables concurrent access\\n */\\n\\nimport { randomUUID } from 'crypto';\\nimport {\\n MemoryEvent,\\n MemoryEventInput,\\n Session,\\n AppendResult,\\n OutboxItem\\n} from './types.js';\\nimport { makeCanonicalKey, makeDedupeKey } from './canonical-key.js';\\nimport {\\n createSQLiteDatabase,\\n sqliteRun,\\n sqliteAll,\\n sqliteGet,\\n sqliteClose,\\n sqliteExec,\\n toDateFromSQLite,\\n toSQLiteTimestamp,\\n type SQLiteDatabase,\\n type SQLiteOptions\\n} from './sqlite-wrapper.js';\\nimport { MarkdownMirror } from './markdown-mirror.js';\\n\\nexport interface SQLiteEventStoreOptions extends SQLiteOptions {\\n markdownMirrorRoot?: string;\\n}\\n\\nexport class SQLiteEventStore {\\n private db: SQLiteDatabase;\\n private initialized = false;\\n private readonly readOnly: boolean;\\n private readonly markdownMirror: MarkdownMirror | null;\\n\\n constructor(private dbPath: string, options?: SQLiteEventStoreOptions) {\\n this.readOnly = options?.readonly ?? false;\\n this.db = createSQLiteDatabase(dbPath, {\\n readonly: this.readOnly,\\n walMode: !this.readOnly\\n });\\n this.markdownMirror = this.readOnly || !options?.markdownMirrorRoot\\n ? null\\n : new MarkdownMirror(options.markdownMirrorRoot);\\n }\\n\\n /**\\n * Initialize database schema\\n */\\n async initialize(): Promise<void> {\\n if (this.initialized) return;\\n\\n // In read-only mode, skip schema creation\\n if (this.readOnly) {\\n this.initialized = true;\\n return;\\n }\\n\\n // Create all tables in a single exec for efficiency\\n sqliteExec(this.db, `\\n -- L0 EventStore: Single Source of Truth (immutable, append-only)\\n CREATE TABLE IF NOT EXISTS events (\\n id TEXT PRIMARY KEY,\\n event_type TEXT NOT NULL,\\n session_id TEXT NOT NULL,\\n timestamp TEXT NOT NULL,\\n content TEXT NOT NULL,\\n canonical_key TEXT NOT NULL,\\n dedupe_key TEXT UNIQUE,\\n metadata TEXT,\\n access_count INTEGER DEFAULT 0,\\n last_accessed_at TEXT\\n );\\n\\n -- Dedup table for idempotency\\n CREATE TABLE IF NOT EXISTS event_dedup (\\n dedupe_key TEXT PRIMARY KEY,\\n event_id TEXT NOT NULL,\\n created_at TEXT DEFAULT (datetime('now'))\\n );\\n\\n -- Session metadata\\n CREATE TABLE IF NOT EXISTS sessions (\\n id TEXT PRIMARY KEY,\\n started_at TEXT NOT NULL,\\n ended_at TEXT,\\n project_path TEXT,\\n summary TEXT,\\n tags TEXT\\n );\\n\\n -- Insights (derived data, rebuildable)\\n CREATE TABLE IF NOT EXISTS insights (\\n id TEXT PRIMARY KEY,\\n insight_type TEXT NOT NULL,\\n content TEXT NOT NULL,\\n canonical_key TEXT NOT NULL,\\n confidence REAL,\\n source_events TEXT,\\n created_at TEXT,\\n last_updated TEXT\\n );\\n\\n -- Embedding Outbox (Single-Writer Pattern)\\n CREATE TABLE IF NOT EXISTS embedding_outbox (\\n id TEXT PRIMARY KEY,\\n event_id TEXT NOT NULL,\\n content TEXT NOT NULL,\\n status TEXT DEFAULT 'pending',\\n retry_count INTEGER DEFAULT 0,\\n created_at TEXT DEFAULT (datetime('now')),\\n processed_at TEXT,\\n error_message TEXT\\n );\\n\\n -- Projection offset tracking\\n CREATE TABLE IF NOT EXISTS projection_offsets (\\n projection_name TEXT PRIMARY KEY,\\n last_event_id TEXT,\\n last_timestamp TEXT,\\n updated_at TEXT DEFAULT (datetime('now'))\\n );\\n\\n -- Memory level tracking\\n CREATE TABLE IF NOT EXISTS memory_levels (\\n event_id TEXT PRIMARY KEY,\\n level TEXT NOT NULL DEFAULT 'L0',\\n promoted_at TEXT DEFAULT (datetime('now'))\\n );\\n\\n -- Entries (immutable memory units)\\n CREATE TABLE IF NOT EXISTS entries (\n... [49537 characters truncated] ...\n {\\n const metadata = JSON.parse(row.metadata);\\n if (metadata.turnId) {\\n sqliteRun(\\n this.db,\\n `UPDATE events SET turn_id = ? WHERE id = ?`,\\n [metadata.turnId, row.id]\\n );\\n updated++;\\n }\\n } catch {\\n // Skip rows with invalid JSON\\n }\\n }\\n\\n return updated;\\n }\\n\\n /**\\n * Delete all events for a session (for force reimport)\\n */\\n async deleteSessionEvents(sessionId: string): Promise<number> {\\n await this.initialize();\\n\\n // Get event IDs first for cascading deletes\\n const events = sqliteAll<{ id: string }>(\\n this.db,\\n `SELECT id FROM events WHERE session_id = ?`,\\n [sessionId]\\n );\\n\\n if (events.length === 0) return 0;\\n\\n const eventIds = events.map(e => e.id);\\n const placeholders = eventIds.map(() => '?').join(',');\\n\\n // Drop FTS triggers to prevent SQLITE_CORRUPT_VTAB during bulk delete\\n const ftsTriggersDropped: string[] = [];\\n for (const triggerName of ['events_fts_delete', 'events_fts_update', 'events_fts_insert']) {\\n try {\\n sqliteRun(this.db, `DROP TRIGGER IF EXISTS ${triggerName}`);\\n ftsTriggersDropped.push(triggerName);\\n } catch {\\n // Trigger may not exist\\n }\\n }\\n\\n // Delete from related tables first (some may not exist depending on DB version)\\n for (const table of ['event_dedup', 'memory_levels', 'embedding_queue', 'embedding_outbox', 'vector_outbox']) {\\n try {\\n sqliteRun(this.db, `DELETE FROM ${table} WHERE event_id IN (${placeholders})`, eventIds);\\n } catch {\\n // Table may not exist\\n }\\n }\\n\\n // Delete events\\n const result = sqliteRun(this.db, `DELETE FROM events WHERE session_id = ?`, [sessionId]);\\n\\n // Rebuild FTS index if we dropped triggers\\n if (ftsTriggersDropped.length > 0) {\\n try {\\n // Rebuild FTS from remaining events\\n sqliteRun(this.db, `INSERT INTO events_fts(events_fts) VALUES('rebuild')`);\\n\\n // Recreate triggers\\n sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events BEGIN\\n INSERT INTO events_fts(rowid, content) VALUES (NEW.rowid, NEW.content);\\n END`);\\n sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN\\n INSERT INTO events_fts(events_fts, rowid, content) VALUES('delete', OLD.rowid, OLD.content);\\n END`);\\n sqliteRun(this.db, `CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN\\n INSERT INTO events_fts(events_fts, rowid, content) VALUES('delete', OLD.rowid, OLD.content);\\n INSERT INTO events_fts(rowid, content) VALUES (NEW.rowid, NEW.content);\\n END`);\\n } catch {\\n // FTS rebuild failed - non-critical, will be rebuilt on next initialize\\n }\\n }\\n\\n return result.changes || 0;\\n }\\n\\n /**\\n * Convert database row to MemoryEvent\\n */\\n private rowToEvent(row: Record<string, unknown>): MemoryEvent {\\n const event: any = {\\n id: row.id as string,\\n eventType: row.event_type as 'user_prompt' | 'agent_response' | 'session_summary',\\n sessionId: row.session_id as string,\\n timestamp: toDateFromSQLite(row.timestamp),\\n content: row.content as string,\\n canonicalKey: row.canonical_key as string,\\n dedupeKey: row.dedupe_key as string,\\n metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined\\n };\\n\\n // Include access tracking fields if present\\n if (row.access_count !== undefined) {\\n event.access_count = row.access_count;\\n }\\n if (row.last_accessed_at !== undefined) {\\n event.last_accessed_at = row.last_accessed_at;\\n }\\n // Include turn_id if present\\n if (row.turn_id !== undefined && row.turn_id !== null) {\\n event.turn_id = row.turn_id;\\n }\\n\\n return event;\\n }\\n}\\n\",\"structuredPatch\":[{\"oldStart\":1252,\"oldLines\":12,\"newStart\":1252,\"newLines\":16,\"lines\":[\" }\",\" \",\" // Calculate helpfulness score\",\"+ // Weights tuned for shopping-assistant-like corpora where sessions\",\"+ // continue on the same topic (was_reasked was over-penalising normal conversation flow)\",\" const retrievalScore = retrieval.retrieval_score as number || 0;\",\"+ // More prompts after retrieval = memory was actually useful to the conversation\",\"+ const promptNorm = Math.min(promptCountAfter / 2, 1.0);\",\" const helpfulnessScore = (\",\"- 0.30 * Math.min(retrievalScore, 1.0) +\",\"- 0.25 * (sessionContinued ? 1.0 : 0.0) +\",\"- 0.25 * toolSuccessRatio +\",\"- 0.20 * (wasReasked ? 0.0 : 1.0)\",\"+ 0.40 * Math.min(retrievalScore, 1.0) +\",\"+ 0.30 * promptNorm +\",\"+ 0.20 * toolSuccessRatio +\",\"+ 0.10 * (sessionContinued ? 1.0 : 0.0)\",\" );\",\" \",\" sqliteRun(\"]}],\"userModified\":false,\"replaceAll\":false}","durationMs":0,"success":true,"metadata":{"filePath":"/home/walter/workspace/opensource/claude-memory-layer/src/core/sqlite-event-store.ts","fileType":"typescript","turnId":"e42caf45-de79-4d73-a7bc-59954990b764"}}
10
+ ## 2026-03-04T15:00:40.454Z | ab79f6a8-88f2-40cf-9bf6-ffad63570206
11
+ - type: tool_observation
12
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
13
+ {"toolName":"Edit","toolInput":{"file_path":"/home/walter/workspace/opensource/claude-memory-layer/src/hooks/post-tool-use.ts","old_string":"function isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {\n if (response?.stderr && response.stderr.trim().length > 20) return true;\n const lower = output.toLowerCase();\n if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;\n return output.trim().length > 800;\n}","new_string":"function isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {\n if (response?.stderr && response.stderr.trim().length > 20) return true;\n const lower = output.toLowerCase();\n if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;\n return output.trim().length > 2000;\n}","replace_all":false},"toolOutput":"{\"filePath\":\"/home/walter/workspace/opensource/claude-memory-layer/src/hooks/post-tool-use.ts\",\"oldString\":\"function isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {\\n if (response?.stderr && response.stderr.trim().length > 20) return true;\\n const lower = output.toLowerCase();\\n if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;\\n return output.trim().length > 800;\\n}\",\"newString\":\"function isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {\\n if (response?.stderr && response.stderr.trim().length > 20) return true;\\n const lower = output.toLowerCase();\\n if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;\\n return output.trim().length > 2000;\\n}\",\"originalFile\":\"#!/usr/bin/env node\\n/**\\n * PostToolUse Hook\\n * Called after each tool execution - stores tool observations\\n *\\n * Actual Claude Code input format:\\n * {\\n * session_id, tool_name, tool_input, tool_use_id,\\n * tool_response: { stdout?, stderr?, content?, interrupted?, isImage? },\\n * cwd, transcript_path, permission_mode, hook_event_name\\n * }\\n */\\n\\nimport { getLightweightMemoryService } from '../services/memory-service.js';\\nimport { applyPrivacyFilter, maskSensitiveInput, truncateOutput } from '../core/privacy/index.js';\\nimport { extractMetadata } from '../core/metadata-extractor.js';\\nimport { readTurnState } from '../core/turn-state.js';\\nimport type { PostToolUseInput, ToolObservationPayload, Config } from '../core/types.js';\\n\\n// Default config\\nconst DEFAULT_CONFIG: Config['toolObservation'] = {\\n enabled: true,\\n excludedTools: [\\n // Trivial meta tools\\n 'TodoWrite', 'TodoRead',\\n // Reproducible query tools (no storage value)\\n 'Read', 'Grep', 'Glob',\\n 'ToolSearch', 'WebFetch', 'WebSearch', 'NotebookRead',\\n // Low-value system tools\\n 'Skill', 'EnterPlanMode',\\n ],\\n minOutputLength: parseInt(process.env.CLAUDE_MEMORY_TOOL_MIN_OUTPUT_LEN || '100'),\\n maxOutputLength: 10000,\\n maxOutputLines: 100,\\n storeOnlyOnSuccess: false\\n};\\n\\n// Tools that are always stored regardless of output length\\nconst ALWAYS_STORE_TOOLS = new Set([\\n 'Write', 'Edit', 'MultiEdit', 'Agent', 'Task', 'ExitPlanMode'\\n]);\\n\\n// Keywords that indicate a Bash output is worth storing\\nconst IMPORTANT_BASH_KEYWORDS = [\\n 'error', 'failed', 'exception', 'traceback', 'panic',\\n 'warning', 'deprecated',\\n 'test passed', 'test failed', 'tests passed', 'tests failed',\\n 'coverage', 'assert',\\n 'published', 'deployed', 'built successfully', 'build complete',\\n 'successfully installed', 'successfully created',\\n];\\n\\n/**\\n * For Bash commands, only store output that is significant:\\n * - Has stderr content\\n * - Contains important keywords (errors, test results, deploy events)\\n * - Output is very long (> 800 chars), indicating meaningful work\\n */\\nfunction isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {\\n if (response?.stderr && response.stderr.trim().length > 20) return true;\\n const lower = output.toLowerCase();\\n if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;\\n return output.trim().length > 800;\\n}\\n\\n/**\\n * Determine if a tool output is significant enough to store.\\n * Always-store tools bypass the length check.\\n * Bash uses keyword-based significance detection.\\n * Other tools require non-empty stderr or output length >= minLen.\\n */\\nfunction hasSignificantOutput(\\n toolName: string,\\n output: string,\\n response: PostToolUseInput['tool_response'],\\n minLen: number\\n): boolean {\\n if (ALWAYS_STORE_TOOLS.has(toolName)) return true;\\n if (toolName === 'Bash') return isBashSignificant(output, response);\\n if (response?.stderr && response.stderr.trim().length > 0) return true;\\n return output.trim().length >= minLen;\\n}\\n\\nconst DEFAULT_PRIVACY_CONFIG: Config['privacy'] = {\\n excludePatterns: ['password', 'secret', 'api_key', 'token', 'bearer'],\\n anonymize: false,\\n privateTags: {\\n enabled: true,\\n marker: '[PRIVATE]\n',\\n preserveLineCount: false,\\n supportedFormats: ['xml']\\n }\\n};\\n\\n/**\\n * Extract text output from tool_response object\\n */\\nfunction extractToolOutput(response: PostToolUseInput['tool_response']): string {\\n if (!response) return '';\\n\\n // Bash tools: stdout + stderr\\n if (response.stdout !== undefined) {\\n const parts: string[] = [];\\n if (response.stdout) parts.push(response.stdout);\\n if (response.stderr) parts.push(`[stderr] ${response.stderr}`);\\n return parts.join('\\\\n') || '';\\n }\\n\\n // Other tools may have content field\\n if (response.content !== undefined) {\\n return typeof response.content === 'string'\\n ? response.content\\n : JSON.stringify(response.content);\\n }\\n\\n // Fallback: stringify the whole response\\n return JSON.stringify(response);\\n}\\n\\n/**\\n * Determine if the tool execution was successful\\n */\\nfunction isToolSuccess(response: PostToolUseInput['tool_response']): boolean {\\n if (!response) return false;\\n if (response.interrupted) return false;\\n // If stderr has content but stdout also has content, still consider success\\n return true;\\n}\\n\\nasync function main(): Promise<void> {\\n // Read input from stdin\\n const inputData = await readStdin();\\n const input: PostToolUseInput = JSON.parse(inputData);\\n\\n const config = { ...DEFAULT_CONFIG };\\n const privacyConfig = DEFAULT_PRIVACY_CONFIG;\\n\\n // Allow env-based blocklist override\\n const envBlocklist = process.env.CLAUDE_MEMORY_TOOL_BLOCKLIST;\\n if (envBlocklist !== undefined) {\\n config.excludedTools = envBlocklist.split(',').map((s) => s.trim()).filter(Boolean);\\n }\\n\\n // 1. Check if tool observation is enabled\\n if (!config.enabled) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n // 2. Check if tool is excluded\\n if (config.excludedTools?.includes(input.tool_name)) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n // 3. Extract output from tool_response object\\n const toolOutput = extractToolOutput(input.tool_response);\\n const success = isToolSuccess(input.tool_response);\\n\\n // 4. Check success filter\\n if (!success && config.storeOnlyOnSuccess) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n // 4.5. Output-level filter: skip low-signal outputs\\n if (!hasSignificantOutput(\\n input.tool_name, toolOutput, input.tool_response,\\n config.minOutputLength ?? 100\\n )) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n try {\\n const memoryService = getLightweightMemoryService(input.session_id);\\n\\n // 5. Mask sensitive data in input\\n const maskedInput = maskSensitiveInput(input.tool_input);\\n\\n // 6. Apply privacy filter to output\\n const filterResult = applyPrivacyFilter(toolOutput, privacyConfig);\\n const maskedOutput = filterResult.content;\\n\\n // 7. Truncate output\\n const truncatedOutput = truncateOutput(maskedOutput, {\\n maxLength: config.maxOutputLength,\\n maxLines: config.maxOutputLines\\n });\\n\\n // 8. Extract metadata\\n const metadata = extractMetadata(\\n input.tool_name,\\n maskedInput,\\n toolOutput,\\n success\\n );\\n\\n // 8.5. Read current turn_id from state file\\n const turnId = readTurnState(input.session_id);\\n\\n // 9. Create payload (include turnId in metadata for grouping)\\n const payload: ToolObservationPayload = {\\n toolName: input.tool_name,\\n toolInput: maskedInput,\\n toolOutput: truncatedOutput,\\n durationMs: 0, // Claude Code doesn't provide timing info\\n success,\\n errorMessage: input.tool_response?.stderr || undefined,\\n metadata: {\\n ...metadata,\\n ...(turnId ? { turnId } : {})\\n }\\n };\\n\\n // 10. Store observation\\n await memoryService.storeToolObservation(input.session_id, payload);\\n\\n // Output empty (hook doesn't return context)\\n console.log(JSON.stringify({}));\\n } catch (error) {\\n if (process.env.CLAUDE_MEMORY_DEBUG) {\\n console.error('PostToolUse hook error:', error);\\n }\\n console.log(JSON.stringify({}));\\n }\\n}\\n\\nfunction readStdin(): Promise<string> {\\n return new Promise((resolve) => {\\n let data = '';\\n process.stdin.setEncoding('utf8');\\n process.stdin.on('data', (chunk) => {\\n data += chunk;\\n });\\n process.stdin.on('end', () => {\\n resolve(data);\\n });\\n });\\n}\\n\\nmain().catch(console.error);\\n\",\"structuredPatch\":[{\"oldStart\":60,\"oldLines\":7,\"newStart\":60,\"newLines\":7,\"lines\":[\" if (response?.stderr && response.stderr.trim().length > 20) return true;\",\" const lower = output.toLowerCase();\",\" if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;\",\"- return output.trim().length > 800;\",\"+ return output.trim().length > 2000;\",\" }\",\" \",\" /**\"]}],\"userModified\":false,\"replaceAll\":false}","durationMs":0,"success":true,"metadata":{"filePath":"/home/walter/workspace/opensource/claude-memory-layer/src/hooks/post-tool-use.ts","fileType":"typescript","turnId":"e42caf45-de79-4d73-a7bc-59954990b764"}}
14
+ ## 2026-03-04T15:00:48.809Z | 588474cb-d13f-435b-bf4f-dd87751d9318
15
+ - type: tool_observation
16
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
17
+ {"toolName":"Edit","toolInput":{"file_path":"/home/walter/workspace/opensource/claude-memory-layer/src/hooks/post-tool-use.ts","old_string":" * - Output is very long (> 800 chars), indicating meaningful work","new_string":" * - Output is very long (> 2000 chars), indicating meaningful work","replace_all":false},"toolOutput":"{\"filePath\":\"/home/walter/workspace/opensource/claude-memory-layer/src/hooks/post-tool-use.ts\",\"oldString\":\" * - Output is very long (> 800 chars), indicating meaningful work\",\"newString\":\" * - Output is very long (> 2000 chars), indicating meaningful work\",\"originalFile\":\"#!/usr/bin/env node\\n/**\\n * PostToolUse Hook\\n * Called after each tool execution - stores tool observations\\n *\\n * Actual Claude Code input format:\\n * {\\n * session_id, tool_name, tool_input, tool_use_id,\\n * tool_response: { stdout?, stderr?, content?, interrupted?, isImage? },\\n * cwd, transcript_path, permission_mode, hook_event_name\\n * }\\n */\\n\\nimport { getLightweightMemoryService } from '../services/memory-service.js';\\nimport { applyPrivacyFilter, maskSensitiveInput, truncateOutput } from '../core/privacy/index.js';\\nimport { extractMetadata } from '../core/metadata-extractor.js';\\nimport { readTurnState } from '../core/turn-state.js';\\nimport type { PostToolUseInput, ToolObservationPayload, Config } from '../core/types.js';\\n\\n// Default config\\nconst DEFAULT_CONFIG: Config['toolObservation'] = {\\n enabled: true,\\n excludedTools: [\\n // Trivial meta tools\\n 'TodoWrite', 'TodoRead',\\n // Reproducible query tools (no storage value)\\n 'Read', 'Grep', 'Glob',\\n 'ToolSearch', 'WebFetch', 'WebSearch', 'NotebookRead',\\n // Low-value system tools\\n 'Skill', 'EnterPlanMode',\\n ],\\n minOutputLength: parseInt(process.env.CLAUDE_MEMORY_TOOL_MIN_OUTPUT_LEN || '100'),\\n maxOutputLength: 10000,\\n maxOutputLines: 100,\\n storeOnlyOnSuccess: false\\n};\\n\\n// Tools that are always stored regardless of output length\\nconst ALWAYS_STORE_TOOLS = new Set([\\n 'Write', 'Edit', 'MultiEdit', 'Agent', 'Task', 'ExitPlanMode'\\n]);\\n\\n// Keywords that indicate a Bash output is worth storing\\nconst IMPORTANT_BASH_KEYWORDS = [\\n 'error', 'failed', 'exception', 'traceback', 'panic',\\n 'warning', 'deprecated',\\n 'test passed', 'test failed', 'tests passed', 'tests failed',\\n 'coverage', 'assert',\\n 'published', 'deployed', 'built successfully', 'build complete',\\n 'successfully installed', 'successfully created',\\n];\\n\\n/**\\n * For Bash commands, only store output that is significant:\\n * - Has stderr content\\n * - Contains important keywords (errors, test results, deploy events)\\n * - Output is very long (> 800 chars), indicating meaningful work\\n */\\nfunction isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {\\n if (response?.stderr && response.stderr.trim().length > 20) return true;\\n const lower = output.toLowerCase();\\n if (IMPORTANT_BASH_KEYWORDS.some((kw) => lower.includes(kw))) return true;\\n return output.trim().length > 2000;\\n}\\n\\n/**\\n * Determine if a tool output is significant enough to store.\\n * Always-store tools bypass the length check.\\n * Bash uses keyword-based significance detection.\\n * Other tools require non-empty stderr or output length >= minLen.\\n */\\nfunction hasSignificantOutput(\\n toolName: string,\\n output: string,\\n response: PostToolUseInput['tool_response'],\\n minLen: number\\n): boolean {\\n if (ALWAYS_STORE_TOOLS.has(toolName)) return true;\\n if (toolName === 'Bash') return isBashSignificant(output, response);\\n if (response?.stderr && response.stderr.trim().length > 0) return true;\\n return output.trim().length >= minLen;\\n}\\n\\nconst DEFAULT_PRIVACY_CONFIG: Config['privacy'] = {\\n excludePatterns: ['password', 'secret', 'api_key', 'token', 'bearer'],\\n anonymize: false,\\n privateTags: {\\n enabled: true,\\n marker: '[PRIVATE]\n',\\n preserveLineCount: false,\\n supportedFormats: ['xml']\\n }\\n};\\n\\n/**\\n * Extract text output from tool_response object\\n */\\nfunction extractToolOutput(response: PostToolUseInput['tool_response']): string {\\n if (!response) return '';\\n\\n // Bash tools: stdout + stderr\\n if (response.stdout !== undefined) {\\n const parts: string[] = [];\\n if (response.stdout) parts.push(response.stdout);\\n if (response.stderr) parts.push(`[stderr] ${response.stderr}`);\\n return parts.join('\\\\n') || '';\\n }\\n\\n // Other tools may have content field\\n if (response.content !== undefined) {\\n return typeof response.content === 'string'\\n ? response.content\\n : JSON.stringify(response.content);\\n }\\n\\n // Fallback: stringify the whole response\\n return JSON.stringify(response);\\n}\\n\\n/**\\n * Determine if the tool execution was successful\\n */\\nfunction isToolSuccess(response: PostToolUseInput['tool_response']): boolean {\\n if (!response) return false;\\n if (response.interrupted) return false;\\n // If stderr has content but stdout also has content, still consider success\\n return true;\\n}\\n\\nasync function main(): Promise<void> {\\n // Read input from stdin\\n const inputData = await readStdin();\\n const input: PostToolUseInput = JSON.parse(inputData);\\n\\n const config = { ...DEFAULT_CONFIG };\\n const privacyConfig = DEFAULT_PRIVACY_CONFIG;\\n\\n // Allow env-based blocklist override\\n const envBlocklist = process.env.CLAUDE_MEMORY_TOOL_BLOCKLIST;\\n if (envBlocklist !== undefined) {\\n config.excludedTools = envBlocklist.split(',').map((s) => s.trim()).filter(Boolean);\\n }\\n\\n // 1. Check if tool observation is enabled\\n if (!config.enabled) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n // 2. Check if tool is excluded\\n if (config.excludedTools?.includes(input.tool_name)) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n // 3. Extract output from tool_response object\\n const toolOutput = extractToolOutput(input.tool_response);\\n const success = isToolSuccess(input.tool_response);\\n\\n // 4. Check success filter\\n if (!success && config.storeOnlyOnSuccess) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n // 4.5. Output-level filter: skip low-signal outputs\\n if (!hasSignificantOutput(\\n input.tool_name, toolOutput, input.tool_response,\\n config.minOutputLength ?? 100\\n )) {\\n console.log(JSON.stringify({}));\\n return;\\n }\\n\\n try {\\n const memoryService = getLightweightMemoryService(input.session_id);\\n\\n // 5. Mask sensitive data in input\\n const maskedInput = maskSensitiveInput(input.tool_input);\\n\\n // 6. Apply privacy filter to output\\n const filterResult = applyPrivacyFilter(toolOutput, privacyConfig);\\n const maskedOutput = filterResult.content;\\n\\n // 7. Truncate output\\n const truncatedOutput = truncateOutput(maskedOutput, {\\n maxLength: config.maxOutputLength,\\n maxLines: config.maxOutputLines\\n });\\n\\n // 8. Extract metadata\\n const metadata = extractMetadata(\\n input.tool_name,\\n maskedInput,\\n toolOutput,\\n success\\n );\\n\\n // 8.5. Read current turn_id from state file\\n const turnId = readTurnState(input.session_id);\\n\\n // 9. Create payload (include turnId in metadata for grouping)\\n const payload: ToolObservationPayload = {\\n toolName: input.tool_name,\\n toolInput: maskedInput,\\n toolOutput: truncatedOutput,\\n durationMs: 0, // Claude Code doesn't provide timing info\\n success,\\n errorMessage: input.tool_response?.stderr || undefined,\\n metadata: {\\n ...metadata,\\n ...(turnId ? { turnId } : {})\\n }\\n };\\n\\n // 10. Store observation\\n await memoryService.storeToolObservation(input.session_id, payload);\\n\\n // Output empty (hook doesn't return context)\\n console.log(JSON.stringify({}));\\n } catch (error) {\\n if (process.env.CLAUDE_MEMORY_DEBUG) {\\n console.error('PostToolUse hook error:', error);\\n }\\n console.log(JSON.stringify({}));\\n }\\n}\\n\\nfunction readStdin(): Promise<string> {\\n return new Promise((resolve) => {\\n let data = '';\\n process.stdin.setEncoding('utf8');\\n process.stdin.on('data', (chunk) => {\\n data += chunk;\\n });\\n process.stdin.on('end', () => {\\n resolve(data);\\n });\\n });\\n}\\n\\nmain().catch(console.error);\\n\",\"structuredPatch\":[{\"oldStart\":54,\"oldLines\":7,\"newStart\":54,\"newLines\":7,\"lines\":[\" * For Bash commands, only store output that is significant:\",\" * - Has stderr content\",\" * - Contains important keywords (errors, test results, deploy events)\",\"- * - Output is very long (> 800 chars), indicating meaningful work\",\"+ * - Output is very long (> 2000 chars), indicating meaningful work\",\" */\",\" function isBashSignificant(output: string, response: PostToolUseInput['tool_response']): boolean {\",\" if (response?.stderr && response.stderr.trim().length > 20) return true;\"]}],\"userModified\":false,\"replaceAll\":false}","durationMs":0,"success":true,"metadata":{"filePath":"/home/walter/workspace/opensource/claude-memory-layer/src/hooks/post-tool-use.ts","fileType":"typescript","turnId":"e42caf45-de79-4d73-a7bc-59954990b764"}}
18
+ ## 2026-03-04T15:00:53.922Z | 628373c5-d886-4d2f-824b-060b2655ba04
19
+ - type: tool_observation
20
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
21
+ {"toolName":"Bash","toolInput":{"command":"npm run build 2>&1 | tail -15","timeout":60000,"description":"Build to verify all changes compile"},"toolOutput":"📦 Building services...\n📦 Building server...\n📋 Copying plugin files...\n📋 Copying UI files...\n\n✅ Build complete!\n\nOutput: dist/\n - cli/index.js\n - hooks/*.js\n - core/index.js\n - services/memory-service.js\n - server/index.js\n - ui/index.html\n - .claude-plugin/","durationMs":0,"success":true,"metadata":{"command":"npm run","turnId":"e42caf45-de79-4d73-a7bc-59954990b764"}}
22
+ ## 2026-03-04T15:01:15.931Z | bda6bba3-917f-4a27-9361-3f7057fcb59c
23
+ - type: tool_observation
24
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
25
+ {"toolName":"Edit","toolInput":{"file_path":"/home/walter/workspace/opensource/claude-memory-layer/specs/memory-utilization-improvements/spec.md","old_string":"## 구현 순서\n\n```\nWeek 1 (Critical Path):\n IMP-01: Trace 동기 기록 → 즉시 측정 가능\n IMP-02: Tool Observation 필터링 → 노이즈 감소\n\nWeek 2 (Quality):\n IMP-03: 세션 요약 신뢰성\n IMP-04: Graduation repair CLI\n IMP-05: Embedding 모델 모니터링\n\nWeek 3 (Feedback Loop):\n IMP-06: Helpfulness 피드백\n IMP-07: 컨텍스트 포맷 개선\n```","new_string":"## 2차 개선 (실측 데이터 기반 후속)\n\n### IMP-01b: 대시보드 API projectId 파라미터 불일치 수정\n\n**문제**: 대시보드가 `?projectId=f4d5c120`으로 쿼리하는데 `getServiceFromQuery()`는 `?project=`만 읽음 → 항상 글로벌 서비스 반환\n\n**수정**: `src/server/api/utils.ts`\n```typescript\nconst project = c.req.query('project') || c.req.query('projectId');\n```\n**완료** ✅\n\n---\n\n### IMP-02b: Bash 출력 임계값 800 → 2000\n\n**문제**: 800자 임계값이 너무 낮아 일반적인 bash 출력 대부분이 저장됨\n\n**수정**: `src/hooks/post-tool-use.ts`\n```typescript\nreturn output.trim().length > 2000;\n```\n**완료** ✅\n\n---\n\n### IMP-06b: Helpfulness 평가 알고리즘 변별력 개선\n\n**문제**: `was_reasked` 로직이 자연스러운 대화 연속을 penalty로 처리 → 모든 항목 0.62~0.68 수렴\n\n**원인**:\n- 쇼핑 어시스턴트처럼 같은 주제를 이어가면 `was_reasked=1` 항상 발생\n- 가중치: `0.20 × (wasReasked ? 0 : 1)` → 모든 항목에 -0.20 penalty\n\n**수정**: `src/core/sqlite-event-store.ts`\n```typescript\n// 기존 (변별력 낮음)\n0.30 × retrievalScore + 0.25 × sessionContinued + 0.25 × toolSuccessRatio + 0.20 × !wasReasked\n\n// 개선 (prompt_count_after 활용)\n0.40 × retrievalScore + 0.30 × promptNorm + 0.20 × toolSuccessRatio + 0.10 × sessionContinued\n// promptNorm = min(promptCountAfter / 2, 1.0)\n// 0턴→0.0, 1턴→0.5, 2턴이상→1.0\n```\n**완료** ✅\n\n---\n\n## 구현 순서\n\n```\nWeek 1 (Critical Path) - 완료:\n IMP-01: Trace projectHash 수정 ✅\n IMP-02: Tool Observation 필터링 ✅\n\nWeek 2 (Quality) - 완료:\n IMP-03: 세션 요약 신뢰성 ✅\n IMP-04: Graduation repair CLI\n IMP-05: Embedding 모델 모니터링\n\nWeek 2 (후속 실측 기반) - 완료:\n IMP-01b: 대시보드 projectId 파라미터 수정 ✅\n IMP-02b: Bash 임계값 800→2000 ✅\n IMP-06b: Helpfulness 알고리즘 개선 ✅\n\nWeek 3 (Feedback Loop):\n IMP-06: Helpfulness 피드백 루프 완성\n IMP-07: 컨텍스트 포맷 개선\n```","replace_all":false},"toolOutput":"{\"filePath\":\"/home/walter/workspace/opensource/claude-memory-layer/specs/memory-utilization-improvements/spec.md\",\"oldString\":\"## 구현 순서\\n\\n```\\nWeek 1 (Critical Path):\\n IMP-01: Trace 동기 기록 → 즉시 측정 가능\\n IMP-02: Tool Observation 필터링 → 노이즈 감소\\n\\nWeek 2 (Quality):\\n IMP-03: 세션 요약 신뢰성\\n IMP-04: Graduation repair CLI\\n IMP-05: Embedding 모델 모니터링\\n\\nWeek 3 (Feedback Loop):\\n IMP-06: Helpfulness 피드백\\n IMP-07: 컨텍스트 포맷 개선\\n```\",\"newString\":\"## 2차 개선 (실측 데이터 기반 후속)\\n\\n### IMP-01b: 대시보드 API projectId 파라미터 불일치 수정\\n\\n**문제**: 대시보드가 `?projectId=f4d5c120`으로 쿼리하는데 `getServiceFromQuery()`는 `?project=`만 읽음 → 항상 글로벌 서비스 반환\\n\\n**수정**: `src/server/api/utils.ts`\\n```typescript\\nconst project = c.req.query('project') || c.req.query('projectId');\\n```\\n**완료** ✅\\n\\n---\\n\\n### IMP-02b: Bash 출력 임계값 800 → 2000\\n\\n**문제**: 800자 임계값이 너무 낮아 일반적인 bash 출력 대부분이 저장됨\\n\\n**수정**: `src/hooks/post-tool-use.ts`\\n```typescript\\nreturn output.trim().length > 2000;\\n```\\n**완료** ✅\\n\\n---\\n\\n### IMP-06b: Helpfulness 평가 알고리즘 변별력 개선\\n\\n**문제**: `was_reasked` 로직이 자연스러운 대화 연속을 penalty로 처리 → 모든 항목 0.62~0.68 수렴\\n\\n**원인**:\\n- 쇼핑 어시스턴트처럼 같은 주제를 이어가면 `was_reasked=1` 항상 발생\\n- 가중치: `0.20 × (wasReasked ? 0 : 1)` → 모든 항목에 -0.20 penalty\\n\\n**수정**: `src/core/sqlite-event-store.ts`\\n```typescript\\n// 기존 (변별력 낮음)\\n0.30 × retrievalScore + 0.25 × sessionContinued + 0.25 × toolSuccessRatio + 0.20 × !wasReasked\\n\\n// 개선 (prompt_count_after 활용)\\n0.40 × retrievalScore + 0.30 × promptNorm + 0.20 × toolSuccessRatio + 0.10 × sessionContinued\\n// promptNorm = min(promptCountAfter / 2, 1.0)\\n// 0턴→0.0, 1턴→0.5, 2턴이상→1.0\\n```\\n**완료** ✅\\n\\n---\\n\\n## 구현 순서\\n\\n```\\nWeek 1 (Critical Path) - 완료:\\n IMP-01: Trace projectHash 수정 ✅\\n IMP-02: Tool Observation 필터링 ✅\\n\\nWeek 2 (Quality) - 완료:\\n IMP-03: 세션 요약 신뢰성 ✅\\n IMP-04: Graduation repair CLI\\n IMP-05: Embedding 모델 모니터링\\n\\nWeek 2 (후속 실측 기반) - 완료:\\n IMP-01b: 대시보드 projectId 파라미터 수정 ✅\\n IMP-02b: Bash 임계값 800→2000 ✅\\n IMP-06b: Helpfulness 알고리즘 개선 ✅\\n\\nWeek 3 (Feedback Loop):\\n IMP-06: Helpfulness 피드백 루프 완성\\n IMP-07: 컨텍스트 포맷 개선\\n```\",\"originalFile\":\"# Spec: Memory Utilization Improvements\\n\\n## 목표\\n\\nf4d5c120 프로젝트 분석을 통해 확인된 메모리 시스템의 cascade failure를 해소하고,\\n저장된 메모리가 실제로 Claude 응답에 기여하는 비율을 높인다.\\n\\n**성공 기준**:\\n- Retrieval trace 기록률 > 95% (현재 ~0%)\\n- 메모리 Graduation L1+ 비율 > 10% (현재 0%)\\n- Tool Observation 중 저장 비율 < 30% (현재 100%)\\n- 세션 요약 생성률 > 80% (현재 5%)\\n\\n---\\n\\n## 개선 항목 (우선순위 순)\\n\\n---\\n\\n### IMP-01: Retrieval Trace 동기 기록\\n\\n**우선순위**: P0 (Blocker)\\n\\n**문제**:\\n- `adherence: true` 이벤트가 존재 → 검색은 실행됨\\n- 하지만 retrieval_traces 테이블에 단 1건만 기록\\n- 비동기 trace INSERT가 hook 프로세스 종료 전에 완료되지 않는 것으로 추정\\n\\n**원인 분석**:\\n```typescript\\n// user-prompt-submit.ts 추정 흐름\\nasync function hook() {\\n const memories = await retrieveMemories(query); // 검색 실행\\n await writeToStdout(memories); // 결과 출력 (hook 종료)\\n await recordTrace(memories); // ← 여기서 프로세스가 이미 종료됨\\n}\\n```\\n\\n**해결 방안**:\\n1. `recordTrace()`를 `retrieveMemories()` 직후, stdout 출력 전에 동기적으로 실행\\n2. better-sqlite3의 동기 API 사용 (이미 사용 중) → trace INSERT를 sync로 변경\\n3. trace 기록 실패 시 stderr에 경고 출력\\n\\n**변경 파일**:\\n- `src/hooks/user-prompt-submit.ts` — trace 기록 위치를 stdout 출력 전으로 이동\\n- `src/core/sqlite-event-store.ts` — `recordRetrievalTrace()`를 동기 메서드로 변경\\n\\n**검증**:\\n```sql\\n-- 개선 후: 세션당 retrieval_traces 수 확인\\nSELECT session_id, COUNT(*) as trace_count\\nFROM retrieval_traces\\nGROUP BY session_id\\nORDER BY trace_count DESC;\\n```\\n\\n---\\n\\n### IMP-02: Tool Observation 선택적 저장\\n\\n**우선순위**: P0\\n\\n**문제**:\\n- 저장 이벤트의 93%가 tool observation\\n- 대부분 다음 세션에서 재사용 가치가 없는 ephemeral 데이터\\n- FTS5 검색 코퍼스 비대화 → 검색 노이즈 증가\\n\\n**저장 가치 기준**:\\n\\n| Tool | 저장 조건 | 이유 |\\n|------|---------|------|\\n| Bash | 오류(exit_code≠0), 또는 중요 출력 키워드 포함 | 성공적인 단순 명령은 가치 낮음 |\\n| Write | 항상 저장 | 파일 생성/수정은 중요 컨텍스트 |\\n| Edit | 항상 저장 | 코드 변경은 추적 필요 |\\n| Read | 저장 안 함 (기본값) | 파일 내용은 재현 가능 |\\n| Glob | 저장 안 함 | 디렉토리 구조는 변하지 않음 |\\n| Grep | 결과 10줄 초과 시만 저장 | 대용량 검색 결과만 의미있음 |\\n| TodoRead/Write | 저장 안 함 | 이미 제외됨 |\\n\\n**중요 출력 키워드** (Bash 저장 트리거):\\n```\\nerror, Error, ERROR, failed, Failed, FAILED,\\nwarning, Warning, exception, Exception,\\ntest passed, test failed, coverage,\\nsuccessfully deployed, build complete\\n```\\n\\n**변경 파일**:\\n- `src/hooks/post-tool-use.ts` — 툴별 저장 조건 필터링 로직 추가\\n- `src/core/metadata-extractor.ts` — 중요도 점수 계산 함수 추가\\n\\n**예상 효과**:\\n- 저장 이벤트 수 60~70% 감소\\n- 검색 정밀도 향상\\n- DB/벡터 스토어 용량 절감\\n\\n---\\n\\n### IMP-03: 세션 요약 신뢰성 개선\\n\\n**우선순위**: P1\\n\\n**문제**:\\n- 40 세션 중 2건만 요약 (5%)\\n- Stop hook은 프로세스 강제 종료 시 실행되지 않음\\n- Session-start의 백필 로직이 요약을 생성하지 않고 있음\\n\\n**해결 방안**:\\n\\n**A. Session-start 백필에서 요약 생성 추가**:\\n```typescript\\n// session-start.ts\\nasync function backfillPreviousSession(prevSessionId: string) {\\n const events = await getSessionEvents(prevSessionId);\\n if (events.length > 3 && !hasSummary(prevSessionId)) {\\n const summary = await generateSummary(events);\\n await storeSummaryEvent(prevSessionId, summary);\\n }\\n}\\n```\\n\\n**B. LLM 없이 규칙 기반 요약 생성** (빠른 실행):\\n```\\n요약 템플릿:\\n\\\"[날짜] [N]턴 세션. 주요 작업: [user_prompt 첫\n... [977 characters truncated] ...\n니터링\\n\\n**우선순위**: P1\\n\\n**문제**:\\n- \\\"Unknown model class 'eurobert'\\\" 오류 반복\\n- \\\"RotaryEmbedding node execution errors\\\" 반복\\n- 사용자에게 오류가 노출되지만 자동 대처 없음\\n\\n**해결 방안**:\\n\\n**A. 모델 헬스체크 강화**:\\n```typescript\\n// semantic-daemon.ts\\nasync function checkModelHealth(): Promise<boolean> {\\n try {\\n await embed(\\\"test query\\\");\\n return true;\\n } catch (e) {\\n logger.warn('Primary model failed, switching to fallback');\\n await switchToFallbackModel();\\n return false;\\n }\\n}\\n```\\n\\n**B. 대시보드에 모델 상태 표시**:\\n- 현재 사용 중인 모델명\\n- 최근 임베딩 성공률\\n- 오류 로그 최근 5건\\n\\n**C. 임베딩 오류 시 키워드 전용 모드 자동 전환**:\\n- `CLAUDE_MEMORY_RETRIEVAL_MODE=keyword` 임시 설정\\n- 사용자에게 `<system>` 메시지로 알림\\n\\n**변경 파일**:\\n- `src/hooks/semantic-daemon.ts` — 헬스체크 및 자동 폴백 강화\\n- `src/server/api/stats.ts` — 모델 상태 지표 추가\\n\\n---\\n\\n### IMP-06: Helpfulness 피드백 루프\\n\\n**우선순위**: P2\\n\\n**문제**:\\n- `sessions_helpfulness` 테이블 존재하나 데이터 없음\\n- 검색된 메모리가 실제로 유용했는지 측정 불가\\n- 유용하지 않은 메모리가 계속 상위에 노출될 수 있음\\n\\n**해결 방안**:\\n\\n**A. 휴리스틱 기반 자동 helpfulness 평가**:\\n```\\nStop hook에서:\\n1. 이번 세션에서 검색된 eventId 목록 로드\\n2. agent_response에서 검색된 메모리의 canonicalKey가 언급되었는지 확인\\n3. 언급된 경우 helpfulness = 0.8, 아닌 경우 0.2로 기록\\n```\\n\\n**B. Retrieval Score 가중치에 helpfulness 반영**:\\n```typescript\\n// matcher.ts\\nscore = 0.35 × semanticScore +\\n 0.25 × ftsScore +\\n 0.20 × recencyBonus +\\n 0.10 × statusMultiplier +\\n 0.10 × helpfulnessScore; // ← 신규 추가\\n```\\n\\n**C. 낮은 helpfulness 이벤트 강등**:\\n- 3회 이상 검색 후 helpfulness 평균 < 0.3이면 min_score 요건 상향\\n\\n**변경 파일**:\\n- `src/hooks/stop.ts` — 자동 helpfulness 평가 로직 추가\\n- `src/core/matcher.ts` — helpfulness 가중치 추가\\n- `src/core/graduation.ts` — helpfulness 기반 강등 규칙 추가\\n\\n---\\n\\n### IMP-07: 검색 결과 컨텍스트 포맷 개선\\n\\n**우선순위**: P2\\n\\n**문제**:\\n- 검색된 메모리가 Claude에게 전달될 때의 포맷 불명확\\n- 메모리 출처(세션 날짜, 프로젝트)가 표시되지 않을 수 있음\\n- 100% 선택률 → 품질 필터링 없이 모두 주입\\n\\n**해결 방안**:\\n\\n**A. 메모리 컨텍스트 포맷 구조화**:\\n```\\n<memory source=\\\"2026-02-25\\\" session=\\\"5ef326be\\\" confidence=\\\"0.87\\\">\\n [쇼핑 어시스턴트 LLM function call 구현 중]\\n 이전에 generalize_with_llm() 함수를 사용해서 상품 추천 로직을 개선했음.\\n 결과: 응답 속도 40% 향상.\\n</memory>\\n```\\n\\n**B. 신뢰도 기준 필터링 강화**:\\n- `high` confidence만 자동 주입 (현재 ≥0.92)\\n- `suggested` confidence는 optional hint로 분리\\n- 100% 선택률 → 선택적 포함으로 변경\\n\\n**변경 파일**:\\n- `src/hooks/user-prompt-submit.ts` — 컨텍스트 포맷 템플릿 개선\\n- `src/core/retriever.ts` — suggested confidence 분리 출력\\n\\n---\\n\\n## 구현 순서\\n\\n```\\nWeek 1 (Critical Path):\\n IMP-01: Trace 동기 기록 → 즉시 측정 가능\\n IMP-02: Tool Observation 필터링 → 노이즈 감소\\n\\nWeek 2 (Quality):\\n IMP-03: 세션 요약 신뢰성\\n IMP-04: Graduation repair CLI\\n IMP-05: Embedding 모델 모니터링\\n\\nWeek 3 (Feedback Loop):\\n IMP-06: Helpfulness 피드백\\n IMP-07: 컨텍스트 포맷 개선\\n```\\n\\n---\\n\\n## 리스크\\n\\n| 리스크 | 가능성 | 영향 | 대응 |\\n|--------|--------|------|------|\\n| IMP-02로 중요 tool observation 누락 | 중 | 중 | 키워드 필터 화이트리스트 설정 |\\n| IMP-04 graduation repair로 L0 과잉 승격 | 저 | 중 | dry-run 모드 먼저 실행 |\\n| IMP-01 sync 변환으로 hook 응답 지연 | 저 | 저 | trace INSERT는 < 1ms (SQLite sync) |\\n\",\"structuredPatch\":[{\"oldStart\":280,\"oldLines\":20,\"newStart\":280,\"newLines\":73,\"lines\":[\" \",\" ---\",\" \",\"+## 2차 개선 (실측 데이터 기반 후속)\",\"+\",\"+### IMP-01b: 대시보드 API projectId 파라미터 불일치 수정\",\"+\",\"+**문제**: 대시보드가 `?projectId=f4d5c120`으로 쿼리하는데 `getServiceFromQuery()`는 `?project=`만 읽음 → 항상 글로벌 서비스 반환\",\"+\",\"+**수정**: `src/server/api/utils.ts`\",\"+```typescript\",\"+const project = c.req.query('project') || c.req.query('projectId');\",\"+```\",\"+**완료** ✅\",\"+\",\"+---\",\"+\",\"+### IMP-02b: Bash 출력 임계값 800 → 2000\",\"+\",\"+**문제**: 800자 임계값이 너무 낮아 일반적인 bash 출력 대부분이 저장됨\",\"+\",\"+**수정**: `src/hooks/post-tool-use.ts`\",\"+```typescript\",\"+return output.trim().length > 2000;\",\"+```\",\"+**완료** ✅\",\"+\",\"+---\",\"+\",\"+### IMP-06b: Helpfulness 평가 알고리즘 변별력 개선\",\"+\",\"+**문제**: `was_reasked` 로직이 자연스러운 대화 연속을 penalty로 처리 → 모든 항목 0.62~0.68 수렴\",\"+\",\"+**원인**:\",\"+- 쇼핑 어시스턴트처럼 같은 주제를 이어가면 `was_reasked=1` 항상 발생\",\"+- 가중치: `0.20 × (wasReasked ? 0 : 1)` → 모든 항목에 -0.20 penalty\",\"+\",\"+**수정**: `src/core/sqlite-event-store.ts`\",\"+```typescript\",\"+// 기존 (변별력 낮음)\",\"+0.30 × retrievalScore + 0.25 × sessionContinued + 0.25 × toolSuccessRatio + 0.20 × !wasReasked\",\"+\",\"+// 개선 (prompt_count_after 활용)\",\"+0.40 × retrievalScore + 0.30 × promptNorm + 0.20 × toolSuccessRatio + 0.10 × sessionContinued\",\"+// promptNorm = min(promptCountAfter / 2, 1.0)\",\"+// 0턴→0.0, 1턴→0.5, 2턴이상→1.0\",\"+```\",\"+**완료** ✅\",\"+\",\"+---\",\"+\",\" ## 구현 순서\",\" \",\" ```\",\"-Week 1 (Critical Path):\",\"- IMP-01: Trace 동기 기록 → 즉시 측정 가능\",\"- IMP-02: Tool Observation 필터링 → 노이즈 감소\",\"+Week 1 (Critical Path) - 완료:\",\"+ IMP-01: Trace projectHash 수정 ✅\",\"+ IMP-02: Tool Observation 필터링 ✅\",\" \",\"-Week 2 (Quality):\",\"- IMP-03: 세션 요약 신뢰성\",\"+Week 2 (Quality) - 완료:\",\"+ IMP-03: 세션 요약 신뢰성 ✅\",\" IMP-04: Graduation repair CLI\",\" IMP-05: Embedding 모델 모니터링\",\" \",\"+Week 2 (후속 실측 기반) - 완료:\",\"+ IMP-01b: 대시보드 projectId 파라미터 수정 ✅\",\"+ IMP-02b: Bash 임계값 800→2000 ✅\",\"+ IMP-06b: Helpfulness 알고리즘 개선 ✅\",\"+\",\" Week 3 (Feedback Loop):\",\"- IMP-06: Helpfulness 피드백\",\"+ IMP-06: Helpfulness 피드백 루프 완성\",\" IMP-07: 컨텍스트 포맷 개선\",\" ```\",\" \"]}],\"userModified\":false,\"replaceAll\":false}","durationMs":0,"success":true,"metadata":{"filePath":"/home/walter/workspace/opensource/claude-memory-layer/specs/memory-utilization-improvements/spec.md","fileType":"markdown","turnId":"e42caf45-de79-4d73-a7bc-59954990b764"}}
26
+ ## 2026-03-04T15:01:18.982Z | c1c31b32-0c38-425e-95a4-1cdbcedbb274
27
+ - type: tool_observation
28
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
29
+ {"toolName":"TaskUpdate","toolInput":{"taskId":"3","status":"completed"},"toolOutput":"{\"success\":true,\"taskId\":\"3\",\"updatedFields\":[\"status\"],\"statusChange\":{\"from\":\"in_progress\",\"to\":\"completed\"}}","durationMs":0,"success":true,"metadata":{"turnId":"e42caf45-de79-4d73-a7bc-59954990b764"}}
@@ -439,4 +439,196 @@ https://be2f-222-112-203-51.ngrok-free.app/api/stats?project=f4d5c120 이 프로
439
439
  ## 2026-03-04T10:22:49.182Z | 139c9f43-a92c-4568-ba40-f810ee2e2b20
440
440
  - type: user_prompt
441
441
  - session: 71462bbf-6d77-4cc7-b8d1-f126ac3dc034
442
- 장기적으로 개선 방향이 필요해. 사용자가 명시적으로 뭔가를 실행하지 않더라도, total events 가 쌓이면서 중간 중간에 vector embedding 추출이 자동으로 되게 하고 싶어
442
+ 장기적으로 개선 방향이 필요해. 사용자가 명시적으로 뭔가를 실행하지 않더라도, total events 가 쌓이면서 중간 중간에 vector embedding 추출이 자동으로 되게 하고 싶어
443
+ ## 2026-03-04T10:57:07.241Z | 29b315b9-fc70-488d-a8d1-b1274b71a079
444
+ - type: user_prompt
445
+ - session: e3c1fe2b-c018-4532-86d5-7cdd467408f0
446
+ npm install -g claude-memory-layer@1.0.24
447
+ npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
448
+ npm warn deprecated @npmcli/move-file@2.0.1: This functionality has been moved to @npmcli/fs
449
+ npm warn deprecated npmlog@5.0.1: This package is no longer supported.
450
+ npm warn deprecated npmlog@6.0.2: This package is no longer supported.
451
+ npm warn deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
452
+ npm warn deprecated glob@8.1.0: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
453
+ npm warn deprecated glob@7.2.3: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
454
+ npm warn deprecated prebuild-install@7.1.3: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
455
+ npm warn deprecated are-we-there-yet@2.0.0: This package is no longer supported.
456
+ npm warn deprecated are-we-there-yet@3.0.1: This package is no longer supported.
457
+ npm warn deprecated gauge@3.0.2: This package is no longer supported.
458
+ npm warn deprecated boolean@3.2.0: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
459
+ npm warn deprecated gauge@4.0.4: This package is no longer supported.
460
+ npm warn deprecated tar@6.2.1: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
461
+ npm warn deprecated node-domexception@1.0.0: Use your platform's native DOMException instead
462
+ npm error code 1
463
+ npm error path /Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb
464
+ npm error command failed
465
+ npm error command sh -c node-pre-gyp install --fallback-to-build
466
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb_node.o
467
+ npm error CXX(target) Release/obj.target/duckdb/src/database.o
468
+ npm error CXX(target) Release/obj.target/duckdb/src/data_chunk.o
469
+ npm error CXX(target) Release/obj.target/duckdb/src/connection.o
470
+ npm error CXX(target) Release/obj.target/duckdb/src/statement.o
471
+ npm error CXX(target) Release/obj.target/duckdb/src/utils.o
472
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_catalog.o
473
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_catalog_catalog_entry.o
474
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_catalog_catalog_entry_dependency.o
475
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_catalog_default.o
476
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_adbc.o
477
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_adbc_nanoarrow.o
478
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common.o
479
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_arrow_appender.o
480
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_arrow.o
481
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_crypto.o
482
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_enums.o
483
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_exception.o
484
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_operator.o
485
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_progress_bar.o
486
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_row_operations.o
487
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_serializer.o
488
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_sort.o
489
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_types.o
490
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_types_column.o
491
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_types_row.o
492
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_common_value_operations.o
493
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/boolean_operators.o
494
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/comparison_operators.o
495
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/generators.o
496
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/is_distinct_from.o
497
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/null_operations.o
498
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/numeric_inplace_operators.o
499
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/vector_cast.o
500
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/vector_copy.o
501
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/vector_hash.o
502
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/src/common/vector_operations/vector_storage.o
503
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_core_functions_aggregate_algebraic.o
504
+ npm error CXX(target) Release/obj.target/duckdb/src/duckdb/ub_src_core_functions_aggregate_distributive.o
505
+ npm error Failed to execute '/Users/justin/.nvm/versions/node/v24.13.1/bin/node /Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js build --fallback-to-build --module=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/lib/binding/duckdb.node --module_name=duckdb --module_path=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/lib/binding --napi_version=10 --node_abi_napi=napi --napi_build_version=0 --node_napi_label=node-v137' (1)
506
+ npm error node-pre-gyp info it worked if it ends with ok
507
+ npm error node-pre-gyp info using node-pre-gyp@1.0.11
508
+ npm error node-pre-gyp info using node@24.13.1 | darwin | arm64
509
+ npm error (node:80755) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
510
+ npm error (Use `node --trace-deprecation ...` to show where the warning was created)
511
+ npm error (node:80755) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
512
+ npm error node-pre-gyp info check checked for "/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/lib/binding/duckdb.node" (not found)
513
+ npm error node-pre-gyp http GET https://npm.duckdb.org/duckdb/duckdb-v0.10.2-node-v137-darwin-arm64.tar.gz
514
+ npm error node-pre-gyp ERR! install response status 404 Not Found on https://npm.duckdb.org/duckdb/duckdb-v0.10.2-node-v137-darwin-arm64.tar.gz
515
+ npm error node-pre-gyp WARN Pre-built binaries not installable for duckdb@0.10.2 and node@24.13.1 (node-v137 ABI, unknown) (falling back to source compile with node-gyp)
516
+ npm error node-pre-gyp WARN Hit error response status 404 Not Found on https://npm.duckdb.org/duckdb/duckdb-v0.10.2-node-v137-darwin-arm64.tar.gz
517
+ npm error gyp info it worked if it ends with ok
518
+ npm error gyp info using node-gyp@12.1.0
519
+ npm error gyp info using node@24.13.1 | darwin | arm64
520
+ npm error gyp info ok
521
+ npm error gyp info it worked if it ends with ok
522
+ npm error gyp info using node-gyp@12.1.0
523
+ npm error gyp info using node@24.13.1 | darwin | arm64
524
+ npm error (node:81050) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
525
+ npm error (Use `node --trace-deprecation ...` to show where the warning was created)
526
+ npm error gyp info find Python using Python version 3.9.6 found at "/Library/Developer/CommandLineTools/usr/bin/python3"
527
+ npm error gyp info spawn /Library/Developer/CommandLineTools/usr/bin/python3
528
+ npm error gyp info spawn args [
529
+ npm error gyp info spawn args '/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py',
530
+ npm error gyp info spawn args 'binding.gyp',
531
+ npm error gyp info spawn args '-f',
532
+ npm error gyp info spawn args 'make',
533
+ npm error gyp info spawn args '-I',
534
+ npm error gyp info spawn args '/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/build/config.gypi',
535
+ npm error gyp info spawn args '-I',
536
+ npm error gyp info spawn args '/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/npm/node_modules/node-gyp/addon.gypi',
537
+ npm error gyp info spawn args '-I',
538
+ npm error gyp info spawn args '/Users/justin/Library/Caches/node-gyp/24.13.1/include/node/common.gypi',
539
+ npm error gyp info spawn args '-Dlibrary=shared_library',
540
+ npm error gyp info spawn args '-Dvisibility=default',
541
+ npm error gyp info spawn args '-Dnode_root_dir=/Users/justin/Library/Caches/node-gyp/24.13.1',
542
+ npm error gyp info spawn args '-Dnode_gyp_dir=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/npm/node_modules/node-gyp',
543
+ npm error gyp info spawn args '-Dnode_lib_file=/Users/justin/Library/Caches/node-gyp/24.13.1/<(target_arch)/node.lib',
544
+ npm error gyp info spawn args '-Dmodule_root_dir=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb',
545
+ npm error gyp info spawn args '-Dnode_engine=v8',
546
+ npm error gyp info spawn args '--depth=.',
547
+ npm error gyp info spawn args '--no-parallel',
548
+ npm error gyp info spawn args '--generator-output',
549
+ npm error gyp info spawn args 'build',
550
+ npm error gyp info spawn args '-Goutput_dir=.'
551
+ npm error gyp info spawn args ]
552
+ npm error gyp info ok
553
+ npm error gyp info it worked if it ends with ok
554
+ npm error gyp info using node-gyp@12.1.0
555
+ npm error gyp info using node@24.13.1 | darwin | arm64
556
+ npm error gyp info spawn make
557
+ npm error gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ]
558
+ npm error In file included from ../src/duckdb/ub_src_core_functions_aggregate_distributive.cpp:3:
559
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/arg_min_max.cpp:167:20: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
560
+ npm error 167 | STATE::template ReadValue(finalize_data.result, state.arg, target);
561
+ npm error | ^
562
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/arg_min_max.cpp:249:20: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
563
+ npm error 249 | STATE::template AssignValue(target.value, source.value);
564
+ npm error | ^
565
+ npm error In file included from ../src/duckdb/ub_src_core_functions_aggregate_distributive.cpp:5:
566
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/bitagg.cpp:55:17: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
567
+ npm error 55 | OP::template Assign(state, input);
568
+ npm error | ^
569
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/bitagg.cpp:58:17: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
570
+ npm error 58 | OP::template Execute(state, input);
571
+ npm error | ^
572
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/bitagg.cpp:81:17: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
573
+ npm error 81 | OP::template Assign(target, source.value);
574
+ npm error | ^
575
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/bitagg.cpp:84:17: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
576
+ npm error 84 | OP::template Execute(target, source.value);
577
+ npm error | ^
578
+ npm error In file included from ../src/duckdb/ub_src_core_functions_aggregate_distributive.cpp:15:
579
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/minmax.cpp:490:18: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
580
+ npm error 490 | OP::template Execute(state, input, i, count);
581
+ npm error | ^
582
+ npm error ../src/duckdb/src/core_functions/aggregate/distributive/minmax.cpp:502:17: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
583
+ npm error 502 | OP::template Execute(target, *source.value, 0, 1);
584
+ npm error | ^
585
+ npm error 8 errors generated.
586
+ npm error make: *** [Release/obj.target/duckdb/src/duckdb/ub_src_core_functions_aggregate_distributive.o] Error 1
587
+ npm error gyp ERR! build error
588
+ npm error gyp ERR! stack Error: `make` failed with exit code: 2
589
+ npm error gyp ERR! stack at ChildProcess.<anonymous> (/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:219:23)
590
+ npm error gyp ERR! System Darwin 25.2.0
591
+ npm error gyp ERR! command "/Users/justin/.nvm/versions/node/v24.13.1/bin/node" "/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "build" "--fallback-to-build" "--module=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/lib/binding/duckdb.node" "--module_name=duckdb" "--module_path=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/lib/binding" "--napi_version=10" "--node_abi_napi=napi" "--napi_build_version=0" "--node_napi_label=node-v137"
592
+ npm error gyp ERR! cwd /Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb
593
+ npm error gyp ERR! node -v v24.13.1
594
+ npm error gyp ERR! node-gyp -v v12.1.0
595
+ npm error gyp ERR! not ok
596
+ npm error node-pre-gyp ERR! build error
597
+ npm error node-pre-gyp ERR! stack Error: Failed to execute '/Users/justin/.nvm/versions/node/v24.13.1/bin/node /Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js build --fallback-to-build --module=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/lib/binding/duckdb.node --module_name=duckdb --module_path=/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb/lib/binding --napi_version=10 --node_abi_napi=napi --napi_build_version=0 --node_napi_label=node-v137' (1)
598
+ npm error node-pre-gyp ERR! stack at ChildProcess.<anonymous> (/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/@mapbox/node-pre-gyp/lib/util/compile.js:89:23)
599
+ npm error node-pre-gyp ERR! stack at ChildProcess.emit (node:events:508:28)
600
+ npm error node-pre-gyp ERR! stack at maybeClose (node:internal/child_process:1101:16)
601
+ npm error node-pre-gyp ERR! stack at ChildProcess._handle.onexit (node:internal/child_process:305:5)
602
+ npm error node-pre-gyp ERR! System Darwin 25.2.0
603
+ npm error node-pre-gyp ERR! command "/Users/justin/.nvm/versions/node/v24.13.1/bin/node" "/Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/.bin/node-pre-gyp" "install" "--fallback-to-build"
604
+ npm error node-pre-gyp ERR! cwd /Users/justin/.nvm/versions/node/v24.13.1/lib/node_modules/claude-memory-layer/node_modules/duckdb
605
+ npm error node-pre-gyp ERR! node -v v24.13.1
606
+ npm error node-pre-gyp ERR! node-pre-gyp -v v1.0.11
607
+ npm error node-pre-gyp ERR! not ok
608
+ npm error A complete log of this run can be found in: /Users/justin/.npm/_logs/2026-03-04T10_53_21_351Z-debug-0.log 배포된 것을 다른 서버에서 설치해서 실행 했을때 위와 같은 에러가 나왔어
609
+ 어떻게 대응하면 좋을지 specs 폴더 아래에 기능 폴더를 만들고 spec.md, plan.md, context.md 를 만들어서 계획을 업데이트 해줘
610
+
611
+ ## 2026-03-04T10:59:50.819Z | ebe65907-70ed-4d31-9d78-632c39e6aefa
612
+ - type: user_prompt
613
+ - session: e3c1fe2b-c018-4532-86d5-7cdd467408f0
614
+ duckdb 를 꼭 쓸 필요가 있는거야? 그냥 기존 SQLite/LanceDB 로 다 되는건 아니야?
615
+ ## 2026-03-04T12:36:55.938Z | 3d84b13a-68ea-4b5c-9dad-dba054388715
616
+ - type: user_prompt
617
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
618
+ https://be2f-222-112-203-51.ngrok-free.app/ dashboard 결과를 보고 얼마나 잘 memory 결과가 도움이 되는지 보고, 그동안 기록을 보고 (project - f4d5c120 ) memory 가 더 잘 사용되기 위해서 어떤 부분을 개선하면 좋을지 잘 생각해서 개선안을 설계 해줘 specs 폴더 아래에 기능 폴더를 만들고 spec.md, plan.md, context.md 를 만들어서 계획을 업데이트 해줘
619
+ ## 2026-03-04T12:40:46.256Z | 51f66052-ec65-4868-9346-9ee094fb2433
620
+ - type: user_prompt
621
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
622
+ shopping-assistant 프로젝트 key : f4d5c120 에 대해서 분석한 결과로 다시 개선안 만들어줘
623
+ ## 2026-03-04T12:58:55.967Z | 583b611a-b131-438a-a6df-b5193882bb9d
624
+ - type: user_prompt
625
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
626
+ 방금 재시작 해서 한번 실행 해봤는데, 위 프로젝트에서 잘 적용 되었는지 확인해줘
627
+ ## 2026-03-04T14:56:16.794Z | 42b691d8-ff31-467b-bfd5-9618a350197e
628
+ - type: user_prompt
629
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
630
+ shopping-assistant 프로젝트 key : f4d5c120 에 대해서 위 개선 사항 적용해서 테스트 해보고 있는 최신 데이터 기준으로 잘 활용 되고 있는지 봐줘
631
+ ## 2026-03-04T14:58:28.655Z | e8428936-9bf8-4b06-b421-9747b1ae2985
632
+ - type: user_prompt
633
+ - session: 2fe5b2e5-83ea-4778-b09b-5264c550ea86
634
+ 개선할 부분 개선해줘. 관련된 specs 안에 기능이 있으면 그 안에 있는 문서에 계획을 업데이트 해주고 없으면 specs 폴더 아래에 기능 폴더를 만들고 spec.md, plan.md, context.md 를 만들어서 계획을 업데이트 해줘
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-layer",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "Claude Code plugin that learns from conversations to provide personalized assistance",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -41,7 +41,6 @@
41
41
  "@huggingface/transformers": "^3.8.1",
42
42
  "better-sqlite3": "^12.6.2",
43
43
  "commander": "^12.0.0",
44
- "duckdb": "^0.10.0",
45
44
  "hono": "^4.0.0",
46
45
  "mongodb": "^6.14.0",
47
46
  "zod": "^3.22.0"
@@ -0,0 +1,145 @@
1
+ # Context: Memory Utilization Improvements
2
+
3
+ ## 분석 대상
4
+
5
+ - **프로젝트**: shopping_assistant
6
+ - **프로젝트 키**: f4d5c120
7
+ - **분석 일자**: 2026-03-04
8
+ - **데이터 기간**: 2026-02-25 ~ 2026-03-04
9
+
10
+ ---
11
+
12
+ ## 실제 측정 데이터
13
+
14
+ ### 전체 지표
15
+
16
+ | 지표 | 값 | 상태 |
17
+ |------|-----|------|
18
+ | 총 이벤트 수 | 1,587 | - |
19
+ | L0 이벤트 | **1,587 (100%)** | 🔴 심각 |
20
+ | L1+ 이벤트 | **0** | 🔴 심각 |
21
+ | 총 세션 수 | 40 (f4d5c120) / 55 (전체) | - |
22
+ | 벡터 수 | 1,586 | - |
23
+ | Retrieval Trace | **1건** | 🔴 심각 |
24
+ | 세션 요약 | **2건** | 🔴 심각 |
25
+ | 검색 후보 선택률 | 100% (5/5) | ⚠️ 경고 |
26
+
27
+ ### 이벤트 유형 분포
28
+
29
+ ```
30
+ Tool Observation: 1,475건 (92.9%) ← 노이즈 지배적
31
+ User Prompt: 94건 (5.9%)
32
+ Agent Response: 16건 (1.0%)
33
+ Session Summary: 2건 (0.1%)
34
+ ```
35
+
36
+ ### 활동 타임라인
37
+
38
+ - 2026-02-25: 521건 (피크)
39
+ - 2026-02-26: 478건 (피크)
40
+ - 2026-03-04: 40건 (당일)
41
+ - 7일 누적: 1,039건
42
+
43
+ ### 주요 세션 샘플
44
+
45
+ | 세션 ID | 이벤트 수 | 지속 시간 | 날짜 |
46
+ |---------|---------|---------|------|
47
+ | 5ef326be | 82 | 11분 | 02-25 |
48
+ | 790b37f6 | 53 | 2시간 | 02-25 |
49
+ | 7302c0af | 69 | - | 02-25 |
50
+ | 49653e19 | 31 | 24분 | 03-04 |
51
+
52
+ ---
53
+
54
+ ## 핵심 발견: 실제 증거
55
+
56
+ ### 발견 1: 검색은 실행되나 Trace가 기록되지 않음
57
+
58
+ User Prompt 이벤트의 metadata를 보면:
59
+
60
+ ```json
61
+ // Turn 1 (첫 번째 턴)
62
+ { "adherence": true, "reason": "first-turn" }
63
+
64
+ // Turn 3
65
+ { "adherence": false, "reason": "skip" }
66
+
67
+ // Turn 4
68
+ { "adherence": false, "reason": "skip" }
69
+
70
+ // Turn 5
71
+ { "adherence": true, "reason": "interval-based" }
72
+ ```
73
+
74
+ **결론**: `adherence: true`인 경우 검색이 실행됨 → 검색 자체는 작동함.
75
+ 그러나 DB에 기록된 retrieval trace는 단 1건.
76
+ **검색 실행 ≠ Trace 기록 성공** → trace INSERT가 실패하거나 비동기 타이밍 문제.
77
+
78
+ ### 발견 2: Embedding 모델 반복 오류
79
+
80
+ 세션 로그에서 확인된 오류들:
81
+
82
+ ```
83
+ "RotaryEmbedding node execution errors"
84
+ "Unknown model class 'eurobert'"
85
+ "8389 tokens exceeding limits"
86
+ "Processing embeddings... ONNX Runtime errors"
87
+ ```
88
+
89
+ 전체 이벤트 1,587건 중 벡터 1,586개 생성 → 거의 임베딩은 됨.
90
+ 하지만 모델 오류가 지속적으로 발생 중이며 사용자에게 오류가 노출되고 있음.
91
+
92
+ ### 발견 3: Cascade Failure 구조
93
+
94
+ ```
95
+ Trace 기록 실패
96
+
97
+ access_count 미증가
98
+
99
+ Graduation 미발생 (L0 고착)
100
+
101
+ 고품질 메모리 없음
102
+
103
+ 검색 품질 저하
104
+
105
+ Helpfulness 측정 불가
106
+
107
+ 개선 피드백 루프 단절
108
+ ```
109
+
110
+ ### 발견 4: Tool Observation 과잉 저장
111
+
112
+ 93%가 tool observation이며 내용은 주로:
113
+ - 일상적인 bash 명령 결과 (ls, grep 출력)
114
+ - 파일 읽기 내용 (반복적)
115
+ - Glob 검색 결과
116
+
117
+ 이 데이터들은 다음 세션에서 재사용 가치가 낮음.
118
+ 오히려 FTS5 검색 코퍼스를 비대화시켜 검색 노이즈 증가.
119
+
120
+ ### 발견 5: Stop Hook 미작동
121
+
122
+ 40 세션 중 세션 요약 2건 → 95%의 세션에서 Stop Hook이 발생하지 않음.
123
+ Session-start의 백필 메커니즘도 요약을 생성하지 못하고 있음.
124
+
125
+ ---
126
+
127
+ ## 시스템 환경
128
+
129
+ - **OS**: Linux 5.15 (Ubuntu)
130
+ - **런타임**: Node.js (TSX)
131
+ - **DB**: SQLite (better-sqlite3)
132
+ - **벡터 DB**: LanceDB
133
+ - **임베딩 모델**: jinaai/jina-embeddings-v5-text-nano-text-matching
134
+ - **대체 모델**: onnx-community/embeddinggemma-300m-ONNX
135
+ - **Heap**: 116MB / 135MB (86% 사용)
136
+
137
+ ---
138
+
139
+ ## 관련 기존 스펙
140
+
141
+ - `specs/20260207-dashboard-upgrade/` - 대시보드 개선
142
+ - `specs/vector-outbox-v2/` - 벡터 임베딩 파이프라인
143
+ - `specs/endless-mode/` - 세션 연속성
144
+ - `specs/entity-edge-model/` - 엔티티 추적
145
+ - `specs/selective-tool-observation/` - Tool observation 선택적 저장 (부분 설계됨)