@supermodeltools/mcp-server 0.4.4 → 0.4.6

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.
@@ -1,8 +1,41 @@
1
1
  "use strict";
2
2
  /**
3
- * Automatic repository zipping with gitignore support
3
+ * Automatic repository zipping with gitignore and dockerignore support
4
4
  * Creates temporary ZIP files for codebase analysis
5
5
  */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
6
39
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
40
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
41
  };
@@ -15,9 +48,11 @@ const os_1 = require("os");
15
48
  const archiver_1 = __importDefault(require("archiver"));
16
49
  const ignore_1 = __importDefault(require("ignore"));
17
50
  const crypto_1 = require("crypto");
51
+ const constants_1 = require("../constants");
52
+ const logger = __importStar(require("./logger"));
18
53
  /**
19
54
  * Standard exclusions for security and size optimization
20
- * These patterns are applied in addition to .gitignore
55
+ * These patterns are applied in addition to .gitignore and .dockerignore
21
56
  */
22
57
  const STANDARD_EXCLUSIONS = [
23
58
  // Version control
@@ -82,34 +117,69 @@ const STANDARD_EXCLUSIONS = [
82
117
  '*.dmg',
83
118
  ];
84
119
  /**
85
- * Create a ZIP archive of a directory with gitignore support
120
+ * Create a ZIP archive of a directory with gitignore and dockerignore support
121
+ *
122
+ * @param directoryPath - Path to the directory to archive
123
+ * @param options - Configuration options for ZIP creation
124
+ * @param options.maxSizeBytes - Maximum ZIP size in bytes (default: 500MB)
125
+ * @param options.additionalExclusions - Custom patterns to exclude
126
+ * @param options.includeGitignore - Whether to include .gitignore files (default: true)
127
+ * @param options.onProgress - Optional callback to track progress
128
+ * @param options.progressInterval - Files to process between progress callbacks (default: 100)
129
+ * @returns Promise resolving to ZipResult with path, cleanup function, and metadata
130
+ *
131
+ * @example
132
+ * // Create ZIP excluding .gitignore files with progress tracking
133
+ * const result = await zipRepository('/path/to/repo', {
134
+ * includeGitignore: false,
135
+ * maxSizeBytes: 100 * 1024 * 1024, // 100MB
136
+ * onProgress: (stats) => console.log(`${stats.filesProcessed} files`)
137
+ * });
86
138
  */
87
139
  async function zipRepository(directoryPath, options = {}) {
88
- const maxSizeBytes = options.maxSizeBytes || 500 * 1024 * 1024; // 500MB default
140
+ const maxSizeBytes = options.maxSizeBytes || constants_1.MAX_ZIP_SIZE_BYTES;
89
141
  // Validate directory exists
90
142
  try {
91
143
  const stats = await fs_1.promises.stat(directoryPath);
92
144
  if (!stats.isDirectory()) {
93
- throw new Error(`Path is not a directory: ${directoryPath}`);
145
+ const errorMsg = `Path is not a directory: ${directoryPath}`;
146
+ logger.error(errorMsg);
147
+ throw new Error(errorMsg);
94
148
  }
95
149
  }
96
150
  catch (error) {
97
151
  if (error.code === 'ENOENT') {
98
- throw new Error(`Directory does not exist: ${directoryPath}`);
152
+ const errorMsg = `Directory does not exist: ${directoryPath}`;
153
+ logger.error(errorMsg);
154
+ throw new Error(errorMsg);
99
155
  }
100
156
  if (error.code === 'EACCES') {
101
- throw new Error(`Permission denied accessing directory: ${directoryPath}`);
157
+ const errorMsg = `Permission denied accessing directory: ${directoryPath}`;
158
+ logger.error(errorMsg);
159
+ throw new Error(errorMsg);
102
160
  }
161
+ // Re-throw unknown errors with logging
162
+ logger.error('Failed to validate directory:', directoryPath);
163
+ logger.error('Error:', error.message);
103
164
  throw error;
104
165
  }
105
166
  // Parse gitignore files
106
- const ignoreFilter = await buildIgnoreFilter(directoryPath, options.additionalExclusions);
167
+ const ignoreFilter = await buildIgnoreFilter(directoryPath, options.additionalExclusions, options.includeGitignore);
168
+ // Estimate directory size before starting ZIP creation
169
+ logger.debug('Estimating directory size...');
170
+ const estimatedSize = await estimateDirectorySize(directoryPath, ignoreFilter);
171
+ logger.debug('Estimated size:', formatBytes(estimatedSize));
172
+ // Check if estimated size exceeds limit
173
+ if (estimatedSize > maxSizeBytes) {
174
+ throw new Error(`Directory size (${formatBytes(estimatedSize)}) exceeds maximum allowed size (${formatBytes(maxSizeBytes)}). ` +
175
+ `Consider excluding more directories or analyzing a subdirectory.`);
176
+ }
107
177
  // Create temp file path
108
178
  const tempDir = (0, os_1.tmpdir)();
109
179
  const zipFileName = `supermodel-${(0, crypto_1.randomBytes)(8).toString('hex')}.zip`;
110
180
  const zipPath = (0, path_1.join)(tempDir, zipFileName);
111
- console.error('[DEBUG] Creating ZIP:', zipPath);
112
- console.error('[DEBUG] Source directory:', directoryPath);
181
+ logger.debug('Creating ZIP:', zipPath);
182
+ logger.debug('Source directory:', directoryPath);
113
183
  // Create ZIP archive
114
184
  let fileCount = 0;
115
185
  let totalSize = 0;
@@ -120,14 +190,15 @@ async function zipRepository(directoryPath, options = {}) {
120
190
  // Track errors
121
191
  let archiveError = null;
122
192
  archive.on('error', (err) => {
193
+ logger.error('Archive error:', err.message);
123
194
  archiveError = err;
124
195
  });
125
196
  archive.on('warning', (err) => {
126
197
  if (err.code === 'ENOENT') {
127
- console.error('[WARN] File not found (skipping):', err.message);
198
+ logger.warn('File not found (skipping):', err.message);
128
199
  }
129
200
  else {
130
- console.error('[WARN] Archive warning:', err.message);
201
+ logger.warn('Archive warning:', err.message);
131
202
  }
132
203
  });
133
204
  // Track progress
@@ -136,16 +207,22 @@ async function zipRepository(directoryPath, options = {}) {
136
207
  totalSize += entry.stats?.size || 0;
137
208
  // Check size limit
138
209
  if (totalSize > maxSizeBytes) {
139
- archive.abort();
140
- archiveError = new Error(`ZIP size exceeds limit (${formatBytes(maxSizeBytes)}). ` +
210
+ const errorMsg = `ZIP size exceeds limit (${formatBytes(maxSizeBytes)}). ` +
141
211
  `Current size: ${formatBytes(totalSize)}. ` +
142
- `Consider excluding more directories or analyzing a subdirectory.`);
212
+ `Consider excluding more directories or analyzing a subdirectory.`;
213
+ logger.error(errorMsg);
214
+ archive.abort();
215
+ archiveError = new Error(errorMsg);
143
216
  }
144
217
  });
145
218
  // Pipe to file
146
219
  archive.pipe(output);
147
220
  // Add files recursively with filtering
148
- await addFilesRecursively(archive, directoryPath, directoryPath, ignoreFilter);
221
+ // Initialize progress state if progress callback is provided
222
+ const progressState = options.onProgress
223
+ ? { filesProcessed: 0, bytesProcessed: 0, lastReportedCount: 0, lastFile: '' }
224
+ : undefined;
225
+ await addFilesRecursively(archive, directoryPath, directoryPath, ignoreFilter, options, progressState);
149
226
  // Finalize archive
150
227
  await archive.finalize();
151
228
  // Wait for output stream to finish
@@ -158,10 +235,14 @@ async function zipRepository(directoryPath, options = {}) {
158
235
  resolve();
159
236
  }
160
237
  });
