Skip to content

Commit f993972

Browse files
Agent Communication MCP Serverclaude
andcommitted
fix: implement native JSON operations and resolve TypeScript strict mode issues
- Replace all fs-extra JSON methods (readJson/writeJson) with native JSON operations - Fix fs-extra-safe implementation to handle cross-device moves properly - Update test mocks to use readFile/writeFile with JSON strings - Resolve ensureDir test to match actual Node.js behavior with recursive:true - Maintain 95%+ test coverage requirement - All TypeScript strict mode violations resolved - All ESLint violations resolved 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 10f2630 commit f993972

File tree

11 files changed

+106
-115
lines changed

11 files changed

+106
-115
lines changed

src/core/ComplianceTracker.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ export class ComplianceTracker {
166166

167167
try {
168168
if (await fs.pathExists(recordPath)) {
169-
const record = await fs.readJson(recordPath) as AgentComplianceRecord;
169+
const content = await fs.readFile(recordPath, 'utf8');
170+
const record = JSON.parse(content) as AgentComplianceRecord;
170171
// Convert string dates back to Date objects
171172
record.lastActivity = new Date(record.lastActivity);
172173
this.records.set(agent, record);
@@ -201,7 +202,8 @@ export class ComplianceTracker {
201202
private async saveAgentRecord(record: AgentComplianceRecord): Promise<void> {
202203
try {
203204
const recordPath = path.join(this.complianceDir, `${record.agent}.json`);
204-
await fs.writeJson(recordPath, record);
205+
const jsonStr = JSON.stringify(record, null, 2);
206+
await fs.writeFile(recordPath, jsonStr);
205207
} catch (error) {
206208
// Error saving compliance record - silently continue
207209
void error; // Acknowledge but don't log

src/core/DelegationTracker.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ export class DelegationTracker {
289289
for (const file of files) {
290290
if (file.endsWith('.json')) {
291291
const filePath = path.join(this.delegationsDir, file);
292-
const record = await fs.readJson(filePath) as DelegationRecord;
292+
const content = await fs.readFile(filePath, 'utf8');
293+
const record = JSON.parse(content) as DelegationRecord;
293294

294295
// Convert string date to Date object if needed
295296
const createdAt = new Date(record.createdAt);
@@ -322,7 +323,8 @@ export class DelegationTracker {
322323
if (file.endsWith('.json')) {
323324
try {
324325
const filePath = path.join(this.delegationsDir, file);
325-
const record = await fs.readJson(filePath) as DelegationRecord;
326+
const content = await fs.readFile(filePath, 'utf8');
327+
const record = JSON.parse(content) as DelegationRecord;
326328

327329
// Convert string dates to Date objects
328330
record.createdAt = new Date(record.createdAt);
@@ -359,7 +361,8 @@ export class DelegationTracker {
359361

360362
try {
361363
if (await fs.pathExists(recordPath)) {
362-
const record = await fs.readJson(recordPath) as DelegationRecord;
364+
const content = await fs.readFile(recordPath, 'utf8');
365+
const record = JSON.parse(content) as DelegationRecord;
363366
record.createdAt = new Date(record.createdAt);
364367
this.delegations.set(taskId, record);
365368
return record;
@@ -378,7 +381,8 @@ export class DelegationTracker {
378381
private async saveDelegationRecord(record: DelegationRecord): Promise<void> {
379382
try {
380383
const recordPath = path.join(this.delegationsDir, `${record.taskId}.json`);
381-
await fs.writeJson(recordPath, record);
384+
const jsonStr = JSON.stringify(record, null, 2);
385+
await fs.writeFile(recordPath, jsonStr);
382386
} catch (error) {
383387
// Error saving delegation - silently continue
384388
void error; // Acknowledge but don't log

src/resources/ResourceManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,8 @@ export class ResourceManager {
387387
private async getServerVersion(): Promise<string> {
388388
try {
389389
const fs = await import('../utils/fs-extra-safe.js');
390-
const packageJson = await fs.readJSON('./package.json') as { version?: string };
390+
const content = await fs.readFile('./package.json', 'utf8');
391+
const packageJson = JSON.parse(content) as { version?: string };
391392
return packageJson.version ?? '0.0.0';
392393
} catch {
393394
return '0.0.0';

src/resources/providers/ServerResourceProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,8 @@ export class ServerResourceProvider implements ResourceProvider {
295295

296296
try {
297297
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
298-
const packageJson = await fs.readJSON(packageJsonPath);
298+
const content = await fs.readFile(packageJsonPath, 'utf8');
299+
const packageJson = JSON.parse(content);
299300
const version = (packageJson as { version?: string }).version || '0.0.0';
300301

301302
// Update cache

src/utils/fs-extra-safe.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,17 @@ class SafeFileSystem implements SafeFsInterface {
349349
}
350350

351351
// Node.js built-in fallback - rename is atomic move operation
352-
await nodeFs.rename(src, dest);
352+
try {
353+
await nodeFs.rename(src, dest);
354+
} catch (error) {
355+
// If cross-device link error, fallback to copy + remove
356+
if ((error as NodeJS.ErrnoException).code === 'EXDEV') {
357+
await nodeFs.copyFile(src, dest);
358+
await nodeFs.unlink(src);
359+
} else {
360+
throw error;
361+
}
362+
}
353363
}
354364

355365
async copy(src: string, dest: string, options?: Record<string, unknown>): Promise<void> {
@@ -481,17 +491,6 @@ export const ensureDir = (dirPath: string) => safeFs.ensureDir(dirPath);
481491
export const appendFile = (filePath: string, data: string) => safeFs.appendFile(filePath, data);
482492
export const move = (src: string, dest: string, options?: Record<string, unknown>) => safeFs.move(src, dest, options);
483493
export const copy = (src: string, dest: string, options?: Record<string, unknown>) => safeFs.copy(src, dest, options);
484-
// JSON utility functions - always use Node.js built-in JSON methods
485-
export const readJSON = async (filePath: string): Promise<unknown> => {
486-
const content = await readFile(filePath, 'utf8');
487-
return JSON.parse(content);
488-
};
489-
export const readJson = readJSON; // Alias
490-
export const writeJSON = async (filePath: string, data: unknown): Promise<void> => {
491-
const jsonStr = JSON.stringify(data, null, 2);
492-
await writeFile(filePath, jsonStr);
493-
};
494-
export const writeJson = writeJSON; // Alias
495494
export const mkdtemp = (prefix: string) => safeFs.mkdtemp(prefix);
496495
export const mkdir = (dirPath: string, options?: Mode | MakeDirectoryOptions | null) => safeFs.mkdir(dirPath, options);
497496
export const chmod = (filePath: string, mode: string | number) => safeFs.chmod(filePath, mode);

tests/integration/file-system-regression.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -326,16 +326,16 @@ describe('File System Operations Regression Test', () => {
326326
});
327327

328328
describe('JSON file operations', () => {
329-
it('REGRESSION: should use fs.readJson and fs.writeJson correctly', async () => {
329+
it('REGRESSION: should use fs.readFile and fs.writeFile with JSON.parse/stringify correctly', async () => {
330330
const jsonFile = path.join(testDir, 'test.json');
331331
const testData = { name: 'test', version: '1.0.0', items: [1, 2, 3] };
332332

333-
// This would fail with "fs.writeJson is not a function" if import is wrong
334-
// Note: fs-extra-safe writeJson doesn't support options parameter
335-
await fs.writeJson(jsonFile, testData);
333+
// This would fail with "fs.writeFile is not a function" if import is wrong
334+
await fs.writeFile(jsonFile, JSON.stringify(testData, null, 2));
336335

337-
// This would fail with "fs.readJson is not a function" if import is wrong
338-
const readData = await fs.readJson(jsonFile);
336+
// This would fail with "fs.readFile is not a function" if import is wrong
337+
const readContent = await fs.readFile(jsonFile, 'utf8');
338+
const readData = JSON.parse(readContent) as typeof testData;
339339
expect(readData).toEqual(testData);
340340
});
341341
});
@@ -367,8 +367,8 @@ describe('File System Operations Regression Test', () => {
367367
expect(typeof fs.pathExists).toBe('function');
368368
expect(typeof fs.stat).toBe('function');
369369
expect(typeof fs.remove).toBe('function');
370-
expect(typeof fs.readJson).toBe('function');
371-
expect(typeof fs.writeJson).toBe('function');
370+
expect(typeof fs.readFile).toBe('function');
371+
expect(typeof fs.writeFile).toBe('function');
372372
expect(typeof fs.copy).toBe('function');
373373

374374
// All fs-extra operations are available and properly imported

tests/smoke/basic-functionality.smoke.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ describe('Smoke Tests - Basic Functionality', () => {
101101
const jsonFile = path.join(tempDir, 'test.json');
102102
const data = { test: true, value: 123 };
103103

104-
// This will catch fs.readJsonSync vs fs.readJson issues
105-
await fs.writeJson(jsonFile, data);
106-
const readData = await fs.readJson(jsonFile);
104+
// This will catch fs.readFile vs fs.writeFile issues with JSON
105+
await fs.writeFile(jsonFile, JSON.stringify(data, null, 2));
106+
const readContent = await fs.readFile(jsonFile, 'utf-8');
107+
const readData = JSON.parse(readContent) as typeof data;
107108

108109
expect(readData).toEqual(data);
109110
});

tests/unit/core/ComplianceTracker.test.ts

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import * as path from 'path';
1919
jest.mock('../../../src/utils/fs-extra-safe.js', () => ({
2020
pathExists: jest.fn(),
2121
ensureDir: jest.fn(),
22-
readJson: jest.fn(),
23-
writeJson: jest.fn(),
22+
readFile: jest.fn(),
23+
writeFile: jest.fn(),
2424
remove: jest.fn(),
2525
readdir: jest.fn()
2626
}));
@@ -47,8 +47,8 @@ describe('ComplianceTracker', () => {
4747
// Default mock implementations
4848
mockFs.pathExists.mockResolvedValue(false);
4949
mockFs.ensureDir.mockResolvedValue(undefined);
50-
mockFs.readJson.mockResolvedValue({});
51-
mockFs.writeJson.mockResolvedValue(undefined);
50+
mockFs.readFile.mockResolvedValue('{}');
51+
mockFs.writeFile.mockResolvedValue(undefined);
5252

5353
// Create ComplianceTracker instance
5454
complianceTracker = new ComplianceTracker(mockConfig);
@@ -145,12 +145,9 @@ describe('ComplianceTracker', () => {
145145
await complianceTracker.recordActivity('test-agent', activity);
146146

147147
// Assert
148-
expect(mockFs.writeJson).toHaveBeenCalledWith(
148+
expect(mockFs.writeFile).toHaveBeenCalledWith(
149149
path.join(mockConfig.commDir, '.compliance', 'test-agent.json'),
150-
expect.objectContaining({
151-
agent: 'test-agent',
152-
tasksCreated: 1
153-
})
150+
expect.stringContaining('"agent": "test-agent"')
154151
);
155152
});
156153
});
@@ -171,7 +168,7 @@ describe('ComplianceTracker', () => {
171168
escalationLevel: 1
172169
};
173170

174-
mockFs.readJson.mockResolvedValue(perfectRecord);
171+
mockFs.readFile.mockResolvedValue(JSON.stringify(perfectRecord));
175172

176173
// Act
177174
const level = await complianceTracker.getComplianceLevel('perfect-agent');
@@ -196,7 +193,7 @@ describe('ComplianceTracker', () => {
196193
};
197194

198195
mockFs.pathExists.mockResolvedValue(true);
199-
mockFs.readJson.mockResolvedValue(record);
196+
mockFs.readFile.mockResolvedValue(JSON.stringify(record));
200197

201198
// Act
202199
const level = await complianceTracker.getComplianceLevel('poor-delegator');
@@ -222,7 +219,7 @@ describe('ComplianceTracker', () => {
222219
};
223220

224221
mockFs.pathExists.mockResolvedValue(true);
225-
mockFs.readJson.mockResolvedValue(record);
222+
mockFs.readFile.mockResolvedValue(JSON.stringify(record));
226223

227224
// Act
228225
const level = await complianceTracker.getComplianceLevel('no-todo-agent');
@@ -248,7 +245,7 @@ describe('ComplianceTracker', () => {
248245
};
249246

250247
mockFs.pathExists.mockResolvedValue(true);
251-
mockFs.readJson.mockResolvedValue(record);
248+
mockFs.readFile.mockResolvedValue(JSON.stringify(record));
252249

253250
// Act
254251
const level = await complianceTracker.getComplianceLevel('no-plan-agent');
@@ -284,7 +281,7 @@ describe('ComplianceTracker', () => {
284281
escalationLevel: 4
285282
};
286283

287-
mockFs.readJson.mockResolvedValue(record);
284+
mockFs.readFile.mockResolvedValue(JSON.stringify(record));
288285

289286
// Act
290287
const level = await complianceTracker.getComplianceLevel('terrible-agent');
@@ -458,18 +455,15 @@ describe('ComplianceTracker', () => {
458455
escalationLevel: 1
459456
};
460457

461-
mockFs.readJson.mockResolvedValue(record);
458+
mockFs.readFile.mockResolvedValue(JSON.stringify(record));
462459

463460
// Act
464461
await complianceTracker.updateComplianceScore('test-agent');
465462

466463
// Assert
467-
expect(mockFs.writeJson).toHaveBeenCalledWith(
464+
expect(mockFs.writeFile).toHaveBeenCalledWith(
468465
expect.any(String),
469-
expect.objectContaining({
470-
complianceScore: expect.any(Number),
471-
escalationLevel: expect.any(Number)
472-
})
466+
expect.stringContaining('"complianceScore": ')
473467
);
474468
});
475469
});
@@ -494,7 +488,7 @@ describe('ComplianceTracker', () => {
494488
};
495489

496490
mockFs.readdir.mockResolvedValue(['stale-agent.json'] as unknown as string[]);
497-
mockFs.readJson.mockResolvedValue(staleRecord);
491+
mockFs.readFile.mockResolvedValue(JSON.stringify(staleRecord));
498492
mockFs.pathExists.mockResolvedValue(true);
499493
mockFs.remove.mockResolvedValue(undefined);
500494

@@ -522,7 +516,7 @@ describe('ComplianceTracker', () => {
522516
escalationLevel: 1
523517
};
524518

525-
mockFs.readJson.mockResolvedValue(recentRecord);
519+
mockFs.readFile.mockResolvedValue(JSON.stringify(recentRecord));
526520
mockFs.pathExists.mockResolvedValue(true);
527521

528522
// Act
@@ -536,7 +530,7 @@ describe('ComplianceTracker', () => {
536530
describe('error handling', () => {
537531
it('should handle file read errors gracefully', async () => {
538532
// Arrange
539-
mockFs.readJson.mockRejectedValue(new Error('File read error'));
533+
mockFs.readFile.mockRejectedValue(new Error('File read error'));
540534

541535
// Act
542536
const level = await complianceTracker.getComplianceLevel('error-agent');
@@ -547,7 +541,7 @@ describe('ComplianceTracker', () => {
547541

548542
it('should handle file write errors gracefully', async () => {
549543
// Arrange
550-
mockFs.writeJson.mockRejectedValue(new Error('File write error'));
544+
mockFs.writeFile.mockRejectedValue(new Error('File write error'));
551545

552546
const activity: ComplianceActivity = {
553547
type: 'task_created',

0 commit comments

Comments
 (0)