@codebakers/cli 3.3.7 → 3.3.9

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/src/mcp/server.ts CHANGED
@@ -90,6 +90,50 @@ class CodeBakersServer {
90
90
  this.checkAndAutoUpdate().catch(() => {
91
91
  // Silently ignore errors - don't interrupt user
92
92
  });
93
+
94
+ // Check for CLI version updates (non-blocking)
95
+ this.checkCliVersion().catch(() => {
96
+ // Silently ignore errors
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Check if a newer CLI version is available and notify user
102
+ */
103
+ private async checkCliVersion(): Promise<void> {
104
+ try {
105
+ const currentVersion = getCliVersion();
106
+
107
+ // Fetch latest version from npm
108
+ const response = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
109
+ headers: { 'Accept': 'application/json' },
110
+ });
111
+
112
+ if (!response.ok) return;
113
+
114
+ const data = await response.json();
115
+ const latestVersion = data.version;
116
+
117
+ if (latestVersion && latestVersion !== currentVersion) {
118
+ // Compare versions (simple comparison - assumes semver)
119
+ const current = currentVersion.split('.').map(Number);
120
+ const latest = latestVersion.split('.').map(Number);
121
+
122
+ const isNewer = latest[0] > current[0] ||
123
+ (latest[0] === current[0] && latest[1] > current[1]) ||
124
+ (latest[0] === current[0] && latest[1] === current[1] && latest[2] > current[2]);
125
+
126
+ if (isNewer) {
127
+ console.error(`\n╔════════════════════════════════════════════════════════════╗`);
128
+ console.error(`║ 🆕 CodeBakers CLI Update Available: v${currentVersion} → v${latestVersion.padEnd(10)}║`);
129
+ console.error(`║ ║`);
130
+ console.error(`║ Restart Cursor/Claude Code to get the latest features! ║`);
131
+ console.error(`╚════════════════════════════════════════════════════════════╝\n`);
132
+ }
133
+ }
134
+ } catch {
135
+ // Silently ignore - don't interrupt user experience
136
+ }
93
137
  }
94
138
 
95
139
  /**
@@ -1211,6 +1255,104 @@ class CodeBakersServer {
1211
1255
  },
1212
1256
  },
1213
1257
  },
1258
+ // Ripple Detection Tool
1259
+ {
1260
+ name: 'ripple_check',
1261
+ description:
1262
+ 'Detect all files affected by a change to a type, schema, function, or component. Use this BEFORE making breaking changes to understand impact, or AFTER to find files that need updating. Returns a list of files that import/use the entity and may need updates.',
1263
+ inputSchema: {
1264
+ type: 'object' as const,
1265
+ properties: {
1266
+ entityName: {
1267
+ type: 'string',
1268
+ description: 'Name of the type, schema, function, or component that changed (e.g., "User", "createOrder", "Button")',
1269
+ },
1270
+ changeType: {
1271
+ type: 'string',
1272
+ enum: ['added_field', 'removed_field', 'renamed', 'type_changed', 'signature_changed', 'other'],
1273
+ description: 'What kind of change was made',
1274
+ },
1275
+ changeDescription: {
1276
+ type: 'string',
1277
+ description: 'Brief description of the change (e.g., "added teamId field", "renamed email to emailAddress")',
1278
+ },
1279
+ },
1280
+ required: ['entityName'],
1281
+ },
1282
+ },
1283
+ // ============================================
1284
+ // DEPENDENCY GUARDIAN - Auto-Coherence System
1285
+ // ============================================
1286
+ {
1287
+ name: 'guardian_analyze',
1288
+ description:
1289
+ 'AUTOMATIC: Analyzes code for consistency issues, broken contracts, and coherence problems. Runs silently after every code generation. Returns issues found with auto-fix suggestions. User never needs to call this directly.',
1290
+ inputSchema: {
1291
+ type: 'object' as const,
1292
+ properties: {
1293
+ files: {
1294
+ type: 'array',
1295
+ items: { type: 'string' },
1296
+ description: 'Files that were just created or modified',
1297
+ },
1298
+ changeContext: {
1299
+ type: 'string',
1300
+ description: 'What change was made (e.g., "added user authentication", "created invoice API")',
1301
+ },
1302
+ },
1303
+ required: ['files'],
1304
+ },
1305
+ },
1306
+ {
1307
+ name: 'guardian_heal',
1308
+ description:
1309
+ 'AUTOMATIC: Fixes consistency issues, broken imports, type mismatches, and contract violations. Runs automatically when guardian_analyze finds issues. User never needs to call this directly.',
1310
+ inputSchema: {
1311
+ type: 'object' as const,
1312
+ properties: {
1313
+ issues: {
1314
+ type: 'array',
1315
+ items: {
1316
+ type: 'object',
1317
+ properties: {
1318
+ file: { type: 'string' },
1319
+ issue: { type: 'string' },
1320
+ fix: { type: 'string' },
1321
+ },
1322
+ },
1323
+ description: 'Issues to fix (from guardian_analyze)',
1324
+ },
1325
+ autoFix: {
1326
+ type: 'boolean',
1327
+ description: 'Whether to automatically apply fixes (default: true)',
1328
+ },
1329
+ },
1330
+ required: ['issues'],
1331
+ },
1332
+ },
1333
+ {
1334
+ name: 'guardian_verify',
1335
+ description:
1336
+ 'AUTOMATIC: Verifies that all changes are coherent and the codebase compiles. Runs TypeScript check, verifies imports, and checks for common issues. Returns pass/fail with details.',
1337
+ inputSchema: {
1338
+ type: 'object' as const,
1339
+ properties: {
1340
+ deep: {
1341
+ type: 'boolean',
1342
+ description: 'Run deep verification including cross-file contract checking (default: false for speed)',
1343
+ },
1344
+ },
1345
+ },
1346
+ },
1347
+ {
1348
+ name: 'guardian_status',
1349
+ description:
1350
+ 'Shows the current coherence status of the project. Reports any known issues, pending fixes, and overall health score.',
1351
+ inputSchema: {
1352
+ type: 'object' as const,
1353
+ properties: {},
1354
+ },
1355
+ },
1214
1356
  ],
1215
1357
  }));
1216
1358
 
@@ -1372,6 +1514,22 @@ class CodeBakersServer {
1372
1514
  case 'vapi_generate_webhook':
1373
1515
  return this.handleVapiGenerateWebhook(args as { events?: string[] });
1374
1516
 
1517
+ case 'ripple_check':
1518
+ return this.handleRippleCheck(args as { entityName: string; changeType?: string; changeDescription?: string });
1519
+
1520
+ // Dependency Guardian - Auto-Coherence System
1521
+ case 'guardian_analyze':
1522
+ return this.handleGuardianAnalyze(args as { files: string[]; changeContext?: string });
1523
+
1524
+ case 'guardian_heal':
1525
+ return this.handleGuardianHeal(args as { issues: Array<{ file: string; issue: string; fix: string }>; autoFix?: boolean });
1526
+
1527
+ case 'guardian_verify':
1528
+ return this.handleGuardianVerify(args as { deep?: boolean });
1529
+
1530
+ case 'guardian_status':
1531
+ return this.handleGuardianStatus();
1532
+
1375
1533
  default:
1376
1534
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
1377
1535
  }
@@ -6126,6 +6284,949 @@ ${events.includes('call-started') ? ` case 'call-started':
6126
6284
  return { content: [{ type: 'text' as const, text: response }] };
6127
6285
  }
6128
6286
 
6287
+ // ============================================
6288
+ // DEPENDENCY GUARDIAN - Auto-Coherence System
6289
+ // ============================================
6290
+
6291
+ /**
6292
+ * Guardian Analyze - Detects consistency issues in changed files
6293
+ * Runs automatically after code generation
6294
+ */
6295
+ private handleGuardianAnalyze(args: { files: string[]; changeContext?: string }) {
6296
+ const { files, changeContext } = args;
6297
+ const cwd = process.cwd();
6298
+
6299
+ interface Issue {
6300
+ file: string;
6301
+ line: number;
6302
+ issue: string;
6303
+ severity: 'error' | 'warning' | 'info';
6304
+ fix: string;
6305
+ autoFixable: boolean;
6306
+ }
6307
+
6308
+ const issues: Issue[] = [];
6309
+ const analyzed: string[] = [];
6310
+
6311
+ // Helper to check if an import path resolves
6312
+ const checkImportResolves = (importPath: string, fromFile: string): boolean => {
6313
+ const fromDir = path.dirname(fromFile);
6314
+
6315
+ // Handle alias imports (@/)
6316
+ if (importPath.startsWith('@/')) {
6317
+ const aliasPath = path.join(cwd, 'src', importPath.slice(2));
6318
+ return fs.existsSync(aliasPath + '.ts') ||
6319
+ fs.existsSync(aliasPath + '.tsx') ||
6320
+ fs.existsSync(path.join(aliasPath, 'index.ts')) ||
6321
+ fs.existsSync(path.join(aliasPath, 'index.tsx'));
6322
+ }
6323
+
6324
+ // Handle relative imports
6325
+ if (importPath.startsWith('.')) {
6326
+ const resolved = path.resolve(fromDir, importPath);
6327
+ return fs.existsSync(resolved + '.ts') ||
6328
+ fs.existsSync(resolved + '.tsx') ||
6329
+ fs.existsSync(path.join(resolved, 'index.ts')) ||
6330
+ fs.existsSync(path.join(resolved, 'index.tsx'));
6331
+ }
6332
+
6333
+ // Node modules - assume they exist
6334
+ return true;
6335
+ };
6336
+
6337
+ // Helper to extract imports from a file
6338
+ const extractImports = (content: string): Array<{ path: string; names: string[]; line: number }> => {
6339
+ const imports: Array<{ path: string; names: string[]; line: number }> = [];
6340
+ const lines = content.split('\n');
6341
+
6342
+ lines.forEach((line, i) => {
6343
+ // Match: import { X, Y } from 'path'
6344
+ const namedMatch = line.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
6345
+ if (namedMatch) {
6346
+ const names = namedMatch[1].split(',').map(n => n.trim().split(' as ')[0]);
6347
+ imports.push({ path: namedMatch[2], names, line: i + 1 });
6348
+ }
6349
+
6350
+ // Match: import X from 'path'
6351
+ const defaultMatch = line.match(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
6352
+ if (defaultMatch && !namedMatch) {
6353
+ imports.push({ path: defaultMatch[2], names: [defaultMatch[1]], line: i + 1 });
6354
+ }
6355
+ });
6356
+
6357
+ return imports;
6358
+ };
6359
+
6360
+ // Helper to extract exports from a file
6361
+ const extractExports = (content: string): string[] => {
6362
+ const exports: string[] = [];
6363
+
6364
+ // Match: export const/function/class/type/interface X
6365
+ const exportMatches = content.matchAll(/export\s+(const|function|class|type|interface)\s+(\w+)/g);
6366
+ for (const match of exportMatches) {
6367
+ exports.push(match[2]);
6368
+ }
6369
+
6370
+ // Match: export { X, Y }
6371
+ const namedExportMatch = content.match(/export\s+\{([^}]+)\}/);
6372
+ if (namedExportMatch) {
6373
+ const names = namedExportMatch[1].split(',').map(n => n.trim().split(' as ')[0]);
6374
+ exports.push(...names);
6375
+ }
6376
+
6377
+ // Match: export default
6378
+ if (content.includes('export default')) {
6379
+ exports.push('default');
6380
+ }
6381
+
6382
+ return exports;
6383
+ };
6384
+
6385
+ // Analyze each file
6386
+ for (const file of files) {
6387
+ const fullPath = path.isAbsolute(file) ? file : path.join(cwd, file);
6388
+
6389
+ if (!fs.existsSync(fullPath)) {
6390
+ issues.push({
6391
+ file,
6392
+ line: 0,
6393
+ issue: `File does not exist: ${file}`,
6394
+ severity: 'error',
6395
+ fix: `Create the file or remove the reference`,
6396
+ autoFixable: false,
6397
+ });
6398
+ continue;
6399
+ }
6400
+
6401
+ try {
6402
+ const content = fs.readFileSync(fullPath, 'utf-8');
6403
+ const lines = content.split('\n');
6404
+ analyzed.push(file);
6405
+
6406
+ // Check 1: Broken imports
6407
+ const imports = extractImports(content);
6408
+ for (const imp of imports) {
6409
+ if (!checkImportResolves(imp.path, fullPath)) {
6410
+ issues.push({
6411
+ file,
6412
+ line: imp.line,
6413
+ issue: `Broken import: '${imp.path}' does not resolve`,
6414
+ severity: 'error',
6415
+ fix: `Check the import path or create the missing module`,
6416
+ autoFixable: false,
6417
+ });
6418
+ }
6419
+ }
6420
+
6421
+ // Check 2: Unused imports (basic check)
6422
+ for (const imp of imports) {
6423
+ for (const name of imp.names) {
6424
+ // Skip if it's a type-only import or if name appears elsewhere in file
6425
+ const usageCount = (content.match(new RegExp(`\\b${name}\\b`, 'g')) || []).length;
6426
+ if (usageCount === 1) { // Only appears in import
6427
+ issues.push({
6428
+ file,
6429
+ line: imp.line,
6430
+ issue: `Unused import: '${name}' is imported but never used`,
6431
+ severity: 'warning',
6432
+ fix: `Remove '${name}' from imports`,
6433
+ autoFixable: true,
6434
+ });
6435
+ }
6436
+ }
6437
+ }
6438
+
6439
+ // Check 3: console.log in production code (not in test files)
6440
+ if (!file.includes('.test.') && !file.includes('.spec.')) {
6441
+ lines.forEach((line, i) => {
6442
+ if (line.includes('console.log(') && !line.trim().startsWith('//')) {
6443
+ issues.push({
6444
+ file,
6445
+ line: i + 1,
6446
+ issue: `console.log found in production code`,
6447
+ severity: 'warning',
6448
+ fix: `Remove console.log or replace with proper logging`,
6449
+ autoFixable: true,
6450
+ });
6451
+ }
6452
+ });
6453
+ }
6454
+
6455
+ // Check 4: TODO/FIXME comments
6456
+ lines.forEach((line, i) => {
6457
+ if (line.includes('TODO:') || line.includes('FIXME:')) {
6458
+ issues.push({
6459
+ file,
6460
+ line: i + 1,
6461
+ issue: `Unresolved TODO/FIXME comment`,
6462
+ severity: 'info',
6463
+ fix: `Complete the TODO or remove if no longer needed`,
6464
+ autoFixable: false,
6465
+ });
6466
+ }
6467
+ });
6468
+
6469
+ // Check 5: Type 'any' usage
6470
+ lines.forEach((line, i) => {
6471
+ if (line.includes(': any') || line.includes('<any>') || line.includes('as any')) {
6472
+ issues.push({
6473
+ file,
6474
+ line: i + 1,
6475
+ issue: `'any' type used - weakens type safety`,
6476
+ severity: 'warning',
6477
+ fix: `Replace 'any' with a proper type`,
6478
+ autoFixable: false,
6479
+ });
6480
+ }
6481
+ });
6482
+
6483
+ // Check 6: Missing error handling in async functions
6484
+ if (content.includes('async ') && !content.includes('try {') && !content.includes('.catch(')) {
6485
+ issues.push({
6486
+ file,
6487
+ line: 1,
6488
+ issue: `Async function without error handling`,
6489
+ severity: 'warning',
6490
+ fix: `Add try/catch or .catch() to handle errors`,
6491
+ autoFixable: false,
6492
+ });
6493
+ }
6494
+
6495
+ // Check 7: API routes without proper error handling
6496
+ if (file.includes('/api/') && file.endsWith('route.ts')) {
6497
+ if (!content.includes('try {') || !content.includes('catch')) {
6498
+ issues.push({
6499
+ file,
6500
+ line: 1,
6501
+ issue: `API route without try/catch error handling`,
6502
+ severity: 'error',
6503
+ fix: `Wrap handler logic in try/catch with proper error response`,
6504
+ autoFixable: false,
6505
+ });
6506
+ }
6507
+ }
6508
+
6509
+ // Check 8: Missing return type on exported functions
6510
+ const funcMatches = content.matchAll(/export\s+(async\s+)?function\s+(\w+)\s*\([^)]*\)\s*{/g);
6511
+ for (const match of funcMatches) {
6512
+ const fullMatch = match[0];
6513
+ if (!fullMatch.includes(':')) { // No return type
6514
+ issues.push({
6515
+ file,
6516
+ line: 1,
6517
+ issue: `Exported function '${match[2]}' missing return type`,
6518
+ severity: 'info',
6519
+ fix: `Add explicit return type annotation`,
6520
+ autoFixable: false,
6521
+ });
6522
+ }
6523
+ }
6524
+
6525
+ } catch (err) {
6526
+ issues.push({
6527
+ file,
6528
+ line: 0,
6529
+ issue: `Could not analyze file: ${err instanceof Error ? err.message : 'Unknown error'}`,
6530
+ severity: 'error',
6531
+ fix: `Check file permissions and encoding`,
6532
+ autoFixable: false,
6533
+ });
6534
+ }
6535
+ }
6536
+
6537
+ // Also check cross-file consistency
6538
+ // Look for imports of the changed files from other files
6539
+ const searchDirs = ['src', 'app', 'lib', 'components', 'services'];
6540
+ const extensions = ['.ts', '.tsx'];
6541
+
6542
+ const searchDir = (dir: string) => {
6543
+ if (!fs.existsSync(dir)) return;
6544
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
6545
+
6546
+ for (const entry of entries) {
6547
+ const fullPath = path.join(dir, entry.name);
6548
+
6549
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
6550
+ searchDir(fullPath);
6551
+ } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
6552
+ // Check if this file imports any of our changed files
6553
+ const relativePath = path.relative(cwd, fullPath);
6554
+ if (files.includes(relativePath)) continue; // Skip files we're already analyzing
6555
+
6556
+ try {
6557
+ const content = fs.readFileSync(fullPath, 'utf-8');
6558
+ const imports = extractImports(content);
6559
+
6560
+ for (const imp of imports) {
6561
+ // Check if import matches any of our changed files
6562
+ for (const changedFile of files) {
6563
+ const changedBasename = path.basename(changedFile, path.extname(changedFile));
6564
+ if (imp.path.includes(changedBasename)) {
6565
+ // This file imports a changed file - verify the imports still exist
6566
+ const changedFullPath = path.join(cwd, changedFile);
6567
+ if (fs.existsSync(changedFullPath)) {
6568
+ const changedContent = fs.readFileSync(changedFullPath, 'utf-8');
6569
+ const exports = extractExports(changedContent);
6570
+
6571
+ for (const name of imp.names) {
6572
+ if (!exports.includes(name) && name !== 'default') {
6573
+ issues.push({
6574
+ file: relativePath,
6575
+ line: imp.line,
6576
+ issue: `Import '${name}' no longer exported from '${changedFile}'`,
6577
+ severity: 'error',
6578
+ fix: `Update import or add export to '${changedFile}'`,
6579
+ autoFixable: false,
6580
+ });
6581
+ }
6582
+ }
6583
+ }
6584
+ }
6585
+ }
6586
+ }
6587
+ } catch {
6588
+ // Skip files that can't be read
6589
+ }
6590
+ }
6591
+ }
6592
+ };
6593
+
6594
+ for (const dir of searchDirs) {
6595
+ searchDir(path.join(cwd, dir));
6596
+ }
6597
+
6598
+ // Build response
6599
+ let response = `# 🛡️ Dependency Guardian Analysis\n\n`;
6600
+
6601
+ if (changeContext) {
6602
+ response += `**Context:** ${changeContext}\n\n`;
6603
+ }
6604
+
6605
+ response += `**Files Analyzed:** ${analyzed.length}\n`;
6606
+ response += `**Issues Found:** ${issues.length}\n\n`;
6607
+
6608
+ if (issues.length === 0) {
6609
+ response += `## ✅ All Clear!\n\n`;
6610
+ response += `No consistency issues detected. Code is coherent.\n`;
6611
+ } else {
6612
+ const errors = issues.filter(i => i.severity === 'error');
6613
+ const warnings = issues.filter(i => i.severity === 'warning');
6614
+ const infos = issues.filter(i => i.severity === 'info');
6615
+ const autoFixable = issues.filter(i => i.autoFixable);
6616
+
6617
+ response += `| Severity | Count |\n`;
6618
+ response += `|----------|-------|\n`;
6619
+ response += `| 🔴 Errors | ${errors.length} |\n`;
6620
+ response += `| 🟡 Warnings | ${warnings.length} |\n`;
6621
+ response += `| 🔵 Info | ${infos.length} |\n`;
6622
+ response += `| 🔧 Auto-fixable | ${autoFixable.length} |\n\n`;
6623
+
6624
+ if (errors.length > 0) {
6625
+ response += `## 🔴 Errors (Must Fix)\n\n`;
6626
+ for (const issue of errors) {
6627
+ response += `### \`${issue.file}:${issue.line}\`\n`;
6628
+ response += `**Issue:** ${issue.issue}\n`;
6629
+ response += `**Fix:** ${issue.fix}\n\n`;
6630
+ }
6631
+ }
6632
+
6633
+ if (warnings.length > 0) {
6634
+ response += `## 🟡 Warnings (Should Fix)\n\n`;
6635
+ for (const issue of warnings) {
6636
+ response += `- \`${issue.file}:${issue.line}\` - ${issue.issue}${issue.autoFixable ? ' 🔧' : ''}\n`;
6637
+ }
6638
+ response += `\n`;
6639
+ }
6640
+
6641
+ if (infos.length > 0) {
6642
+ response += `## 🔵 Info (Consider)\n\n`;
6643
+ for (const issue of infos) {
6644
+ response += `- \`${issue.file}:${issue.line}\` - ${issue.issue}\n`;
6645
+ }
6646
+ response += `\n`;
6647
+ }
6648
+
6649
+ if (autoFixable.length > 0) {
6650
+ response += `---\n\n`;
6651
+ response += `**${autoFixable.length} issues can be auto-fixed.** Run \`guardian_heal\` to fix them automatically.\n`;
6652
+ }
6653
+ }
6654
+
6655
+ // Store issues for healing
6656
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
6657
+ try {
6658
+ fs.mkdirSync(path.dirname(guardianStatePath), { recursive: true });
6659
+ fs.writeFileSync(guardianStatePath, JSON.stringify({
6660
+ lastAnalysis: new Date().toISOString(),
6661
+ filesAnalyzed: analyzed,
6662
+ issues,
6663
+ changeContext,
6664
+ }, null, 2));
6665
+ } catch {
6666
+ // Ignore write errors
6667
+ }
6668
+
6669
+ return { content: [{ type: 'text' as const, text: response }] };
6670
+ }
6671
+
6672
+ /**
6673
+ * Guardian Heal - Auto-fixes issues found by guardian_analyze
6674
+ */
6675
+ private handleGuardianHeal(args: { issues: Array<{ file: string; issue: string; fix: string }>; autoFix?: boolean }) {
6676
+ const { autoFix = true } = args;
6677
+ const cwd = process.cwd();
6678
+
6679
+ // Load issues from state if not provided
6680
+ let issues = args.issues;
6681
+ if (!issues || issues.length === 0) {
6682
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
6683
+ if (fs.existsSync(guardianStatePath)) {
6684
+ try {
6685
+ const state = JSON.parse(fs.readFileSync(guardianStatePath, 'utf-8'));
6686
+ issues = state.issues?.filter((i: { autoFixable?: boolean }) => i.autoFixable) || [];
6687
+ } catch {
6688
+ issues = [];
6689
+ }
6690
+ }
6691
+ }
6692
+
6693
+ if (issues.length === 0) {
6694
+ return {
6695
+ content: [{
6696
+ type: 'text' as const,
6697
+ text: `# 🛡️ Guardian Heal\n\n✅ No issues to fix. Run \`guardian_analyze\` first to detect issues.`
6698
+ }]
6699
+ };
6700
+ }
6701
+
6702
+ let response = `# 🛡️ Guardian Heal\n\n`;
6703
+ const fixed: string[] = [];
6704
+ const failed: string[] = [];
6705
+
6706
+ for (const issue of issues) {
6707
+ const fullPath = path.join(cwd, issue.file);
6708
+
6709
+ if (!fs.existsSync(fullPath)) {
6710
+ failed.push(`${issue.file}: File not found`);
6711
+ continue;
6712
+ }
6713
+
6714
+ try {
6715
+ let content = fs.readFileSync(fullPath, 'utf-8');
6716
+ let modified = false;
6717
+
6718
+ // Fix: Remove unused imports
6719
+ if (issue.issue.includes('Unused import')) {
6720
+ const match = issue.issue.match(/Unused import: '(\w+)'/);
6721
+ if (match) {
6722
+ const name = match[1];
6723
+ // Remove from named imports
6724
+ content = content.replace(new RegExp(`\\b${name}\\b,?\\s*`, 'g'), (m, offset) => {
6725
+ // Only replace in import statements
6726
+ const lineStart = content.lastIndexOf('\n', offset) + 1;
6727
+ const line = content.substring(lineStart, content.indexOf('\n', offset));
6728
+ if (line.includes('import')) {
6729
+ modified = true;
6730
+ return '';
6731
+ }
6732
+ return m;
6733
+ });
6734
+ }
6735
+ }
6736
+
6737
+ // Fix: Remove console.log
6738
+ if (issue.issue.includes('console.log')) {
6739
+ const lines = content.split('\n');
6740
+ const lineIndex = parseInt(issue.issue.match(/line (\d+)/)?.[1] || '0') - 1;
6741
+ if (lineIndex >= 0 && lines[lineIndex]?.includes('console.log')) {
6742
+ lines.splice(lineIndex, 1);
6743
+ content = lines.join('\n');
6744
+ modified = true;
6745
+ }
6746
+ }
6747
+
6748
+ if (modified && autoFix) {
6749
+ fs.writeFileSync(fullPath, content);
6750
+ fixed.push(issue.file);
6751
+ } else if (!modified) {
6752
+ failed.push(`${issue.file}: Could not auto-fix "${issue.issue}"`);
6753
+ }
6754
+ } catch (err) {
6755
+ failed.push(`${issue.file}: ${err instanceof Error ? err.message : 'Unknown error'}`);
6756
+ }
6757
+ }
6758
+
6759
+ if (fixed.length > 0) {
6760
+ response += `## ✅ Fixed (${fixed.length})\n\n`;
6761
+ for (const file of fixed) {
6762
+ response += `- \`${file}\`\n`;
6763
+ }
6764
+ response += `\n`;
6765
+ }
6766
+
6767
+ if (failed.length > 0) {
6768
+ response += `## ⚠️ Could Not Auto-Fix (${failed.length})\n\n`;
6769
+ for (const msg of failed) {
6770
+ response += `- ${msg}\n`;
6771
+ }
6772
+ response += `\nThese require manual intervention.\n`;
6773
+ }
6774
+
6775
+ if (fixed.length > 0) {
6776
+ response += `\n---\n\nRun \`guardian_verify\` to confirm all issues are resolved.\n`;
6777
+ }
6778
+
6779
+ return { content: [{ type: 'text' as const, text: response }] };
6780
+ }
6781
+
6782
+ /**
6783
+ * Guardian Verify - Verifies codebase coherence (TypeScript check, imports, etc.)
6784
+ */
6785
+ private handleGuardianVerify(args: { deep?: boolean }) {
6786
+ const { deep = false } = args;
6787
+ const cwd = process.cwd();
6788
+
6789
+ let response = `# 🛡️ Guardian Verify\n\n`;
6790
+ const checks: Array<{ name: string; status: 'pass' | 'fail' | 'warn'; details: string }> = [];
6791
+
6792
+ // Check 1: TypeScript compilation
6793
+ try {
6794
+ execSync('npx tsc --noEmit', { cwd, stdio: 'pipe', timeout: 60000 });
6795
+ checks.push({ name: 'TypeScript', status: 'pass', details: 'No type errors' });
6796
+ } catch (err) {
6797
+ const output = err instanceof Error && 'stdout' in err ? String((err as { stdout: Buffer }).stdout) : 'Unknown error';
6798
+ const errorCount = (output.match(/error TS/g) || []).length;
6799
+ checks.push({
6800
+ name: 'TypeScript',
6801
+ status: 'fail',
6802
+ details: `${errorCount} type error${errorCount !== 1 ? 's' : ''} found`
6803
+ });
6804
+ }
6805
+
6806
+ // Check 2: Package.json exists and is valid
6807
+ const pkgPath = path.join(cwd, 'package.json');
6808
+ if (fs.existsSync(pkgPath)) {
6809
+ try {
6810
+ JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
6811
+ checks.push({ name: 'package.json', status: 'pass', details: 'Valid JSON' });
6812
+ } catch {
6813
+ checks.push({ name: 'package.json', status: 'fail', details: 'Invalid JSON' });
6814
+ }
6815
+ } else {
6816
+ checks.push({ name: 'package.json', status: 'fail', details: 'File not found' });
6817
+ }
6818
+
6819
+ // Check 3: Environment variables
6820
+ const envExample = path.join(cwd, '.env.example');
6821
+ const envLocal = path.join(cwd, '.env.local');
6822
+ const env = path.join(cwd, '.env');
6823
+
6824
+ if (fs.existsSync(envExample)) {
6825
+ checks.push({ name: '.env.example', status: 'pass', details: 'Exists for documentation' });
6826
+ } else {
6827
+ checks.push({ name: '.env.example', status: 'warn', details: 'Missing - should document required env vars' });
6828
+ }
6829
+
6830
+ if (fs.existsSync(envLocal) || fs.existsSync(env)) {
6831
+ checks.push({ name: '.env', status: 'pass', details: 'Environment configured' });
6832
+ } else {
6833
+ checks.push({ name: '.env', status: 'warn', details: 'No .env file - may need configuration' });
6834
+ }
6835
+
6836
+ // Check 4: Git ignore includes sensitive files
6837
+ const gitignore = path.join(cwd, '.gitignore');
6838
+ if (fs.existsSync(gitignore)) {
6839
+ const content = fs.readFileSync(gitignore, 'utf-8');
6840
+ if (content.includes('.env') && content.includes('node_modules')) {
6841
+ checks.push({ name: '.gitignore', status: 'pass', details: 'Properly configured' });
6842
+ } else {
6843
+ checks.push({ name: '.gitignore', status: 'warn', details: 'May be missing important entries' });
6844
+ }
6845
+ } else {
6846
+ checks.push({ name: '.gitignore', status: 'warn', details: 'No .gitignore file' });
6847
+ }
6848
+
6849
+ // Check 5: Deep mode - check all imports resolve
6850
+ if (deep) {
6851
+ let brokenImports = 0;
6852
+ const searchDirs = ['src', 'app'];
6853
+
6854
+ const checkDir = (dir: string) => {
6855
+ if (!fs.existsSync(dir)) return;
6856
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
6857
+
6858
+ for (const entry of entries) {
6859
+ const fullPath = path.join(dir, entry.name);
6860
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
6861
+ checkDir(fullPath);
6862
+ } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
6863
+ try {
6864
+ const content = fs.readFileSync(fullPath, 'utf-8');
6865
+ const importMatches = content.matchAll(/from\s+['"]([^'"]+)['"]/g);
6866
+ for (const match of importMatches) {
6867
+ const importPath = match[1];
6868
+ if (importPath.startsWith('.') || importPath.startsWith('@/')) {
6869
+ // Check if resolves
6870
+ let resolved: string;
6871
+ if (importPath.startsWith('@/')) {
6872
+ resolved = path.join(cwd, 'src', importPath.slice(2));
6873
+ } else {
6874
+ resolved = path.resolve(path.dirname(fullPath), importPath);
6875
+ }
6876
+
6877
+ const exists = fs.existsSync(resolved + '.ts') ||
6878
+ fs.existsSync(resolved + '.tsx') ||
6879
+ fs.existsSync(path.join(resolved, 'index.ts')) ||
6880
+ fs.existsSync(path.join(resolved, 'index.tsx'));
6881
+
6882
+ if (!exists) {
6883
+ brokenImports++;
6884
+ }
6885
+ }
6886
+ }
6887
+ } catch {
6888
+ // Skip unreadable files
6889
+ }
6890
+ }
6891
+ }
6892
+ };
6893
+
6894
+ for (const dir of searchDirs) {
6895
+ checkDir(path.join(cwd, dir));
6896
+ }
6897
+
6898
+ if (brokenImports === 0) {
6899
+ checks.push({ name: 'Import Resolution', status: 'pass', details: 'All imports resolve' });
6900
+ } else {
6901
+ checks.push({ name: 'Import Resolution', status: 'fail', details: `${brokenImports} broken import${brokenImports !== 1 ? 's' : ''}` });
6902
+ }
6903
+ }
6904
+
6905
+ // Build response
6906
+ const passed = checks.filter(c => c.status === 'pass').length;
6907
+ const failed = checks.filter(c => c.status === 'fail').length;
6908
+ const warned = checks.filter(c => c.status === 'warn').length;
6909
+
6910
+ const overallStatus = failed > 0 ? '❌ FAIL' : warned > 0 ? '⚠️ WARN' : '✅ PASS';
6911
+
6912
+ response += `## Overall: ${overallStatus}\n\n`;
6913
+ response += `| Check | Status | Details |\n`;
6914
+ response += `|-------|--------|--------|\n`;
6915
+
6916
+ for (const check of checks) {
6917
+ const icon = check.status === 'pass' ? '✅' : check.status === 'fail' ? '❌' : '⚠️';
6918
+ response += `| ${check.name} | ${icon} | ${check.details} |\n`;
6919
+ }
6920
+
6921
+ response += `\n**Summary:** ${passed} passed, ${failed} failed, ${warned} warnings\n`;
6922
+
6923
+ if (failed > 0) {
6924
+ response += `\n---\n\n⚠️ **Action Required:** Fix the failing checks before deploying.\n`;
6925
+ }
6926
+
6927
+ // Update guardian state
6928
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
6929
+ try {
6930
+ let state: Record<string, unknown> = {};
6931
+ if (fs.existsSync(guardianStatePath)) {
6932
+ state = JSON.parse(fs.readFileSync(guardianStatePath, 'utf-8'));
6933
+ }
6934
+ state.lastVerification = new Date().toISOString();
6935
+ state.verificationResult = { passed, failed, warned, checks };
6936
+ fs.mkdirSync(path.dirname(guardianStatePath), { recursive: true });
6937
+ fs.writeFileSync(guardianStatePath, JSON.stringify(state, null, 2));
6938
+ } catch {
6939
+ // Ignore write errors
6940
+ }
6941
+
6942
+ return { content: [{ type: 'text' as const, text: response }] };
6943
+ }
6944
+
6945
+ /**
6946
+ * Guardian Status - Shows overall project coherence health
6947
+ */
6948
+ private handleGuardianStatus() {
6949
+ const cwd = process.cwd();
6950
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
6951
+
6952
+ let response = `# 🛡️ Dependency Guardian Status\n\n`;
6953
+
6954
+ // Check if guardian state exists
6955
+ if (!fs.existsSync(guardianStatePath)) {
6956
+ response += `## ⚪ Not Initialized\n\n`;
6957
+ response += `Guardian hasn't analyzed this project yet.\n\n`;
6958
+ response += `Run \`guardian_analyze\` on your files to start tracking coherence.\n`;
6959
+ return { content: [{ type: 'text' as const, text: response }] };
6960
+ }
6961
+
6962
+ try {
6963
+ const state = JSON.parse(fs.readFileSync(guardianStatePath, 'utf-8'));
6964
+
6965
+ // Last analysis
6966
+ if (state.lastAnalysis) {
6967
+ const analysisTime = new Date(state.lastAnalysis);
6968
+ const hoursAgo = Math.round((Date.now() - analysisTime.getTime()) / (1000 * 60 * 60));
6969
+ response += `**Last Analysis:** ${hoursAgo < 1 ? 'Just now' : `${hoursAgo} hour${hoursAgo !== 1 ? 's' : ''} ago`}\n`;
6970
+ }
6971
+
6972
+ // Last verification
6973
+ if (state.lastVerification) {
6974
+ const verifyTime = new Date(state.lastVerification);
6975
+ const hoursAgo = Math.round((Date.now() - verifyTime.getTime()) / (1000 * 60 * 60));
6976
+ response += `**Last Verification:** ${hoursAgo < 1 ? 'Just now' : `${hoursAgo} hour${hoursAgo !== 1 ? 's' : ''} ago`}\n`;
6977
+ }
6978
+
6979
+ response += `\n`;
6980
+
6981
+ // Issue summary
6982
+ if (state.issues && state.issues.length > 0) {
6983
+ const errors = state.issues.filter((i: { severity: string }) => i.severity === 'error').length;
6984
+ const warnings = state.issues.filter((i: { severity: string }) => i.severity === 'warning').length;
6985
+
6986
+ if (errors > 0) {
6987
+ response += `## ❌ Issues Found\n\n`;
6988
+ response += `- 🔴 ${errors} error${errors !== 1 ? 's' : ''}\n`;
6989
+ response += `- 🟡 ${warnings} warning${warnings !== 1 ? 's' : ''}\n\n`;
6990
+ response += `Run \`guardian_heal\` to auto-fix what's possible.\n`;
6991
+ } else if (warnings > 0) {
6992
+ response += `## ⚠️ Warnings\n\n`;
6993
+ response += `- 🟡 ${warnings} warning${warnings !== 1 ? 's' : ''} (no critical errors)\n\n`;
6994
+ } else {
6995
+ response += `## ✅ All Clear\n\n`;
6996
+ response += `No known issues in the codebase.\n`;
6997
+ }
6998
+ } else {
6999
+ response += `## ✅ No Issues\n\n`;
7000
+ response += `Last analysis found no problems.\n`;
7001
+ }
7002
+
7003
+ // Verification result
7004
+ if (state.verificationResult) {
7005
+ const { passed, failed, warned } = state.verificationResult;
7006
+ response += `\n### Verification Summary\n`;
7007
+ response += `✅ ${passed} checks passed\n`;
7008
+ if (failed > 0) response += `❌ ${failed} checks failed\n`;
7009
+ if (warned > 0) response += `⚠️ ${warned} warnings\n`;
7010
+ }
7011
+
7012
+ // Health score
7013
+ const issueCount = state.issues?.length || 0;
7014
+ const errorCount = state.issues?.filter((i: { severity: string }) => i.severity === 'error').length || 0;
7015
+ const verifyFailed = state.verificationResult?.failed || 0;
7016
+
7017
+ let healthScore = 100;
7018
+ healthScore -= errorCount * 20;
7019
+ healthScore -= verifyFailed * 15;
7020
+ healthScore -= (issueCount - errorCount) * 5;
7021
+ healthScore = Math.max(0, Math.min(100, healthScore));
7022
+
7023
+ response += `\n---\n\n`;
7024
+ response += `## Health Score: ${healthScore}/100 ${healthScore >= 80 ? '🟢' : healthScore >= 50 ? '🟡' : '🔴'}\n`;
7025
+
7026
+ } catch {
7027
+ response += `## ⚠️ State Corrupted\n\n`;
7028
+ response += `Could not read guardian state. Run \`guardian_analyze\` to rebuild.\n`;
7029
+ }
7030
+
7031
+ return { content: [{ type: 'text' as const, text: response }] };
7032
+ }
7033
+
7034
+ /**
7035
+ * Ripple Check - Detect all files affected by a change to a type/schema/function
7036
+ * Searches the codebase for imports and usages of the entity
7037
+ */
7038
+ private handleRippleCheck(args: { entityName: string; changeType?: string; changeDescription?: string }) {
7039
+ const { entityName, changeType, changeDescription } = args;
7040
+ const cwd = process.cwd();
7041
+
7042
+ let response = `# 🌊 Ripple Check: \`${entityName}\`\n\n`;
7043
+
7044
+ if (changeType || changeDescription) {
7045
+ response += `**Change:** ${changeDescription || changeType || 'Unknown'}\n\n`;
7046
+ }
7047
+
7048
+ // Define search directories
7049
+ const searchDirs = ['src', 'app', 'lib', 'components', 'services', 'types', 'utils', 'hooks'];
7050
+ const extensions = ['.ts', '.tsx', '.js', '.jsx'];
7051
+
7052
+ interface FileMatch {
7053
+ file: string;
7054
+ importLine?: string;
7055
+ usageLines: string[];
7056
+ lineNumbers: number[];
7057
+ isDefinition: boolean;
7058
+ }
7059
+
7060
+ const matches: FileMatch[] = [];
7061
+ let definitionFile: string | null = null;
7062
+
7063
+ // Helper to search a directory recursively
7064
+ const searchDir = (dir: string) => {
7065
+ if (!fs.existsSync(dir)) return;
7066
+
7067
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
7068
+ for (const entry of entries) {
7069
+ const fullPath = path.join(dir, entry.name);
7070
+
7071
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
7072
+ searchDir(fullPath);
7073
+ } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
7074
+ try {
7075
+ const content = fs.readFileSync(fullPath, 'utf-8');
7076
+ const lines = content.split('\n');
7077
+
7078
+ // Check if file contains the entity
7079
+ if (!content.includes(entityName)) continue;
7080
+
7081
+ const relativePath = path.relative(cwd, fullPath);
7082
+ const match: FileMatch = {
7083
+ file: relativePath,
7084
+ usageLines: [],
7085
+ lineNumbers: [],
7086
+ isDefinition: false,
7087
+ };
7088
+
7089
+ lines.forEach((line, i) => {
7090
+ const lineNum = i + 1;
7091
+
7092
+ // Check for import statements
7093
+ if (line.includes('import') && line.includes(entityName)) {
7094
+ match.importLine = line.trim();
7095
+ }
7096
+
7097
+ // Check for type/interface/function/const definition
7098
+ const defPatterns = [
7099
+ `type ${entityName}`,
7100
+ `interface ${entityName}`,
7101
+ `function ${entityName}`,
7102
+ `const ${entityName}`,
7103
+ `class ${entityName}`,
7104
+ `export type ${entityName}`,
7105
+ `export interface ${entityName}`,
7106
+ `export function ${entityName}`,
7107
+ `export const ${entityName}`,
7108
+ `export class ${entityName}`,
7109
+ ];
7110
+
7111
+ if (defPatterns.some(p => line.includes(p))) {
7112
+ match.isDefinition = true;
7113
+ definitionFile = relativePath;
7114
+ }
7115
+
7116
+ // Check for usage (not just import or definition)
7117
+ if (line.includes(entityName) && !line.includes('import ')) {
7118
+ match.usageLines.push(line.trim().substring(0, 100));
7119
+ match.lineNumbers.push(lineNum);
7120
+ }
7121
+ });
7122
+
7123
+ // Only add if there are usages (not just definition)
7124
+ if (match.usageLines.length > 0) {
7125
+ matches.push(match);
7126
+ }
7127
+ } catch {
7128
+ // Skip files that can't be read
7129
+ }
7130
+ }
7131
+ }
7132
+ };
7133
+
7134
+ // Search all directories
7135
+ for (const dir of searchDirs) {
7136
+ searchDir(path.join(cwd, dir));
7137
+ }
7138
+
7139
+ // Sort: definition first, then by number of usages
7140
+ matches.sort((a, b) => {
7141
+ if (a.isDefinition && !b.isDefinition) return -1;
7142
+ if (!a.isDefinition && b.isDefinition) return 1;
7143
+ return b.usageLines.length - a.usageLines.length;
7144
+ });
7145
+
7146
+ // Generate report
7147
+ if (matches.length === 0) {
7148
+ response += `## ✅ No usages found\n\n`;
7149
+ response += `The entity \`${entityName}\` was not found in the codebase, or has no usages.\n`;
7150
+ response += `This change is safe to make without ripple effects.\n`;
7151
+ } else {
7152
+ // Summary
7153
+ response += `## 📊 Impact Summary\n\n`;
7154
+ response += `| Metric | Count |\n`;
7155
+ response += `|--------|-------|\n`;
7156
+ response += `| Files affected | ${matches.length} |\n`;
7157
+ response += `| Total usages | ${matches.reduce((sum, m) => sum + m.usageLines.length, 0)} |\n`;
7158
+ if (definitionFile) {
7159
+ response += `| Definition | \`${definitionFile}\` |\n`;
7160
+ }
7161
+ response += `\n`;
7162
+
7163
+ // Categorize by impact level
7164
+ const highImpact = matches.filter(m => m.usageLines.length >= 5);
7165
+ const mediumImpact = matches.filter(m => m.usageLines.length >= 2 && m.usageLines.length < 5);
7166
+ const lowImpact = matches.filter(m => m.usageLines.length === 1);
7167
+
7168
+ if (highImpact.length > 0) {
7169
+ response += `## 🔴 High Impact (5+ usages)\n\n`;
7170
+ for (const m of highImpact) {
7171
+ response += `### \`${m.file}\`${m.isDefinition ? ' (DEFINITION)' : ''}\n`;
7172
+ if (m.importLine) response += `Import: \`${m.importLine}\`\n`;
7173
+ response += `Usages (${m.usageLines.length}):\n`;
7174
+ for (let i = 0; i < Math.min(5, m.usageLines.length); i++) {
7175
+ response += `- Line ${m.lineNumbers[i]}: \`${m.usageLines[i].substring(0, 80)}${m.usageLines[i].length > 80 ? '...' : ''}\`\n`;
7176
+ }
7177
+ if (m.usageLines.length > 5) {
7178
+ response += `- ... and ${m.usageLines.length - 5} more\n`;
7179
+ }
7180
+ response += `\n`;
7181
+ }
7182
+ }
7183
+
7184
+ if (mediumImpact.length > 0) {
7185
+ response += `## 🟡 Medium Impact (2-4 usages)\n\n`;
7186
+ for (const m of mediumImpact) {
7187
+ response += `- \`${m.file}\` (${m.usageLines.length} usages)\n`;
7188
+ }
7189
+ response += `\n`;
7190
+ }
7191
+
7192
+ if (lowImpact.length > 0) {
7193
+ response += `## 🟢 Low Impact (1 usage)\n\n`;
7194
+ for (const m of lowImpact) {
7195
+ response += `- \`${m.file}\`\n`;
7196
+ }
7197
+ response += `\n`;
7198
+ }
7199
+
7200
+ // Recommendations
7201
+ response += `## 💡 Recommendations\n\n`;
7202
+
7203
+ if (changeType === 'added_field') {
7204
+ response += `**Adding a field is usually safe.** Optional fields won't break existing code.\n`;
7205
+ response += `If the field is required, update these files to provide the new field.\n`;
7206
+ } else if (changeType === 'removed_field') {
7207
+ response += `**Removing a field is breaking.** Check each file above to remove usages of the deleted field.\n`;
7208
+ response += `Run \`npx tsc --noEmit\` after making changes to find remaining issues.\n`;
7209
+ } else if (changeType === 'renamed') {
7210
+ response += `**Renaming is breaking.** Update all files above to use the new name.\n`;
7211
+ response += `Consider using IDE "Rename Symbol" for safer refactoring.\n`;
7212
+ } else if (changeType === 'type_changed') {
7213
+ response += `**Type changes may break.** Review each usage to ensure compatibility.\n`;
7214
+ response += `Run \`npx tsc --noEmit\` to find type errors.\n`;
7215
+ } else if (changeType === 'signature_changed') {
7216
+ response += `**Signature changes are breaking.** Update all call sites with new parameters.\n`;
7217
+ } else {
7218
+ response += `1. Review the high-impact files first\n`;
7219
+ response += `2. Run \`npx tsc --noEmit\` after changes to catch type errors\n`;
7220
+ response += `3. Run tests to verify functionality\n`;
7221
+ }
7222
+
7223
+ response += `\n---\n`;
7224
+ response += `*Run this check again after making changes to verify all ripples are addressed.*\n`;
7225
+ }
7226
+
7227
+ return { content: [{ type: 'text' as const, text: response }] };
7228
+ }
7229
+
6129
7230
  async run(): Promise<void> {
6130
7231
  const transport = new StdioServerTransport();
6131
7232
  await this.server.connect(transport);