161
- output.on('error', reject);
238
+ output.on('error', (err) => {
239
+ logger.error('Output stream error:', err.message);
240
+ reject(err);
241
+ });
162
242
  });
163
243
  // Check for errors during archiving
164
244
  if (archiveError) {
245
+ logger.error('Archiving failed, cleaning up partial ZIP');
165
246
  // Clean up partial ZIP
166
247
  await fs_1.promises.unlink(zipPath).catch(() => { });
167
248
  throw archiveError;
@@ -169,18 +250,18 @@ async function zipRepository(directoryPath, options = {}) {
169
250
  // Get final file size
170
251
  const zipStats = await fs_1.promises.stat(zipPath);
171
252
  const zipSizeBytes = zipStats.size;
172
- console.error('[DEBUG] ZIP created successfully');
173
- console.error('[DEBUG] Files included:', fileCount);
174
- console.error('[DEBUG] ZIP size:', formatBytes(zipSizeBytes));
253
+ logger.debug('ZIP created successfully');
254
+ logger.debug('Files included:', fileCount);
255
+ logger.debug('ZIP size:', formatBytes(zipSizeBytes));
175
256
  // Create cleanup function
176
257
  const cleanup = async () => {
177
258
  try {
178
259
  await fs_1.promises.unlink(zipPath);
179
- console.error('[DEBUG] Cleaned up ZIP:', zipPath);
260
+ logger.debug('Cleaned up ZIP:', zipPath);
180
261
  }
181
262
  catch (error) {
182
263
  if (error.code !== 'ENOENT') {
183
- console.error('[WARN] Failed to cleanup ZIP:', error.message);
264
+ logger.warn('Failed to cleanup ZIP:', error.message);
184
265
  }
185
266
  }
186
267
  };
@@ -192,9 +273,76 @@ async function zipRepository(directoryPath, options = {}) {
192
273
  };
193
274
  }
194
275
  /**
195
- * Build ignore filter from .gitignore files and standard exclusions
276
+ * Estimate total size of directory with ignore filters applied
277
+ * Returns total size in bytes of files that would be included in the ZIP
196
278
  */
197
- async function buildIgnoreFilter(rootDir, additionalExclusions = []) {
279
+ async function estimateDirectorySize(rootDir, ignoreFilter) {
280
+ let totalSize = 0;
281
+ async function walkDirectory(currentDir) {
282
+ let entries;
283
+ try {
284
+ entries = await fs_1.promises.readdir(currentDir);
285
+ }
286
+ catch (error) {
287
+ if (error.code === 'EACCES') {
288
+ logger.warn('Permission denied:', currentDir);
289
+ return;
290
+ }
291
+ throw error;
292
+ }
293
+ for (const entry of entries) {
294
+ const fullPath = (0, path_1.join)(currentDir, entry);
295
+ const relativePath = (0, path_1.relative)(rootDir, fullPath);
296
+ // Normalize path for ignore matching (use forward slashes)
297
+ const normalizedRelativePath = relativePath.split(path_1.sep).join('/');
298
+ // Check if ignored
299
+ if (ignoreFilter.ignores(normalizedRelativePath)) {
300
+ continue;
301
+ }
302
+ let stats;
303
+ try {
304
+ stats = await fs_1.promises.lstat(fullPath);
305
+ }
306
+ catch (error) {
307
+ if (error.code === 'ENOENT') {
308
+ // File disappeared, skip
309
+ continue;
310
+ }
311
+ logger.warn('Failed to stat:', fullPath, error.message);
312
+ continue;
313
+ }
314
+ // Skip symlinks to prevent following links outside the repository
315
+ if (stats.isSymbolicLink()) {
316
+ continue;
317
+ }
318
+ if (stats.isDirectory()) {
319
+ // Check if directory itself should be ignored
320
+ const dirPath = normalizedRelativePath + '/';
321
+ if (ignoreFilter.ignores(dirPath)) {
322
+ continue;
323
+ }
324
+ // Recurse into directory
325
+ await walkDirectory(fullPath);
326
+ }
327
+ else if (stats.isFile()) {
328
+ // Add file size to total
329
+ totalSize += stats.size;
330
+ }
331
+ // Skip other special files (sockets, FIFOs, etc.)
332
+ }
333
+ }
334
+ await walkDirectory(rootDir);
335
+ return totalSize;
336
+ }
337
+ /**
338
+ * Build ignore filter from .gitignore, .dockerignore files and standard exclusions
339
+ * Recursively finds and parses .gitignore files in subdirectories
340
+ *
341
+ * @param rootDir - Root directory to build filter for
342
+ * @param additionalExclusions - Additional patterns to exclude
343
+ * @param includeGitignore - Whether to include .gitignore files in the archive (default: true)
344
+ */
345
+ async function buildIgnoreFilter(rootDir, additionalExclusions = [], includeGitignore = true) {
198
346
  const ig = (0, ignore_1.default)();
199
347
  // Add standard exclusions
200
348
  ig.add(STANDARD_EXCLUSIONS);
@@ -202,39 +350,142 @@ async function buildIgnoreFilter(rootDir, additionalExclusions = []) {
202
350
  if (additionalExclusions.length > 0) {
203
351
  ig.add(additionalExclusions);
204
352
  }
205
- // Parse .gitignore in root
206
- const gitignorePath = (0, path_1.join)(rootDir, '.gitignore');
353
+ // Exclude .gitignore files if requested
354
+ if (includeGitignore === false) {
355
+ ig.add(['.gitignore', '**/.gitignore']);
356
+ logger.debug('Excluding .gitignore files from archive');
357
+ }
358
+ // Recursively find and parse all .gitignore files
359
+ const gitignoreFiles = await findGitignoreFiles(rootDir);
360
+ for (const gitignorePath of gitignoreFiles) {
361
+ try {
362
+ const gitignoreContent = await fs_1.promises.readFile(gitignorePath, 'utf-8');
363
+ const patterns = gitignoreContent
364
+ .split('\n')
365
+ .map(line => line.trim())
366
+ .filter(line => line && !line.startsWith('#'));
367
+ if (patterns.length > 0) {
368
+ // Get the directory containing this .gitignore
369
+ const gitignoreDir = gitignorePath.substring(0, gitignorePath.length - '.gitignore'.length);
370
+ const relativeDir = (0, path_1.relative)(rootDir, gitignoreDir);
371
+ // Scope patterns to their directory
372
+ const scopedPatterns = patterns.map(pattern => {
373
+ // If pattern starts with '/', it's relative to the .gitignore location
374
+ if (pattern.startsWith('/')) {
375
+ const patternWithoutSlash = pattern.substring(1);
376
+ return relativeDir ? `${relativeDir}/${patternWithoutSlash}` : patternWithoutSlash;
377
+ }
378
+ // If pattern starts with '!', handle negation
379
+ else if (pattern.startsWith('!')) {
380
+ const negatedPattern = pattern.substring(1);
381
+ if (negatedPattern.startsWith('/')) {
382
+ const patternWithoutSlash = negatedPattern.substring(1);
383
+ return relativeDir ? `!${relativeDir}/${patternWithoutSlash}` : `!${patternWithoutSlash}`;
384
+ }
385
+ // For non-rooted negation patterns, prefix with directory
386
+ return relativeDir ? `!${relativeDir}/${negatedPattern}` : `!${negatedPattern}`;
387
+ }
388
+ // For non-rooted patterns, prefix with the directory path
389
+ else {
390
+ return relativeDir ? `${relativeDir}/${pattern}` : pattern;
391
+ }
392
+ });
393
+ ig.add(scopedPatterns);
394
+ const location = relativeDir ? `in ${relativeDir}/` : 'in root';
395
+ logger.debug(`Loaded .gitignore ${location} with ${patterns.length} patterns`);
396
+ }
397
+ }
398
+ catch (error) {
399
+ if (error.code !== 'ENOENT') {
400
+ logger.warn('Failed to read .gitignore at', gitignorePath, ':', error.message);
401
+ }
402
+ }
403
+ }
404
+ // Parse .dockerignore in root
405
+ const dockerignorePath = (0, path_1.join)(rootDir, '.dockerignore');
207
406
  try {
208
- const gitignoreContent = await fs_1.promises.readFile(gitignorePath, 'utf-8');
209
- const patterns = gitignoreContent
407
+ const dockerignoreContent = await fs_1.promises.readFile(dockerignorePath, 'utf-8');
408
+ const patterns = dockerignoreContent
210
409
  .split('\n')
211
410
  .map(line => line.trim())
212
411
  .filter(line => line && !line.startsWith('#'));
213
412
  if (patterns.length > 0) {
214
413
  ig.add(patterns);
215
- console.error('[DEBUG] Loaded .gitignore with', patterns.length, 'patterns');
414
+ console.error('[DEBUG] Loaded .dockerignore with', patterns.length, 'patterns');
216
415
  }
217
416
  }
218
417
  catch (error) {
219
418
  if (error.code !== 'ENOENT') {
220
- console.error('[WARN] Failed to read .gitignore:', error.message);
419
+ console.error('[WARN] Failed to read .dockerignore:', error.message);
221
420
  }
222
421
  }
223
422
  return ig;
224
423
  }
424
+ /**
425
+ * Recursively find all .gitignore files in a directory tree
426
+ */
427
+ async function findGitignoreFiles(rootDir) {
428
+ const gitignoreFiles = [];
429
+ async function searchDirectory(dir) {
430
+ let entries;
431
+ try {
432
+ entries = await fs_1.promises.readdir(dir);
433
+ }
434
+ catch (error) {
435
+ if (error.code === 'EACCES') {
436
+ logger.warn('Permission denied:', dir);
437
+ return;
438
+ }
439
+ return;
440
+ }
441
+ for (const entry of entries) {
442
+ // Skip .git directory and other version control directories
443
+ if (entry === '.git' || entry === '.svn' || entry === '.hg') {
444
+ continue;
445
+ }
446
+ const fullPath = (0, path_1.join)(dir, entry);
447
+ // If this is a .gitignore file, add it to the list
448
+ if (entry === '.gitignore') {
449
+ gitignoreFiles.push(fullPath);
450
+ continue;
451
+ }
452
+ // If it's a directory, recurse into it
453
+ try {
454
+ const stats = await fs_1.promises.lstat(fullPath);
455
+ if (stats.isDirectory() && !stats.isSymbolicLink()) {
456
+ await searchDirectory(fullPath);
457
+ }
458
+ }
459
+ catch (error) {
460
+ // Skip files we can't access
461
+ continue;
462
+ }
463
+ }
464
+ }
465
+ await searchDirectory(rootDir);
466
+ // Sort so root .gitignore is processed first
467
+ gitignoreFiles.sort((a, b) => {
468
+ const aDepth = a.split(path_1.sep).length;
469
+ const bDepth = b.split(path_1.sep).length;
470
+ return aDepth - bDepth;
471
+ });
472
+ return gitignoreFiles;
473
+ }
225
474
  /**
226
475
  * Recursively add files to archive with filtering
227
476
  */
228
- async function addFilesRecursively(archive, rootDir, currentDir, ignoreFilter) {
477
+ async function addFilesRecursively(archive, rootDir, currentDir, ignoreFilter, options, progressState) {
229
478
  let entries;
230
479
  try {
231
480
  entries = await fs_1.promises.readdir(currentDir);
232
481
  }
233
482
  catch (error) {
234
483
  if (error.code === 'EACCES') {
235
- console.error('[WARN] Permission denied:', currentDir);
484
+ logger.warn('Permission denied:', currentDir);
236
485
  return;
237
486
  }
487
+ logger.error('Failed to read directory:', currentDir);
488
+ logger.error('Error:', error.message);
238
489
  throw error;
239
490
  }
240
491
  for (const entry of entries) {
@@ -248,16 +499,21 @@ async function addFilesRecursively(archive, rootDir, currentDir, ignoreFilter) {
248
499
  }
249
500
  let stats;
250
501
  try {
251
- stats = await fs_1.promises.stat(fullPath);
502
+ stats = await fs_1.promises.lstat(fullPath);
252
503
  }
253
504
  catch (error) {
254
505
  if (error.code === 'ENOENT') {
255
- // Symlink pointing to non-existent file, skip
506
+ // File disappeared, skip
256
507
  continue;
257
508
  }
258
509
  console.error('[WARN] Failed to stat:', fullPath, error.message);
259
510
  continue;
260
511
  }
512
+ // Skip symlinks to prevent following links outside the repository
513
+ if (stats.isSymbolicLink()) {
514
+ logger.warn('Skipping symlink:', fullPath);
515
+ continue;
516
+ }
261
517
  if (stats.isDirectory()) {
262
518
  // Check if directory itself should be ignored
263
519
  const dirPath = normalizedRelativePath + '/';
@@ -265,18 +521,48 @@ async function addFilesRecursively(archive, rootDir, currentDir, ignoreFilter) {
265
521
  continue;
266
522
  }
267
523
  // Recurse into directory
268
- await addFilesRecursively(archive, rootDir, fullPath, ignoreFilter);
524
+ await addFilesRecursively(archive, rootDir, fullPath, ignoreFilter, options, progressState);
269
525
  }
270
526
  else if (stats.isFile()) {
271
527
  // Add file to archive
272
528
  try {
273
529
  archive.file(fullPath, { name: normalizedRelativePath });
530
+ // Track progress if callback is provided
531
+ if (progressState && options?.onProgress) {
532
+ progressState.filesProcessed++;
533
+ progressState.bytesProcessed += stats.size;
534
+ progressState.lastFile = normalizedRelativePath;
535
+ const progressInterval = options.progressInterval || 100;
536
+ // Report progress every N files
537
+ if (progressState.filesProcessed - progressState.lastReportedCount >= progressInterval) {
538
+ logger.debug(`Progress: ${progressState.filesProcessed} files, ${formatBytes(progressState.bytesProcessed)}`);
539
+ options.onProgress({
540
+ filesProcessed: progressState.filesProcessed,
541
+ currentFile: normalizedRelativePath,
542
+ bytesProcessed: progressState.bytesProcessed,
543
+ });
544
+ progressState.lastReportedCount = progressState.filesProcessed;
545
+ }
546
+ }
274
547
  }
275
548
  catch (error) {
276
- console.error('[WARN] Failed to add file:', fullPath, error.message);
549
+ logger.warn('Failed to add file:', fullPath, error.message);
277
550
  }
278
551
  }
279
- // Skip symlinks, sockets, etc.
552
+ // Skip other special files (sockets, FIFOs, etc.)
553
+ }
554
+ // Report final progress if we have a callback and there are unreported files
555
+ // Only report at the root level (when currentDir === rootDir)
556
+ if (currentDir === rootDir &&
557
+ progressState &&
558
+ options?.onProgress &&
559
+ progressState.filesProcessed > progressState.lastReportedCount) {
560
+ logger.debug(`Final progress: ${progressState.filesProcessed} files, ${formatBytes(progressState.bytesProcessed)}`);
561
+ options.onProgress({
562
+ filesProcessed: progressState.filesProcessed,
563
+ currentFile: progressState.lastFile,
564
+ bytesProcessed: progressState.bytesProcessed,
565
+ });
280
566
  }
281
567
  }
282
568
  /**
@@ -295,7 +581,7 @@ function formatBytes(bytes) {
295
581
  * Clean up old ZIP files from temp directory
296
582
  * Removes ZIPs older than the specified age
297
583
  */
298
- async function cleanupOldZips(maxAgeMs = 24 * 60 * 60 * 1000) {
584
+ async function cleanupOldZips(maxAgeMs = constants_1.ZIP_CLEANUP_AGE_MS) {
299
585
  const tempDir = (0, os_1.tmpdir)();
300
586
  const now = Date.now();
301
587
  try {
@@ -318,15 +604,15 @@ async function cleanupOldZips(maxAgeMs = 24 * 60 * 60 * 1000) {
318
604
  catch (error) {
319
605
  // File might have been deleted already, ignore
320
606
  if (error.code !== 'ENOENT') {
321
- console.error('[WARN] Failed to cleanup:', fullPath, error.message);
607
+ logger.warn('Failed to cleanup:', fullPath, error.message);
322
608
  }
323
609
  }
324
610
  }
325
611
  if (removedCount > 0) {
326
- console.error('[DEBUG] Cleaned up', removedCount, 'old ZIP files');
612
+ logger.debug('Cleaned up', removedCount, 'old ZIP files');
327
613
  }
328
614
  }
329
615
  catch (error) {
330
- console.error('[WARN] Failed to cleanup temp directory:', error.message);
616
+ logger.warn('Failed to cleanup temp directory:', error.message);
331
617
  }
332
618
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supermodeltools/mcp-server",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "MCP server for Supermodel API - code graph generation for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,7 +13,10 @@
13
13
  "scripts": {
14
14
  "build": "tsc",
15
15
  "start": "node dist/index.js",
16
- "typecheck": "tsc --noEmit"
16
+ "typecheck": "tsc --noEmit",
17
+ "test": "jest",
18
+ "test:coverage": "jest --coverage",
19
+ "test:watch": "jest --watch"
17
20
  },
18
21
  "repository": {
19
22
  "type": "git",
@@ -39,11 +42,16 @@
39
42
  "archiver": "^7.0.1",
40
43
  "ignore": "^7.0.5",
41
44
  "jq-web": "^0.6.2",
45
+ "undici": "^7.18.2",
42
46
  "zod": "^3.22.4"
43
47
  },
44
48
  "devDependencies": {
49
+ "@jest/globals": "^30.2.0",
45
50
  "@types/archiver": "^7.0.0",
51
+ "@types/jest": "^30.0.0",
46
52
  "@types/node": "^20.11.0",
53
+ "jest": "^30.2.0",
54
+ "ts-jest": "^29.4.6",
47
55
  "typescript": "^5.3.3"
48
56
  }
49
57
  }