@azerate/claudette-mcp 1.8.2 → 1.8.4

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MCP server for Claudette IDE - providing Claude with 40+ comprehensive workspace management tools.
4
4
 
5
- > **v1.8.2** - Added pino logger setup tools and console.log migration utilities.
5
+ > **v1.8.4** - Fixed `generate_commit_message` tool returning HTML errors instead of JSON by adding the missing `/api/git/changes` endpoint.
6
6
 
7
7
  ## Installation
8
8
 
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,183 @@
1
+ import { setupLogger, crawlConsoleLogs, formatCrawlConsoleLogsResult } from '../logger-setup.js';
2
+ import { existsSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ describe('logger-setup', () => {
6
+ let testDir;
7
+ beforeEach(() => {
8
+ // Create a unique temp directory for each test
9
+ testDir = join(tmpdir(), `logger-setup-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
10
+ mkdirSync(testDir, { recursive: true });
11
+ });
12
+ afterEach(() => {
13
+ // Clean up
14
+ try {
15
+ rmSync(testDir, { recursive: true, force: true });
16
+ }
17
+ catch {
18
+ // Ignore cleanup errors
19
+ }
20
+ });
21
+ describe('setupLogger', () => {
22
+ it('should create logger file in src/utils directory', () => {
23
+ // Create minimal project structure
24
+ mkdirSync(join(testDir, 'src'), { recursive: true });
25
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({
26
+ name: 'test-project',
27
+ dependencies: { pino: '^9.0.0' } // Pretend pino is installed
28
+ }));
29
+ writeFileSync(join(testDir, 'tsconfig.json'), '{}');
30
+ const result = setupLogger(testDir);
31
+ expect(result.success).toBe(true);
32
+ // Normalize path separators for cross-platform
33
+ expect(result.loggerPath?.replace(/\\/g, '/')).toBe('src/utils/logger.ts');
34
+ expect(result.alreadyExists).toBe(false);
35
+ expect(existsSync(join(testDir, 'src/utils/logger.ts'))).toBe(true);
36
+ });
37
+ it('should return alreadyExists when logger file exists', () => {
38
+ // Create existing logger
39
+ mkdirSync(join(testDir, 'src/utils'), { recursive: true });
40
+ writeFileSync(join(testDir, 'src/utils/logger.ts'), 'export const logger = {}');
41
+ const result = setupLogger(testDir);
42
+ expect(result.success).toBe(true);
43
+ expect(result.alreadyExists).toBe(true);
44
+ expect(result.loggerPath).toBe('src/utils/logger.ts');
45
+ });
46
+ it('should include bundler warning note in generated logger', () => {
47
+ mkdirSync(join(testDir, 'src'), { recursive: true });
48
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({
49
+ name: 'test-project',
50
+ dependencies: { pino: '^9.0.0' }
51
+ }));
52
+ writeFileSync(join(testDir, 'tsconfig.json'), '{}');
53
+ setupLogger(testDir);
54
+ const loggerContent = readFileSync(join(testDir, 'src/utils/logger.ts'), 'utf-8');
55
+ expect(loggerContent).toContain('BUNDLER NOTE');
56
+ expect(loggerContent).toContain('pino');
57
+ expect(loggerContent).toContain('pino-pretty');
58
+ expect(loggerContent).toContain('thread-stream');
59
+ expect(loggerContent).toContain('external');
60
+ });
61
+ it('should detect esbuild configs that need pino externalized', () => {
62
+ mkdirSync(join(testDir, 'src'), { recursive: true });
63
+ mkdirSync(join(testDir, 'scripts'), { recursive: true });
64
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({
65
+ name: 'test-project',
66
+ dependencies: { pino: '^9.0.0' }
67
+ }));
68
+ writeFileSync(join(testDir, 'tsconfig.json'), '{}');
69
+ // Create esbuild config without pino external
70
+ writeFileSync(join(testDir, 'scripts/build-server.js'), `
71
+ import { build } from 'esbuild';
72
+ build({
73
+ entryPoints: ['src/index.ts'],
74
+ external: ['node-pty'],
75
+ });
76
+ `);
77
+ const result = setupLogger(testDir);
78
+ expect(result.success).toBe(true);
79
+ expect(result.bundlerWarning).toBeDefined();
80
+ expect(result.bundlerWarning).toContain('pino');
81
+ expect(result.bundlerWarning).toContain('scripts/build-server.js');
82
+ expect(result.esbuildConfigs).toContain('scripts/build-server.js');
83
+ });
84
+ it('should not warn about esbuild configs that already have pino external', () => {
85
+ mkdirSync(join(testDir, 'src'), { recursive: true });
86
+ mkdirSync(join(testDir, 'scripts'), { recursive: true });
87
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({
88
+ name: 'test-project',
89
+ dependencies: { pino: '^9.0.0' }
90
+ }));
91
+ writeFileSync(join(testDir, 'tsconfig.json'), '{}');
92
+ // Create esbuild config WITH pino external
93
+ writeFileSync(join(testDir, 'scripts/build-server.js'), `
94
+ import { build } from 'esbuild';
95
+ build({
96
+ entryPoints: ['src/index.ts'],
97
+ external: ['node-pty', 'pino', 'pino-pretty'],
98
+ });
99
+ `);
100
+ const result = setupLogger(testDir);
101
+ expect(result.success).toBe(true);
102
+ expect(result.bundlerWarning).toBeUndefined();
103
+ expect(result.esbuildConfigs).toBeUndefined();
104
+ });
105
+ it('should create JavaScript logger for non-TypeScript projects', () => {
106
+ mkdirSync(join(testDir, 'src'), { recursive: true });
107
+ writeFileSync(join(testDir, 'package.json'), JSON.stringify({
108
+ name: 'test-project',
109
+ dependencies: { pino: '^9.0.0' }
110
+ }));
111
+ // No tsconfig.json = JavaScript project
112
+ const result = setupLogger(testDir);
113
+ expect(result.success).toBe(true);
114
+ // Normalize path separators for cross-platform
115
+ expect(result.loggerPath?.replace(/\\/g, '/')).toBe('src/utils/logger.js');
116
+ expect(existsSync(join(testDir, 'src/utils/logger.js'))).toBe(true);
117
+ });
118
+ });
119
+ describe('crawlConsoleLogs', () => {
120
+ it('should find console.log statements', () => {
121
+ mkdirSync(join(testDir, 'src'), { recursive: true });
122
+ writeFileSync(join(testDir, 'src/index.ts'), `
123
+ console.log('Hello world');
124
+ console.error('Error message');
125
+ `);
126
+ const result = crawlConsoleLogs(testDir);
127
+ expect(result.totalCount).toBe(2);
128
+ expect(result.byType.log).toBe(1);
129
+ expect(result.byType.error).toBe(1);
130
+ });
131
+ it('should not scan node_modules', () => {
132
+ mkdirSync(join(testDir, 'node_modules/some-lib'), { recursive: true });
133
+ writeFileSync(join(testDir, 'node_modules/some-lib/index.js'), `
134
+ console.log('Should not be found');
135
+ `);
136
+ const result = crawlConsoleLogs(testDir);
137
+ expect(result.totalCount).toBe(0);
138
+ });
139
+ it('should suggest logger replacements', () => {
140
+ mkdirSync(join(testDir, 'src'), { recursive: true });
141
+ writeFileSync(join(testDir, 'src/index.ts'), `console.log('message')`);
142
+ const result = crawlConsoleLogs(testDir);
143
+ expect(result.matches.length).toBe(1);
144
+ expect(result.matches[0].suggestedReplacement).toContain('logger.info');
145
+ });
146
+ it('should detect existing logger', () => {
147
+ mkdirSync(join(testDir, 'src/utils'), { recursive: true });
148
+ writeFileSync(join(testDir, 'src/utils/logger.ts'), 'export const logger = {}');
149
+ const result = crawlConsoleLogs(testDir);
150
+ expect(result.hasLogger).toBe(true);
151
+ expect(result.loggerPath).toBe('src/utils/logger.ts');
152
+ });
153
+ });
154
+ describe('formatCrawlConsoleLogsResult', () => {
155
+ it('should format empty results', () => {
156
+ const result = formatCrawlConsoleLogsResult({
157
+ matches: [],
158
+ totalCount: 0,
159
+ byType: { log: 0, debug: 0, info: 0, warn: 0, error: 0, trace: 0 },
160
+ hasLogger: false,
161
+ });
162
+ expect(result).toContain('No console statements found');
163
+ });
164
+ it('should format results with matches', () => {
165
+ const result = formatCrawlConsoleLogsResult({
166
+ matches: [{
167
+ file: 'src/index.ts',
168
+ line: 5,
169
+ column: 1,
170
+ code: "console.log('test')",
171
+ type: 'log',
172
+ suggestedReplacement: "logger.info('test')",
173
+ }],
174
+ totalCount: 1,
175
+ byType: { log: 1, debug: 0, info: 0, warn: 0, error: 0, trace: 0 },
176
+ hasLogger: false,
177
+ });
178
+ expect(result).toContain('Found **1** console statements');
179
+ expect(result).toContain('console.log: 1');
180
+ expect(result).toContain('src/index.ts:5');
181
+ });
182
+ });
183
+ });
package/dist/crawler.js CHANGED
@@ -5,7 +5,22 @@ import { join, extname, relative } from 'path';
5
5
  // Default config
6
6
  export const DEFAULT_CONFIG = {
7
7
  enabled: ['quality', 'security', 'react', 'typescript'],
8
- ignore: ['node_modules', 'dist', 'build', '.git', 'coverage', '*.min.js', '*.map'],
8
+ ignore: [
9
+ 'node_modules',
10
+ 'dist',
11
+ 'dist-electron',
12
+ 'dist-server',
13
+ 'build',
14
+ '.git',
15
+ 'coverage',
16
+ '*.min.js',
17
+ '*.map',
18
+ 'scripts', // Dev scripts legitimately use console.log
19
+ 'hooks', // Claude hooks use console.log for JSON output
20
+ 'benchmarks', // Benchmarks use console for reporting
21
+ 'templates', // Template files
22
+ '__mocks__', // Test mocks
23
+ ],
9
24
  include: ['*.ts', '*.tsx', '*.js', '*.jsx'],
10
25
  thresholds: {
11
26
  maxFileLength: 500,
@@ -138,7 +153,8 @@ export function findPatternMatches(content, pattern, filePath, rule, severity, m
138
153
  }
139
154
  return issues;
140
155
  }
141
- // Format crawl results for display
156
+ // Format crawl results for display (with output limiting)
157
+ const MAX_ISSUES_TO_SHOW = 50;
142
158
  export function formatCrawlResult(result) {
143
159
  const { category, issues, scannedFiles, duration } = result;
144
160
  let output = `${category.toUpperCase()} CRAWL RESULTS\n`;
@@ -147,20 +163,24 @@ export function formatCrawlResult(result) {
147
163
  output += `✅ No issues found!\n`;
148
164
  }
149
165
  else {
150
- const errors = issues.filter(i => i.severity === 'error').length;
151
- const warnings = issues.filter(i => i.severity === 'warning').length;
152
- const infos = issues.filter(i => i.severity === 'info').length;
166
+ const errors = issues.filter(i => i.severity === 'error');
167
+ const warnings = issues.filter(i => i.severity === 'warning');
168
+ const infos = issues.filter(i => i.severity === 'info');
153
169
  output += `Found ${issues.length} issue(s):\n`;
154
- if (errors > 0)
155
- output += ` 🔴 ${errors} error(s)\n`;
156
- if (warnings > 0)
157
- output += ` 🟡 ${warnings} warning(s)\n`;
158
- if (infos > 0)
159
- output += ` 🔵 ${infos} info(s)\n`;
170
+ if (errors.length > 0)
171
+ output += ` 🔴 ${errors.length} error(s)\n`;
172
+ if (warnings.length > 0)
173
+ output += ` 🟡 ${warnings.length} warning(s)\n`;
174
+ if (infos.length > 0)
175
+ output += ` 🔵 ${infos.length} info(s)\n`;
160
176
  output += '\n';
177
+ // Prioritize: errors first, then warnings, then info
178
+ // Limit total output to MAX_ISSUES_TO_SHOW
179
+ const prioritizedIssues = [...errors, ...warnings, ...infos].slice(0, MAX_ISSUES_TO_SHOW);
180
+ const skippedCount = issues.length - prioritizedIssues.length;
161
181
  // Group by file
162
182
  const byFile = new Map();
163
- for (const issue of issues) {
183
+ for (const issue of prioritizedIssues) {
164
184
  const existing = byFile.get(issue.file) || [];
165
185
  existing.push(issue);
166
186
  byFile.set(issue.file, existing);
@@ -176,6 +196,9 @@ export function formatCrawlResult(result) {
176
196
  }
177
197
  output += '\n';
178
198
  }
199
+ if (skippedCount > 0) {
200
+ output += `... and ${skippedCount} more issue(s) not shown (errors/warnings prioritized)\n\n`;
201
+ }
179
202
  }
180
203
  output += `────────────────────────────────────────\n`;
181
204
  output += `Scanned: ${scannedFiles} files in ${duration}ms\n`;
package/dist/index.js CHANGED
@@ -1564,7 +1564,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1564
1564
  return { content: [{ type: "text", text: "Error: workspace_path is required" }] };
1565
1565
  }
1566
1566
  try {
1567
- const changesResponse = await fetchApi(`/api/changes?path=${encodeURIComponent(workspacePath)}`);
1567
+ const changesResponse = await fetchApi(`/api/git/changes?path=${encodeURIComponent(workspacePath)}`);
1568
1568
  const changes = await changesResponse.json();
1569
1569
  let output = `## Changes to Commit\n\n`;
1570
1570
  if (changes.staged?.length > 0) {
@@ -8,6 +8,8 @@ export interface SetupLoggerResult {
8
8
  installed?: boolean;
9
9
  alreadyExists?: boolean;
10
10
  error?: string;
11
+ bundlerWarning?: string;
12
+ esbuildConfigs?: string[];
11
13
  }
12
14
  export interface ConsoleLogMatch {
13
15
  file: string;
@@ -20,6 +20,10 @@ const LOGGER_TEMPLATE_TS = `/**
20
20
  * Environment variables:
21
21
  * LOG_LEVEL: 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' (default: 'info')
22
22
  * NODE_ENV: 'development' | 'production' (affects formatting)
23
+ *
24
+ * BUNDLER NOTE (esbuild/webpack):
25
+ * Pino uses worker threads that don't work when bundled. Add these to your
26
+ * bundler's \`external\` array: 'pino', 'pino-pretty', 'thread-stream'
23
27
  */
24
28
 
25
29
  import pino from 'pino'
@@ -101,6 +105,10 @@ const LOGGER_TEMPLATE_JS = `/**
101
105
  * Environment variables:
102
106
  * LOG_LEVEL: 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' (default: 'info')
103
107
  * NODE_ENV: 'development' | 'production' (affects formatting)
108
+ *
109
+ * BUNDLER NOTE (esbuild/webpack):
110
+ * Pino uses worker threads that don't work when bundled. Add these to your
111
+ * bundler's \`external\` array: 'pino', 'pino-pretty', 'thread-stream'
104
112
  */
105
113
 
106
114
  import pino from 'pino'
@@ -186,6 +194,40 @@ function getSourceDir(workspacePath) {
186
194
  }
187
195
  return 'src'; // Default to src
188
196
  }
197
+ /**
198
+ * Find esbuild config files that may need pino externalized
199
+ */
200
+ function findEsbuildConfigs(workspacePath) {
201
+ const configs = [];
202
+ const possiblePaths = [
203
+ 'scripts/build-server.js',
204
+ 'scripts/build-electron.js',
205
+ 'scripts/build.js',
206
+ 'esbuild.config.js',
207
+ 'esbuild.config.mjs',
208
+ 'build.js',
209
+ 'build.mjs',
210
+ ];
211
+ for (const p of possiblePaths) {
212
+ const fullPath = join(workspacePath, p);
213
+ if (existsSync(fullPath)) {
214
+ // Check if it actually uses esbuild and doesn't already have pino external
215
+ try {
216
+ const content = readFileSync(fullPath, 'utf-8');
217
+ if (content.includes('esbuild') || content.includes('build(') || content.includes('context(')) {
218
+ // Check if pino is already in externals
219
+ if (!content.includes("'pino'") && !content.includes('"pino"')) {
220
+ configs.push(p);
221
+ }
222
+ }
223
+ }
224
+ catch {
225
+ // Skip unreadable files
226
+ }
227
+ }
228
+ }
229
+ return configs;
230
+ }
189
231
  /**
190
232
  * Find existing logger file in the project
191
233
  */
@@ -259,11 +301,20 @@ export function setupLogger(workspacePath) {
259
301
  console.error('Failed to install pino:', e);
260
302
  }
261
303
  }
304
+ // Check for esbuild configs that need updating
305
+ const esbuildConfigs = findEsbuildConfigs(workspacePath);
306
+ let bundlerWarning;
307
+ if (esbuildConfigs.length > 0) {
308
+ bundlerWarning = `IMPORTANT: Add 'pino', 'pino-pretty', 'thread-stream' to the external array in: ${esbuildConfigs.join(', ')}. ` +
309
+ `Pino uses worker threads that crash when bundled.`;
310
+ }
262
311
  return {
263
312
  success: true,
264
313
  loggerPath: relativePath,
265
314
  installed,
266
315
  alreadyExists: false,
316
+ bundlerWarning,
317
+ esbuildConfigs: esbuildConfigs.length > 0 ? esbuildConfigs : undefined,
267
318
  };
268
319
  }
269
320
  catch (err) {
@@ -326,7 +377,29 @@ export function crawlConsoleLogs(workspacePath) {
326
377
  const pattern = /console\.(log|debug|info|warn|error|trace)\s*\(([^)]*)\)/g;
327
378
  // Get files to scan (reuse crawler logic)
328
379
  const extensions = ['.ts', '.tsx', '.js', '.jsx'];
329
- const ignoreDirs = ['node_modules', 'dist', 'build', '.next', 'coverage'];
380
+ const ignoreDirs = [
381
+ 'node_modules',
382
+ 'dist',
383
+ 'dist-electron',
384
+ 'dist-server',
385
+ 'build',
386
+ '.next',
387
+ 'coverage',
388
+ 'scripts', // Dev scripts legitimately use console.log
389
+ 'hooks', // Claude hooks MUST use console.log for JSON output
390
+ 'benchmarks', // Benchmarks use console for reporting
391
+ 'templates', // Template files
392
+ '__tests__', // Test files
393
+ ];
394
+ const ignoreFiles = [
395
+ 'vite.config.ts',
396
+ 'vite.config.js',
397
+ 'jest.config.ts',
398
+ 'jest.config.js',
399
+ 'vitest.config.ts',
400
+ 'vitest.config.js',
401
+ '.eslintrc.js',
402
+ ];
330
403
  function scanDir(dir) {
331
404
  const entries = readdirSync(dir, { withFileTypes: true });
332
405
  for (const entry of entries) {
@@ -338,7 +411,9 @@ export function crawlConsoleLogs(workspacePath) {
338
411
  }
339
412
  else if (entry.isFile()) {
340
413
  const ext = entry.name.substring(entry.name.lastIndexOf('.'));
341
- if (extensions.includes(ext) && !entry.name.includes('.test.') && !entry.name.includes('.spec.')) {
414
+ const isIgnoredFile = ignoreFiles.includes(entry.name);
415
+ const isTestFile = entry.name.includes('.test.') || entry.name.includes('.spec.');
416
+ if (extensions.includes(ext) && !isIgnoredFile && !isTestFile) {
342
417
  scanFile(fullPath);
343
418
  }
344
419
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azerate/claudette-mcp",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
4
4
  "description": "MCP server for Claudette IDE - 40+ tools for TypeScript errors, git changes, workflow automation, testing, benchmarks, refactoring, code quality crawlers, logging setup, checkpoints, memory, and script management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",