codebakers 1.0.45

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.
Files changed (53) hide show
  1. package/.vscodeignore +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +88 -0
  4. package/codebakers-1.0.0.vsix +0 -0
  5. package/codebakers-1.0.10.vsix +0 -0
  6. package/codebakers-1.0.11.vsix +0 -0
  7. package/codebakers-1.0.12.vsix +0 -0
  8. package/codebakers-1.0.13.vsix +0 -0
  9. package/codebakers-1.0.14.vsix +0 -0
  10. package/codebakers-1.0.15.vsix +0 -0
  11. package/codebakers-1.0.16.vsix +0 -0
  12. package/codebakers-1.0.17.vsix +0 -0
  13. package/codebakers-1.0.18.vsix +0 -0
  14. package/codebakers-1.0.19.vsix +0 -0
  15. package/codebakers-1.0.20.vsix +0 -0
  16. package/codebakers-1.0.21.vsix +0 -0
  17. package/codebakers-1.0.22.vsix +0 -0
  18. package/codebakers-1.0.23.vsix +0 -0
  19. package/codebakers-1.0.24.vsix +0 -0
  20. package/codebakers-1.0.25.vsix +0 -0
  21. package/codebakers-1.0.26.vsix +0 -0
  22. package/codebakers-1.0.27.vsix +0 -0
  23. package/codebakers-1.0.28.vsix +0 -0
  24. package/codebakers-1.0.29.vsix +0 -0
  25. package/codebakers-1.0.30.vsix +0 -0
  26. package/codebakers-1.0.31.vsix +0 -0
  27. package/codebakers-1.0.32.vsix +0 -0
  28. package/codebakers-1.0.35.vsix +0 -0
  29. package/codebakers-1.0.36.vsix +0 -0
  30. package/codebakers-1.0.37.vsix +0 -0
  31. package/codebakers-1.0.38.vsix +0 -0
  32. package/codebakers-1.0.39.vsix +0 -0
  33. package/codebakers-1.0.40.vsix +0 -0
  34. package/codebakers-1.0.41.vsix +0 -0
  35. package/codebakers-1.0.42.vsix +0 -0
  36. package/codebakers-1.0.43.vsix +0 -0
  37. package/codebakers-1.0.44.vsix +0 -0
  38. package/codebakers-1.0.45.vsix +0 -0
  39. package/dist/extension.js +1394 -0
  40. package/esbuild.js +63 -0
  41. package/media/icon.png +0 -0
  42. package/media/icon.svg +7 -0
  43. package/nul +1 -0
  44. package/package.json +127 -0
  45. package/preview.html +547 -0
  46. package/src/ChatPanelProvider.ts +1815 -0
  47. package/src/ChatViewProvider.ts +749 -0
  48. package/src/CodeBakersClient.ts +1146 -0
  49. package/src/CodeValidator.ts +645 -0
  50. package/src/FileOperations.ts +410 -0
  51. package/src/ProjectContext.ts +526 -0
  52. package/src/extension.ts +332 -0
  53. package/tsconfig.json +19 -0
