@persistadev/mcp-server 3.2.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/dist/index.js ADDED
@@ -0,0 +1,1936 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Persista MCP Server v3.0 - Complete AI Memory Infrastructure
5
+ *
6
+ * FEATURES:
7
+ * ═══════════════════════════════════════════════════════════════
8
+ *
9
+ * 🧠 MEMORY (v3)
10
+ * - Confidence decay over time
11
+ * - Semantic search
12
+ * - Auto-extraction from text
13
+ * - Code-attached memories
14
+ * - Memory history/versioning
15
+ * - Smart merge for conflicts
16
+ *
17
+ * 📝 CONTEXT (v3)
18
+ * - Relevance scoring
19
+ * - Smart context packing
20
+ * - Cross-session threading
21
+ * - Auto-compression
22
+ * - Milestone marking
23
+ *
24
+ * 🔄 SYNC (v3)
25
+ * - Intent broadcasting
26
+ * - Conflict detection
27
+ * - Auto-discovery
28
+ * - Cursor position sharing
29
+ *
30
+ * 📚 KNOWLEDGE (v3)
31
+ * - Version-aware queries
32
+ * - Deprecation scanning
33
+ * - User pattern learning
34
+ * - Migration guides
35
+ *
36
+ * 🚀 AUTO-HOOKS
37
+ * - Git tracking between sessions
38
+ * - Codebase analysis
39
+ * - Proactive context injection
40
+ * ═══════════════════════════════════════════════════════════════
41
+ */
42
+ var __importDefault = (this && this.__importDefault) || function (mod) {
43
+ return (mod && mod.__esModule) ? mod : { "default": mod };
44
+ };
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
47
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
48
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
49
+ const os_1 = __importDefault(require("os"));
50
+ const child_process_1 = require("child_process");
51
+ const fs_1 = __importDefault(require("fs"));
52
+ const path_1 = __importDefault(require("path"));
53
+ // ============================================
54
+ // CONFIGURATION
55
+ // ============================================
56
+ const API_KEY = process.env.PERSISTA_API_KEY;
57
+ // Default to our clean API URL (custom domain on Supabase)
58
+ const BASE_URL = process.env.PERSISTA_API_URL || 'https://api.persista.dev/functions/v1';
59
+ const USER_ID = process.env.PERSISTA_USER_ID || os_1.default.userInfo().username || 'default';
60
+ // Smart project directory detection
61
+ function findProjectDir() {
62
+ // 1. Check explicit env var
63
+ if (process.env.PERSISTA_PROJECT_DIR) {
64
+ return process.env.PERSISTA_PROJECT_DIR;
65
+ }
66
+ // 2. Try to find nearest package.json by walking up from cwd
67
+ let current = process.cwd();
68
+ const root = path_1.default.parse(current).root;
69
+ while (current !== root) {
70
+ if (fs_1.default.existsSync(path_1.default.join(current, 'package.json')) ||
71
+ fs_1.default.existsSync(path_1.default.join(current, 'pyproject.toml')) ||
72
+ fs_1.default.existsSync(path_1.default.join(current, 'Cargo.toml')) ||
73
+ fs_1.default.existsSync(path_1.default.join(current, 'go.mod')) ||
74
+ fs_1.default.existsSync(path_1.default.join(current, '.git'))) {
75
+ return current;
76
+ }
77
+ current = path_1.default.dirname(current);
78
+ }
79
+ // 3. Fall back to cwd
80
+ return process.cwd();
81
+ }
82
+ const PROJECT_DIR = findProjectDir();
83
+ if (!API_KEY) {
84
+ console.error('Error: PERSISTA_API_KEY environment variable is required');
85
+ process.exit(1);
86
+ }
87
+ // Global session state
88
+ let sessionState = {
89
+ startTime: new Date(),
90
+ userId: USER_ID,
91
+ projectDir: PROJECT_DIR,
92
+ gitInfo: null,
93
+ codebaseDNA: null,
94
+ userMemory: null,
95
+ recentErrors: [],
96
+ lastKnownCommit: null,
97
+ packageVersions: {},
98
+ // Phase 2
99
+ buildHistory: [],
100
+ testHistory: [],
101
+ antiPatterns: [],
102
+ lastGitCheck: new Date()
103
+ };
104
+ // Background watcher interval (check every 30 seconds)
105
+ let watcherInterval = null;
106
+ const WATCHER_INTERVAL_MS = 30000;
107
+ // ============================================
108
+ // API HELPER
109
+ // ============================================
110
+ async function apiRequest(method, path, body) {
111
+ const response = await fetch(`${BASE_URL}${path}`, {
112
+ method,
113
+ headers: {
114
+ 'Authorization': `Bearer ${API_KEY}`,
115
+ 'Content-Type': 'application/json',
116
+ },
117
+ body: body ? JSON.stringify(body) : undefined,
118
+ });
119
+ if (!response.ok) {
120
+ const errorData = await response.json().catch(() => ({}));
121
+ throw new Error(errorData.error || `API request failed: ${response.status}`);
122
+ }
123
+ if (response.status === 204)
124
+ return null;
125
+ return response.json();
126
+ }
127
+ // ============================================
128
+ // AUTO-HOOKS - Run on startup
129
+ // ============================================
130
+ function runGitCommand(cmd) {
131
+ try {
132
+ return (0, child_process_1.execSync)(cmd, {
133
+ cwd: PROJECT_DIR,
134
+ encoding: 'utf-8',
135
+ stdio: ['pipe', 'pipe', 'pipe']
136
+ }).trim();
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ }
142
+ function sanitizeProjectName(dir) {
143
+ return path_1.default.basename(dir).toLowerCase().replace(/[^a-z0-9]/g, '_');
144
+ }
145
+ async function loadGitInfo() {
146
+ const isGitRepo = runGitCommand('git rev-parse --is-inside-work-tree');
147
+ if (isGitRepo !== 'true')
148
+ return null;
149
+ const branch = runGitCommand('git branch --show-current') || 'unknown';
150
+ const lastCommit = runGitCommand('git rev-parse HEAD') || '';
151
+ const hasChanges = runGitCommand('git status --porcelain');
152
+ let commitsSinceLastSession = [];
153
+ try {
154
+ const projectData = await apiRequest('GET', `/memory/${USER_ID}/context/last_commit_${sanitizeProjectName(PROJECT_DIR)}`);
155
+ if (projectData && projectData.value) {
156
+ sessionState.lastKnownCommit = projectData.value;
157
+ const logOutput = runGitCommand(`git log ${projectData.value}..HEAD --pretty=format:"%H|%s|%an|%ai" 2>/dev/null`);
158
+ if (logOutput) {
159
+ commitsSinceLastSession = logOutput.split('\n').filter(Boolean).map(line => {
160
+ const [hash, message, author, date] = line.split('|');
161
+ return { hash, message, author, date };
162
+ });
163
+ }
164
+ }
165
+ }
166
+ catch {
167
+ // No previous commit stored
168
+ }
169
+ const changedFilesOutput = runGitCommand('git diff --name-only HEAD~5 2>/dev/null') || '';
170
+ const changedFiles = changedFilesOutput.split('\n').filter(Boolean);
171
+ return {
172
+ branch,
173
+ lastCommit,
174
+ commitsSinceLastSession,
175
+ changedFiles,
176
+ hasUncommittedChanges: Boolean(hasChanges && hasChanges.length > 0)
177
+ };
178
+ }
179
+ async function analyzeCodebase() {
180
+ try {
181
+ const projectName = path_1.default.basename(PROJECT_DIR);
182
+ const techStack = [];
183
+ const frameworks = [];
184
+ const entryPoints = [];
185
+ const packageJsonPath = path_1.default.join(PROJECT_DIR, 'package.json');
186
+ let packageManager = null;
187
+ if (fs_1.default.existsSync(packageJsonPath)) {
188
+ const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
189
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
190
+ // Store versions for knowledge queries
191
+ Object.entries(allDeps).forEach(([name, version]) => {
192
+ sessionState.packageVersions[name] = version.replace(/[\^~]/g, '');
193
+ });
194
+ if (allDeps['react']) {
195
+ techStack.push('React');
196
+ frameworks.push('React');
197
+ }
198
+ if (allDeps['next']) {
199
+ techStack.push('Next.js');
200
+ frameworks.push('Next.js');
201
+ }
202
+ if (allDeps['vue']) {
203
+ techStack.push('Vue');
204
+ frameworks.push('Vue');
205
+ }
206
+ if (allDeps['svelte']) {
207
+ techStack.push('Svelte');
208
+ frameworks.push('Svelte');
209
+ }
210
+ if (allDeps['express']) {
211
+ techStack.push('Express');
212
+ frameworks.push('Express');
213
+ }
214
+ if (allDeps['fastify']) {
215
+ techStack.push('Fastify');
216
+ frameworks.push('Fastify');
217
+ }
218
+ if (allDeps['typescript'])
219
+ techStack.push('TypeScript');
220
+ if (allDeps['tailwindcss'])
221
+ techStack.push('Tailwind CSS');
222
+ if (allDeps['@supabase/supabase-js'])
223
+ techStack.push('Supabase');
224
+ if (allDeps['prisma'] || allDeps['@prisma/client'])
225
+ techStack.push('Prisma');
226
+ if (allDeps['drizzle-orm'])
227
+ techStack.push('Drizzle');
228
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'bun.lockb')))
229
+ packageManager = 'bun';
230
+ else if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'pnpm-lock.yaml')))
231
+ packageManager = 'pnpm';
232
+ else if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'yarn.lock')))
233
+ packageManager = 'yarn';
234
+ else if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'package-lock.json')))
235
+ packageManager = 'npm';
236
+ if (pkg.main)
237
+ entryPoints.push(pkg.main);
238
+ if (pkg.module)
239
+ entryPoints.push(pkg.module);
240
+ }
241
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'requirements.txt')) ||
242
+ fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'pyproject.toml'))) {
243
+ techStack.push('Python');
244
+ }
245
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'go.mod')))
246
+ techStack.push('Go');
247
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'Cargo.toml')))
248
+ techStack.push('Rust');
249
+ let structure = 'flat';
250
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'src')))
251
+ structure = 'src-based';
252
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'app')))
253
+ structure = 'app-based';
254
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'packages')))
255
+ structure = 'monorepo';
256
+ const hasTests = fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'tests')) ||
257
+ fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, '__tests__')) ||
258
+ fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'test'));
259
+ const hasTypeScript = fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'tsconfig.json'));
260
+ return {
261
+ projectName,
262
+ techStack,
263
+ structure,
264
+ hasTests,
265
+ hasTypeScript,
266
+ packageManager,
267
+ frameworks,
268
+ entryPoints
269
+ };
270
+ }
271
+ catch {
272
+ return null;
273
+ }
274
+ }
275
+ async function loadUserMemory() {
276
+ try {
277
+ const [preferences, facts, behaviors, projects, history, patterns] = await Promise.all([
278
+ apiRequest('GET', `/memory/${USER_ID}/context/preference`).catch(() => []),
279
+ apiRequest('GET', `/memory/${USER_ID}/context/fact`).catch(() => []),
280
+ apiRequest('GET', `/memory/${USER_ID}/context/behavior`).catch(() => []),
281
+ apiRequest('GET', `/memory/${USER_ID}/projects`).catch(() => []),
282
+ apiRequest('GET', `/memory/${USER_ID}/history?limit=5`).catch(() => []),
283
+ apiRequest('GET', `/knowledge/user/${USER_ID}/patterns`).catch(() => [])
284
+ ]);
285
+ return {
286
+ preferences: Array.isArray(preferences) ? preferences : [],
287
+ facts: Array.isArray(facts) ? facts : [],
288
+ behaviors: Array.isArray(behaviors) ? behaviors : [],
289
+ projects: Array.isArray(projects) ? projects : [],
290
+ recentHistory: Array.isArray(history) ? history : [],
291
+ patterns: Array.isArray(patterns) ? patterns : []
292
+ };
293
+ }
294
+ catch {
295
+ return null;
296
+ }
297
+ }
298
+ async function initializeSession() {
299
+ console.error('🚀 Persista v3: Initializing session...');
300
+ const [gitInfo, codebaseDNA, userMemory] = await Promise.all([
301
+ loadGitInfo(),
302
+ analyzeCodebase(),
303
+ loadUserMemory()
304
+ ]);
305
+ sessionState = {
306
+ ...sessionState,
307
+ startTime: new Date(),
308
+ gitInfo,
309
+ codebaseDNA,
310
+ userMemory
311
+ };
312
+ // Save current commit for next session
313
+ if (gitInfo?.lastCommit) {
314
+ const projectKey = `last_commit_${sanitizeProjectName(PROJECT_DIR)}`;
315
+ apiRequest('POST', `/memory/${USER_ID}/context`, {
316
+ category: 'project',
317
+ key: projectKey,
318
+ value: gitInfo.lastCommit,
319
+ source: 'auto'
320
+ }).catch(() => { });
321
+ }
322
+ console.error(`✅ Persista v3: Session initialized for ${USER_ID}`);
323
+ console.error(` Project: ${codebaseDNA?.projectName || 'Unknown'}`);
324
+ console.error(` Tech: ${codebaseDNA?.techStack.join(', ') || 'Unknown'}`);
325
+ console.error(` Git: ${gitInfo?.branch || 'Not a git repo'}`);
326
+ if (gitInfo?.commitsSinceLastSession.length) {
327
+ console.error(` 📝 ${gitInfo.commitsSinceLastSession.length} new commits since last session`);
328
+ }
329
+ if (userMemory) {
330
+ console.error(` 🧠 Loaded ${userMemory.preferences.length} preferences, ${userMemory.facts.length} facts, ${userMemory.patterns.length} patterns`);
331
+ }
332
+ }
333
+ // Error capture helper
334
+ function captureError(error) {
335
+ sessionState.recentErrors.push(error);
336
+ apiRequest('POST', `/memory/${USER_ID}/context`, {
337
+ category: 'behavior',
338
+ key: `error_${Date.now()}`,
339
+ value: error,
340
+ source: 'auto',
341
+ confidence: 0.8
342
+ }).catch(() => { });
343
+ }
344
+ // ============================================
345
+ // PHASE 2: BACKGROUND WATCHERS & ERROR LEARNING
346
+ // ============================================
347
+ // Common error patterns to detect
348
+ const ERROR_PATTERNS = [
349
+ { pattern: /error TS\d+:/i, type: 'typescript', severity: 'high' },
350
+ { pattern: /SyntaxError:/i, type: 'syntax', severity: 'high' },
351
+ { pattern: /ReferenceError:/i, type: 'reference', severity: 'high' },
352
+ { pattern: /TypeError:/i, type: 'type', severity: 'high' },
353
+ { pattern: /Cannot find module/i, type: 'module_not_found', severity: 'medium' },
354
+ { pattern: /ENOENT/i, type: 'file_not_found', severity: 'medium' },
355
+ { pattern: /EACCES/i, type: 'permission', severity: 'medium' },
356
+ { pattern: /npm ERR!/i, type: 'npm', severity: 'high' },
357
+ { pattern: /error: /i, type: 'generic', severity: 'low' },
358
+ { pattern: /failed/i, type: 'failure', severity: 'medium' },
359
+ { pattern: /FAIL /i, type: 'test_failure', severity: 'high' },
360
+ { pattern: /✖|✗|❌/i, type: 'failure_symbol', severity: 'medium' },
361
+ ];
362
+ // Parse build output for errors
363
+ function parseBuildOutput(output) {
364
+ const lines = output.split('\n');
365
+ const errors = [];
366
+ const warnings = [];
367
+ for (const line of lines) {
368
+ const lineLower = line.toLowerCase();
369
+ if (lineLower.includes('error') || lineLower.includes('failed') || lineLower.includes('fail ')) {
370
+ errors.push(line.trim());
371
+ }
372
+ else if (lineLower.includes('warning') || lineLower.includes('warn')) {
373
+ warnings.push(line.trim());
374
+ }
375
+ }
376
+ return { errors: errors.slice(0, 20), warnings: warnings.slice(0, 10) };
377
+ }
378
+ // Extract anti-pattern from error
379
+ function extractAntiPattern(error, context) {
380
+ for (const { pattern, type, severity } of ERROR_PATTERNS) {
381
+ if (pattern.test(error)) {
382
+ return {
383
+ id: `ap_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
384
+ pattern: error.substring(0, 200),
385
+ context: context.substring(0, 500),
386
+ occurrences: 1,
387
+ lastSeen: new Date(),
388
+ severity
389
+ };
390
+ }
391
+ }
392
+ return null;
393
+ }
394
+ // Check for test result files
395
+ function findTestResults() {
396
+ const testResultPaths = [
397
+ // Jest
398
+ path_1.default.join(PROJECT_DIR, 'coverage', 'coverage-summary.json'),
399
+ path_1.default.join(PROJECT_DIR, 'jest-results.json'),
400
+ // Vitest
401
+ path_1.default.join(PROJECT_DIR, 'vitest-results.json'),
402
+ // Generic
403
+ path_1.default.join(PROJECT_DIR, 'test-results.json'),
404
+ ];
405
+ for (const resultPath of testResultPaths) {
406
+ try {
407
+ if (fs_1.default.existsSync(resultPath)) {
408
+ const content = fs_1.default.readFileSync(resultPath, 'utf-8');
409
+ const data = JSON.parse(content);
410
+ // Jest format
411
+ if (data.numPassedTests !== undefined) {
412
+ return {
413
+ framework: 'jest',
414
+ passed: data.numPassedTests || 0,
415
+ failed: data.numFailedTests || 0,
416
+ skipped: data.numPendingTests || 0,
417
+ timestamp: new Date(),
418
+ failures: (data.testResults || [])
419
+ .flatMap((r) => r.assertionResults || [])
420
+ .filter((a) => a.status === 'failed')
421
+ .slice(0, 10)
422
+ .map((a) => ({
423
+ testName: a.title || a.fullName,
424
+ message: (a.failureMessages || []).join('\n').substring(0, 500)
425
+ }))
426
+ };
427
+ }
428
+ // Generic format
429
+ if (data.passed !== undefined) {
430
+ return {
431
+ framework: 'unknown',
432
+ passed: data.passed || 0,
433
+ failed: data.failed || 0,
434
+ skipped: data.skipped || 0,
435
+ timestamp: new Date(),
436
+ failures: []
437
+ };
438
+ }
439
+ }
440
+ }
441
+ catch {
442
+ // Ignore parse errors
443
+ }
444
+ }
445
+ return null;
446
+ }
447
+ // Check for build logs
448
+ function findBuildErrors() {
449
+ const buildLogPaths = [
450
+ path_1.default.join(PROJECT_DIR, 'npm-debug.log'),
451
+ path_1.default.join(PROJECT_DIR, '.npm', '_logs'),
452
+ path_1.default.join(PROJECT_DIR, 'yarn-error.log'),
453
+ path_1.default.join(PROJECT_DIR, 'tsconfig.tsbuildinfo'),
454
+ ];
455
+ // Check for TypeScript build info
456
+ const tsBuildInfo = path_1.default.join(PROJECT_DIR, 'tsconfig.tsbuildinfo');
457
+ if (fs_1.default.existsSync(tsBuildInfo)) {
458
+ try {
459
+ const stat = fs_1.default.statSync(tsBuildInfo);
460
+ const content = fs_1.default.readFileSync(tsBuildInfo, 'utf-8');
461
+ const data = JSON.parse(content);
462
+ // If there are semantic diagnostics, there were errors
463
+ if (data.semanticDiagnosticsPerFile && data.semanticDiagnosticsPerFile.length > 0) {
464
+ const errors = data.semanticDiagnosticsPerFile
465
+ .flatMap((f) => Array.isArray(f) ? f.slice(1) : [])
466
+ .filter((d) => d && d.messageText)
467
+ .map((d) => typeof d.messageText === 'string' ? d.messageText : d.messageText?.messageText || '')
468
+ .slice(0, 10);
469
+ if (errors.length > 0) {
470
+ return {
471
+ command: 'tsc',
472
+ exitCode: 1,
473
+ timestamp: stat.mtime,
474
+ errors,
475
+ warnings: []
476
+ };
477
+ }
478
+ }
479
+ }
480
+ catch {
481
+ // Ignore
482
+ }
483
+ }
484
+ return null;
485
+ }
486
+ // Check git for new changes
487
+ async function checkGitChanges() {
488
+ const currentCommit = runGitCommand('git rev-parse HEAD');
489
+ if (!currentCommit)
490
+ return false;
491
+ if (sessionState.gitInfo && currentCommit !== sessionState.gitInfo.lastCommit) {
492
+ // New commits detected!
493
+ const newGitInfo = await loadGitInfo();
494
+ if (newGitInfo) {
495
+ sessionState.gitInfo = newGitInfo;
496
+ console.error(`📝 Persista: Detected ${newGitInfo.commitsSinceLastSession.length} new commits`);
497
+ return true;
498
+ }
499
+ }
500
+ return false;
501
+ }
502
+ // Background watcher tick
503
+ async function watcherTick() {
504
+ try {
505
+ // 1. Check for git changes
506
+ const gitChanged = await checkGitChanges();
507
+ sessionState.lastGitCheck = new Date();
508
+ // 2. Check for test results
509
+ const testResult = findTestResults();
510
+ if (testResult && testResult.failed > 0) {
511
+ // New test failures
512
+ const lastTest = sessionState.testHistory[sessionState.testHistory.length - 1];
513
+ if (!lastTest || lastTest.timestamp.getTime() !== testResult.timestamp.getTime()) {
514
+ sessionState.testHistory.push(testResult);
515
+ console.error(`🧪 Persista: Detected test results - ${testResult.passed} passed, ${testResult.failed} failed`);
516
+ // Extract anti-patterns from failures
517
+ for (const failure of testResult.failures) {
518
+ const antiPattern = extractAntiPattern(failure.message, failure.testName);
519
+ if (antiPattern) {
520
+ sessionState.antiPatterns.push(antiPattern);
521
+ // Save to Persista
522
+ apiRequest('POST', `/memory/${USER_ID}/context`, {
523
+ category: 'behavior',
524
+ key: `test_failure_${antiPattern.id}`,
525
+ value: {
526
+ type: 'test_failure',
527
+ testName: failure.testName,
528
+ message: failure.message,
529
+ file: failure.file,
530
+ pattern: antiPattern.pattern
531
+ },
532
+ source: 'auto',
533
+ confidence: 0.9
534
+ }).catch(() => { });
535
+ }
536
+ }
537
+ }
538
+ }
539
+ // 3. Check for build errors
540
+ const buildResult = findBuildErrors();
541
+ if (buildResult && buildResult.errors.length > 0) {
542
+ const lastBuild = sessionState.buildHistory[sessionState.buildHistory.length - 1];
543
+ if (!lastBuild || lastBuild.timestamp.getTime() !== buildResult.timestamp.getTime()) {
544
+ sessionState.buildHistory.push(buildResult);
545
+ console.error(`🔨 Persista: Detected build errors - ${buildResult.errors.length} errors`);
546
+ // Extract anti-patterns
547
+ for (const error of buildResult.errors) {
548
+ const antiPattern = extractAntiPattern(error, buildResult.command);
549
+ if (antiPattern) {
550
+ sessionState.antiPatterns.push(antiPattern);
551
+ // Save to Persista
552
+ apiRequest('POST', `/memory/${USER_ID}/context`, {
553
+ category: 'behavior',
554
+ key: `build_error_${antiPattern.id}`,
555
+ value: {
556
+ type: 'build_error',
557
+ command: buildResult.command,
558
+ error: error,
559
+ pattern: antiPattern.pattern
560
+ },
561
+ source: 'auto',
562
+ confidence: 0.85
563
+ }).catch(() => { });
564
+ }
565
+ }
566
+ }
567
+ }
568
+ }
569
+ catch (err) {
570
+ // Silent fail - don't interrupt the session
571
+ }
572
+ }
573
+ // Start background watcher
574
+ function startWatcher() {
575
+ if (watcherInterval)
576
+ return;
577
+ console.error('👀 Persista: Starting background watcher...');
578
+ watcherInterval = setInterval(watcherTick, WATCHER_INTERVAL_MS);
579
+ // Also run immediately
580
+ watcherTick();
581
+ }
582
+ // Stop background watcher
583
+ function stopWatcher() {
584
+ if (watcherInterval) {
585
+ clearInterval(watcherInterval);
586
+ watcherInterval = null;
587
+ console.error('👀 Persista: Stopped background watcher');
588
+ }
589
+ }
590
+ // Deep DNA state
591
+ let codebaseDNADeep = null;
592
+ // Analyze file for conventions
593
+ function analyzeFileConventions(content, filePath) {
594
+ const lines = content.split('\n');
595
+ // Indentation
596
+ let tabCount = 0;
597
+ let spaceCount = 0;
598
+ let spaceSizes = [];
599
+ for (const line of lines) {
600
+ if (line.startsWith('\t'))
601
+ tabCount++;
602
+ else if (line.startsWith(' ')) {
603
+ spaceCount++;
604
+ const match = line.match(/^( +)/);
605
+ if (match)
606
+ spaceSizes.push(match[1].length);
607
+ }
608
+ }
609
+ const indentation = tabCount > spaceCount ? 'tabs' : spaceCount > tabCount ? 'spaces' : 'unknown';
610
+ const indentSize = spaceSizes.length > 0
611
+ ? Math.min(...spaceSizes.filter(s => s > 0))
612
+ : 2;
613
+ // Semicolons (for JS/TS files)
614
+ const hasSemicolons = /;\s*$/.test(content);
615
+ const hasNoSemicolons = /[^;{}\s]\s*\n/.test(content);
616
+ const semicolons = filePath.match(/\.(js|ts|tsx|jsx)$/)
617
+ ? (hasSemicolons && !hasNoSemicolons ? true : !hasSemicolons ? false : null)
618
+ : null;
619
+ // Quotes
620
+ const singleQuotes = (content.match(/'/g) || []).length;
621
+ const doubleQuotes = (content.match(/"/g) || []).length;
622
+ const quotes = singleQuotes > doubleQuotes * 1.5 ? 'single'
623
+ : doubleQuotes > singleQuotes * 1.5 ? 'double'
624
+ : null;
625
+ return { indentation, indentSize, semicolons, quotes };
626
+ }
627
+ // Detect import patterns
628
+ function detectImportPatterns(content) {
629
+ const patterns = [];
630
+ // Check for barrel exports
631
+ if (/from ['"]\.\/index['"]/.test(content) || /from ['"]@\//.test(content)) {
632
+ patterns.push('barrel-exports');
633
+ }
634
+ // Check for path aliases
635
+ if (/from ['"]@\//.test(content) || /from ['"]~\//.test(content)) {
636
+ patterns.push('path-aliases');
637
+ }
638
+ // Check for absolute imports
639
+ if (/from ['"]src\//.test(content)) {
640
+ patterns.push('absolute-imports');
641
+ }
642
+ // Check for relative imports depth
643
+ if (/from ['"]\.\.\/\.\.\//.test(content)) {
644
+ patterns.push('deep-relative-imports');
645
+ }
646
+ // Check for named exports preference
647
+ if (/^export \{/.test(content)) {
648
+ patterns.push('named-exports');
649
+ }
650
+ // Check for default exports
651
+ if (/^export default/.test(content)) {
652
+ patterns.push('default-exports');
653
+ }
654
+ return patterns;
655
+ }
656
+ // Detect React/component patterns
657
+ function detectComponentPatterns(content, filePath) {
658
+ const patterns = [];
659
+ if (!filePath.match(/\.(jsx|tsx)$/))
660
+ return patterns;
661
+ // Functional vs class components
662
+ if (/function\s+\w+.*\(.*\).*{[\s\S]*return\s*\(/.test(content) ||
663
+ /const\s+\w+.*=.*\(.*\).*=>/.test(content)) {
664
+ patterns.push('functional-components');
665
+ }
666
+ if (/class\s+\w+\s+extends\s+(React\.)?Component/.test(content)) {
667
+ patterns.push('class-components');
668
+ }
669
+ // Hooks usage
670
+ if (/use[A-Z]\w+\(/.test(content)) {
671
+ patterns.push('hooks');
672
+ }
673
+ // Custom hooks
674
+ if (/^(export\s+)?(function|const)\s+use[A-Z]/.test(content)) {
675
+ patterns.push('custom-hooks');
676
+ }
677
+ // Render props
678
+ if (/render\s*=\s*\{/.test(content) || /children\s*\(/.test(content)) {
679
+ patterns.push('render-props');
680
+ }
681
+ // HOCs
682
+ if (/with[A-Z]\w+\(/.test(content) || /export default \w+\(\w+\)/.test(content)) {
683
+ patterns.push('higher-order-components');
684
+ }
685
+ // Compound components
686
+ if (/\w+\.\w+\s*=/.test(content)) {
687
+ patterns.push('compound-components');
688
+ }
689
+ // Styled components
690
+ if (/styled\.\w+`/.test(content) || /styled\(\w+\)`/.test(content)) {
691
+ patterns.push('styled-components');
692
+ }
693
+ // CSS Modules
694
+ if (/import\s+\w+\s+from\s+['"].*\.module\.(css|scss)['"]/.test(content)) {
695
+ patterns.push('css-modules');
696
+ }
697
+ // Tailwind
698
+ if (/className\s*=\s*['"][^'"]*\b(flex|grid|p-|m-|text-|bg-)\b/.test(content)) {
699
+ patterns.push('tailwind');
700
+ }
701
+ return patterns;
702
+ }
703
+ // Detect architecture style
704
+ function detectArchitecture() {
705
+ const dirs = new Set();
706
+ // Check common directories
707
+ const checkDirs = [
708
+ 'src/components', 'src/pages', 'src/views', 'src/screens',
709
+ 'src/hooks', 'src/utils', 'src/helpers', 'src/lib',
710
+ 'src/services', 'src/api', 'src/store', 'src/state',
711
+ 'src/features', 'src/modules', 'src/domains',
712
+ 'src/types', 'src/interfaces', 'src/models',
713
+ 'app', 'pages', 'components', 'lib', 'utils'
714
+ ];
715
+ for (const dir of checkDirs) {
716
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, dir))) {
717
+ dirs.add(dir.split('/').pop() || dir);
718
+ }
719
+ }
720
+ const layers = Array.from(dirs);
721
+ // Determine style
722
+ let style = 'unknown';
723
+ if (dirs.has('features') || dirs.has('modules') || dirs.has('domains')) {
724
+ style = 'feature-based';
725
+ }
726
+ else if (dirs.has('components') && dirs.has('services') && dirs.has('utils')) {
727
+ style = 'layer-based';
728
+ }
729
+ else if (dirs.has('pages') && dirs.has('components')) {
730
+ style = 'page-based';
731
+ }
732
+ else if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'app'))) {
733
+ style = 'app-router'; // Next.js 13+
734
+ }
735
+ return { style, layers };
736
+ }
737
+ // Detect test framework
738
+ function detectTestFramework() {
739
+ const packageJsonPath = path_1.default.join(PROJECT_DIR, 'package.json');
740
+ if (!fs_1.default.existsSync(packageJsonPath))
741
+ return null;
742
+ try {
743
+ const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
744
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
745
+ if (allDeps['jest'])
746
+ return 'jest';
747
+ if (allDeps['vitest'])
748
+ return 'vitest';
749
+ if (allDeps['mocha'])
750
+ return 'mocha';
751
+ if (allDeps['ava'])
752
+ return 'ava';
753
+ if (allDeps['@testing-library/react'])
754
+ return 'testing-library';
755
+ if (allDeps['cypress'])
756
+ return 'cypress';
757
+ if (allDeps['playwright'])
758
+ return 'playwright';
759
+ }
760
+ catch { }
761
+ return null;
762
+ }
763
+ // Detect state management
764
+ function detectStateManagement() {
765
+ const packageJsonPath = path_1.default.join(PROJECT_DIR, 'package.json');
766
+ if (!fs_1.default.existsSync(packageJsonPath))
767
+ return [];
768
+ try {
769
+ const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
770
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
771
+ const patterns = [];
772
+ if (allDeps['zustand'])
773
+ patterns.push('zustand');
774
+ if (allDeps['@reduxjs/toolkit'] || allDeps['redux'])
775
+ patterns.push('redux');
776
+ if (allDeps['recoil'])
777
+ patterns.push('recoil');
778
+ if (allDeps['jotai'])
779
+ patterns.push('jotai');
780
+ if (allDeps['mobx'])
781
+ patterns.push('mobx');
782
+ if (allDeps['valtio'])
783
+ patterns.push('valtio');
784
+ if (allDeps['@tanstack/react-query'] || allDeps['react-query'])
785
+ patterns.push('react-query');
786
+ if (allDeps['swr'])
787
+ patterns.push('swr');
788
+ if (patterns.length === 0)
789
+ patterns.push('context-only');
790
+ return patterns;
791
+ }
792
+ catch {
793
+ return [];
794
+ }
795
+ }
796
+ // Detect API patterns
797
+ function detectApiPatterns() {
798
+ const packageJsonPath = path_1.default.join(PROJECT_DIR, 'package.json');
799
+ if (!fs_1.default.existsSync(packageJsonPath))
800
+ return [];
801
+ try {
802
+ const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
803
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
804
+ const patterns = [];
805
+ if (allDeps['@trpc/client'] || allDeps['@trpc/server'])
806
+ patterns.push('tRPC');
807
+ if (allDeps['graphql'] || allDeps['@apollo/client'])
808
+ patterns.push('GraphQL');
809
+ if (allDeps['axios'] || allDeps['ky'] || allDeps['got'])
810
+ patterns.push('REST');
811
+ if (allDeps['@tanstack/react-query'])
812
+ patterns.push('React Query');
813
+ if (allDeps['swr'])
814
+ patterns.push('SWR');
815
+ // Check for API routes
816
+ if (fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'pages/api')) ||
817
+ fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'app/api'))) {
818
+ patterns.push('API-routes');
819
+ }
820
+ return patterns;
821
+ }
822
+ catch {
823
+ return [];
824
+ }
825
+ }
826
+ // Find critical paths
827
+ function findCriticalPaths() {
828
+ const critical = [];
829
+ const criticalPatterns = [
830
+ { pattern: '**/auth/**', reason: 'Authentication logic' },
831
+ { pattern: '**/api/**', reason: 'API endpoints' },
832
+ { pattern: '**/middleware*', reason: 'Request middleware' },
833
+ { pattern: '**/config*', reason: 'Configuration' },
834
+ { pattern: '**/.env*', reason: 'Environment variables' },
835
+ { pattern: '**/database*', reason: 'Database logic' },
836
+ { pattern: '**/prisma/**', reason: 'Database schema' },
837
+ { pattern: '**/supabase/**', reason: 'Supabase configuration' },
838
+ ];
839
+ // Check for common critical directories
840
+ const checkPaths = [
841
+ { path: 'src/auth', reason: 'Authentication logic' },
842
+ { path: 'src/api', reason: 'API layer' },
843
+ { path: 'src/lib/auth', reason: 'Auth utilities' },
844
+ { path: 'src/middleware.ts', reason: 'Next.js middleware' },
845
+ { path: 'middleware.ts', reason: 'Next.js middleware' },
846
+ { path: 'prisma/schema.prisma', reason: 'Database schema' },
847
+ { path: 'supabase/migrations', reason: 'Database migrations' },
848
+ { path: '.env', reason: 'Environment config' },
849
+ { path: '.env.local', reason: 'Local environment' },
850
+ ];
851
+ for (const { path: checkPath, reason } of checkPaths) {
852
+ const fullPath = path_1.default.join(PROJECT_DIR, checkPath);
853
+ if (fs_1.default.existsSync(fullPath)) {
854
+ critical.push({ path: checkPath, reason });
855
+ }
856
+ }
857
+ return critical.slice(0, 10); // Limit to 10
858
+ }
859
+ // Get file naming convention
860
+ function getFileNamingConvention() {
861
+ const srcDir = path_1.default.join(PROJECT_DIR, 'src');
862
+ if (!fs_1.default.existsSync(srcDir))
863
+ return 'mixed';
864
+ try {
865
+ const files = [];
866
+ function scanDir(dir, depth = 0) {
867
+ if (depth > 2)
868
+ return;
869
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
870
+ for (const entry of entries) {
871
+ if (entry.isFile() && entry.name.match(/\.(ts|tsx|js|jsx)$/)) {
872
+ files.push(entry.name.replace(/\.(ts|tsx|js|jsx)$/, ''));
873
+ }
874
+ else if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
875
+ scanDir(path_1.default.join(dir, entry.name), depth + 1);
876
+ }
877
+ }
878
+ }
879
+ scanDir(srcDir);
880
+ let camelCase = 0, pascalCase = 0, kebabCase = 0, snakeCase = 0;
881
+ for (const file of files) {
882
+ if (/^[a-z][a-zA-Z0-9]*$/.test(file) && /[A-Z]/.test(file))
883
+ camelCase++;
884
+ else if (/^[A-Z][a-zA-Z0-9]*$/.test(file))
885
+ pascalCase++;
886
+ else if (/^[a-z][a-z0-9-]*$/.test(file) && file.includes('-'))
887
+ kebabCase++;
888
+ else if (/^[a-z][a-z0-9_]*$/.test(file) && file.includes('_'))
889
+ snakeCase++;
890
+ }
891
+ const max = Math.max(camelCase, pascalCase, kebabCase, snakeCase);
892
+ if (max === 0)
893
+ return 'mixed';
894
+ if (camelCase === max)
895
+ return 'camelCase';
896
+ if (pascalCase === max)
897
+ return 'PascalCase';
898
+ if (kebabCase === max)
899
+ return 'kebab-case';
900
+ if (snakeCase === max)
901
+ return 'snake_case';
902
+ return 'mixed';
903
+ }
904
+ catch {
905
+ return 'mixed';
906
+ }
907
+ }
908
+ // Full deep DNA analysis
909
+ async function analyzeCodebaseDNADeep() {
910
+ console.error('🧬 Persista: Analyzing codebase DNA...');
911
+ try {
912
+ const basicDNA = sessionState.codebaseDNA;
913
+ if (!basicDNA)
914
+ return null;
915
+ // Analyze sample files for conventions
916
+ const srcDir = path_1.default.join(PROJECT_DIR, 'src');
917
+ let sampleContent = '';
918
+ let filesAnalyzed = 0;
919
+ const importPatterns = new Set();
920
+ const componentPatterns = new Set();
921
+ function scanForSamples(dir, depth = 0) {
922
+ if (depth > 3 || filesAnalyzed > 20)
923
+ return;
924
+ if (!fs_1.default.existsSync(dir))
925
+ return;
926
+ try {
927
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
928
+ for (const entry of entries) {
929
+ if (filesAnalyzed > 20)
930
+ return;
931
+ const fullPath = path_1.default.join(dir, entry.name);
932
+ if (entry.isFile() && entry.name.match(/\.(ts|tsx|js|jsx)$/) && !entry.name.includes('.test.') && !entry.name.includes('.spec.')) {
933
+ try {
934
+ const content = fs_1.default.readFileSync(fullPath, 'utf-8');
935
+ if (content.length < 50000) { // Skip huge files
936
+ sampleContent += content + '\n';
937
+ filesAnalyzed++;
938
+ // Collect patterns
939
+ detectImportPatterns(content).forEach(p => importPatterns.add(p));
940
+ detectComponentPatterns(content, fullPath).forEach(p => componentPatterns.add(p));
941
+ }
942
+ }
943
+ catch { }
944
+ }
945
+ else if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
946
+ scanForSamples(fullPath, depth + 1);
947
+ }
948
+ }
949
+ }
950
+ catch { }
951
+ }
952
+ scanForSamples(srcDir);
953
+ scanForSamples(path_1.default.join(PROJECT_DIR, 'app'));
954
+ scanForSamples(path_1.default.join(PROJECT_DIR, 'pages'));
955
+ // Analyze conventions
956
+ const convAnalysis = analyzeFileConventions(sampleContent, 'sample.tsx');
957
+ // Detect architecture
958
+ const { style, layers } = detectArchitecture();
959
+ // Build deep DNA
960
+ const dna = {
961
+ projectName: basicDNA.projectName,
962
+ techStack: basicDNA.techStack,
963
+ frameworks: basicDNA.frameworks,
964
+ structure: basicDNA.structure,
965
+ conventions: {
966
+ indentation: convAnalysis.indentation,
967
+ indentSize: convAnalysis.indentSize,
968
+ semicolons: convAnalysis.semicolons ?? 'mixed',
969
+ quotes: convAnalysis.quotes ?? 'mixed',
970
+ trailingCommas: 'mixed', // Hard to detect reliably
971
+ fileNaming: getFileNamingConvention(),
972
+ componentNaming: componentPatterns.has('functional-components') ? 'PascalCase' : 'unknown'
973
+ },
974
+ patterns: {
975
+ imports: Array.from(importPatterns),
976
+ components: Array.from(componentPatterns),
977
+ stateManagement: detectStateManagement(),
978
+ apiPatterns: detectApiPatterns(),
979
+ errorHandling: [] // TODO: detect error handling patterns
980
+ },
981
+ architecture: {
982
+ style,
983
+ layers,
984
+ hasTests: basicDNA.hasTests,
985
+ testFramework: detectTestFramework(),
986
+ hasCICD: fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, '.github/workflows')) ||
987
+ fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, '.gitlab-ci.yml')),
988
+ hasDocker: fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'Dockerfile')) ||
989
+ fs_1.default.existsSync(path_1.default.join(PROJECT_DIR, 'docker-compose.yml'))
990
+ },
991
+ criticalPaths: findCriticalPaths(),
992
+ decisions: [] // Loaded from Persista
993
+ };
994
+ console.error(`✅ Persista: DNA analyzed - ${style} architecture, ${filesAnalyzed} files scanned`);
995
+ console.error(` Patterns: ${Array.from(componentPatterns).slice(0, 5).join(', ')}`);
996
+ console.error(` State: ${dna.patterns.stateManagement.join(', ') || 'none detected'}`);
997
+ return dna;
998
+ }
999
+ catch (err) {
1000
+ console.error('Error analyzing DNA:', err);
1001
+ return null;
1002
+ }
1003
+ }
1004
+ // Load decisions from Persista
1005
+ async function loadDecisions() {
1006
+ try {
1007
+ const projectName = sessionState.codebaseDNA?.projectName || 'unknown';
1008
+ const context = await apiRequest('GET', `/memory/${USER_ID}/context/project`);
1009
+ if (!Array.isArray(context))
1010
+ return [];
1011
+ return context
1012
+ .filter((c) => c.key?.startsWith('decision_'))
1013
+ .map((c) => ({
1014
+ decision: c.value?.decision || '',
1015
+ reason: c.value?.reason || '',
1016
+ date: new Date(c.created_at || Date.now()),
1017
+ alternatives: c.value?.alternatives
1018
+ }))
1019
+ .slice(0, 20);
1020
+ }
1021
+ catch {
1022
+ return [];
1023
+ }
1024
+ }
1025
+ // ============================================
1026
+ // CREATE MCP SERVER
1027
+ // ============================================
1028
+ const server = new index_js_1.Server({
1029
+ name: 'persista',
1030
+ version: '3.0.0',
1031
+ }, {
1032
+ capabilities: {
1033
+ tools: {},
1034
+ resources: {},
1035
+ },
1036
+ });
1037
+ // ============================================
1038
+ // RESOURCES - Auto-injected context
1039
+ // ============================================
1040
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
1041
+ return {
1042
+ resources: [
1043
+ {
1044
+ uri: 'persista://session/context',
1045
+ name: '🧠 Persista Session Context',
1046
+ description: 'IMPORTANT: Contains user memories, preferences, project context, git changes, and patterns. Read this for personalized responses.',
1047
+ mimeType: 'application/json'
1048
+ },
1049
+ {
1050
+ uri: 'persista://user/memory',
1051
+ name: 'User Memory & Preferences',
1052
+ description: 'All stored memories with effective confidence scores',
1053
+ mimeType: 'application/json'
1054
+ },
1055
+ {
1056
+ uri: 'persista://codebase/dna',
1057
+ name: 'Codebase DNA',
1058
+ description: 'Tech stack, frameworks, structure, package versions',
1059
+ mimeType: 'application/json'
1060
+ },
1061
+ {
1062
+ uri: 'persista://codebase/dna-deep',
1063
+ name: '🧬 Deep Codebase DNA',
1064
+ description: 'Conventions, patterns, architecture, critical paths - the soul of the codebase',
1065
+ mimeType: 'application/json'
1066
+ },
1067
+ {
1068
+ uri: 'persista://git/changes',
1069
+ name: 'Git Changes',
1070
+ description: 'Commits and changes since last session',
1071
+ mimeType: 'application/json'
1072
+ },
1073
+ {
1074
+ uri: 'persista://knowledge/relevant',
1075
+ name: 'Relevant Knowledge',
1076
+ description: 'Best practices for this project\'s tech stack',
1077
+ mimeType: 'application/json'
1078
+ },
1079
+ {
1080
+ uri: 'persista://errors/learned',
1081
+ name: '⚠️ Error Learning',
1082
+ description: 'Build errors, test failures, and anti-patterns detected this session',
1083
+ mimeType: 'application/json'
1084
+ }
1085
+ ]
1086
+ };
1087
+ });
1088
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
1089
+ const { uri } = request.params;
1090
+ switch (uri) {
1091
+ case 'persista://session/context': {
1092
+ const context = {
1093
+ _note: 'Your persistent context. Use this to personalize responses.',
1094
+ user: {
1095
+ id: sessionState.userId,
1096
+ preferences: sessionState.userMemory?.preferences || [],
1097
+ facts: sessionState.userMemory?.facts || [],
1098
+ behaviors: sessionState.userMemory?.behaviors || [],
1099
+ patterns: sessionState.userMemory?.patterns || []
1100
+ },
1101
+ currentProject: {
1102
+ name: sessionState.codebaseDNA?.projectName,
1103
+ directory: sessionState.projectDir,
1104
+ techStack: sessionState.codebaseDNA?.techStack || [],
1105
+ frameworks: sessionState.codebaseDNA?.frameworks || [],
1106
+ structure: sessionState.codebaseDNA?.structure,
1107
+ hasTypeScript: sessionState.codebaseDNA?.hasTypeScript,
1108
+ packageManager: sessionState.codebaseDNA?.packageManager,
1109
+ packageVersions: sessionState.packageVersions
1110
+ },
1111
+ gitStatus: sessionState.gitInfo ? {
1112
+ branch: sessionState.gitInfo.branch,
1113
+ hasUncommittedChanges: sessionState.gitInfo.hasUncommittedChanges,
1114
+ commitsSinceLastSession: sessionState.gitInfo.commitsSinceLastSession.length,
1115
+ recentChanges: sessionState.gitInfo.commitsSinceLastSession.slice(0, 5).map(c => c.message),
1116
+ changedFiles: sessionState.gitInfo.changedFiles.slice(0, 10)
1117
+ } : null,
1118
+ session: {
1119
+ startTime: sessionState.startTime,
1120
+ errorsThisSession: sessionState.recentErrors.length,
1121
+ buildErrors: sessionState.buildHistory.length,
1122
+ testFailures: sessionState.testHistory.filter(t => t.failed > 0).length,
1123
+ antiPatternsLearned: sessionState.antiPatterns.length
1124
+ },
1125
+ recentConversations: sessionState.userMemory?.recentHistory?.slice(0, 3) || [],
1126
+ errorLearning: {
1127
+ recentBuildErrors: sessionState.buildHistory.slice(-3).map(b => ({
1128
+ command: b.command,
1129
+ errorCount: b.errors.length,
1130
+ timestamp: b.timestamp
1131
+ })),
1132
+ recentTestFailures: sessionState.testHistory.slice(-3).map(t => ({
1133
+ framework: t.framework,
1134
+ failed: t.failed,
1135
+ passed: t.passed,
1136
+ timestamp: t.timestamp
1137
+ })),
1138
+ antiPatterns: sessionState.antiPatterns.slice(-5).map(ap => ({
1139
+ pattern: ap.pattern,
1140
+ severity: ap.severity,
1141
+ occurrences: ap.occurrences
1142
+ }))
1143
+ }
1144
+ };
1145
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(context, null, 2) }] };
1146
+ }
1147
+ case 'persista://user/memory': {
1148
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(sessionState.userMemory || {}, null, 2) }] };
1149
+ }
1150
+ case 'persista://codebase/dna': {
1151
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({
1152
+ ...sessionState.codebaseDNA,
1153
+ packageVersions: sessionState.packageVersions
1154
+ }, null, 2) }] };
1155
+ }
1156
+ case 'persista://codebase/dna-deep': {
1157
+ // Lazy load deep DNA if not already analyzed
1158
+ if (!codebaseDNADeep) {
1159
+ codebaseDNADeep = await analyzeCodebaseDNADeep();
1160
+ if (codebaseDNADeep) {
1161
+ codebaseDNADeep.decisions = await loadDecisions();
1162
+ }
1163
+ }
1164
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(codebaseDNADeep || {
1165
+ error: 'Could not analyze codebase'
1166
+ }, null, 2) }] };
1167
+ }
1168
+ case 'persista://git/changes': {
1169
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(sessionState.gitInfo || {}, null, 2) }] };
1170
+ }
1171
+ case 'persista://knowledge/relevant': {
1172
+ // Fetch relevant knowledge for this project's tech stack
1173
+ const knowledge = [];
1174
+ for (const tech of (sessionState.codebaseDNA?.techStack || [])) {
1175
+ try {
1176
+ const version = sessionState.packageVersions[tech.toLowerCase()];
1177
+ const result = await apiRequest('GET', `/knowledge?domain=${tech.toLowerCase()}${version ? `&version=${version}` : ''}`);
1178
+ if (Array.isArray(result))
1179
+ knowledge.push(...result);
1180
+ }
1181
+ catch { }
1182
+ }
1183
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(knowledge, null, 2) }] };
1184
+ }
1185
+ case 'persista://errors/learned': {
1186
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({
1187
+ _note: 'Errors and anti-patterns detected this session. Avoid these patterns!',
1188
+ buildHistory: sessionState.buildHistory.map(b => ({
1189
+ command: b.command,
1190
+ exitCode: b.exitCode,
1191
+ timestamp: b.timestamp,
1192
+ errors: b.errors,
1193
+ warnings: b.warnings
1194
+ })),
1195
+ testHistory: sessionState.testHistory.map(t => ({
1196
+ framework: t.framework,
1197
+ passed: t.passed,
1198
+ failed: t.failed,
1199
+ skipped: t.skipped,
1200
+ timestamp: t.timestamp,
1201
+ failures: t.failures
1202
+ })),
1203
+ antiPatterns: sessionState.antiPatterns.map(ap => ({
1204
+ pattern: ap.pattern,
1205
+ context: ap.context,
1206
+ severity: ap.severity,
1207
+ occurrences: ap.occurrences,
1208
+ lastSeen: ap.lastSeen
1209
+ })),
1210
+ recentErrors: sessionState.recentErrors.slice(-10)
1211
+ }, null, 2) }] };
1212
+ }
1213
+ default:
1214
+ throw new Error(`Unknown resource: ${uri}`);
1215
+ }
1216
+ });
1217
+ // ============================================
1218
+ // TOOLS - All capabilities
1219
+ // ============================================
1220
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
1221
+ return {
1222
+ tools: [
1223
+ // ═══════════════════════════════════════
1224
+ // MEMORY TOOLS (v3)
1225
+ // ═══════════════════════════════════════
1226
+ {
1227
+ name: 'memory_save',
1228
+ description: 'Save something about the user to persistent memory. Supports confidence decay, code attachment, and smart merging.',
1229
+ inputSchema: {
1230
+ type: 'object',
1231
+ properties: {
1232
+ category: { type: 'string', enum: ['preference', 'fact', 'project', 'behavior', 'custom'] },
1233
+ key: { type: 'string', description: 'Unique identifier' },
1234
+ value: { type: 'object', description: 'Data to store (JSON)' },
1235
+ confidence: { type: 'number', minimum: 0, maximum: 1, description: 'Confidence 0-1 (default 1.0)' },
1236
+ decay_rate: { type: 'number', description: 'How fast to decay (default 0.01)' },
1237
+ code_file: { type: 'string', description: 'Attach to specific file' },
1238
+ code_line_start: { type: 'number', description: 'Start line for code attachment' },
1239
+ code_line_end: { type: 'number', description: 'End line for code attachment' },
1240
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags for categorization' },
1241
+ merge: { type: 'boolean', description: 'Smart merge with existing (default false)' }
1242
+ },
1243
+ required: ['category', 'key', 'value']
1244
+ }
1245
+ },
1246
+ {
1247
+ name: 'memory_get',
1248
+ description: 'Get all memories with effective confidence scores',
1249
+ inputSchema: { type: 'object', properties: {}, required: [] }
1250
+ },
1251
+ {
1252
+ name: 'memory_search',
1253
+ description: 'Semantic search through memories',
1254
+ inputSchema: {
1255
+ type: 'object',
1256
+ properties: {
1257
+ query: { type: 'string', description: 'Search query' },
1258
+ category: { type: 'string', description: 'Filter by category' }
1259
+ },
1260
+ required: ['query']
1261
+ }
1262
+ },
1263
+ {
1264
+ name: 'memory_for_file',
1265
+ description: 'Get memories attached to a specific file/line',
1266
+ inputSchema: {
1267
+ type: 'object',
1268
+ properties: {
1269
+ file: { type: 'string', description: 'File path' },
1270
+ line: { type: 'number', description: 'Line number (optional)' }
1271
+ },
1272
+ required: ['file']
1273
+ }
1274
+ },
1275
+ {
1276
+ name: 'memory_history',
1277
+ description: 'Get history of changes to a memory',
1278
+ inputSchema: {
1279
+ type: 'object',
1280
+ properties: { key: { type: 'string' } },
1281
+ required: ['key']
1282
+ }
1283
+ },
1284
+ {
1285
+ name: 'memory_extract',
1286
+ description: 'Auto-extract memories from text (preferences, facts, tech choices)',
1287
+ inputSchema: {
1288
+ type: 'object',
1289
+ properties: {
1290
+ text: { type: 'string', description: 'Text to analyze' }
1291
+ },
1292
+ required: ['text']
1293
+ }
1294
+ },
1295
+ {
1296
+ name: 'memory_forget',
1297
+ description: 'Delete a memory',
1298
+ inputSchema: {
1299
+ type: 'object',
1300
+ properties: { key: { type: 'string' } },
1301
+ required: ['key']
1302
+ }
1303
+ },
1304
+ // ═══════════════════════════════════════
1305
+ // CONTEXT TOOLS (v3)
1306
+ // ═══════════════════════════════════════
1307
+ {
1308
+ name: 'context_pack',
1309
+ description: 'Smart pack context within token budget, prioritizing relevance',
1310
+ inputSchema: {
1311
+ type: 'object',
1312
+ properties: {
1313
+ max_tokens: { type: 'number', description: 'Token budget (default 4000)' },
1314
+ current_context: { type: 'string', description: 'Current conversation for relevance matching' },
1315
+ include_memories: { type: 'boolean', description: 'Include memories (default true)' },
1316
+ include_history: { type: 'boolean', description: 'Include history (default true)' }
1317
+ },
1318
+ required: []
1319
+ }
1320
+ },
1321
+ {
1322
+ name: 'context_relevant',
1323
+ description: 'Get most relevant context for current conversation',
1324
+ inputSchema: {
1325
+ type: 'object',
1326
+ properties: {
1327
+ context: { type: 'string', description: 'Current conversation/topic' },
1328
+ limit: { type: 'number', description: 'Max results (default 10)' }
1329
+ },
1330
+ required: []
1331
+ }
1332
+ },
1333
+ {
1334
+ name: 'context_thread_create',
1335
+ description: 'Create a context thread to link related conversations',
1336
+ inputSchema: {
1337
+ type: 'object',
1338
+ properties: {
1339
+ name: { type: 'string', description: 'Thread name' },
1340
+ description: { type: 'string' },
1341
+ tags: { type: 'array', items: { type: 'string' } }
1342
+ },
1343
+ required: ['name']
1344
+ }
1345
+ },
1346
+ {
1347
+ name: 'context_summary_save',
1348
+ description: 'Save conversation summary with threading and milestone support',
1349
+ inputSchema: {
1350
+ type: 'object',
1351
+ properties: {
1352
+ summary: { type: 'string' },
1353
+ key_points: { type: 'array', items: { type: 'string' } },
1354
+ decisions: { type: 'array', items: { type: 'string' } },
1355
+ action_items: { type: 'array', items: { type: 'string' } },
1356
+ thread_id: { type: 'string', description: 'Link to thread' },
1357
+ priority: { type: 'number', description: '1-10 (default 5)' },
1358
+ is_milestone: { type: 'boolean', description: 'Mark as important milestone' }
1359
+ },
1360
+ required: ['summary']
1361
+ }
1362
+ },
1363
+ // ═══════════════════════════════════════
1364
+ // SYNC TOOLS (v3)
1365
+ // ═══════════════════════════════════════
1366
+ {
1367
+ name: 'sync_discover',
1368
+ description: 'Discover if other AI instances are working on this project',
1369
+ inputSchema: { type: 'object', properties: {}, required: [] }
1370
+ },
1371
+ {
1372
+ name: 'sync_intent',
1373
+ description: 'Broadcast intent to edit a file (prevents conflicts)',
1374
+ inputSchema: {
1375
+ type: 'object',
1376
+ properties: {
1377
+ intent_type: { type: 'string', enum: ['edit', 'refactor', 'delete', 'create'] },
1378
+ target_file: { type: 'string', description: 'File to edit' },
1379
+ target_lines: { type: 'string', description: 'Line range e.g. "10-50"' },
1380
+ description: { type: 'string', description: 'What you plan to do' }
1381
+ },
1382
+ required: ['intent_type', 'target_file']
1383
+ }
1384
+ },
1385
+ {
1386
+ name: 'sync_check_conflicts',
1387
+ description: 'Check if anyone else is working on a file',
1388
+ inputSchema: {
1389
+ type: 'object',
1390
+ properties: {
1391
+ file: { type: 'string', description: 'File to check' }
1392
+ },
1393
+ required: ['file']
1394
+ }
1395
+ },
1396
+ // ═══════════════════════════════════════
1397
+ // KNOWLEDGE TOOLS (v3)
1398
+ // ═══════════════════════════════════════
1399
+ {
1400
+ name: 'knowledge_check',
1401
+ description: 'Check best practices - VERSION AWARE based on your package.json',
1402
+ inputSchema: {
1403
+ type: 'object',
1404
+ properties: {
1405
+ domain: { type: 'string', description: 'Tech domain (react, nextjs, etc)' },
1406
+ topic: { type: 'string', description: 'Specific topic' },
1407
+ version: { type: 'string', description: 'Override version (auto-detected from package.json)' }
1408
+ },
1409
+ required: ['domain']
1410
+ }
1411
+ },
1412
+ {
1413
+ name: 'knowledge_deprecations',
1414
+ description: 'Get deprecation warnings for a technology',
1415
+ inputSchema: {
1416
+ type: 'object',
1417
+ properties: {
1418
+ domain: { type: 'string' },
1419
+ version: { type: 'string' }
1420
+ },
1421
+ required: ['domain']
1422
+ }
1423
+ },
1424
+ {
1425
+ name: 'knowledge_scan_code',
1426
+ description: 'Scan code for deprecated patterns',
1427
+ inputSchema: {
1428
+ type: 'object',
1429
+ properties: {
1430
+ code: { type: 'string', description: 'Code to scan' },
1431
+ file_path: { type: 'string', description: 'File path for context' }
1432
+ },
1433
+ required: ['code']
1434
+ }
1435
+ },
1436
+ {
1437
+ name: 'knowledge_learn_pattern',
1438
+ description: 'Learn a user pattern (your personal best practice)',
1439
+ inputSchema: {
1440
+ type: 'object',
1441
+ properties: {
1442
+ domain: { type: 'string', description: 'Tech domain' },
1443
+ pattern_name: { type: 'string', description: 'Name of the pattern' },
1444
+ description: { type: 'string' },
1445
+ code_example: { type: 'string' }
1446
+ },
1447
+ required: ['domain', 'pattern_name']
1448
+ }
1449
+ },
1450
+ {
1451
+ name: 'knowledge_my_patterns',
1452
+ description: 'Get user\'s learned patterns',
1453
+ inputSchema: {
1454
+ type: 'object',
1455
+ properties: {
1456
+ domain: { type: 'string', description: 'Filter by domain' }
1457
+ },
1458
+ required: []
1459
+ }
1460
+ },
1461
+ // ═══════════════════════════════════════
1462
+ // ERROR TRACKING
1463
+ // ═══════════════════════════════════════
1464
+ {
1465
+ name: 'error_capture',
1466
+ description: 'Capture an error for learning (AI will avoid this pattern)',
1467
+ inputSchema: {
1468
+ type: 'object',
1469
+ properties: {
1470
+ type: { type: 'string', enum: ['build_error', 'runtime_error', 'test_failure', 'lint_error', 'type_error', 'other'] },
1471
+ message: { type: 'string' },
1472
+ file: { type: 'string' },
1473
+ context: { type: 'string', description: 'What was being attempted' },
1474
+ ai_generated: { type: 'boolean', description: 'Was this AI-generated code?' }
1475
+ },
1476
+ required: ['type', 'message']
1477
+ }
1478
+ },
1479
+ {
1480
+ name: 'error_patterns',
1481
+ description: 'Get known error patterns to avoid',
1482
+ inputSchema: { type: 'object', properties: {}, required: [] }
1483
+ },
1484
+ // ═══════════════════════════════════════
1485
+ // SESSION
1486
+ // ═══════════════════════════════════════
1487
+ {
1488
+ name: 'session_refresh',
1489
+ description: 'Refresh session context (reload memories, git, etc)',
1490
+ inputSchema: { type: 'object', properties: {}, required: [] }
1491
+ },
1492
+ {
1493
+ name: 'session_summary',
1494
+ description: 'Get full session context summary',
1495
+ inputSchema: { type: 'object', properties: {}, required: [] }
1496
+ },
1497
+ // ═══════════════════════════════════════
1498
+ // DNA TOOLS (Phase 3)
1499
+ // ═══════════════════════════════════════
1500
+ {
1501
+ name: 'dna_analyze',
1502
+ description: 'Deep analyze the codebase DNA - patterns, conventions, architecture. Run this to understand the codebase deeply.',
1503
+ inputSchema: { type: 'object', properties: {}, required: [] }
1504
+ },
1505
+ {
1506
+ name: 'dna_conventions',
1507
+ description: 'Get coding conventions for this codebase (indentation, quotes, naming, etc.)',
1508
+ inputSchema: { type: 'object', properties: {}, required: [] }
1509
+ },
1510
+ {
1511
+ name: 'dna_patterns',
1512
+ description: 'Get detected code patterns (imports, components, state management, API patterns)',
1513
+ inputSchema: { type: 'object', properties: {}, required: [] }
1514
+ },
1515
+ {
1516
+ name: 'dna_architecture',
1517
+ description: 'Get architecture info (style, layers, critical paths)',
1518
+ inputSchema: { type: 'object', properties: {}, required: [] }
1519
+ },
1520
+ {
1521
+ name: 'dna_add_decision',
1522
+ description: 'Record an architecture or tech decision for this project',
1523
+ inputSchema: {
1524
+ type: 'object',
1525
+ properties: {
1526
+ decision: { type: 'string', description: 'What was decided' },
1527
+ reason: { type: 'string', description: 'Why this decision was made' },
1528
+ alternatives: { type: 'array', items: { type: 'string' }, description: 'Alternatives considered' }
1529
+ },
1530
+ required: ['decision', 'reason']
1531
+ }
1532
+ },
1533
+ {
1534
+ name: 'dna_decisions',
1535
+ description: 'Get all recorded decisions for this project',
1536
+ inputSchema: { type: 'object', properties: {}, required: [] }
1537
+ },
1538
+ {
1539
+ name: 'dna_critical_paths',
1540
+ description: 'Get critical paths in the codebase that need careful handling',
1541
+ inputSchema: { type: 'object', properties: {}, required: [] }
1542
+ },
1543
+ {
1544
+ name: 'dna_mark_critical',
1545
+ description: 'Mark a path as critical (needs careful handling)',
1546
+ inputSchema: {
1547
+ type: 'object',
1548
+ properties: {
1549
+ path: { type: 'string', description: 'File or directory path' },
1550
+ reason: { type: 'string', description: 'Why this is critical' }
1551
+ },
1552
+ required: ['path', 'reason']
1553
+ }
1554
+ }
1555
+ ]
1556
+ };
1557
+ });
1558
+ // ============================================
1559
+ // TOOL HANDLERS
1560
+ // ============================================
1561
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
1562
+ const { name, arguments: args } = request.params;
1563
+ const typedArgs = args;
1564
+ try {
1565
+ switch (name) {
1566
+ // ═══════════════════════════════════════
1567
+ // MEMORY HANDLERS
1568
+ // ═══════════════════════════════════════
1569
+ case 'memory_save': {
1570
+ const result = await apiRequest('POST', `/memory/${USER_ID}/context`, {
1571
+ category: typedArgs.category,
1572
+ key: typedArgs.key,
1573
+ value: typedArgs.value,
1574
+ confidence: typedArgs.confidence ?? 1.0,
1575
+ decay_rate: typedArgs.decay_rate ?? 0.01,
1576
+ code_file: typedArgs.code_file,
1577
+ code_line_start: typedArgs.code_line_start,
1578
+ code_line_end: typedArgs.code_line_end,
1579
+ tags: typedArgs.tags || [],
1580
+ merge: typedArgs.merge ?? false,
1581
+ source: 'explicit'
1582
+ });
1583
+ return { content: [{ type: 'text', text: `✅ Saved: ${typedArgs.key}\n${JSON.stringify(result, null, 2)}` }] };
1584
+ }
1585
+ case 'memory_get': {
1586
+ const result = await apiRequest('GET', `/memory/${USER_ID}`);
1587
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1588
+ }
1589
+ case 'memory_search': {
1590
+ const params = new URLSearchParams({ q: typedArgs.query });
1591
+ if (typedArgs.category)
1592
+ params.append('category', typedArgs.category);
1593
+ const result = await apiRequest('GET', `/memory/${USER_ID}/search?${params}`);
1594
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1595
+ }
1596
+ case 'memory_for_file': {
1597
+ const file = encodeURIComponent(typedArgs.file);
1598
+ const line = typedArgs.line ? `?line=${typedArgs.line}` : '';
1599
+ const result = await apiRequest('GET', `/memory/${USER_ID}/code/${file}${line}`);
1600
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1601
+ }
1602
+ case 'memory_history': {
1603
+ const result = await apiRequest('GET', `/memory/${USER_ID}/history/${typedArgs.key}`);
1604
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1605
+ }
1606
+ case 'memory_extract': {
1607
+ const result = await apiRequest('POST', `/memory/${USER_ID}/extract`, {
1608
+ text: typedArgs.text
1609
+ });
1610
+ return { content: [{ type: 'text', text: `📝 Extracted:\n${JSON.stringify(result, null, 2)}` }] };
1611
+ }
1612
+ case 'memory_forget': {
1613
+ await apiRequest('DELETE', `/memory/${USER_ID}/context/${typedArgs.key}`);
1614
+ return { content: [{ type: 'text', text: `✅ Deleted: ${typedArgs.key}` }] };
1615
+ }
1616
+ // ═══════════════════════════════════════
1617
+ // CONTEXT HANDLERS
1618
+ // ═══════════════════════════════════════
1619
+ case 'context_pack': {
1620
+ const result = await apiRequest('POST', `/context/${USER_ID}/pack`, {
1621
+ max_tokens: typedArgs.max_tokens ?? 4000,
1622
+ current_context: typedArgs.current_context,
1623
+ include_memories: typedArgs.include_memories ?? true,
1624
+ include_history: typedArgs.include_history ?? true
1625
+ });
1626
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1627
+ }
1628
+ case 'context_relevant': {
1629
+ const params = new URLSearchParams();
1630
+ if (typedArgs.context)
1631
+ params.append('context', typedArgs.context);
1632
+ if (typedArgs.limit)
1633
+ params.append('limit', String(typedArgs.limit));
1634
+ const result = await apiRequest('GET', `/context/${USER_ID}/relevant?${params}`);
1635
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1636
+ }
1637
+ case 'context_thread_create': {
1638
+ const result = await apiRequest('POST', `/context/${USER_ID}/thread`, {
1639
+ name: typedArgs.name,
1640
+ description: typedArgs.description,
1641
+ tags: typedArgs.tags
1642
+ });
1643
+ return { content: [{ type: 'text', text: `✅ Thread created: ${result.id}` }] };
1644
+ }
1645
+ case 'context_summary_save': {
1646
+ const result = await apiRequest('POST', `/context/${USER_ID}/summary`, {
1647
+ summary: typedArgs.summary,
1648
+ key_points: typedArgs.key_points,
1649
+ decisions: typedArgs.decisions,
1650
+ action_items: typedArgs.action_items,
1651
+ thread_id: typedArgs.thread_id,
1652
+ priority: typedArgs.priority ?? 5,
1653
+ is_milestone: typedArgs.is_milestone ?? false
1654
+ });
1655
+ return { content: [{ type: 'text', text: `✅ Summary saved${typedArgs.is_milestone ? ' (MILESTONE)' : ''}` }] };
1656
+ }
1657
+ // ═══════════════════════════════════════
1658
+ // SYNC HANDLERS
1659
+ // ═══════════════════════════════════════
1660
+ case 'sync_discover': {
1661
+ const result = await apiRequest('GET', `/sync/discover/${encodeURIComponent(PROJECT_DIR)}`);
1662
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1663
+ }
1664
+ case 'sync_intent': {
1665
+ // First discover/join workspace
1666
+ const discovery = await apiRequest('GET', `/sync/discover/${encodeURIComponent(PROJECT_DIR)}`);
1667
+ if (!discovery.found || !discovery.workspace) {
1668
+ return { content: [{ type: 'text', text: 'No workspace found. Create one to enable intent broadcasting.' }] };
1669
+ }
1670
+ const result = await apiRequest('POST', `/sync/workspace/${discovery.workspace.id}/intent`, {
1671
+ instance_id: `${USER_ID}_${sessionState.startTime.getTime()}`,
1672
+ intent_type: typedArgs.intent_type,
1673
+ target_file: typedArgs.target_file,
1674
+ target_lines: typedArgs.target_lines,
1675
+ description: typedArgs.description
1676
+ });
1677
+ if (result.conflict) {
1678
+ return { content: [{ type: 'text', text: `⚠️ CONFLICT:\n${JSON.stringify(result, null, 2)}` }] };
1679
+ }
1680
+ return { content: [{ type: 'text', text: `✅ Intent registered: ${typedArgs.intent_type} on ${typedArgs.target_file}` }] };
1681
+ }
1682
+ case 'sync_check_conflicts': {
1683
+ const discovery = await apiRequest('GET', `/sync/discover/${encodeURIComponent(PROJECT_DIR)}`);
1684
+ if (!discovery.found || !discovery.workspace) {
1685
+ return { content: [{ type: 'text', text: 'No workspace - no conflicts possible' }] };
1686
+ }
1687
+ const result = await apiRequest('GET', `/sync/workspace/${discovery.workspace.id}/intents?file=${encodeURIComponent(typedArgs.file)}`);
1688
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1689
+ }
1690
+ // ═══════════════════════════════════════
1691
+ // KNOWLEDGE HANDLERS
1692
+ // ═══════════════════════════════════════
1693
+ case 'knowledge_check': {
1694
+ const domain = typedArgs.domain.toLowerCase();
1695
+ const version = typedArgs.version || sessionState.packageVersions[domain];
1696
+ const params = new URLSearchParams({ domain });
1697
+ if (typedArgs.topic)
1698
+ params.append('topic', typedArgs.topic);
1699
+ if (version)
1700
+ params.append('version', version);
1701
+ const result = await apiRequest('GET', `/knowledge?${params}`);
1702
+ if (!result || result.length === 0) {
1703
+ return { content: [{ type: 'text', text: `No knowledge for ${domain}${version ? ` v${version}` : ''}` }] };
1704
+ }
1705
+ return { content: [{ type: 'text', text: `📚 Knowledge for ${domain}${version ? ` v${version}` : ''}:\n${JSON.stringify(result, null, 2)}` }] };
1706
+ }
1707
+ case 'knowledge_deprecations': {
1708
+ const domain = typedArgs.domain.toLowerCase();
1709
+ const version = typedArgs.version || sessionState.packageVersions[domain];
1710
+ const params = version ? `?version=${version}` : '';
1711
+ const result = await apiRequest('GET', `/knowledge/deprecated/${domain}${params}`);
1712
+ return { content: [{ type: 'text', text: `⚠️ Deprecations:\n${JSON.stringify(result, null, 2)}` }] };
1713
+ }
1714
+ case 'knowledge_scan_code': {
1715
+ const result = await apiRequest('POST', `/knowledge/scan`, {
1716
+ code: typedArgs.code,
1717
+ file_path: typedArgs.file_path,
1718
+ package_json: { dependencies: sessionState.packageVersions },
1719
+ user_id: USER_ID
1720
+ });
1721
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1722
+ }
1723
+ case 'knowledge_learn_pattern': {
1724
+ const result = await apiRequest('POST', `/knowledge/user/${USER_ID}/patterns`, {
1725
+ domain: typedArgs.domain,
1726
+ pattern_name: typedArgs.pattern_name,
1727
+ description: typedArgs.description,
1728
+ code_example: typedArgs.code_example
1729
+ });
1730
+ return { content: [{ type: 'text', text: `✅ Pattern learned: ${typedArgs.pattern_name}` }] };
1731
+ }
1732
+ case 'knowledge_my_patterns': {
1733
+ const params = typedArgs.domain ? `?domain=${typedArgs.domain}` : '';
1734
+ const result = await apiRequest('GET', `/knowledge/user/${USER_ID}/patterns${params}`);
1735
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
1736
+ }
1737
+ // ═══════════════════════════════════════
1738
+ // ERROR HANDLERS
1739
+ // ═══════════════════════════════════════
1740
+ case 'error_capture': {
1741
+ const error = {
1742
+ type: typedArgs.type,
1743
+ message: typedArgs.message,
1744
+ file: typedArgs.file,
1745
+ context: typedArgs.context,
1746
+ timestamp: new Date()
1747
+ };
1748
+ captureError(error);
1749
+ if (typedArgs.ai_generated) {
1750
+ await apiRequest('POST', `/memory/${USER_ID}/context`, {
1751
+ category: 'behavior',
1752
+ key: `anti_pattern_${Date.now()}`,
1753
+ value: {
1754
+ type: 'error_pattern',
1755
+ errorType: typedArgs.type,
1756
+ context: typedArgs.context,
1757
+ message: typedArgs.message,
1758
+ file: typedArgs.file
1759
+ },
1760
+ source: 'auto',
1761
+ confidence: 0.9
1762
+ });
1763
+ }
1764
+ return { content: [{ type: 'text', text: `✅ Error captured for learning` }] };
1765
+ }
1766
+ case 'error_patterns': {
1767
+ const behaviors = await apiRequest('GET', `/memory/${USER_ID}/context/behavior`);
1768
+ const behaviorArray = Array.isArray(behaviors) ? behaviors : [];
1769
+ const antiPatterns = behaviorArray.filter((b) => b.value && b.value.type === 'error_pattern');
1770
+ return { content: [{ type: 'text', text: JSON.stringify({
1771
+ antiPatterns,
1772
+ recentSessionErrors: sessionState.recentErrors
1773
+ }, null, 2) }] };
1774
+ }
1775
+ // ═══════════════════════════════════════
1776
+ // SESSION HANDLERS
1777
+ // ═══════════════════════════════════════
1778
+ case 'session_refresh': {
1779
+ await initializeSession();
1780
+ return { content: [{ type: 'text', text: '✅ Session context refreshed' }] };
1781
+ }
1782
+ case 'session_summary': {
1783
+ return { content: [{ type: 'text', text: JSON.stringify({
1784
+ user: sessionState.userId,
1785
+ project: sessionState.codebaseDNA,
1786
+ git: sessionState.gitInfo,
1787
+ memory: {
1788
+ preferences: sessionState.userMemory?.preferences.length || 0,
1789
+ facts: sessionState.userMemory?.facts.length || 0,
1790
+ behaviors: sessionState.userMemory?.behaviors.length || 0,
1791
+ patterns: sessionState.userMemory?.patterns.length || 0
1792
+ },
1793
+ errors: sessionState.recentErrors.length,
1794
+ packageVersions: sessionState.packageVersions,
1795
+ sessionStart: sessionState.startTime
1796
+ }, null, 2) }] };
1797
+ }
1798
+ // ═══════════════════════════════════════
1799
+ // DNA HANDLERS (Phase 3)
1800
+ // ═══════════════════════════════════════
1801
+ case 'dna_analyze': {
1802
+ codebaseDNADeep = await analyzeCodebaseDNADeep();
1803
+ if (codebaseDNADeep) {
1804
+ codebaseDNADeep.decisions = await loadDecisions();
1805
+ }
1806
+ return { content: [{ type: 'text', text: `🧬 Codebase DNA analyzed:\n${JSON.stringify(codebaseDNADeep, null, 2)}` }] };
1807
+ }
1808
+ case 'dna_conventions': {
1809
+ if (!codebaseDNADeep) {
1810
+ codebaseDNADeep = await analyzeCodebaseDNADeep();
1811
+ }
1812
+ return { content: [{ type: 'text', text: JSON.stringify({
1813
+ conventions: codebaseDNADeep?.conventions || {},
1814
+ _note: 'Follow these conventions when writing code for this project'
1815
+ }, null, 2) }] };
1816
+ }
1817
+ case 'dna_patterns': {
1818
+ if (!codebaseDNADeep) {
1819
+ codebaseDNADeep = await analyzeCodebaseDNADeep();
1820
+ }
1821
+ return { content: [{ type: 'text', text: JSON.stringify({
1822
+ patterns: codebaseDNADeep?.patterns || {},
1823
+ _note: 'These patterns are used throughout the codebase'
1824
+ }, null, 2) }] };
1825
+ }
1826
+ case 'dna_architecture': {
1827
+ if (!codebaseDNADeep) {
1828
+ codebaseDNADeep = await analyzeCodebaseDNADeep();
1829
+ }
1830
+ return { content: [{ type: 'text', text: JSON.stringify({
1831
+ architecture: codebaseDNADeep?.architecture || {},
1832
+ criticalPaths: codebaseDNADeep?.criticalPaths || [],
1833
+ _note: 'Architecture style and important paths'
1834
+ }, null, 2) }] };
1835
+ }
1836
+ case 'dna_add_decision': {
1837
+ const decision = {
1838
+ decision: typedArgs.decision,
1839
+ reason: typedArgs.reason,
1840
+ alternatives: typedArgs.alternatives,
1841
+ project: sessionState.codebaseDNA?.projectName,
1842
+ timestamp: new Date().toISOString()
1843
+ };
1844
+ await apiRequest('POST', `/memory/${USER_ID}/context`, {
1845
+ category: 'project',
1846
+ key: `decision_${Date.now()}`,
1847
+ value: decision,
1848
+ source: 'explicit',
1849
+ confidence: 1.0
1850
+ });
1851
+ // Update local cache
1852
+ if (codebaseDNADeep) {
1853
+ codebaseDNADeep.decisions.push({
1854
+ decision: decision.decision,
1855
+ reason: decision.reason,
1856
+ date: new Date(),
1857
+ alternatives: decision.alternatives
1858
+ });
1859
+ }
1860
+ return { content: [{ type: 'text', text: `✅ Decision recorded: ${decision.decision}` }] };
1861
+ }
1862
+ case 'dna_decisions': {
1863
+ const decisions = await loadDecisions();
1864
+ return { content: [{ type: 'text', text: JSON.stringify({
1865
+ decisions,
1866
+ count: decisions.length,
1867
+ _note: 'Past decisions for this project - consider these when making new choices'
1868
+ }, null, 2) }] };
1869
+ }
1870
+ case 'dna_critical_paths': {
1871
+ if (!codebaseDNADeep) {
1872
+ codebaseDNADeep = await analyzeCodebaseDNADeep();
1873
+ }
1874
+ // Also load any manually marked critical paths
1875
+ const manualCritical = await apiRequest('GET', `/memory/${USER_ID}/context/project`).catch(() => []);
1876
+ const manual = (Array.isArray(manualCritical) ? manualCritical : [])
1877
+ .filter((c) => c.key?.startsWith('critical_'))
1878
+ .map((c) => ({ path: c.value?.path, reason: c.value?.reason }));
1879
+ const allCritical = [
1880
+ ...(codebaseDNADeep?.criticalPaths || []),
1881
+ ...manual
1882
+ ];
1883
+ return { content: [{ type: 'text', text: JSON.stringify({
1884
+ criticalPaths: allCritical,
1885
+ _note: 'Be careful when modifying these paths!'
1886
+ }, null, 2) }] };
1887
+ }
1888
+ case 'dna_mark_critical': {
1889
+ await apiRequest('POST', `/memory/${USER_ID}/context`, {
1890
+ category: 'project',
1891
+ key: `critical_${Date.now()}`,
1892
+ value: {
1893
+ path: typedArgs.path,
1894
+ reason: typedArgs.reason,
1895
+ markedAt: new Date().toISOString()
1896
+ },
1897
+ source: 'explicit',
1898
+ confidence: 1.0
1899
+ });
1900
+ return { content: [{ type: 'text', text: `⚠️ Marked as critical: ${typedArgs.path}\nReason: ${typedArgs.reason}` }] };
1901
+ }
1902
+ default:
1903
+ return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
1904
+ }
1905
+ }
1906
+ catch (error) {
1907
+ const message = error instanceof Error ? error.message : 'Unknown error';
1908
+ return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
1909
+ }
1910
+ });
1911
+ // ============================================
1912
+ // START SERVER
1913
+ // ============================================
1914
+ async function main() {
1915
+ await initializeSession();
1916
+ // Start background watchers (Phase 2)
1917
+ startWatcher();
1918
+ const transport = new stdio_js_1.StdioServerTransport();
1919
+ await server.connect(transport);
1920
+ console.error('✨ Persista MCP v3.1 running with background watchers');
1921
+ }
1922
+ // Cleanup on exit
1923
+ process.on('SIGINT', () => {
1924
+ stopWatcher();
1925
+ process.exit(0);
1926
+ });
1927
+ process.on('SIGTERM', () => {
1928
+ stopWatcher();
1929
+ process.exit(0);
1930
+ });
1931
+ main().catch((error) => {
1932
+ console.error('Fatal error:', error);
1933
+ stopWatcher();
1934
+ process.exit(1);
1935
+ });
1936
+ //# sourceMappingURL=index.js.map