@exaudeus/memory-mcp 0.1.0 → 1.0.0
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.
- package/package.json +2 -2
- package/dist/__tests__/clock-and-validators.test.d.ts +0 -1
- package/dist/__tests__/clock-and-validators.test.js +0 -237
- package/dist/__tests__/config-manager.test.d.ts +0 -1
- package/dist/__tests__/config-manager.test.js +0 -142
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -236
- package/dist/__tests__/crash-journal.test.d.ts +0 -1
- package/dist/__tests__/crash-journal.test.js +0 -203
- package/dist/__tests__/e2e.test.d.ts +0 -1
- package/dist/__tests__/e2e.test.js +0 -788
- package/dist/__tests__/ephemeral-benchmark.test.d.ts +0 -1
- package/dist/__tests__/ephemeral-benchmark.test.js +0 -651
- package/dist/__tests__/ephemeral.test.d.ts +0 -1
- package/dist/__tests__/ephemeral.test.js +0 -435
- package/dist/__tests__/git-service.test.d.ts +0 -1
- package/dist/__tests__/git-service.test.js +0 -43
- package/dist/__tests__/normalize.test.d.ts +0 -1
- package/dist/__tests__/normalize.test.js +0 -161
- package/dist/__tests__/store.test.d.ts +0 -1
- package/dist/__tests__/store.test.js +0 -1153
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
// Tests for crash-journal.ts — crash report lifecycle: build, write, read, clear, format.
|
|
2
|
-
// Uses real disk I/O with isolated temp directories — no mocks.
|
|
3
|
-
import { describe, it, afterEach } from 'node:test';
|
|
4
|
-
import assert from 'node:assert';
|
|
5
|
-
import { promises as fs } from 'fs';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import os from 'os';
|
|
8
|
-
import { buildCrashReport, writeCrashReport, readLatestCrash, readCrashHistory, clearLatestCrash, formatCrashReport, formatCrashSummary, markServerStarted, } from '../crash-journal.js';
|
|
9
|
-
// The crash journal writes to ~/.memory-mcp/crashes/ — we need to isolate tests.
|
|
10
|
-
// We'll monkey-patch the module's internal CRASH_DIR by writing to a temp dir
|
|
11
|
-
// and reading back. Since the module uses hardcoded paths, we test the public
|
|
12
|
-
// API behaviors: buildCrashReport (pure), format functions (pure), and
|
|
13
|
-
// read/write lifecycle (I/O — uses real paths, so we clean up carefully).
|
|
14
|
-
describe('buildCrashReport', () => {
|
|
15
|
-
it('builds a report from an Error', () => {
|
|
16
|
-
markServerStarted();
|
|
17
|
-
const error = new Error('Test failure');
|
|
18
|
-
const context = {
|
|
19
|
-
phase: 'running',
|
|
20
|
-
lastToolCall: 'memory_store',
|
|
21
|
-
configSource: 'file',
|
|
22
|
-
lobeCount: 2,
|
|
23
|
-
};
|
|
24
|
-
const report = buildCrashReport(error, 'uncaught-exception', context);
|
|
25
|
-
assert.strictEqual(report.error, 'Test failure');
|
|
26
|
-
assert.ok(report.stack?.includes('Test failure'), 'Should include stack trace');
|
|
27
|
-
assert.strictEqual(report.type, 'uncaught-exception');
|
|
28
|
-
assert.strictEqual(report.context.phase, 'running');
|
|
29
|
-
assert.strictEqual(report.context.lastToolCall, 'memory_store');
|
|
30
|
-
assert.ok(report.timestamp, 'Should have ISO timestamp');
|
|
31
|
-
assert.ok(report.pid > 0, 'Should have process ID');
|
|
32
|
-
assert.ok(Array.isArray(report.recovery), 'Should have recovery steps');
|
|
33
|
-
assert.ok(report.recovery.length > 0, 'Should have at least one recovery step');
|
|
34
|
-
});
|
|
35
|
-
it('builds a report from a non-Error value', () => {
|
|
36
|
-
markServerStarted();
|
|
37
|
-
const report = buildCrashReport('string error', 'unknown', { phase: 'startup' });
|
|
38
|
-
assert.strictEqual(report.error, 'string error');
|
|
39
|
-
assert.strictEqual(report.stack, undefined, 'Non-Error has no stack');
|
|
40
|
-
assert.strictEqual(report.type, 'unknown');
|
|
41
|
-
});
|
|
42
|
-
it('includes server uptime', () => {
|
|
43
|
-
markServerStarted();
|
|
44
|
-
const report = buildCrashReport(new Error('test'), 'unknown', { phase: 'running' });
|
|
45
|
-
assert.ok(report.serverUptime >= 0, 'Uptime should be non-negative');
|
|
46
|
-
});
|
|
47
|
-
it('generates recovery steps for startup-failure', () => {
|
|
48
|
-
const report = buildCrashReport(new Error('Cannot read memory-config.json'), 'startup-failure', { phase: 'startup', configSource: 'file' });
|
|
49
|
-
assert.ok(report.recovery.some(s => s.includes('memory-config.json')), 'Should mention config file in recovery');
|
|
50
|
-
});
|
|
51
|
-
it('generates recovery steps for lobe-init-failure', () => {
|
|
52
|
-
const report = buildCrashReport(new Error('ENOENT: no such directory'), 'lobe-init-failure', { phase: 'startup', activeLobe: 'my-repo' });
|
|
53
|
-
assert.ok(report.recovery.some(s => s.includes('my-repo')), 'Should mention the failed lobe');
|
|
54
|
-
});
|
|
55
|
-
it('generates recovery steps for transport-error', () => {
|
|
56
|
-
const report = buildCrashReport(new Error('pipe broken'), 'transport-error', { phase: 'running' });
|
|
57
|
-
assert.ok(report.recovery.some(s => s.includes('toggle') || s.includes('Toggle')), 'Should suggest toggling MCP');
|
|
58
|
-
});
|
|
59
|
-
it('detects disk-full errors in recovery', () => {
|
|
60
|
-
const report = buildCrashReport(new Error('ENOSPC: no space left on device'), 'uncaught-exception', { phase: 'running' });
|
|
61
|
-
assert.ok(report.recovery.some(s => s.toLowerCase().includes('disk') || s.toLowerCase().includes('space')), 'Should mention disk space');
|
|
62
|
-
});
|
|
63
|
-
it('detects permission errors in recovery', () => {
|
|
64
|
-
const report = buildCrashReport(new Error('EACCES: permission denied'), 'uncaught-exception', { phase: 'running' });
|
|
65
|
-
assert.ok(report.recovery.some(s => s.toLowerCase().includes('permission')), 'Should mention permissions');
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
describe('formatCrashReport', () => {
|
|
69
|
-
const sampleReport = {
|
|
70
|
-
timestamp: '2026-01-15T10:30:00.000Z',
|
|
71
|
-
pid: 12345,
|
|
72
|
-
error: 'ENOENT: file not found',
|
|
73
|
-
stack: 'Error: ENOENT\n at readFile (node:fs)\n at Store.init (store.ts:42)',
|
|
74
|
-
type: 'startup-failure',
|
|
75
|
-
context: {
|
|
76
|
-
phase: 'startup',
|
|
77
|
-
lastToolCall: 'memory_bootstrap',
|
|
78
|
-
activeLobe: 'zillow',
|
|
79
|
-
configSource: 'file',
|
|
80
|
-
lobeCount: 3,
|
|
81
|
-
},
|
|
82
|
-
recovery: ['Check file permissions', 'Toggle MCP to restart'],
|
|
83
|
-
serverUptime: 0,
|
|
84
|
-
};
|
|
85
|
-
it('includes all key fields', () => {
|
|
86
|
-
const formatted = formatCrashReport(sampleReport);
|
|
87
|
-
assert.ok(formatted.includes('2026-01-15'), 'Should include timestamp');
|
|
88
|
-
assert.ok(formatted.includes('startup-failure'), 'Should include crash type');
|
|
89
|
-
assert.ok(formatted.includes('startup'), 'Should include phase');
|
|
90
|
-
assert.ok(formatted.includes('ENOENT'), 'Should include error message');
|
|
91
|
-
assert.ok(formatted.includes('memory_bootstrap'), 'Should include last tool call');
|
|
92
|
-
assert.ok(formatted.includes('zillow'), 'Should include affected lobe');
|
|
93
|
-
});
|
|
94
|
-
it('includes recovery steps', () => {
|
|
95
|
-
const formatted = formatCrashReport(sampleReport);
|
|
96
|
-
assert.ok(formatted.includes('Check file permissions'));
|
|
97
|
-
assert.ok(formatted.includes('Toggle MCP to restart'));
|
|
98
|
-
});
|
|
99
|
-
it('includes truncated stack trace', () => {
|
|
100
|
-
const formatted = formatCrashReport(sampleReport);
|
|
101
|
-
assert.ok(formatted.includes('Stack Trace'), 'Should have stack trace section');
|
|
102
|
-
assert.ok(formatted.includes('ENOENT'), 'Should include stack content');
|
|
103
|
-
});
|
|
104
|
-
it('omits stack trace section when absent', () => {
|
|
105
|
-
const noStack = { ...sampleReport, stack: undefined };
|
|
106
|
-
const formatted = formatCrashReport(noStack);
|
|
107
|
-
assert.ok(!formatted.includes('Stack Trace'), 'Should not have stack trace section');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
describe('formatCrashSummary', () => {
|
|
111
|
-
it('formats a short summary with age', () => {
|
|
112
|
-
const report = {
|
|
113
|
-
timestamp: new Date(Date.now() - 5 * 60 * 1000).toISOString(), // 5 minutes ago
|
|
114
|
-
pid: 1,
|
|
115
|
-
error: 'Something broke unexpectedly during startup',
|
|
116
|
-
type: 'startup-failure',
|
|
117
|
-
context: { phase: 'startup' },
|
|
118
|
-
recovery: [],
|
|
119
|
-
serverUptime: 10,
|
|
120
|
-
};
|
|
121
|
-
const summary = formatCrashSummary(report);
|
|
122
|
-
assert.ok(summary.includes('5m ago') || summary.includes('4m ago'), 'Should show age in minutes');
|
|
123
|
-
assert.ok(summary.includes('startup-failure'), 'Should include type');
|
|
124
|
-
assert.ok(summary.includes('Something broke'), 'Should include error text');
|
|
125
|
-
});
|
|
126
|
-
it('truncates long error messages', () => {
|
|
127
|
-
const report = {
|
|
128
|
-
timestamp: new Date().toISOString(),
|
|
129
|
-
pid: 1,
|
|
130
|
-
error: 'A'.repeat(200),
|
|
131
|
-
type: 'unknown',
|
|
132
|
-
context: { phase: 'running' },
|
|
133
|
-
recovery: [],
|
|
134
|
-
serverUptime: 0,
|
|
135
|
-
};
|
|
136
|
-
const summary = formatCrashSummary(report);
|
|
137
|
-
assert.ok(summary.length < 250, 'Summary should be concise');
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
describe('crash report write/read lifecycle', () => {
|
|
141
|
-
// These tests use the real crash directory (~/.memory-mcp/crashes/)
|
|
142
|
-
// We write unique reports and clean them up after.
|
|
143
|
-
const testReports = [];
|
|
144
|
-
afterEach(async () => {
|
|
145
|
-
// Clean up test crash files
|
|
146
|
-
for (const report of testReports) {
|
|
147
|
-
const filename = `crash-${report.timestamp.replace(/[:.]/g, '-')}.json`;
|
|
148
|
-
const filepath = path.join(os.homedir(), '.memory-mcp', 'crashes', filename);
|
|
149
|
-
await fs.unlink(filepath).catch(() => { });
|
|
150
|
-
}
|
|
151
|
-
testReports.length = 0;
|
|
152
|
-
// Always clear LATEST.json to not interfere with real server
|
|
153
|
-
await clearLatestCrash();
|
|
154
|
-
});
|
|
155
|
-
it('writes and reads back a crash report', async () => {
|
|
156
|
-
markServerStarted();
|
|
157
|
-
const report = buildCrashReport(new Error('e2e test crash'), 'uncaught-exception', { phase: 'running' });
|
|
158
|
-
testReports.push(report);
|
|
159
|
-
const filepath = await writeCrashReport(report);
|
|
160
|
-
assert.ok(filepath.endsWith('.json'), 'Should write a .json file');
|
|
161
|
-
// Verify the file exists and is parseable
|
|
162
|
-
const content = await fs.readFile(filepath, 'utf-8');
|
|
163
|
-
const parsed = JSON.parse(content);
|
|
164
|
-
assert.strictEqual(parsed.error, 'e2e test crash');
|
|
165
|
-
assert.strictEqual(parsed.type, 'uncaught-exception');
|
|
166
|
-
});
|
|
167
|
-
it('readLatestCrash returns the most recent crash', async () => {
|
|
168
|
-
markServerStarted();
|
|
169
|
-
const report = buildCrashReport(new Error('latest test crash'), 'startup-failure', { phase: 'startup' });
|
|
170
|
-
testReports.push(report);
|
|
171
|
-
await writeCrashReport(report);
|
|
172
|
-
const latest = await readLatestCrash();
|
|
173
|
-
assert.ok(latest, 'Should find a latest crash');
|
|
174
|
-
assert.strictEqual(latest.error, 'latest test crash');
|
|
175
|
-
});
|
|
176
|
-
it('clearLatestCrash removes the latest indicator', async () => {
|
|
177
|
-
markServerStarted();
|
|
178
|
-
const report = buildCrashReport(new Error('to clear'), 'unknown', { phase: 'running' });
|
|
179
|
-
testReports.push(report);
|
|
180
|
-
await writeCrashReport(report);
|
|
181
|
-
await clearLatestCrash();
|
|
182
|
-
const latest = await readLatestCrash();
|
|
183
|
-
assert.strictEqual(latest, null, 'Should be null after clear');
|
|
184
|
-
});
|
|
185
|
-
it('readCrashHistory returns reports in reverse chronological order', async () => {
|
|
186
|
-
markServerStarted();
|
|
187
|
-
const report1 = buildCrashReport(new Error('crash-1'), 'unknown', { phase: 'running' });
|
|
188
|
-
// Small delay to ensure different timestamps
|
|
189
|
-
await new Promise(r => setTimeout(r, 10));
|
|
190
|
-
const report2 = buildCrashReport(new Error('crash-2'), 'unknown', { phase: 'running' });
|
|
191
|
-
testReports.push(report1, report2);
|
|
192
|
-
await writeCrashReport(report1);
|
|
193
|
-
await writeCrashReport(report2);
|
|
194
|
-
const history = await readCrashHistory(10);
|
|
195
|
-
assert.ok(history.length >= 2, 'Should have at least 2 reports');
|
|
196
|
-
// Most recent should be first
|
|
197
|
-
const idx1 = history.findIndex(r => r.error === 'crash-1');
|
|
198
|
-
const idx2 = history.findIndex(r => r.error === 'crash-2');
|
|
199
|
-
if (idx1 >= 0 && idx2 >= 0) {
|
|
200
|
-
assert.ok(idx2 < idx1, 'crash-2 (newer) should come before crash-1');
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|