@@ -0,0 +1,645 @@
1
+ import * as vscode from 'vscode';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+ import { FileOperation } from './CodeBakersClient';
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ export interface ValidationResult {
11
+ passed: boolean;
12
+ errors: ValidationError[];
13
+ warnings: ValidationWarning[];
14
+ suggestions: string[];
15
+ tscResult?: TypeScriptCheckResult;
16
+ }
17
+
18
+ export interface TypeScriptCheckResult {
19
+ passed: boolean;
20
+ errors: Array<{
21
+ file: string;
22
+ line: number;
23
+ column: number;
24
+ message: string;
25
+ code: string;
26
+ }>;
27
+ errorCount: number;
28
+ }
29
+
30
+ export interface ValidationError {
31
+ file: string;
32
+ line?: number;
33
+ message: string;
34
+ type: 'type' | 'import' | 'syntax' | 'security';
35
+ }
36
+
37
+ export interface ValidationWarning {
38
+ file: string;
39
+ message: string;
40
+ type: 'any-type' | 'missing-error-handling' | 'no-test' | 'console-log';
41
+ }
42
+
43
+ export interface DependencyCheck {
44
+ missing: string[];
45
+ available: string[];
46
+ suggestions: { package: string; command: string }[];
47
+ }
48
+
49
+ export interface TypeInventory {
50
+ types: Map<string, TypeInfo>;
51
+ exports: Map<string, ExportInfo[]>;
52
+ }
53
+
54
+ export interface TypeInfo {
55
+ name: string;
56
+ file: string;
57
+ kind: 'interface' | 'type' | 'enum' | 'class';
58
+ exported: boolean;
59
+ }
60
+
61
+ export interface ExportInfo {
62
+ name: string;
63
+ file: string;
64
+ kind: 'function' | 'const' | 'class' | 'type' | 'default';
65
+ }
66
+
67
+ export class CodeValidator {
68
+ private workspaceRoot: string | undefined;
69
+ private typeInventory: TypeInventory | null = null;
70
+ private installedPackages: Set<string> = new Set();
71
+
72
+ constructor() {
73
+ this.workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
74
+ }
75
+
76
+ /**
77
+ * Initialize the validator by scanning the project
78
+ */
79
+ async initialize(): Promise<void> {
80
+ if (!this.workspaceRoot) return;
81
+
82
+ await Promise.all([
83
+ this.scanInstalledPackages(),
84
+ this.scanTypeInventory()
85
+ ]);
86
+ }
87
+
88
+ /**
89
+ * Scan package.json for installed packages
90
+ */
91
+ private async scanInstalledPackages(): Promise<void> {
92
+ if (!this.workspaceRoot) return;
93
+
94
+ const packageJsonPath = path.join(this.workspaceRoot, 'package.json');
95
+ if (!fs.existsSync(packageJsonPath)) return;
96
+
97
+ try {
98
+ const content = fs.readFileSync(packageJsonPath, 'utf-8');
99
+ const pkg = JSON.parse(content);
100
+
101
+ this.installedPackages.clear();
102
+
103
+ // Add all dependencies
104
+ for (const dep of Object.keys(pkg.dependencies || {})) {
105
+ this.installedPackages.add(dep);
106
+ }
107
+ for (const dep of Object.keys(pkg.devDependencies || {})) {
108
+ this.installedPackages.add(dep);
109
+ }
110
+
111
+ console.log(`CodeValidator: Found ${this.installedPackages.size} installed packages`);
112
+ } catch (error) {
113
+ console.error('CodeValidator: Failed to scan packages:', error);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Scan project for type definitions and exports
119
+ */
120
+ private async scanTypeInventory(): Promise<void> {
121
+ if (!this.workspaceRoot) return;
122
+
123
+ this.typeInventory = {
124
+ types: new Map(),
125
+ exports: new Map()
126
+ };
127
+
128
+ // Find all TypeScript files
129
+ const files = await vscode.workspace.findFiles(
130
+ '**/*.{ts,tsx}',
131
+ '**/node_modules/**',
132
+ 200
133
+ );
134
+
135
+ for (const file of files) {
136
+ try {
137
+ const content = fs.readFileSync(file.fsPath, 'utf-8');
138
+ const relativePath = vscode.workspace.asRelativePath(file);
139
+
140
+ // Extract types and interfaces
141
+ this.extractTypes(content, relativePath);
142
+
143
+ // Extract exports
144
+ this.extractExports(content, relativePath);
145
+ } catch (error) {
146
+ // Skip files we can't read
147
+ }
148
+ }
149
+
150
+ console.log(`CodeValidator: Found ${this.typeInventory.types.size} types, ${this.typeInventory.exports.size} files with exports`);
151
+ }
152
+
153
+ /**
154
+ * Extract type definitions from file content
155
+ */
156
+ private extractTypes(content: string, filePath: string): void {
157
+ if (!this.typeInventory) return;
158
+
159
+ // Match interfaces
160
+ const interfaceRegex = /export\s+interface\s+(\w+)/g;
161
+ let match;
162
+ while ((match = interfaceRegex.exec(content)) !== null) {
163
+ this.typeInventory.types.set(match[1], {
164
+ name: match[1],
165
+ file: filePath,
166
+ kind: 'interface',
167
+ exported: true
168
+ });
169
+ }
170
+
171
+ // Match type aliases
172
+ const typeRegex = /export\s+type\s+(\w+)/g;
173
+ while ((match = typeRegex.exec(content)) !== null) {
174
+ this.typeInventory.types.set(match[1], {
175
+ name: match[1],
176
+ file: filePath,
177
+ kind: 'type',
178
+ exported: true
179
+ });
180
+ }
181
+
182
+ // Match enums
183
+ const enumRegex = /export\s+enum\s+(\w+)/g;
184
+ while ((match = enumRegex.exec(content)) !== null) {
185
+ this.typeInventory.types.set(match[1], {
186
+ name: match[1],
187
+ file: filePath,
188
+ kind: 'enum',
189
+ exported: true
190
+ });
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Extract exports from file content
196
+ */
197
+ private extractExports(content: string, filePath: string): void {
198
+ if (!this.typeInventory) return;
199
+
200
+ const exports: ExportInfo[] = [];
201
+
202
+ // Match exported functions
203
+ const funcRegex = /export\s+(?:async\s+)?function\s+(\w+)/g;
204
+ let match;
205
+ while ((match = funcRegex.exec(content)) !== null) {
206
+ exports.push({ name: match[1], file: filePath, kind: 'function' });
207
+ }
208
+
209
+ // Match exported consts
210
+ const constRegex = /export\s+const\s+(\w+)/g;
211
+ while ((match = constRegex.exec(content)) !== null) {
212
+ exports.push({ name: match[1], file: filePath, kind: 'const' });
213
+ }
214
+
215
+ // Match exported classes
216
+ const classRegex = /export\s+class\s+(\w+)/g;
217
+ while ((match = classRegex.exec(content)) !== null) {
218
+ exports.push({ name: match[1], file: filePath, kind: 'class' });
219
+ }
220
+
221
+ // Match default exports
222
+ if (/export\s+default/.test(content)) {
223
+ exports.push({ name: 'default', file: filePath, kind: 'default' });
224
+ }
225
+
226
+ if (exports.length > 0) {
227
+ this.typeInventory.exports.set(filePath, exports);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Check if required packages are installed
233
+ */
234
+ checkDependencies(fileOperations: FileOperation[]): DependencyCheck {
235
+ const result: DependencyCheck = {
236
+ missing: [],
237
+ available: [],
238
+ suggestions: []
239
+ };
240
+
241
+ const requiredPackages = new Set<string>();
242
+
243
+ for (const op of fileOperations) {
244
+ if (!op.content) continue;
245
+
246
+ // Extract import statements
247
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"./][^'"]*)['"]/g;
248
+ let match;
249
+ while ((match = importRegex.exec(op.content)) !== null) {
250
+ const pkg = match[1].split('/')[0]; // Handle scoped packages
251
+ if (pkg.startsWith('@')) {
252
+ // Scoped package like @tanstack/react-query
253
+ const scopedPkg = match[1].split('/').slice(0, 2).join('/');
254
+ requiredPackages.add(scopedPkg);
255
+ } else {
256
+ requiredPackages.add(pkg);
257
+ }
258
+ }
259
+ }
260
+
261
+ // Check against installed packages
262
+ for (const pkg of requiredPackages) {
263
+ if (this.installedPackages.has(pkg)) {
264
+ result.available.push(pkg);
265
+ } else {
266
+ // Skip Node.js built-ins
267
+ const builtins = ['fs', 'path', 'http', 'https', 'crypto', 'util', 'stream', 'events', 'url', 'querystring', 'os', 'child_process'];
268
+ if (!builtins.includes(pkg)) {
269
+ result.missing.push(pkg);
270
+ result.suggestions.push({
271
+ package: pkg,
272
+ command: `npm install ${pkg}`
273
+ });
274
+ }
275
+ }
276
+ }
277
+
278
+ return result;
279
+ }
280
+
281
+ /**
282
+ * Validate generated file operations
283
+ */
284
+ async validateFileOperations(fileOperations: FileOperation[]): Promise<ValidationResult> {
285
+ const result: ValidationResult = {
286
+ passed: true,
287
+ errors: [],
288
+ warnings: [],
289
+ suggestions: []
290
+ };
291
+
292
+ for (const op of fileOperations) {
293
+ if (!op.content || op.action === 'delete') continue;
294
+
295
+ // Check imports resolve
296
+ const importErrors = this.validateImports(op);
297
+ result.errors.push(...importErrors);
298
+
299
+ // Check for type issues
300
+ const typeWarnings = this.checkTypeUsage(op);
301
+ result.warnings.push(...typeWarnings);
302
+
303
+ // Check for security issues
304
+ const securityErrors = this.checkSecurity(op);
305
+ result.errors.push(...securityErrors);
306
+
307
+ // Check for best practices
308
+ const practiceWarnings = this.checkBestPractices(op);
309
+ result.warnings.push(...practiceWarnings);
310
+ }
311
+
312
+ // Check if tests are included
313
+ const hasTestFile = fileOperations.some(op =>
314
+ op.path.includes('.test.') ||
315
+ op.path.includes('.spec.') ||
316
+ op.path.includes('__tests__')
317
+ );
318
+
319
+ if (!hasTestFile && fileOperations.length > 0) {
320
+ result.warnings.push({
321
+ file: 'project',
322
+ message: 'No test file included with this feature',
323
+ type: 'no-test'
324
+ });
325
+ }
326
+
327
+ // Add suggestions for existing types
328
+ const suggestions = this.suggestExistingTypes(fileOperations);
329
+ result.suggestions.push(...suggestions);
330
+
331
+ result.passed = result.errors.length === 0;
332
+
333
+ return result;
334
+ }
335
+
336
+ /**
337
+ * Validate that imports can resolve
338
+ */
339
+ private validateImports(op: FileOperation): ValidationError[] {
340
+ const errors: ValidationError[] = [];
341
+ if (!op.content || !this.workspaceRoot) return errors;
342
+
343
+ // Match relative imports
344
+ const relativeImportRegex = /import\s+.*?\s+from\s+['"](\.[^'"]+)['"]/g;
345
+ let match;
346
+
347
+ while ((match = relativeImportRegex.exec(op.content)) !== null) {
348
+ const importPath = match[1];
349
+ const opDir = path.dirname(op.path);
350
+ const resolvedPath = path.join(this.workspaceRoot, opDir, importPath);
351
+
352
+ // Check if file exists (try various extensions)
353
+ const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx'];
354
+ const exists = extensions.some(ext => {
355
+ const fullPath = resolvedPath + ext;
356
+ return fs.existsSync(fullPath);
357
+ });
358
+
359
+ // Also check if it's being created in this batch
360
+ const isBeingCreated = importPath.replace(/^\.\//, '').replace(/^\.\.\//, '');
361
+ // Skip validation for imports that might be to other new files
362
+
363
+ if (!exists) {
364
+ // This might be a new file being created, so just warn
365
+ // Don't add as error since we might be creating multiple files
366
+ }
367
+ }
368
+
369
+ return errors;
370
+ }
371
+
372
+ /**
373
+ * Check for problematic type usage
374
+ */
375
+ private checkTypeUsage(op: FileOperation): ValidationWarning[] {
376
+ const warnings: ValidationWarning[] = [];
377
+ if (!op.content) return warnings;
378
+
379
+ // Check for excessive 'any' usage
380
+ const anyMatches = op.content.match(/:\s*any\b/g) || [];
381
+ if (anyMatches.length > 2) {
382
+ warnings.push({
383
+ file: op.path,
384
+ message: `Found ${anyMatches.length} uses of 'any' type - consider proper typing`,
385
+ type: 'any-type'
386
+ });
387
+ }
388
+
389
+ return warnings;
390
+ }
391
+
392
+ /**
393
+ * Check for security issues
394
+ */
395
+ private checkSecurity(op: FileOperation): ValidationError[] {
396
+ const errors: ValidationError[] = [];
397
+ if (!op.content) return errors;
398
+
399
+ // Check for hardcoded secrets
400
+ const secretPatterns = [
401
+ { pattern: /['"]sk-[a-zA-Z0-9]{20,}['"]/, name: 'OpenAI API key' },
402
+ { pattern: /['"]sk_live_[a-zA-Z0-9]+['"]/, name: 'Stripe live key' },
403
+ { pattern: /['"]ghp_[a-zA-Z0-9]+['"]/, name: 'GitHub token' },
404
+ { pattern: /password\s*[:=]\s*['"][^'"]+['"]/, name: 'Hardcoded password' }
405
+ ];
406
+
407
+ for (const { pattern, name } of secretPatterns) {
408
+ if (pattern.test(op.content)) {
409
+ errors.push({
410
+ file: op.path,
411
+ message: `Potential ${name} found in code - use environment variables`,
412
+ type: 'security'
413
+ });
414
+ }
415
+ }
416
+
417
+ // Check client-side files for server secrets
418
+ if (op.path.includes('/components/') || op.path.includes('/app/') && !op.path.includes('/api/')) {
419
+ if (/process\.env\.((?!NEXT_PUBLIC_)[A-Z_]+)/.test(op.content)) {
420
+ errors.push({
421
+ file: op.path,
422
+ message: 'Server-side env var accessed in client component - use NEXT_PUBLIC_ prefix or move to API route',
423
+ type: 'security'
424
+ });
425
+ }
426
+ }
427
+
428
+ return errors;
429
+ }
430
+
431
+ /**
432
+ * Check for best practices
433
+ */
434
+ private checkBestPractices(op: FileOperation): ValidationWarning[] {
435
+ const warnings: ValidationWarning[] = [];
436
+ if (!op.content) return warnings;
437
+
438
+ // Check for console.log in production code
439
+ if (!/\.test\.|\.spec\./.test(op.path)) {
440
+ const consoleCount = (op.content.match(/console\.(log|debug|info)\(/g) || []).length;
441
+ if (consoleCount > 0) {
442
+ warnings.push({
443
+ file: op.path,
444
+ message: `Found ${consoleCount} console.log statement(s) - remove before production`,
445
+ type: 'console-log'
446
+ });
447
+ }
448
+ }
449
+
450
+ // Check async functions have error handling
451
+ if (op.content.includes('async ') && op.content.includes('await ')) {
452
+ if (!op.content.includes('try') && !op.content.includes('catch')) {
453
+ warnings.push({
454
+ file: op.path,
455
+ message: 'Async function without try/catch error handling',
456
+ type: 'missing-error-handling'
457
+ });
458
+ }
459
+ }
460
+
461
+ // Check API routes have proper error responses
462
+ if (op.path.includes('/api/')) {
463
+ if (!op.content.includes('catch') && !op.content.includes('error')) {
464
+ warnings.push({
465
+ file: op.path,
466
+ message: 'API route may lack error handling',
467
+ type: 'missing-error-handling'
468
+ });
469
+ }
470
+ }
471
+
472
+ return warnings;
473
+ }
474
+
475
+ /**
476
+ * Suggest using existing types instead of creating new ones
477
+ */
478
+ private suggestExistingTypes(fileOperations: FileOperation[]): string[] {
479
+ const suggestions: string[] = [];
480
+ if (!this.typeInventory) return suggestions;
481
+
482
+ for (const op of fileOperations) {
483
+ if (!op.content) continue;
484
+
485
+ // Look for new interface/type definitions
486
+ const newTypeRegex = /(?:interface|type)\s+(\w+)/g;
487
+ let match;
488
+ while ((match = newTypeRegex.exec(op.content)) !== null) {
489
+ const typeName = match[1];
490
+
491
+ // Check if similar type already exists
492
+ if (this.typeInventory.types.has(typeName)) {
493
+ const existing = this.typeInventory.types.get(typeName)!;
494
+ suggestions.push(
495
+ `Type '${typeName}' already exists in ${existing.file} - consider importing instead of redefining`
496
+ );
497
+ }
498
+ }
499
+ }
500
+
501
+ return suggestions;
502
+ }
503
+
504
+ /**
505
+ * Get inventory of existing types for AI context
506
+ */
507
+ getTypeInventoryForContext(): string {
508
+ if (!this.typeInventory || this.typeInventory.types.size === 0) {
509
+ return 'No existing types found';
510
+ }
511
+
512
+ const lines: string[] = ['Existing types in project:'];
513
+
514
+ // Group by file
515
+ const byFile = new Map<string, TypeInfo[]>();
516
+ for (const [, info] of this.typeInventory.types) {
517
+ const types = byFile.get(info.file) || [];
518
+ types.push(info);
519
+ byFile.set(info.file, types);
520
+ }
521
+
522
+ // Output grouped by file
523
+ for (const [file, types] of byFile) {
524
+ if (types.length > 0) {
525
+ lines.push(` ${file}:`);
526
+ for (const t of types.slice(0, 10)) { // Limit per file
527
+ lines.push(` - ${t.kind} ${t.name}`);
528
+ }
529
+ if (types.length > 10) {
530
+ lines.push(` ... and ${types.length - 10} more`);
531
+ }
532
+ }
533
+ }
534
+
535
+ return lines.slice(0, 50).join('\n'); // Limit total output
536
+ }
537
+
538
+ /**
539
+ * Get installed packages for AI context
540
+ */
541
+ getInstalledPackagesForContext(): string[] {
542
+ return Array.from(this.installedPackages).slice(0, 50); // Limit for context
543
+ }
544
+
545
+ /**
546
+ * Run TypeScript compile check on the project
547
+ * This catches type errors that the AI might introduce
548
+ */
549
+ async runTypeScriptCheck(): Promise<TypeScriptCheckResult> {
550
+ if (!this.workspaceRoot) {
551
+ return { passed: true, errors: [], errorCount: 0 };
552
+ }
553
+
554
+ // Check if tsconfig.json exists
555
+ const tsconfigPath = path.join(this.workspaceRoot, 'tsconfig.json');
556
+ if (!fs.existsSync(tsconfigPath)) {
557
+ console.log('CodeValidator: No tsconfig.json found, skipping TypeScript check');
558
+ return { passed: true, errors: [], errorCount: 0 };
559
+ }
560
+
561
+ try {
562
+ // Run tsc --noEmit to check for type errors without emitting
563
+ await execAsync('npx tsc --noEmit', {
564
+ cwd: this.workspaceRoot,
565
+ timeout: 60000 // 60 second timeout
566
+ });
567
+
568
+ // If we get here, no errors
569
+ return { passed: true, errors: [], errorCount: 0 };
570
+ } catch (error: any) {
571
+ // Parse TypeScript errors from stdout/stderr
572
+ const output = error.stdout || error.stderr || '';
573
+ const errors = this.parseTypeScriptErrors(output);
574
+
575
+ return {
576
+ passed: false,
577
+ errors,
578
+ errorCount: errors.length
579
+ };
580
+ }
581
+ }
582
+
583
+ /**
584
+ * Parse TypeScript compiler errors from output
585
+ */
586
+ private parseTypeScriptErrors(output: string): TypeScriptCheckResult['errors'] {
587
+ const errors: TypeScriptCheckResult['errors'] = [];
588
+ const lines = output.split('\n');
589
+
590
+ // TypeScript error format: src/file.ts(10,5): error TS2339: Property 'x' does not exist
591
+ const errorRegex = /^(.+?)\((\d+),(\d+)\): error (TS\d+): (.+)$/;
592
+
593
+ for (const line of lines) {
594
+ const match = line.match(errorRegex);
595
+ if (match) {
596
+ errors.push({
597
+ file: match[1],
598
+ line: parseInt(match[2], 10),
599
+ column: parseInt(match[3], 10),
600
+ code: match[4],
601
+ message: match[5]
602
+ });
603
+ }
604
+ }
605
+
606
+ // Limit to first 20 errors to avoid overwhelming output
607
+ return errors.slice(0, 20);
608
+ }
609
+
610
+ /**
611
+ * Quick TypeScript check - only checks specific files
612
+ * Faster than full project check, good for validating just the generated code
613
+ */
614
+ async checkSpecificFiles(filePaths: string[]): Promise<TypeScriptCheckResult> {
615
+ if (!this.workspaceRoot || filePaths.length === 0) {
616
+ return { passed: true, errors: [], errorCount: 0 };
617
+ }
618
+
619
+ // Filter to only TypeScript files
620
+ const tsFiles = filePaths.filter(f => f.endsWith('.ts') || f.endsWith('.tsx'));
621
+ if (tsFiles.length === 0) {
622
+ return { passed: true, errors: [], errorCount: 0 };
623
+ }
624
+
625
+ try {
626
+ // Check specific files with tsc
627
+ const filesArg = tsFiles.map(f => `"${f}"`).join(' ');
628
+ await execAsync(`npx tsc --noEmit ${filesArg}`, {
629
+ cwd: this.workspaceRoot,
630
+ timeout: 30000 // 30 second timeout for specific files
631
+ });
632
+
633
+ return { passed: true, errors: [], errorCount: 0 };
634
+ } catch (error: any) {
635
+ const output = error.stdout || error.stderr || '';
636
+ const errors = this.parseTypeScriptErrors(output);
637
+
638
+ return {
639
+ passed: false,
640
+ errors,
641
+ errorCount: errors.length
642
+ };
643
+ }
644
+ }
645
+ }