@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/README.md +126 -0
- package/dist/index-direct.js +307 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1936 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
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
|