Skip to content

Commit 3c08f29

Browse files
committed
fix(i18n): respect config language setting and add diagnostic logging
Remove hardcoded 'language': 'en' from 27 agent JSON files that was overriding the user's configured language in codingbuddy.config.js. Add DiagnosticLogService for file-based logging to help debug config loading issues. Logs are written to docs/codingbuddy/log/diagnostic.log. resolve #268
1 parent 5ed1688 commit 3c08f29

39 files changed

Lines changed: 626 additions & 31 deletions

.github/templates/marketplace-index.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@
3636
<body>
3737
<h1>🚀 CodingBuddy Marketplace</h1>
3838
<p>Welcome to the CodingBuddy plugin marketplace for Claude Code.</p>
39-
<p><strong>Note:</strong> This page is for informational purposes. To install the plugin, use the CLI commands below.</p>
39+
<p>
40+
<strong>Note:</strong> This page is for informational purposes. To install
41+
the plugin, use the CLI commands below.
42+
</p>
4043

4144
<h2>Installation</h2>
4245
<pre><code># Add the marketplace (use GitHub repository format)
@@ -45,7 +48,13 @@ <h2>Installation</h2>
4548
# Install the plugin
4649
claude plugin install codingbuddy@jeremydev87</code></pre>
4750

48-
<p><em>⚠️ Do not use this page's URL directly with <code>claude marketplace add</code>. Use the GitHub repository format shown above.</em></p>
51+
<p>
52+
<em
53+
>⚠️ Do not use this page's URL directly with
54+
<code>claude marketplace add</code>. Use the GitHub repository format
55+
shown above.</em
56+
>
57+
</p>
4958

