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,1146 @@
1
+ import * as vscode from 'vscode';
2
+ import Anthropic from '@anthropic-ai/sdk';
3
+ import { CodeValidator, ValidationResult, DependencyCheck, TypeScriptCheckResult } from './CodeValidator';
4
+
5
+ export interface FileOperation {
6
+ action: 'create' | 'edit' | 'delete';
7
+ path: string;
8
+ description?: string;
9
+ content?: string;
10
+ }
11
+
12
+ export interface CommandToRun {
13
+ command: string;
14
+ description?: string;
15
+ }
16
+
17
+ interface ChatResponse {
18
+ content: string;
19
+ thinking?: string;
20
+ fileOperations?: FileOperation[];
21
+ commands?: CommandToRun[];
22
+ projectUpdates?: {
23
+ patterns?: string[];
24
+ tasks?: string[];
25
+ decisions?: Record<string, string>;
26
+ };
27
+ validation?: ValidationResult;
28
+ dependencyCheck?: DependencyCheck;
29
+ }
30
+
31
+ interface StreamCallbacks {
32
+ onThinking?: (text: string) => void;
33
+ onContent?: (text: string) => void;
34
+ onDone?: () => void;
35
+ onError?: (error: Error) => void;
36
+ abortSignal?: AbortSignal;
37
+ }
38
+
39
+ interface ChatOptions {
40
+ maxRetries?: number;
41
+ runTypeScriptCheck?: boolean;
42
+ }
43
+
44
+ interface Pattern {
45
+ name: string;
46
+ description: string;
47
+ content: string;
48
+ }
49
+
50
+ export class CodeBakersClient {
51
+ private anthropic: Anthropic | null = null;
52
+ private sessionToken: string | null = null;
53
+ private patterns: Map<string, Pattern> = new Map();
54
+ private readonly DEFAULT_TIMEOUT = 10000; // 10 seconds
55
+ private validator: CodeValidator;
56
+ private validatorInitialized: boolean = false;
57
+
58
+ constructor(private readonly context: vscode.ExtensionContext) {
59
+ // Initialize code validator
60
+ this.validator = new CodeValidator();
61
+ // Load cached session token
62
+ this.sessionToken = context.globalState.get('codebakers.sessionToken') || null;
63
+
64
+ // Clean up corrupted tokens (URL-encoded or invalid)
65
+ if (this.sessionToken && this.sessionToken.includes('%')) {
66
+ console.log('CodeBakers: Clearing corrupted URL-encoded token');
67
+ this.sessionToken = null;
68
+ context.globalState.update('codebakers.sessionToken', undefined);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Logout and clear session
74
+ */
75
+ async logout(): Promise<void> {
76
+ this.sessionToken = null;
77
+ this.anthropic = null;
78
+ await this.context.globalState.update('codebakers.sessionToken', undefined);
79
+ await this.context.globalState.update('codebakers.user', undefined);
80
+ vscode.window.showInformationMessage('Logged out of CodeBakers');
81
+ }
82
+
83
+ /**
84
+ * Fetch with timeout to prevent hanging
85
+ */
86
+ private async _fetchWithTimeout(url: string, options: RequestInit = {}, timeout = this.DEFAULT_TIMEOUT): Promise<Response> {
87
+ const controller = new AbortController();
88
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
89
+
90
+ try {
91
+ const response = await fetch(url, {
92
+ ...options,
93
+ signal: controller.signal,
94
+ });
95
+ return response;
96
+ } finally {
97
+ clearTimeout(timeoutId);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Check if user has a valid session token (doesn't validate with server)
103
+ */
104
+ hasSessionToken(): boolean {
105
+ return !!this.sessionToken;
106
+ }
107
+
108
+ /**
109
+ * Handle OAuth callback from VS Code URI handler
110
+ * Called when vscode://codebakers.codebakers/callback?token=xxx is received
111
+ */
112
+ async handleOAuthCallback(encodedToken: string): Promise<boolean> {
113
+ try {
114
+ console.log('handleOAuthCallback: token length:', encodedToken?.length);
115
+ console.log('handleOAuthCallback: token preview:', encodedToken?.substring(0, 50));
116
+
117
+ if (!encodedToken) {
118
+ vscode.window.showErrorMessage('Login failed: No token received');
119
+ return false;
120
+ }
121
+
122
+ // Try to decode the base64url token payload
123
+ // The token might be URL-encoded, so try decoding that first
124
+ let tokenToDecode = encodedToken;
125
+ if (encodedToken.includes('%')) {
126
+ tokenToDecode = decodeURIComponent(encodedToken);
127
+ console.log('handleOAuthCallback: URL-decoded token');
128
+ }
129
+
130
+ // Decode base64url (supports both with and without padding)
131
+ let decoded: string;
132
+ try {
133
+ decoded = Buffer.from(tokenToDecode, 'base64url').toString('utf-8');
134
+ } catch {
135
+ // Try standard base64 as fallback
136
+ const base64 = tokenToDecode.replace(/-/g, '+').replace(/_/g, '/');
137
+ decoded = Buffer.from(base64, 'base64').toString('utf-8');
138
+ }
139
+
140
+ console.log('handleOAuthCallback: decoded preview:', decoded?.substring(0, 100));
141
+
142
+ const tokenPayload = JSON.parse(decoded) as {
143
+ token: string;
144
+ teamId: string;
145
+ profileId: string;
146
+ githubId: string;
147
+ githubUsername: string;
148
+ email: string;
149
+ plan: string;
150
+ trial: { endsAt: string; daysRemaining: number } | null;
151
+ createdAt: string;
152
+ };
153
+
154
+ // Store session token (the decoded base64url payload, not URL-encoded)
155
+ // If the token was URL-encoded, we need to store the decoded version
156
+ const cleanToken = encodedToken.includes('%') ? decodeURIComponent(encodedToken) : encodedToken;
157
+ this.sessionToken = cleanToken;
158
+ await this.context.globalState.update('codebakers.sessionToken', cleanToken);
159
+
160
+ // Store auth info for display
161
+ this.currentPlan = tokenPayload.plan;
162
+ this.trialInfo = tokenPayload.trial;
163
+ this.isUnlimited = tokenPayload.plan === 'pro';
164
+
165
+ // Store additional user info
166
+ await this.context.globalState.update('codebakers.user', {
167
+ teamId: tokenPayload.teamId,
168
+ profileId: tokenPayload.profileId,
169
+ githubUsername: tokenPayload.githubUsername,
170
+ email: tokenPayload.email,
171
+ });
172
+
173
+ // Initialize Anthropic client with our API key
174
+ await this._initializeAnthropic();
175
+
176
+ vscode.window.showInformationMessage(
177
+ `Welcome to CodeBakers, @${tokenPayload.githubUsername}! ${tokenPayload.trial ? `Trial: ${tokenPayload.trial.daysRemaining} days remaining` : ''}`
178
+ );
179
+
180
+ return true;
181
+ } catch (e) {
182
+ console.error('Failed to handle OAuth callback:', e);
183
+ console.error('Token was:', encodedToken?.substring(0, 100));
184
+ vscode.window.showErrorMessage(`Login failed: ${e instanceof Error ? e.message : 'Invalid token'}`);
185
+ return false;
186
+ }
187
+ }
188
+
189
+ async checkAuth(): Promise<boolean> {
190
+ if (!this.sessionToken) return false;
191
+
192
+ try {
193
+ // Include token in query param as fallback (VS Code may strip headers)
194
+ const url = `${this._getApiEndpoint()}/api/auth/check?token=${encodeURIComponent(this.sessionToken)}`;
195
+ const response = await this._fetchWithTimeout(url, {
196
+ headers: {
197
+ 'Authorization': `Bearer ${this.sessionToken}`
198
+ }
199
+ }, 5000); // 5 second timeout for auth check
200
+ return response.ok;
201
+ } catch {
202
+ return false;
203
+ }
204
+ }
205
+
206
+ async login(): Promise<boolean> {
207
+ try {
208
+ console.log('CodeBakers: login() called');
209
+
210
+ // Open browser to CodeBakers login
211
+ // The callback will be handled by the global URI handler in extension.ts
212
+ const uriScheme = vscode.env.uriScheme;
213
+ console.log('CodeBakers: uriScheme =', uriScheme);
214
+
215
+ const rawCallbackUri = vscode.Uri.parse(`${uriScheme}://codebakers.codebakers/callback`);
216
+ console.log('CodeBakers: rawCallbackUri =', rawCallbackUri.toString());
217
+
218
+ const callbackUri = await vscode.env.asExternalUri(rawCallbackUri);
219
+ console.log('CodeBakers: callbackUri =', callbackUri.toString());
220
+
221
+ const apiEndpoint = this._getApiEndpoint();
222
+ console.log('CodeBakers: apiEndpoint =', apiEndpoint);
223
+
224
+ const loginUrl = `${apiEndpoint}/vscode-login?callback=${encodeURIComponent(callbackUri.toString())}`;
225
+ console.log('CodeBakers: loginUrl =', loginUrl);
226
+
227
+ await vscode.env.openExternal(vscode.Uri.parse(loginUrl));
228
+ console.log('CodeBakers: openExternal called successfully');
229
+
230
+ // Return true to indicate login was initiated
231
+ // The actual login completion is handled by handleOAuthCallback
232
+ return true;
233
+ } catch (error) {
234
+ console.error('CodeBakers: login() error:', error);
235
+ throw error;
236
+ }
237
+ }
238
+
239
+ private currentPlan: string = 'trial';
240
+ private isUnlimited: boolean = false;
241
+ private trialInfo: { endsAt: string; daysRemaining: number } | null = null;
242
+
243
+ private async _initializeAnthropic(): Promise<void> {
244
+ // Get API key from our server (user's CodeBakers subscription includes Claude access)
245
+ try {
246
+ console.log('_initializeAnthropic: sessionToken exists:', !!this.sessionToken);
247
+ console.log('_initializeAnthropic: sessionToken length:', this.sessionToken?.length);
248
+ console.log('_initializeAnthropic: sessionToken preview:', this.sessionToken?.substring(0, 50));
249
+ console.log('_initializeAnthropic: contains %:', this.sessionToken?.includes('%'));
250
+
251
+ // Verify token can be decoded
252
+ if (this.sessionToken) {
253
+ try {
254
+ const decoded = Buffer.from(this.sessionToken, 'base64url').toString('utf-8');
255
+ const parsed = JSON.parse(decoded);
256
+ console.log('_initializeAnthropic: token decoded successfully, teamId:', parsed.teamId);
257
+ } catch (e) {
258
+ console.error('_initializeAnthropic: FAILED to decode token locally:', e);
259
+ }
260
+ }
261
+
262
+ // Use plain object for headers (more compatible)
263
+ const authHeader = `Bearer ${this.sessionToken}`;
264
+ console.log('_initializeAnthropic: authHeader length:', authHeader.length);
265
+ console.log('_initializeAnthropic: authHeader preview:', authHeader.substring(0, 60));
266
+
267
+ const fetchOptions: RequestInit = {
268
+ method: 'GET',
269
+ headers: {
270
+ 'Authorization': authHeader,
271
+ 'Content-Type': 'application/json',
272
+ },
273
+ };
274
+
275
+ console.log('_initializeAnthropic: fetchOptions.headers:', JSON.stringify(fetchOptions.headers));
276
+
277
+ // Include token in query param as fallback (VS Code may strip headers)
278
+ const url = `${this._getApiEndpoint()}/api/claude/key?token=${encodeURIComponent(this.sessionToken || '')}`;
279
+ console.log('_initializeAnthropic: fetching:', url.substring(0, 80) + '...');
280
+
281
+ const response = await this._fetchWithTimeout(url, fetchOptions);
282
+
283
+ interface ClaudeKeyData {
284
+ apiKey?: string;
285
+ plan?: string;
286
+ unlimited?: boolean;
287
+ trial?: { endsAt: string; daysRemaining: number };
288
+ }
289
+
290
+ interface ClaudeKeyResponse {
291
+ data?: ClaudeKeyData;
292
+ error?: string;
293
+ message?: string;
294
+ upgradeUrl?: string;
295
+ trialUrl?: string;
296
+ }
297
+
298
+ const rawResponse = await response.json() as ClaudeKeyResponse;
299
+ // Server wraps successful responses in {data: ...}
300
+ const data = rawResponse.data || rawResponse as unknown as ClaudeKeyData;
301
+
302
+ console.log('Claude key response:', response.status, JSON.stringify(data));
303
+
304
+ // Handle subscription required error
305
+ if (response.status === 402) {
306
+ const selection = await vscode.window.showWarningMessage(
307
+ rawResponse.message || 'Subscribe to CodeBakers Pro ($99/month) for unlimited access',
308
+ 'Subscribe Now',
309
+ 'Start Free Trial'
310
+ );
311
+
312
+ if (selection === 'Subscribe Now') {
313
+ vscode.env.openExternal(vscode.Uri.parse(rawResponse.upgradeUrl || 'https://www.codebakers.ai/dashboard/billing'));
314
+ } else if (selection === 'Start Free Trial') {
315
+ vscode.env.openExternal(vscode.Uri.parse(rawResponse.trialUrl || 'https://www.codebakers.ai/dashboard/billing'));
316
+ }
317
+
318
+ throw new Error('SUBSCRIPTION_REQUIRED');
319
+ }
320
+
321
+ if (!response.ok) {
322
+ console.error('API error response:', JSON.stringify(rawResponse));
323
+ // Include token info in error for debugging
324
+ const tokenInfo = this.sessionToken
325
+ ? `token len=${this.sessionToken.length}, starts=${this.sessionToken.substring(0,10)}`
326
+ : 'NO TOKEN';
327
+ throw new Error(`API ${response.status}: ${rawResponse.error || rawResponse.message || 'Unknown'} [${tokenInfo}]`);
328
+ }
329
+
330
+ if (!data.apiKey) {
331
+ throw new Error(`No API key in response: ${JSON.stringify(data)}`);
332
+ }
333
+
334
+ const { apiKey, plan, unlimited, trial } = data;
335
+
336
+ // Store plan info
337
+ this.currentPlan = plan || 'trial';
338
+ this.isUnlimited = unlimited || false;
339
+ this.trialInfo = trial || null;
340
+
341
+ // Show trial warning if applicable
342
+ if (trial && trial.daysRemaining <= 3) {
343
+ vscode.window.showWarningMessage(
344
+ `Your CodeBakers trial expires in ${trial.daysRemaining} day${trial.daysRemaining === 1 ? '' : 's'}. Subscribe to keep using the extension.`,
345
+ 'Subscribe Now'
346
+ ).then(selection => {
347
+ if (selection === 'Subscribe Now') {
348
+ vscode.env.openExternal(vscode.Uri.parse('https://www.codebakers.ai/dashboard/billing'));
349
+ }
350
+ });
351
+ }
352
+
353
+ this.anthropic = new Anthropic({ apiKey });
354
+
355
+ // Also fetch patterns
356
+ await this._loadPatterns();
357
+ } catch (error) {
358
+ console.error('Failed to initialize Anthropic client:', error);
359
+ throw error;
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Get current plan info for display
365
+ */
366
+ getPlanInfo(): { plan: string; unlimited: boolean; trial: { endsAt: string; daysRemaining: number } | null } {
367
+ return {
368
+ plan: this.currentPlan,
369
+ unlimited: this.isUnlimited,
370
+ trial: this.trialInfo,
371
+ };
372
+ }
373
+
374
+ private async _loadPatterns(): Promise<void> {
375
+ try {
376
+ // Include token in query param as fallback (VS Code may strip headers)
377
+ const url = `${this._getApiEndpoint()}/api/patterns/list?token=${encodeURIComponent(this.sessionToken || '')}`;
378
+ const response = await this._fetchWithTimeout(url, {
379
+ headers: {
380
+ 'Authorization': `Bearer ${this.sessionToken}`
381
+ }
382
+ }, 15000); // 15 seconds for pattern loading
383
+
384
+ if (response.ok) {
385
+ const data = await response.json() as { patterns?: Pattern[] };
386
+ if (data.patterns) {
387
+ data.patterns.forEach((p: Pattern) => this.patterns.set(p.name, p));
388
+ }
389
+ }
390
+ } catch (error) {
391
+ console.error('Failed to load patterns:', error);
392
+ }
393
+ }
394
+
395
+ async chat(
396
+ messages: any[],
397
+ projectState: any,
398
+ callbacks?: StreamCallbacks,
399
+ options?: ChatOptions
400
+ ): Promise<ChatResponse> {
401
+ const maxRetries = options?.maxRetries ?? 3;
402
+ const runTscCheck = options?.runTypeScriptCheck ?? true;
403
+
404
+ if (!this.anthropic) {
405
+ await this._initializeAnthropic();
406
+ }
407
+
408
+ if (!this.anthropic) {
409
+ throw new Error('Not authenticated. Please login first.');
410
+ }
411
+
412
+ // Build the system prompt with CodeBakers enforcement
413
+ const systemPrompt = this._buildSystemPrompt(projectState);
414
+
415
+ // Detect which patterns might be relevant based on the conversation
416
+ const relevantPatterns = await this._detectRelevantPatterns(messages);
417
+
418
+ // Include pattern content in system prompt
419
+ const patternsContent = relevantPatterns
420
+ .map(p => `## Pattern: ${p.name}\n${p.content}`)
421
+ .join('\n\n');
422
+
423
+ // Extract system messages and add to system prompt (Claude API doesn't accept role: "system" in messages)
424
+ const systemMessages = messages.filter(m => m.role === 'system');
425
+ const chatMessages = messages.filter(m => m.role !== 'system');
426
+
427
+ // Build full system prompt including any context from "system" role messages
428
+ const contextFromMessages = systemMessages.length > 0
429
+ ? '\n\n# CONTEXT\n' + systemMessages.map(m => m.content).join('\n\n')
430
+ : '';
431
+
432
+ const fullSystemPrompt = `${systemPrompt}\n\n# LOADED PATTERNS\n${patternsContent}${contextFromMessages}`;
433
+
434
+ // Retry logic with exponential backoff
435
+ let lastError: Error | null = null;
436
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
437
+ try {
438
+ // Check if aborted before starting
439
+ if (callbacks?.abortSignal?.aborted) {
440
+ throw new Error('Request was cancelled');
441
+ }
442
+
443
+ // Use streaming to show response in real-time
444
+ let fullText = '';
445
+
446
+ const stream = this.anthropic.messages.stream({
447
+ model: 'claude-sonnet-4-20250514',
448
+ max_tokens: 16000,
449
+ system: fullSystemPrompt,
450
+ messages: chatMessages.map(m => ({
451
+ role: m.role as 'user' | 'assistant',
452
+ content: m.content
453
+ }))
454
+ });
455
+
456
+ // Set up abort handling
457
+ if (callbacks?.abortSignal) {
458
+ callbacks.abortSignal.addEventListener('abort', () => {
459
+ stream.abort();
460
+ });
461
+ }
462
+
463
+ // Handle streaming text events
464
+ stream.on('text', (text) => {
465
+ // Check if aborted during streaming
466
+ if (callbacks?.abortSignal?.aborted) {
467
+ stream.abort();
468
+ return;
469
+ }
470
+
471
+ fullText += text;
472
+
473
+ // Parse thinking and content in real-time
474
+ const { thinking, content } = this._parseThinkingAndContent(fullText);
475
+
476
+ if (thinking) {
477
+ callbacks?.onThinking?.(thinking);
478
+ }
479
+ callbacks?.onContent?.(content);
480
+ });
481
+
482
+ // Wait for completion
483
+ await stream.finalMessage();
484
+
485
+ // Check if aborted
486
+ if (callbacks?.abortSignal?.aborted) {
487
+ throw new Error('Request was cancelled');
488
+ }
489
+
490
+ callbacks?.onDone?.();
491
+
492
+ // Parse final thinking and content
493
+ const { thinking, content } = this._parseThinkingAndContent(fullText);
494
+
495
+ // Parse file operations and commands from response
496
+ const fileOperations = this.parseFileOperations(content);
497
+ const commands = this.parseCommands(content);
498
+
499
+ // Clean content for display (remove XML tags)
500
+ const cleanContent = this.cleanContentForDisplay(content);
501
+
502
+ // COMPLIANCE CHECK - Verify AI followed rules
503
+ const compliance = this._checkCompliance(fullText, thinking, fileOperations, cleanContent);
504
+
505
+ // VALIDATION - Deep code quality checks
506
+ let validation: ValidationResult | undefined;
507
+ let dependencyCheck: DependencyCheck | undefined;
508
+ let tscResult: TypeScriptCheckResult | undefined;
509
+
510
+ if (fileOperations.length > 0) {
511
+ // Initialize validator if needed
512
+ if (!this.validatorInitialized) {
513
+ await this.validator.initialize();
514
+ this.validatorInitialized = true;
515
+ }
516
+
517
+ // Check dependencies before allowing apply
518
+ dependencyCheck = this.validator.checkDependencies(fileOperations);
519
+
520
+ // Validate generated code
521
+ validation = await this.validator.validateFileOperations(fileOperations);
522
+
523
+ // Run TypeScript check if enabled
524
+ if (runTscCheck) {
525
+ tscResult = await this.validator.runTypeScriptCheck();
526
+ if (validation) {
527
+ validation.tscResult = tscResult;
528
+ }
529
+ }
530
+ }
531
+
532
+ // Parse any project updates from the response
533
+ const projectUpdates = this._extractProjectUpdates(cleanContent);
534
+
535
+ // Build footer with counts and validation status
536
+ const fileCount = fileOperations.length;
537
+ const cmdCount = commands.length;
538
+ const patternNames = relevantPatterns.map(p => p.name).join(', ') || 'core';
539
+
540
+ // Determine overall status
541
+ const hasComplianceIssues = !compliance.passed;
542
+ const hasValidationErrors = validation && !validation.passed;
543
+ const hasMissingDeps = dependencyCheck && dependencyCheck.missing.length > 0;
544
+ const hasTscErrors = tscResult && !tscResult.passed;
545
+
546
+ let statusIcon = '✅';
547
+ if (hasValidationErrors || hasTscErrors) statusIcon = '❌';
548
+ else if (hasComplianceIssues || hasMissingDeps) statusIcon = '⚠️';
549
+
550
+ let contentWithFooter = cleanContent;
551
+
552
+ // Add dependency warning if packages missing
553
+ if (hasMissingDeps && dependencyCheck) {
554
+ contentWithFooter += '\n\n---\n📦 **Missing Packages:**\n```bash\nnpm install ' +
555
+ dependencyCheck.missing.join(' ') + '\n```';
556
+ }
557
+
558
+ // Add TypeScript errors if any
559
+ if (hasTscErrors && tscResult) {
560
+ contentWithFooter += '\n\n---\n🔴 **TypeScript Errors (' + tscResult.errorCount + '):**\n' +
561
+ tscResult.errors.slice(0, 5).map(e => `- ${e.file}:${e.line} - ${e.message}`).join('\n');
562
+ if (tscResult.errorCount > 5) {
563
+ contentWithFooter += `\n ...and ${tscResult.errorCount - 5} more`;
564
+ }
565
+ }
566
+
567
+ // Add validation errors/warnings
568
+ if (validation) {
569
+ if (validation.errors.length > 0) {
570
+ contentWithFooter += '\n\n---\n❌ **Validation Errors:**\n' +
571
+ validation.errors.map(e => `- ${e.file}: ${e.message}`).join('\n');
572
+ }
573
+ if (validation.warnings.length > 0) {
574
+ contentWithFooter += '\n\n---\n⚠️ **Warnings:**\n' +
575
+ validation.warnings.map(w => `- ${w.file}: ${w.message}`).join('\n');
576
+ }
577
+ if (validation.suggestions.length > 0) {
578
+ contentWithFooter += '\n\n---\n💡 **Suggestions:**\n' +
579
+ validation.suggestions.map(s => `- ${s}`).join('\n');
580
+ }
581
+ }
582
+
583
+ // Add compliance warning if rules were violated
584
+ if (hasComplianceIssues) {
585
+ contentWithFooter += '\n\n---\n⚠️ **Quality Warning:** ' + compliance.issues.join(', ');
586
+ }
587
+
588
+ // Build TSC status for footer
589
+ const tscStatus = tscResult ? (tscResult.passed ? '✅' : '❌') : '⏭️';
590
+
591
+ contentWithFooter += '\n\n---\n🍪 **CodeBakers** ' + statusIcon + ' | Files: ' +
592
+ fileCount + ' | Commands: ' + cmdCount + ' | TSC: ' + tscStatus + ' | Patterns: ' + patternNames + ' | v1.0.41';
593
+
594
+ return {
595
+ content: contentWithFooter,
596
+ thinking: thinking || undefined,
597
+ fileOperations: fileOperations.length > 0 ? fileOperations : undefined,
598
+ commands: commands.length > 0 ? commands : undefined,
599
+ projectUpdates,
600
+ validation,
601
+ dependencyCheck
602
+ };
603
+ } catch (error: any) {
604
+ lastError = error;
605
+ console.error(`Claude API error (attempt ${attempt}/${maxRetries}):`, error);
606
+
607
+ // Don't retry if cancelled
608
+ if (error.message === 'Request was cancelled') {
609
+ throw error;
610
+ }
611
+
612
+ // Don't retry auth errors
613
+ if (error.message?.includes('authenticated') || error.message?.includes('SUBSCRIPTION')) {
614
+ throw error;
615
+ }
616
+
617
+ // Retry with exponential backoff for network/API errors
618
+ if (attempt < maxRetries) {
619
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000); // Max 10 seconds
620
+ callbacks?.onError?.(new Error(`Request failed, retrying in ${delay / 1000}s...`));
621
+ await new Promise(resolve => setTimeout(resolve, delay));
622
+ }
623
+ }
624
+ }
625
+
626
+ // All retries exhausted
627
+ const finalError = lastError || new Error('Request failed after multiple retries');
628
+ callbacks?.onError?.(finalError);
629
+ throw finalError;
630
+ }
631
+
632
+ /**
633
+ * Parse <thinking> tags from response to separate reasoning from content
634
+ */
635
+ private _parseThinkingAndContent(text: string): { thinking: string | null; content: string } {
636
+ const thinkingMatch = text.match(/<thinking>([\s\S]*?)<\/thinking>/);
637
+
638
+ if (thinkingMatch) {
639
+ const thinking = thinkingMatch[1].trim();
640
+ const content = text.replace(/<thinking>[\s\S]*?<\/thinking>\s*/g, '').trim();
641
+ return { thinking, content };
642
+ }
643
+
644
+ return { thinking: null, content: text };
645
+ }
646
+
647
+ /**
648
+ * Parse <file_operation> tags from response
649
+ */
650
+ parseFileOperations(text: string): FileOperation[] {
651
+ const operations: FileOperation[] = [];
652
+ const regex = /<file_operation>([\s\S]*?)<\/file_operation>/g;
653
+ let match;
654
+
655
+ while ((match = regex.exec(text)) !== null) {
656
+ const block = match[1];
657
+
658
+ // Extract action
659
+ const actionMatch = block.match(/<action>(create|edit|delete)<\/action>/);
660
+ if (!actionMatch) continue;
661
+
662
+ // Extract path
663
+ const pathMatch = block.match(/<path>([^<]+)<\/path>/);
664
+ if (!pathMatch) continue;
665
+
666
+ // Extract optional description
667
+ const descMatch = block.match(/<description>([^<]+)<\/description>/);
668
+
669
+ // Extract content (only for create/edit)
670
+ const contentMatch = block.match(/<content>([\s\S]*?)<\/content>/);
671
+
672
+ operations.push({
673
+ action: actionMatch[1] as 'create' | 'edit' | 'delete',
674
+ path: pathMatch[1].trim(),
675
+ description: descMatch ? descMatch[1].trim() : undefined,
676
+ content: contentMatch ? contentMatch[1].trim() : undefined,
677
+ });
678
+ }
679
+
680
+ return operations;
681
+ }
682
+
683
+ /**
684
+ * Parse <run_command> tags from response
685
+ */
686
+ parseCommands(text: string): CommandToRun[] {
687
+ const commands: CommandToRun[] = [];
688
+ const regex = /<run_command>([\s\S]*?)<\/run_command>/g;
689
+ let match;
690
+
691
+ while ((match = regex.exec(text)) !== null) {
692
+ const block = match[1];
693
+
694
+ // Extract command
695
+ const cmdMatch = block.match(/<command>([^<]+)<\/command>/);
696
+ if (!cmdMatch) continue;
697
+
698
+ // Extract optional description
699
+ const descMatch = block.match(/<description>([^<]+)<\/description>/);
700
+
701
+ commands.push({
702
+ command: cmdMatch[1].trim(),
703
+ description: descMatch ? descMatch[1].trim() : undefined,
704
+ });
705
+ }
706
+
707
+ return commands;
708
+ }
709
+
710
+ /**
711
+ * Remove file operation and command tags from content for display
712
+ */
713
+ cleanContentForDisplay(text: string): string {
714
+ return text
715
+ .replace(/<file_operation>[\s\S]*?<\/file_operation>/g, '')
716
+ .replace(/<run_command>[\s\S]*?<\/run_command>/g, '')
717
+ .trim();
718
+ }
719
+
720
+ /**
721
+ * COMPLIANCE CHECK - Verify the AI followed CodeBakers rules
722
+ * This is programmatic enforcement - catches when AI ignores instructions
723
+ */
724
+ private _checkCompliance(
725
+ fullText: string,
726
+ thinking: string | null,
727
+ fileOperations: FileOperation[],
728
+ cleanContent: string
729
+ ): { passed: boolean; issues: string[] } {
730
+ const issues: string[] = [];
731
+
732
+ // Check 1: Did AI include thinking block?
733
+ if (!thinking && cleanContent.length > 200) {
734
+ // Only flag for substantial responses
735
+ issues.push('Missing reasoning (thinking block)');
736
+ }
737
+
738
+ // Check 2: If response mentions code but no file_operation tags
739
+ const hasCodeBlocks = /```[\s\S]*?```/.test(fullText);
740
+ const mentionsFiles = /\.(tsx?|jsx?|css|json|md)\b/.test(cleanContent);
741
+ if (hasCodeBlocks && mentionsFiles && fileOperations.length === 0) {
742
+ issues.push('Code in markdown instead of file_operation tags');
743
+ }
744
+
745
+ // Check 3: Check for 'any' type usage in file operations
746
+ for (const op of fileOperations) {
747
+ if (op.content) {
748
+ // Check for `: any` type annotations
749
+ const anyCount = (op.content.match(/:\s*any\b/g) || []).length;
750
+ if (anyCount > 2) {
751
+ issues.push(`Excessive 'any' types in ${op.path} (${anyCount} found)`);
752
+ }
753
+
754
+ // Check for missing error handling in async functions
755
+ if (op.content.includes('async ') && !op.content.includes('try') && !op.content.includes('catch')) {
756
+ if (op.content.includes('await ')) {
757
+ issues.push(`Missing try/catch in ${op.path}`);
758
+ }
759
+ }
760
+ }
761
+ }
762
+
763
+ // Check 4: API routes should have error handling
764
+ for (const op of fileOperations) {
765
+ if (op.path.includes('/api/') && op.content) {
766
+ if (!op.content.includes('catch') && !op.content.includes('NextResponse.json')) {
767
+ issues.push(`API route ${op.path} may lack error handling`);
768
+ }
769
+ }
770
+ }
771
+
772
+ return {
773
+ passed: issues.length === 0,
774
+ issues
775
+ };
776
+ }
777
+
778
+ async summarize(text: string): Promise<string> {
779
+ if (!this.anthropic) {
780
+ throw new Error('Not authenticated');
781
+ }
782
+
783
+ const response = await this.anthropic.messages.create({
784
+ model: 'claude-sonnet-4-20250514',
785
+ max_tokens: 1024,
786
+ system: 'You are a conversation summarizer. Create concise summaries that preserve key decisions, code changes, and context. Be specific about file names and technical decisions.',
787
+ messages: [{
788
+ role: 'user',
789
+ content: text
790
+ }]
791
+ });
792
+
793
+ return response.content[0].type === 'text' ? response.content[0].text : '';
794
+ }
795
+
796
+ async getAvailablePatterns(): Promise<Pattern[]> {
797
+ return Array.from(this.patterns.values());
798
+ }
799
+
800
+ // ==========================================
801
+ // TOOL EXECUTION (All MCP tools available)
802
+ // ==========================================
803
+
804
+ /**
805
+ * Execute any CodeBakers tool
806
+ */
807
+ async executeTool(toolName: string, args: Record<string, any> = {}): Promise<any> {
808
+ if (!this.sessionToken) {
809
+ throw new Error('Not authenticated. Please login first.');
810
+ }
811
+
812
+ const projectPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
813
+
814
+ // Include token in query param as fallback (VS Code may strip headers)
815
+ const url = `${this._getApiEndpoint()}/api/tools?token=${encodeURIComponent(this.sessionToken)}`;
816
+ const response = await this._fetchWithTimeout(url, {
817
+ method: 'POST',
818
+ headers: {
819
+ 'Authorization': `Bearer ${this.sessionToken}`,
820
+ 'Content-Type': 'application/json',
821
+ },
822
+ body: JSON.stringify({
823
+ tool: toolName,
824
+ args,
825
+ projectPath,
826
+ }),
827
+ }, 30000); // 30 seconds for tool execution
828
+
829
+ if (!response.ok) {
830
+ const errorData = await response.json() as { message?: string };
831
+ throw new Error(errorData.message || `Tool execution failed: ${toolName}`);
832
+ }
833
+
834
+ return response.json();
835
+ }
836
+
837
+ /**
838
+ * List all available tools
839
+ */
840
+ async listTools(): Promise<Array<{ name: string; category: string }>> {
841
+ if (!this.sessionToken) {
842
+ return [];
843
+ }
844
+
845
+ // Include token in query param as fallback (VS Code may strip headers)
846
+ const url = `${this._getApiEndpoint()}/api/tools?token=${encodeURIComponent(this.sessionToken)}`;
847
+ const response = await this._fetchWithTimeout(url, {
848
+ headers: {
849
+ 'Authorization': `Bearer ${this.sessionToken}`,
850
+ },
851
+ });
852
+
853
+ if (!response.ok) {
854
+ throw new Error('Failed to fetch tools');
855
+ }
856
+
857
+ const data = await response.json() as { data: { tools: Array<{ name: string; category: string }> } };
858
+ return data.data?.tools || [];
859
+ }
860
+
861
+ // Convenience methods for common tools
862
+
863
+ async discoverPatterns(task: string, keywords: string[] = []): Promise<any> {
864
+ return this.executeTool('discover_patterns', { task, keywords });
865
+ }
866
+
867
+ async validateComplete(feature: string, files: string[]): Promise<any> {
868
+ return this.executeTool('validate_complete', { feature, files });
869
+ }
870
+
871
+ async guardianAnalyze(files: string[]): Promise<any> {
872
+ return this.executeTool('guardian_analyze', { files });
873
+ }
874
+
875
+ async guardianHeal(issues: any[]): Promise<any> {
876
+ return this.executeTool('guardian_heal', { issues });
877
+ }
878
+
879
+ async guardianVerify(): Promise<any> {
880
+ return this.executeTool('guardian_verify', {});
881
+ }
882
+
883
+ async guardianStatus(): Promise<any> {
884
+ return this.executeTool('guardian_status', {});
885
+ }
886
+
887
+ async rippleCheck(entityName: string, changeType?: string): Promise<any> {
888
+ return this.executeTool('ripple_check', { entityName, changeType });
889
+ }
890
+
891
+ async runAudit(): Promise<any> {
892
+ return this.executeTool('run_audit', {});
893
+ }
894
+
895
+ async runTests(): Promise<any> {
896
+ return this.executeTool('run_tests', {});
897
+ }
898
+
899
+ async detectIntent(message: string): Promise<any> {
900
+ return this.executeTool('detect_intent', { message });
901
+ }
902
+
903
+ private _buildSystemPrompt(projectState: any): string {
904
+ return `# CodeBakers AI Assistant
905
+ Version: 1.0.37
906
+
907
+ You are CodeBakers, an advanced AI coding assistant that can READ files, WRITE files, and RUN commands - just like Claude Code or Cursor.
908
+
909
+ ## YOUR CAPABILITIES
910
+
911
+ You can:
912
+ 1. **Read files** - Access any file in the workspace
913
+ 2. **Write files** - Create new files or edit existing ones
914
+ 3. **Run commands** - Execute terminal commands (npm, git, etc.)
915
+ 4. **Apply patterns** - Use production-ready code patterns
916
+
917
+ ## THINKING PROCESS (REQUIRED)
918
+
919
+ Before EVERY response, show your reasoning in <thinking> tags:
920
+
921
+ <thinking>
922
+ - Understanding: What is the user asking for?
923
+ - Analysis: What files exist? What needs to change?
924
+ - Plan: Step-by-step implementation approach
925
+ - Patterns: Which CodeBakers patterns apply?
926
+ - Risks: What could go wrong? Edge cases?
927
+ </thinking>
928
+
929
+ ## FILE OPERATIONS FORMAT
930
+
931
+ When you need to create or edit files, use this EXACT format:
932
+
933
+ <file_operation>
934
+ <action>create</action>
935
+ <path>src/components/LoginForm.tsx</path>
936
+ <description>Create login form component with validation</description>
937
+ <content>
938
+ // File content here
939
+ import React from 'react';
940
+ // ... rest of code
941
+ </content>
942
+ </file_operation>
943
+
944
+ For editing existing files:
945
+ <file_operation>
946
+ <action>edit</action>
947
+ <path>src/app/page.tsx</path>
948
+ <description>Add login button to homepage</description>
949
+ <content>
950
+ // FULL new file content (not a diff)
951
+ </content>
952
+ </file_operation>
953
+
954
+ For deleting files:
955
+ <file_operation>
956
+ <action>delete</action>
957
+ <path>src/old-file.ts</path>
958
+ <description>Remove deprecated file</description>
959
+ </file_operation>
960
+
961
+ ## COMMAND EXECUTION FORMAT
962
+
963
+ When you need to run terminal commands:
964
+
965
+ <run_command>
966
+ <command>npm install zod react-hook-form</command>
967
+ <description>Install form validation dependencies</description>
968
+ </run_command>
969
+
970
+ <run_command>
971
+ <command>npx prisma db push</command>
972
+ <description>Push schema changes to database</description>
973
+ </run_command>
974
+
975
+ ## CODING STANDARDS
976
+
977
+ 1. **TypeScript** - Always use TypeScript with proper types
978
+ 2. **Error Handling** - Always wrap async operations in try/catch
979
+ 3. **Validation** - Use Zod for input validation
980
+ 4. **Loading States** - Always handle loading and error states
981
+ 5. **Accessibility** - Include ARIA labels, keyboard navigation
982
+ 6. **Security** - Never expose secrets, validate all inputs
983
+
984
+ ## CODE QUALITY REQUIREMENTS
985
+
986
+ Every file you create/edit MUST have:
987
+ - Proper imports (no unused imports)
988
+ - Type annotations (no 'any' unless absolutely necessary)
989
+ - Error boundaries for React components
990
+ - Loading and error states for async operations
991
+ - Comments for complex logic only
992
+
993
+ ## RESPONSE STRUCTURE
994
+
995
+ 1. <thinking>...</thinking> - Your reasoning (REQUIRED)
996
+ 2. Brief explanation of what you'll do
997
+ 3. <file_operation>...</file_operation> blocks for each file
998
+ 4. <run_command>...</run_command> blocks for commands
999
+ 5. Summary of changes made
1000
+ 6. Footer with patterns used
1001
+
1002
+ ## CURRENT PROJECT STATE
1003
+ ${projectState ? JSON.stringify(projectState, null, 2) : 'No workspace open - ask user to open a project folder'}
1004
+
1005
+ ## PROJECT FILE STRUCTURE
1006
+ ${projectState?.fileTree ? `
1007
+ IMPORTANT: Use this structure to know WHERE to create files:
1008
+ \`\`\`
1009
+ ${projectState.fileTree}
1010
+ \`\`\`
1011
+ - Create API routes in the existing api/ folder
1012
+ - Create components in the existing components/ folder
1013
+ - Follow the existing project structure - DO NOT create new top-level folders unless necessary
1014
+ ` : 'No file tree available - ask user to open a project folder'}
1015
+
1016
+ ## EXISTING TYPES (Reuse these - do NOT recreate)
1017
+ ${projectState?.existingTypes || 'No existing types found - you may create new types as needed'}
1018
+
1019
+ ## INSTALLED PACKAGES
1020
+ ${projectState?.installedPackages?.length > 0 ? `
1021
+ Available packages (already installed):
1022
+ ${projectState.installedPackages.slice(0, 30).join(', ')}
1023
+
1024
+ IMPORTANT: Only import from packages listed above or Node.js built-ins.
1025
+ If you need a package not listed, include a <run_command> to install it.
1026
+ ` : 'No package.json found'}
1027
+
1028
+ ## FOOTER (Required on every response with code)
1029
+
1030
+ ---
1031
+ 🍪 **CodeBakers** | Files: [count] | Commands: [count] | Patterns: [list] | v1.0.40
1032
+
1033
+ ## CRITICAL RULES (ENFORCED - NOT OPTIONAL)
1034
+
1035
+ These rules are STRUCTURALLY ENFORCED. The user PAID for this quality guarantee.
1036
+
1037
+ ### MANDATORY THINKING BLOCK
1038
+ You MUST start every response with <thinking>...</thinking> containing:
1039
+ 1. What patterns from LOADED PATTERNS section apply?
1040
+ 2. What existing code patterns should I match?
1041
+ 3. What could go wrong? (error cases, edge cases)
1042
+
1043
+ If your response lacks <thinking> tags, it is INVALID and will be rejected.
1044
+
1045
+ ### MANDATORY PATTERN USAGE
1046
+ Look at the "# LOADED PATTERNS" section below. You MUST:
1047
+ 1. Use code structures shown in the patterns
1048
+ 2. Use the same libraries (Zod, React Hook Form, etc.)
1049
+ 3. Match the error handling style
1050
+ 4. Include all required elements (loading states, validation, etc.)
1051
+
1052
+ If you write code that ignores the loaded patterns, you are FAILING your job.
1053
+
1054
+ ### MANDATORY FILE OPERATION FORMAT
1055
+ For ANY file change, you MUST use:
1056
+ <file_operation>
1057
+ <action>create|edit|delete</action>
1058
+ <path>relative/path/to/file.ts</path>
1059
+ <description>What this change does</description>
1060
+ <content>COMPLETE file content - never partial</content>
1061
+ </file_operation>
1062
+
1063
+ Code in regular markdown blocks will NOT be applied. Only <file_operation> blocks work.
1064
+
1065
+ ### MANDATORY TEST REQUIREMENT
1066
+ Every feature MUST include at least one test file. Do not ask "want me to add tests?" - just add them.
1067
+
1068
+ ### MANDATORY FOOTER
1069
+ End every code response with:
1070
+ ---
1071
+ 🍪 **CodeBakers** | Files: [count] | Commands: [count] | Patterns: [list] | v1.0.40
1072
+
1073
+ ### NEVER DO THESE (Pattern Violations)
1074
+ - ❌ Skip error handling (wrap async in try/catch)
1075
+ - ❌ Use 'any' type (use proper types from patterns)
1076
+ - ❌ Ignore loaded patterns (they exist for a reason)
1077
+ - ❌ Create files without validation (use Zod)
1078
+ - ❌ Skip loading states (always handle pending/error/success)
1079
+ - ❌ Write code from memory when patterns exist
1080
+
1081
+ ### SELF-CHECK BEFORE RESPONDING
1082
+ Before sending, verify:
1083
+ [ ] <thinking> block present?
1084
+ [ ] Patterns from LOADED PATTERNS section used?
1085
+ [ ] <file_operation> tags for all file changes?
1086
+ [ ] Error handling included?
1087
+ [ ] Loading states handled?
1088
+ [ ] Footer included?
1089
+
1090
+ If any checkbox is NO, fix it before responding.
1091
+ `;
1092
+ }
1093
+
1094
+ private async _detectRelevantPatterns(messages: any[]): Promise<Pattern[]> {
1095
+ const lastMessage = messages[messages.length - 1]?.content?.toLowerCase() || '';
1096
+ const relevant: Pattern[] = [];
1097
+
1098
+ // Keyword-based pattern detection
1099
+ const keywordMap: Record<string, string[]> = {
1100
+ '00-core': ['any', 'code', 'build', 'create'],
1101
+ '01-database': ['database', 'db', 'schema', 'table', 'query', 'drizzle', 'postgres'],
1102
+ '02-auth': ['auth', 'login', 'signup', 'session', 'password', 'oauth'],
1103
+ '03-api': ['api', 'route', 'endpoint', 'rest', 'fetch'],
1104
+ '04-frontend': ['component', 'react', 'form', 'ui', 'page'],
1105
+ '05-payments': ['payment', 'stripe', 'billing', 'subscription', 'checkout'],
1106
+ '08-testing': ['test', 'testing', 'playwright', 'vitest'],
1107
+ };
1108
+
1109
+ // Always include core
1110
+ if (this.patterns.has('00-core')) {
1111
+ relevant.push(this.patterns.get('00-core')!);
1112
+ }
1113
+
1114
+ // Add relevant patterns based on keywords
1115
+ for (const [pattern, keywords] of Object.entries(keywordMap)) {
1116
+ if (pattern === '00-core') continue;
1117
+
1118
+ if (keywords.some(kw => lastMessage.includes(kw))) {
1119
+ const p = this.patterns.get(pattern);
1120
+ if (p) relevant.push(p);
1121
+ }
1122
+ }
1123
+
1124
+ return relevant;
1125
+ }
1126
+
1127
+ private _extractProjectUpdates(content: string): ChatResponse['projectUpdates'] {
1128
+ // Extract any structured updates from the response
1129
+ // This is a simple implementation - could be enhanced with XML tags in prompt
1130
+ const updates: ChatResponse['projectUpdates'] = {};
1131
+
1132
+ // Look for patterns like "Added pattern: X"
1133
+ const patternMatches = content.match(/(?:using|loaded|applied) pattern[s]?: ([^\n]+)/gi);
1134
+ if (patternMatches) {
1135
+ updates.patterns = patternMatches.flatMap(m =>
1136
+ m.split(':')[1]?.split(',').map(s => s.trim()) || []
1137
+ );
1138
+ }
1139
+
1140
+ return Object.keys(updates).length > 0 ? updates : undefined;
1141
+ }
1142
+
1143
+ private _getApiEndpoint(): string {
1144
+ return vscode.workspace.getConfiguration('codebakers').get('apiEndpoint', 'https://www.codebakers.ai');
1145
+ }
1146
+ }