5059
<h2>Available Plugins</h2>
5160
<div class="plugin">
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2+
import { DiagnosticLogService } from './diagnostic-log.service';
3+
import { ConfigService } from '../config/config.service';
4+
import * as fs from 'fs/promises';
5+
import { existsSync, mkdirSync } from 'fs';
6+
import * as path from 'path';
7+
8+
vi.mock('fs/promises');
9+
vi.mock('fs', () => ({
10+
existsSync: vi.fn(),
11+
mkdirSync: vi.fn(),
12+
}));
13+
14+
describe('DiagnosticLogService', () => {
15+
let service: DiagnosticLogService;
16+
let mockConfigService: ConfigService;
17+
18+
const TEST_PROJECT_ROOT = '/test/project';
19+
const LOG_DIR = path.join(TEST_PROJECT_ROOT, 'docs/codingbuddy/log');
20+
const LOG_FILE = path.join(LOG_DIR, 'diagnostic.log');
21+
22+
beforeEach(() => {
23+
vi.clearAllMocks();
24+
25+
mockConfigService = {
26+
getProjectRoot: vi.fn().mockReturnValue(TEST_PROJECT_ROOT),
27+
} as unknown as ConfigService;
28+
29+
service = new DiagnosticLogService(mockConfigService);
30+
});
31+
32+
afterEach(() => {
33+
vi.restoreAllMocks();
34+
});
35+
36+
describe('getLogDir', () => {
37+
it('should return correct log directory path', () => {
38+
const result = service.getLogDir();
39+
expect(result).toBe(LOG_DIR);
40+
});
41+
});
42+
43+
describe('getLogFilePath', () => {
44+
it('should return correct log file path', () => {
45+
const result = service.getLogFilePath();
46+
expect(result).toBe(LOG_FILE);
47+
});
48+
});
49+
50+
describe('log', () => {
51+
it('should create log entry and write to file', async () => {
52+
vi.mocked(existsSync).mockReturnValue(false);
53+
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
54+
55+
const result = await service.log('info', 'test', 'Test message', {
56+
key: 'value',
57+
});
58+
59+
expect(result.success).toBe(true);
60+
expect(result.filePath).toBe(LOG_FILE);
61+
expect(mkdirSync).toHaveBeenCalledWith(LOG_DIR, { recursive: true });
62+
expect(fs.writeFile).toHaveBeenCalled();
63+
64+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
65+
const writtenContent = JSON.parse(writeCall[1] as string);
66+
expect(writtenContent.version).toBe('1.0.0');
67+
expect(writtenContent.entries).toHaveLength(1);
68+
expect(writtenContent.entries[0].level).toBe('info');
69+
expect(writtenContent.entries[0].category).toBe('test');
70+
expect(writtenContent.entries[0].message).toBe('Test message');
71+
});
72+
73+
it('should append to existing log file', async () => {
74+
const existingLog = {
75+
version: '1.0.0',
76+
createdAt: '2024-01-01T00:00:00.000Z',
77+
entries: [
78+
{
79+
timestamp: '2024-01-01T00:00:00.000Z',
80+
level: 'info',
81+
category: 'existing',
82+
message: 'Existing entry',
83+
},
84+
],
85+
};
86+
87+
vi.mocked(existsSync).mockReturnValue(true);
88+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(existingLog));
89+
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
90+
91+
await service.log('warn', 'new', 'New entry');
92+
93+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
94+
const writtenContent = JSON.parse(writeCall[1] as string);
95+
expect(writtenContent.entries).toHaveLength(2);
96+
});
97+
98+
it('should handle write errors gracefully', async () => {
99+
vi.mocked(existsSync).mockReturnValue(false);
100+
vi.mocked(fs.writeFile).mockRejectedValue(new Error('Write failed'));
101+
102+
const result = await service.log('error', 'test', 'Test message');
103+
104+
expect(result.success).toBe(false);
105+
expect(result.error).toBe('Write failed');
106+
});
107+
});
108+
109+
describe('convenience methods', () => {
110+
beforeEach(() => {
111+
vi.mocked(existsSync).mockReturnValue(false);
112+
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
113+
});
114+
115+
it('debug should log with debug level', async () => {
116+
await service.debug('cat', 'msg');
117+
118+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
119+
const writtenContent = JSON.parse(writeCall[1] as string);
120+
expect(writtenContent.entries[0].level).toBe('debug');
121+
});
122+
123+
it('info should log with info level', async () => {
124+
await service.info('cat', 'msg');
125+
126+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
127+
const writtenContent = JSON.parse(writeCall[1] as string);
128+
expect(writtenContent.entries[0].level).toBe('info');
129+
});
130+
131+
it('warn should log with warn level', async () => {
132+
await service.warn('cat', 'msg');
133+
134+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
135+
const writtenContent = JSON.parse(writeCall[1] as string);
136+
expect(writtenContent.entries[0].level).toBe('warn');
137+
});
138+
139+
it('error should log with error level', async () => {
140+
await service.error('cat', 'msg');
141+
142+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
143+
const writtenContent = JSON.parse(writeCall[1] as string);
144+
expect(writtenContent.entries[0].level).toBe('error');
145+
});
146+
});
147+
148+
describe('logConfigLoading', () => {
149+
beforeEach(() => {
150+
vi.mocked(existsSync).mockReturnValue(false);
151+
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
152+
});
153+
154+
it('should log successful config loading', async () => {
155+
await service.logConfigLoading(true, '/project', 'ko');
156+
157+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
158+
const writtenContent = JSON.parse(writeCall[1] as string);
159+
expect(writtenContent.entries[0].level).toBe('info');
160+
expect(writtenContent.entries[0].category).toBe('config');
161+
expect(writtenContent.entries[0].context.configLanguage).toBe('ko');
162+
});
163+
164+
it('should log failed config loading', async () => {
165+
await service.logConfigLoading(
166+
false,
167+
'/project',
168+
undefined,
169+
'Config not found',
170+
);
171+
172+
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
173+
const writtenContent = JSON.parse(writeCall[1] as string);
174+
expect(writtenContent.entries[0].level).toBe('warn');
175+
expect(writtenContent.entries[0].context.error).toBe('Config not found');
176+
});
177+
});
178+
179+
describe('readLogs', () => {
180+
it('should return empty array if file does not exist', async () => {
181+
vi.mocked(existsSync).mockReturnValue(false);
182+
183+
const result = await service.readLogs();
184+
185+
expect(result).toEqual([]);
186+
});
187+
188+
it('should return log entries from file', async () => {
189+
const logFile = {
190+
version: '1.0.0',
191+
createdAt: '2024-01-01T00:00:00.000Z',
192+
entries: [
193+
{
194+
timestamp: '2024-01-01T00:00:00.000Z',
195+
level: 'info',
196+
category: 'test',
197+
message: 'Test',
198+
},
199+
],
200+
};
201+
202+
vi.mocked(existsSync).mockReturnValue(true);
203+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(logFile));
204+
205+
const result = await service.readLogs();
206+
207+
expect(result).toHaveLength(1);
208+
expect(result[0].message).toBe('Test');
209+
});
210+
});
211+
212+
describe('clearLogs', () => {
213+
it('should delete log file if exists', async () => {
214+
vi.mocked(existsSync).mockReturnValue(true);
215+
vi.mocked(fs.unlink).mockResolvedValue(undefined);
216+
217+
const result = await service.clearLogs();
218+
219+
expect(result.success).toBe(true);
220+
expect(fs.unlink).toHaveBeenCalledWith(LOG_FILE);
221+
});
222+
223+
it('should succeed even if file does not exist', async () => {
224+
vi.mocked(existsSync).mockReturnValue(false);
225+
226+
const result = await service.clearLogs();
227+
228+
expect(result.success).toBe(true);
229+
expect(fs.unlink).not.toHaveBeenCalled();
230+
});
231+
});
232+
});

0 commit comments

Comments
 (0)