@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.
@@ -75,6 +75,44 @@ class CodeBakersServer {
75
75
  this.checkAndAutoUpdate().catch(() => {
76
76
  // Silently ignore errors - don't interrupt user
77
77
  });
78
+ // Check for CLI version updates (non-blocking)
79
+ this.checkCliVersion().catch(() => {
80
+ // Silently ignore errors
81
+ });
82
+ }
83
+ /**
84
+ * Check if a newer CLI version is available and notify user
85
+ */
86
+ async checkCliVersion() {
87
+ try {
88
+ const currentVersion = (0, api_js_1.getCliVersion)();
89
+ // Fetch latest version from npm
90
+ const response = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
91
+ headers: { 'Accept': 'application/json' },
92
+ });
93
+ if (!response.ok)
94
+ return;
95
+ const data = await response.json();
96
+ const latestVersion = data.version;
97
+ if (latestVersion && latestVersion !== currentVersion) {
98
+ // Compare versions (simple comparison - assumes semver)
99
+ const current = currentVersion.split('.').map(Number);
100
+ const latest = latestVersion.split('.').map(Number);
101
+ const isNewer = latest[0] > current[0] ||
102
+ (latest[0] === current[0] && latest[1] > current[1]) ||
103
+ (latest[0] === current[0] && latest[1] === current[1] && latest[2] > current[2]);
104
+ if (isNewer) {
105
+ console.error(`\n╔════════════════════════════════════════════════════════════╗`);
106
+ console.error(`║ 🆕 CodeBakers CLI Update Available: v${currentVersion} → v${latestVersion.padEnd(10)}║`);
107
+ console.error(`║ ║`);
108
+ console.error(`║ Restart Cursor/Claude Code to get the latest features! ║`);
109
+ console.error(`╚════════════════════════════════════════════════════════════╝\n`);
110
+ }
111
+ }
112
+ }
113
+ catch {
114
+ // Silently ignore - don't interrupt user experience
115
+ }
78
116
  }
79
117
  /**
80
118
  * Get authorization headers for API requests
@@ -1115,6 +1153,99 @@ class CodeBakersServer {
1115
1153
  },
1116
1154
  },
1117
1155
  },
1156
+ // Ripple Detection Tool
1157
+ {
1158
+ name: 'ripple_check',
1159
+ description: '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.',
1160
+ inputSchema: {
1161
+ type: 'object',
1162
+ properties: {
1163
+ entityName: {
1164
+ type: 'string',
1165
+ description: 'Name of the type, schema, function, or component that changed (e.g., "User", "createOrder", "Button")',
1166
+ },
1167
+ changeType: {
1168
+ type: 'string',
1169
+ enum: ['added_field', 'removed_field', 'renamed', 'type_changed', 'signature_changed', 'other'],
1170
+ description: 'What kind of change was made',
1171
+ },
1172
+ changeDescription: {
1173
+ type: 'string',
1174
+ description: 'Brief description of the change (e.g., "added teamId field", "renamed email to emailAddress")',
1175
+ },
1176
+ },
1177
+ required: ['entityName'],
1178
+ },
1179
+ },
1180
+ // ============================================
1181
+ // DEPENDENCY GUARDIAN - Auto-Coherence System
1182
+ // ============================================
1183
+ {
1184
+ name: 'guardian_analyze',
1185
+ description: '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.',
1186
+ inputSchema: {
1187
+ type: 'object',
1188
+ properties: {
1189
+ files: {
1190
+ type: 'array',
1191
+ items: { type: 'string' },
1192
+ description: 'Files that were just created or modified',
1193
+ },
1194
+ changeContext: {
1195
+ type: 'string',
1196
+ description: 'What change was made (e.g., "added user authentication", "created invoice API")',
1197
+ },
1198
+ },
1199
+ required: ['files'],
1200
+ },
1201
+ },
1202
+ {
1203
+ name: 'guardian_heal',
1204
+ description: '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.',
1205
+ inputSchema: {
1206
+ type: 'object',
1207
+ properties: {
1208
+ issues: {
1209
+ type: 'array',
1210
+ items: {
1211
+ type: 'object',
1212
+ properties: {
1213
+ file: { type: 'string' },
1214
+ issue: { type: 'string' },
1215
+ fix: { type: 'string' },
1216
+ },
1217
+ },
1218
+ description: 'Issues to fix (from guardian_analyze)',
1219
+ },
1220
+ autoFix: {
1221
+ type: 'boolean',
1222
+ description: 'Whether to automatically apply fixes (default: true)',
1223
+ },
1224
+ },
1225
+ required: ['issues'],
1226
+ },
1227
+ },
1228
+ {
1229
+ name: 'guardian_verify',
1230
+ description: '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.',
1231
+ inputSchema: {
1232
+ type: 'object',
1233
+ properties: {
1234
+ deep: {
1235
+ type: 'boolean',
1236
+ description: 'Run deep verification including cross-file contract checking (default: false for speed)',
1237
+ },
1238
+ },
1239
+ },
1240
+ },
1241
+ {
1242
+ name: 'guardian_status',
1243
+ description: 'Shows the current coherence status of the project. Reports any known issues, pending fixes, and overall health score.',
1244
+ inputSchema: {
1245
+ type: 'object',
1246
+ properties: {},
1247
+ },
1248
+ },
1118
1249
  ],
1119
1250
  }));
1120
1251
  // Handle tool calls
@@ -1223,6 +1354,17 @@ class CodeBakersServer {
1223
1354
  return this.handleVapiGetCall(args);
1224
1355
  case 'vapi_generate_webhook':
1225
1356
  return this.handleVapiGenerateWebhook(args);
1357
+ case 'ripple_check':
1358
+ return this.handleRippleCheck(args);
1359
+ // Dependency Guardian - Auto-Coherence System
1360
+ case 'guardian_analyze':
1361
+ return this.handleGuardianAnalyze(args);
1362
+ case 'guardian_heal':
1363
+ return this.handleGuardianHeal(args);
1364
+ case 'guardian_verify':
1365
+ return this.handleGuardianVerify(args);
1366
+ case 'guardian_status':
1367
+ return this.handleGuardianStatus();
1226
1368
  default:
1227
1369
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
1228
1370
  }
@@ -5410,6 +5552,848 @@ ${events.includes('call-started') ? ` case 'call-started':
5410
5552
  response += `- \`function-call\`: When assistant calls a custom function\n`;
5411
5553
  return { content: [{ type: 'text', text: response }] };
5412
5554
  }
5555
+ // ============================================
5556
+ // DEPENDENCY GUARDIAN - Auto-Coherence System
5557
+ // ============================================
5558
+ /**
5559
+ * Guardian Analyze - Detects consistency issues in changed files
5560
+ * Runs automatically after code generation
5561
+ */
5562
+ handleGuardianAnalyze(args) {
5563
+ const { files, changeContext } = args;
5564
+ const cwd = process.cwd();
5565
+ const issues = [];
5566
+ const analyzed = [];
5567
+ // Helper to check if an import path resolves
5568
+ const checkImportResolves = (importPath, fromFile) => {
5569
+ const fromDir = path.dirname(fromFile);
5570
+ // Handle alias imports (@/)
5571
+ if (importPath.startsWith('@/')) {
5572
+ const aliasPath = path.join(cwd, 'src', importPath.slice(2));
5573
+ return fs.existsSync(aliasPath + '.ts') ||
5574
+ fs.existsSync(aliasPath + '.tsx') ||
5575
+ fs.existsSync(path.join(aliasPath, 'index.ts')) ||
5576
+ fs.existsSync(path.join(aliasPath, 'index.tsx'));
5577
+ }
5578
+ // Handle relative imports
5579
+ if (importPath.startsWith('.')) {
5580
+ const resolved = path.resolve(fromDir, importPath);
5581
+ return fs.existsSync(resolved + '.ts') ||
5582
+ fs.existsSync(resolved + '.tsx') ||
5583
+ fs.existsSync(path.join(resolved, 'index.ts')) ||
5584
+ fs.existsSync(path.join(resolved, 'index.tsx'));
5585
+ }
5586
+ // Node modules - assume they exist
5587
+ return true;
5588
+ };
5589
+ // Helper to extract imports from a file
5590
+ const extractImports = (content) => {
5591
+ const imports = [];
5592
+ const lines = content.split('\n');
5593
+ lines.forEach((line, i) => {
5594
+ // Match: import { X, Y } from 'path'
5595
+ const namedMatch = line.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
5596
+ if (namedMatch) {
5597
+ const names = namedMatch[1].split(',').map(n => n.trim().split(' as ')[0]);
5598
+ imports.push({ path: namedMatch[2], names, line: i + 1 });
5599
+ }
5600
+ // Match: import X from 'path'
5601
+ const defaultMatch = line.match(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
5602
+ if (defaultMatch && !namedMatch) {
5603
+ imports.push({ path: defaultMatch[2], names: [defaultMatch[1]], line: i + 1 });
5604
+ }
5605
+ });
5606
+ return imports;
5607
+ };
5608
+ // Helper to extract exports from a file
5609
+ const extractExports = (content) => {
5610
+ const exports = [];
5611
+ // Match: export const/function/class/type/interface X
5612
+ const exportMatches = content.matchAll(/export\s+(const|function|class|type|interface)\s+(\w+)/g);
5613
+ for (const match of exportMatches) {
5614
+ exports.push(match[2]);
5615
+ }
5616
+ // Match: export { X, Y }
5617
+ const namedExportMatch = content.match(/export\s+\{([^}]+)\}/);
5618
+ if (namedExportMatch) {
5619
+ const names = namedExportMatch[1].split(',').map(n => n.trim().split(' as ')[0]);
5620
+ exports.push(...names);
5621
+ }
5622
+ // Match: export default
5623
+ if (content.includes('export default')) {
5624
+ exports.push('default');
5625
+ }
5626
+ return exports;
5627
+ };
5628
+ // Analyze each file
5629
+ for (const file of files) {
5630
+ const fullPath = path.isAbsolute(file) ? file : path.join(cwd, file);
5631
+ if (!fs.existsSync(fullPath)) {
5632
+ issues.push({
5633
+ file,
5634
+ line: 0,
5635
+ issue: `File does not exist: ${file}`,
5636
+ severity: 'error',
5637
+ fix: `Create the file or remove the reference`,
5638
+ autoFixable: false,
5639
+ });
5640
+ continue;
5641
+ }
5642
+ try {
5643
+ const content = fs.readFileSync(fullPath, 'utf-8');
5644
+ const lines = content.split('\n');
5645
+ analyzed.push(file);
5646
+ // Check 1: Broken imports
5647
+ const imports = extractImports(content);
5648
+ for (const imp of imports) {
5649
+ if (!checkImportResolves(imp.path, fullPath)) {
5650
+ issues.push({
5651
+ file,
5652
+ line: imp.line,
5653
+ issue: `Broken import: '${imp.path}' does not resolve`,
5654
+ severity: 'error',
5655
+ fix: `Check the import path or create the missing module`,
5656
+ autoFixable: false,
5657
+ });
5658
+ }
5659
+ }
5660
+ // Check 2: Unused imports (basic check)
5661
+ for (const imp of imports) {
5662
+ for (const name of imp.names) {
5663
+ // Skip if it's a type-only import or if name appears elsewhere in file
5664
+ const usageCount = (content.match(new RegExp(`\\b${name}\\b`, 'g')) || []).length;
5665
+ if (usageCount === 1) { // Only appears in import
5666
+ issues.push({
5667
+ file,
5668
+ line: imp.line,
5669
+ issue: `Unused import: '${name}' is imported but never used`,
5670
+ severity: 'warning',
5671
+ fix: `Remove '${name}' from imports`,
5672
+ autoFixable: true,
5673
+ });
5674
+ }
5675
+ }
5676
+ }
5677
+ // Check 3: console.log in production code (not in test files)
5678
+ if (!file.includes('.test.') && !file.includes('.spec.')) {
5679
+ lines.forEach((line, i) => {
5680
+ if (line.includes('console.log(') && !line.trim().startsWith('//')) {
5681
+ issues.push({
5682
+ file,
5683
+ line: i + 1,
5684
+ issue: `console.log found in production code`,
5685
+ severity: 'warning',
5686
+ fix: `Remove console.log or replace with proper logging`,
5687
+ autoFixable: true,
5688
+ });
5689
+ }
5690
+ });
5691
+ }
5692
+ // Check 4: TODO/FIXME comments
5693
+ lines.forEach((line, i) => {
5694
+ if (line.includes('TODO:') || line.includes('FIXME:')) {
5695
+ issues.push({
5696
+ file,
5697
+ line: i + 1,
5698
+ issue: `Unresolved TODO/FIXME comment`,
5699
+ severity: 'info',
5700
+ fix: `Complete the TODO or remove if no longer needed`,
5701
+ autoFixable: false,
5702
+ });
5703
+ }
5704
+ });
5705
+ // Check 5: Type 'any' usage
5706
+ lines.forEach((line, i) => {
5707
+ if (line.includes(': any') || line.includes('<any>') || line.includes('as any')) {
5708
+ issues.push({
5709
+ file,
5710
+ line: i + 1,
5711
+ issue: `'any' type used - weakens type safety`,
5712
+ severity: 'warning',
5713
+ fix: `Replace 'any' with a proper type`,
5714
+ autoFixable: false,
5715
+ });
5716
+ }
5717
+ });
5718
+ // Check 6: Missing error handling in async functions
5719
+ if (content.includes('async ') && !content.includes('try {') && !content.includes('.catch(')) {
5720
+ issues.push({
5721
+ file,
5722
+ line: 1,
5723
+ issue: `Async function without error handling`,
5724
+ severity: 'warning',
5725
+ fix: `Add try/catch or .catch() to handle errors`,
5726
+ autoFixable: false,
5727
+ });
5728
+ }
5729
+ // Check 7: API routes without proper error handling
5730
+ if (file.includes('/api/') && file.endsWith('route.ts')) {
5731
+ if (!content.includes('try {') || !content.includes('catch')) {
5732
+ issues.push({
5733
+ file,
5734
+ line: 1,
5735
+ issue: `API route without try/catch error handling`,
5736
+ severity: 'error',
5737
+ fix: `Wrap handler logic in try/catch with proper error response`,
5738
+ autoFixable: false,
5739
+ });
5740
+ }
5741
+ }
5742
+ // Check 8: Missing return type on exported functions
5743
+ const funcMatches = content.matchAll(/export\s+(async\s+)?function\s+(\w+)\s*\([^)]*\)\s*{/g);
5744
+ for (const match of funcMatches) {
5745
+ const fullMatch = match[0];
5746
+ if (!fullMatch.includes(':')) { // No return type
5747
+ issues.push({
5748
+ file,
5749
+ line: 1,
5750
+ issue: `Exported function '${match[2]}' missing return type`,
5751
+ severity: 'info',
5752
+ fix: `Add explicit return type annotation`,
5753
+ autoFixable: false,
5754
+ });
5755
+ }
5756
+ }
5757
+ }
5758
+ catch (err) {
5759
+ issues.push({
5760
+ file,
5761
+ line: 0,
5762
+ issue: `Could not analyze file: ${err instanceof Error ? err.message : 'Unknown error'}`,
5763
+ severity: 'error',
5764
+ fix: `Check file permissions and encoding`,
5765
+ autoFixable: false,
5766
+ });
5767
+ }
5768
+ }
5769
+ // Also check cross-file consistency
5770
+ // Look for imports of the changed files from other files
5771
+ const searchDirs = ['src', 'app', 'lib', 'components', 'services'];
5772
+ const extensions = ['.ts', '.tsx'];
5773
+ const searchDir = (dir) => {
5774
+ if (!fs.existsSync(dir))
5775
+ return;
5776
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
5777
+ for (const entry of entries) {
5778
+ const fullPath = path.join(dir, entry.name);
5779
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
5780
+ searchDir(fullPath);
5781
+ }
5782
+ else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
5783
+ // Check if this file imports any of our changed files
5784
+ const relativePath = path.relative(cwd, fullPath);
5785
+ if (files.includes(relativePath))
5786
+ continue; // Skip files we're already analyzing
5787
+ try {
5788
+ const content = fs.readFileSync(fullPath, 'utf-8');
5789
+ const imports = extractImports(content);
5790
+ for (const imp of imports) {
5791
+ // Check if import matches any of our changed files
5792
+ for (const changedFile of files) {
5793
+ const changedBasename = path.basename(changedFile, path.extname(changedFile));
5794
+ if (imp.path.includes(changedBasename)) {
5795
+ // This file imports a changed file - verify the imports still exist
5796
+ const changedFullPath = path.join(cwd, changedFile);
5797
+ if (fs.existsSync(changedFullPath)) {
5798
+ const changedContent = fs.readFileSync(changedFullPath, 'utf-8');
5799
+ const exports = extractExports(changedContent);
5800
+ for (const name of imp.names) {
5801
+ if (!exports.includes(name) && name !== 'default') {
5802
+ issues.push({
5803
+ file: relativePath,
5804
+ line: imp.line,
5805
+ issue: `Import '${name}' no longer exported from '${changedFile}'`,
5806
+ severity: 'error',
5807
+ fix: `Update import or add export to '${changedFile}'`,
5808
+ autoFixable: false,
5809
+ });
5810
+ }
5811
+ }
5812
+ }
5813
+ }
5814
+ }
5815
+ }
5816
+ }
5817
+ catch {
5818
+ // Skip files that can't be read
5819
+ }
5820
+ }
5821
+ }
5822
+ };
5823
+ for (const dir of searchDirs) {
5824
+ searchDir(path.join(cwd, dir));
5825
+ }
5826
+ // Build response
5827
+ let response = `# 🛡️ Dependency Guardian Analysis\n\n`;
5828
+ if (changeContext) {
5829
+ response += `**Context:** ${changeContext}\n\n`;
5830
+ }
5831
+ response += `**Files Analyzed:** ${analyzed.length}\n`;
5832
+ response += `**Issues Found:** ${issues.length}\n\n`;
5833
+ if (issues.length === 0) {
5834
+ response += `## ✅ All Clear!\n\n`;
5835
+ response += `No consistency issues detected. Code is coherent.\n`;
5836
+ }
5837
+ else {
5838
+ const errors = issues.filter(i => i.severity === 'error');
5839
+ const warnings = issues.filter(i => i.severity === 'warning');
5840
+ const infos = issues.filter(i => i.severity === 'info');
5841
+ const autoFixable = issues.filter(i => i.autoFixable);
5842
+ response += `| Severity | Count |\n`;
5843
+ response += `|----------|-------|\n`;
5844
+ response += `| 🔴 Errors | ${errors.length} |\n`;
5845
+ response += `| 🟡 Warnings | ${warnings.length} |\n`;
5846
+ response += `| 🔵 Info | ${infos.length} |\n`;
5847
+ response += `| 🔧 Auto-fixable | ${autoFixable.length} |\n\n`;
5848
+ if (errors.length > 0) {
5849
+ response += `## 🔴 Errors (Must Fix)\n\n`;
5850
+ for (const issue of errors) {
5851
+ response += `### \`${issue.file}:${issue.line}\`\n`;
5852
+ response += `**Issue:** ${issue.issue}\n`;
5853
+ response += `**Fix:** ${issue.fix}\n\n`;
5854
+ }
5855
+ }
5856
+ if (warnings.length > 0) {
5857
+ response += `## 🟡 Warnings (Should Fix)\n\n`;
5858
+ for (const issue of warnings) {
5859
+ response += `- \`${issue.file}:${issue.line}\` - ${issue.issue}${issue.autoFixable ? ' 🔧' : ''}\n`;
5860
+ }
5861
+ response += `\n`;
5862
+ }
5863
+ if (infos.length > 0) {
5864
+ response += `## 🔵 Info (Consider)\n\n`;
5865
+ for (const issue of infos) {
5866
+ response += `- \`${issue.file}:${issue.line}\` - ${issue.issue}\n`;
5867
+ }
5868
+ response += `\n`;
5869
+ }
5870
+ if (autoFixable.length > 0) {
5871
+ response += `---\n\n`;
5872
+ response += `**${autoFixable.length} issues can be auto-fixed.** Run \`guardian_heal\` to fix them automatically.\n`;
5873
+ }
5874
+ }
5875
+ // Store issues for healing
5876
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
5877
+ try {
5878
+ fs.mkdirSync(path.dirname(guardianStatePath), { recursive: true });
5879
+ fs.writeFileSync(guardianStatePath, JSON.stringify({
5880
+ lastAnalysis: new Date().toISOString(),
5881
+ filesAnalyzed: analyzed,
5882
+ issues,
5883
+ changeContext,
5884
+ }, null, 2));
5885
+ }
5886
+ catch {
5887
+ // Ignore write errors
5888
+ }
5889
+ return { content: [{ type: 'text', text: response }] };
5890
+ }
5891
+ /**
5892
+ * Guardian Heal - Auto-fixes issues found by guardian_analyze
5893
+ */
5894
+ handleGuardianHeal(args) {
5895
+ const { autoFix = true } = args;
5896
+ const cwd = process.cwd();
5897
+ // Load issues from state if not provided
5898
+ let issues = args.issues;
5899
+ if (!issues || issues.length === 0) {
5900
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
5901
+ if (fs.existsSync(guardianStatePath)) {
5902
+ try {
5903
+ const state = JSON.parse(fs.readFileSync(guardianStatePath, 'utf-8'));
5904
+ issues = state.issues?.filter((i) => i.autoFixable) || [];
5905
+ }
5906
+ catch {
5907
+ issues = [];
5908
+ }
5909
+ }
5910
+ }
5911
+ if (issues.length === 0) {
5912
+ return {
5913
+ content: [{
5914
+ type: 'text',
5915
+ text: `# 🛡️ Guardian Heal\n\n✅ No issues to fix. Run \`guardian_analyze\` first to detect issues.`
5916
+ }]
5917
+ };
5918
+ }
5919
+ let response = `# 🛡️ Guardian Heal\n\n`;
5920
+ const fixed = [];
5921
+ const failed = [];
5922
+ for (const issue of issues) {
5923
+ const fullPath = path.join(cwd, issue.file);
5924
+ if (!fs.existsSync(fullPath)) {
5925
+ failed.push(`${issue.file}: File not found`);
5926
+ continue;
5927
+ }
5928
+ try {
5929
+ let content = fs.readFileSync(fullPath, 'utf-8');
5930
+ let modified = false;
5931
+ // Fix: Remove unused imports
5932
+ if (issue.issue.includes('Unused import')) {
5933
+ const match = issue.issue.match(/Unused import: '(\w+)'/);
5934
+ if (match) {
5935
+ const name = match[1];
5936
+ // Remove from named imports
5937
+ content = content.replace(new RegExp(`\\b${name}\\b,?\\s*`, 'g'), (m, offset) => {
5938
+ // Only replace in import statements
5939
+ const lineStart = content.lastIndexOf('\n', offset) + 1;
5940
+ const line = content.substring(lineStart, content.indexOf('\n', offset));
5941
+ if (line.includes('import')) {
5942
+ modified = true;
5943
+ return '';
5944
+ }
5945
+ return m;
5946
+ });
5947
+ }
5948
+ }
5949
+ // Fix: Remove console.log
5950
+ if (issue.issue.includes('console.log')) {
5951
+ const lines = content.split('\n');
5952
+ const lineIndex = parseInt(issue.issue.match(/line (\d+)/)?.[1] || '0') - 1;
5953
+ if (lineIndex >= 0 && lines[lineIndex]?.includes('console.log')) {
5954
+ lines.splice(lineIndex, 1);
5955
+ content = lines.join('\n');
5956
+ modified = true;
5957
+ }
5958
+ }
5959
+ if (modified && autoFix) {
5960
+ fs.writeFileSync(fullPath, content);
5961
+ fixed.push(issue.file);
5962
+ }
5963
+ else if (!modified) {
5964
+ failed.push(`${issue.file}: Could not auto-fix "${issue.issue}"`);
5965
+ }
5966
+ }
5967
+ catch (err) {
5968
+ failed.push(`${issue.file}: ${err instanceof Error ? err.message : 'Unknown error'}`);
5969
+ }
5970
+ }
5971
+ if (fixed.length > 0) {
5972
+ response += `## ✅ Fixed (${fixed.length})\n\n`;
5973
+ for (const file of fixed) {
5974
+ response += `- \`${file}\`\n`;
5975
+ }
5976
+ response += `\n`;
5977
+ }
5978
+ if (failed.length > 0) {
5979
+ response += `## ⚠️ Could Not Auto-Fix (${failed.length})\n\n`;
5980
+ for (const msg of failed) {
5981
+ response += `- ${msg}\n`;
5982
+ }
5983
+ response += `\nThese require manual intervention.\n`;
5984
+ }
5985
+ if (fixed.length > 0) {
5986
+ response += `\n---\n\nRun \`guardian_verify\` to confirm all issues are resolved.\n`;
5987
+ }
5988
+ return { content: [{ type: 'text', text: response }] };
5989
+ }
5990
+ /**
5991
+ * Guardian Verify - Verifies codebase coherence (TypeScript check, imports, etc.)
5992
+ */
5993
+ handleGuardianVerify(args) {
5994
+ const { deep = false } = args;
5995
+ const cwd = process.cwd();
5996
+ let response = `# 🛡️ Guardian Verify\n\n`;
5997
+ const checks = [];
5998
+ // Check 1: TypeScript compilation
5999
+ try {
6000
+ (0, child_process_1.execSync)('npx tsc --noEmit', { cwd, stdio: 'pipe', timeout: 60000 });
6001
+ checks.push({ name: 'TypeScript', status: 'pass', details: 'No type errors' });
6002
+ }
6003
+ catch (err) {
6004
+ const output = err instanceof Error && 'stdout' in err ? String(err.stdout) : 'Unknown error';
6005
+ const errorCount = (output.match(/error TS/g) || []).length;
6006
+ checks.push({
6007
+ name: 'TypeScript',
6008
+ status: 'fail',
6009
+ details: `${errorCount} type error${errorCount !== 1 ? 's' : ''} found`
6010
+ });
6011
+ }
6012
+ // Check 2: Package.json exists and is valid
6013
+ const pkgPath = path.join(cwd, 'package.json');
6014
+ if (fs.existsSync(pkgPath)) {
6015
+ try {
6016
+ JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
6017
+ checks.push({ name: 'package.json', status: 'pass', details: 'Valid JSON' });
6018
+ }
6019
+ catch {
6020
+ checks.push({ name: 'package.json', status: 'fail', details: 'Invalid JSON' });
6021
+ }
6022
+ }
6023
+ else {
6024
+ checks.push({ name: 'package.json', status: 'fail', details: 'File not found' });
6025
+ }
6026
+ // Check 3: Environment variables
6027
+ const envExample = path.join(cwd, '.env.example');
6028
+ const envLocal = path.join(cwd, '.env.local');
6029
+ const env = path.join(cwd, '.env');
6030
+ if (fs.existsSync(envExample)) {
6031
+ checks.push({ name: '.env.example', status: 'pass', details: 'Exists for documentation' });
6032
+ }
6033
+ else {
6034
+ checks.push({ name: '.env.example', status: 'warn', details: 'Missing - should document required env vars' });
6035
+ }
6036
+ if (fs.existsSync(envLocal) || fs.existsSync(env)) {
6037
+ checks.push({ name: '.env', status: 'pass', details: 'Environment configured' });
6038
+ }
6039
+ else {
6040
+ checks.push({ name: '.env', status: 'warn', details: 'No .env file - may need configuration' });
6041
+ }
6042
+ // Check 4: Git ignore includes sensitive files
6043
+ const gitignore = path.join(cwd, '.gitignore');
6044
+ if (fs.existsSync(gitignore)) {
6045
+ const content = fs.readFileSync(gitignore, 'utf-8');
6046
+ if (content.includes('.env') && content.includes('node_modules')) {
6047
+ checks.push({ name: '.gitignore', status: 'pass', details: 'Properly configured' });
6048
+ }
6049
+ else {
6050
+ checks.push({ name: '.gitignore', status: 'warn', details: 'May be missing important entries' });
6051
+ }
6052
+ }
6053
+ else {
6054
+ checks.push({ name: '.gitignore', status: 'warn', details: 'No .gitignore file' });
6055
+ }
6056
+ // Check 5: Deep mode - check all imports resolve
6057
+ if (deep) {
6058
+ let brokenImports = 0;
6059
+ const searchDirs = ['src', 'app'];
6060
+ const checkDir = (dir) => {
6061
+ if (!fs.existsSync(dir))
6062
+ return;
6063
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
6064
+ for (const entry of entries) {
6065
+ const fullPath = path.join(dir, entry.name);
6066
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
6067
+ checkDir(fullPath);
6068
+ }
6069
+ else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
6070
+ try {
6071
+ const content = fs.readFileSync(fullPath, 'utf-8');
6072
+ const importMatches = content.matchAll(/from\s+['"]([^'"]+)['"]/g);
6073
+ for (const match of importMatches) {
6074
+ const importPath = match[1];
6075
+ if (importPath.startsWith('.') || importPath.startsWith('@/')) {
6076
+ // Check if resolves
6077
+ let resolved;
6078
+ if (importPath.startsWith('@/')) {
6079
+ resolved = path.join(cwd, 'src', importPath.slice(2));
6080
+ }
6081
+ else {
6082
+ resolved = path.resolve(path.dirname(fullPath), importPath);
6083
+ }
6084
+ const exists = fs.existsSync(resolved + '.ts') ||
6085
+ fs.existsSync(resolved + '.tsx') ||
6086
+ fs.existsSync(path.join(resolved, 'index.ts')) ||
6087
+ fs.existsSync(path.join(resolved, 'index.tsx'));
6088
+ if (!exists) {
6089
+ brokenImports++;
6090
+ }
6091
+ }
6092
+ }
6093
+ }
6094
+ catch {
6095
+ // Skip unreadable files
6096
+ }
6097
+ }
6098
+ }
6099
+ };
6100
+ for (const dir of searchDirs) {
6101
+ checkDir(path.join(cwd, dir));
6102
+ }
6103
+ if (brokenImports === 0) {
6104
+ checks.push({ name: 'Import Resolution', status: 'pass', details: 'All imports resolve' });
6105
+ }
6106
+ else {
6107
+ checks.push({ name: 'Import Resolution', status: 'fail', details: `${brokenImports} broken import${brokenImports !== 1 ? 's' : ''}` });
6108
+ }
6109
+ }
6110
+ // Build response
6111
+ const passed = checks.filter(c => c.status === 'pass').length;
6112
+ const failed = checks.filter(c => c.status === 'fail').length;
6113
+ const warned = checks.filter(c => c.status === 'warn').length;
6114
+ const overallStatus = failed > 0 ? '❌ FAIL' : warned > 0 ? '⚠️ WARN' : '✅ PASS';
6115
+ response += `## Overall: ${overallStatus}\n\n`;
6116
+ response += `| Check | Status | Details |\n`;
6117
+ response += `|-------|--------|--------|\n`;
6118
+ for (const check of checks) {
6119
+ const icon = check.status === 'pass' ? '✅' : check.status === 'fail' ? '❌' : '⚠️';
6120
+ response += `| ${check.name} | ${icon} | ${check.details} |\n`;
6121
+ }
6122
+ response += `\n**Summary:** ${passed} passed, ${failed} failed, ${warned} warnings\n`;
6123
+ if (failed > 0) {
6124
+ response += `\n---\n\n⚠️ **Action Required:** Fix the failing checks before deploying.\n`;
6125
+ }
6126
+ // Update guardian state
6127
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
6128
+ try {
6129
+ let state = {};
6130
+ if (fs.existsSync(guardianStatePath)) {
6131
+ state = JSON.parse(fs.readFileSync(guardianStatePath, 'utf-8'));
6132
+ }
6133
+ state.lastVerification = new Date().toISOString();
6134
+ state.verificationResult = { passed, failed, warned, checks };
6135
+ fs.mkdirSync(path.dirname(guardianStatePath), { recursive: true });
6136
+ fs.writeFileSync(guardianStatePath, JSON.stringify(state, null, 2));
6137
+ }
6138
+ catch {
6139
+ // Ignore write errors
6140
+ }
6141
+ return { content: [{ type: 'text', text: response }] };
6142
+ }
6143
+ /**
6144
+ * Guardian Status - Shows overall project coherence health
6145
+ */
6146
+ handleGuardianStatus() {
6147
+ const cwd = process.cwd();
6148
+ const guardianStatePath = path.join(cwd, '.codebakers', 'guardian-state.json');
6149
+ let response = `# 🛡️ Dependency Guardian Status\n\n`;
6150
+ // Check if guardian state exists
6151
+ if (!fs.existsSync(guardianStatePath)) {
6152
+ response += `## ⚪ Not Initialized\n\n`;
6153
+ response += `Guardian hasn't analyzed this project yet.\n\n`;
6154
+ response += `Run \`guardian_analyze\` on your files to start tracking coherence.\n`;
6155
+ return { content: [{ type: 'text', text: response }] };
6156
+ }
6157
+ try {
6158
+ const state = JSON.parse(fs.readFileSync(guardianStatePath, 'utf-8'));
6159
+ // Last analysis
6160
+ if (state.lastAnalysis) {
6161
+ const analysisTime = new Date(state.lastAnalysis);
6162
+ const hoursAgo = Math.round((Date.now() - analysisTime.getTime()) / (1000 * 60 * 60));
6163
+ response += `**Last Analysis:** ${hoursAgo < 1 ? 'Just now' : `${hoursAgo} hour${hoursAgo !== 1 ? 's' : ''} ago`}\n`;
6164
+ }
6165
+ // Last verification
6166
+ if (state.lastVerification) {
6167
+ const verifyTime = new Date(state.lastVerification);
6168
+ const hoursAgo = Math.round((Date.now() - verifyTime.getTime()) / (1000 * 60 * 60));
6169
+ response += `**Last Verification:** ${hoursAgo < 1 ? 'Just now' : `${hoursAgo} hour${hoursAgo !== 1 ? 's' : ''} ago`}\n`;
6170
+ }
6171
+ response += `\n`;
6172
+ // Issue summary
6173
+ if (state.issues && state.issues.length > 0) {
6174
+ const errors = state.issues.filter((i) => i.severity === 'error').length;
6175
+ const warnings = state.issues.filter((i) => i.severity === 'warning').length;
6176
+ if (errors > 0) {
6177
+ response += `## ❌ Issues Found\n\n`;
6178
+ response += `- 🔴 ${errors} error${errors !== 1 ? 's' : ''}\n`;
6179
+ response += `- 🟡 ${warnings} warning${warnings !== 1 ? 's' : ''}\n\n`;
6180
+ response += `Run \`guardian_heal\` to auto-fix what's possible.\n`;
6181
+ }
6182
+ else if (warnings > 0) {
6183
+ response += `## ⚠️ Warnings\n\n`;
6184
+ response += `- 🟡 ${warnings} warning${warnings !== 1 ? 's' : ''} (no critical errors)\n\n`;
6185
+ }
6186
+ else {
6187
+ response += `## ✅ All Clear\n\n`;
6188
+ response += `No known issues in the codebase.\n`;
6189
+ }
6190
+ }
6191
+ else {
6192
+ response += `## ✅ No Issues\n\n`;
6193
+ response += `Last analysis found no problems.\n`;
6194
+ }
6195
+ // Verification result
6196
+ if (state.verificationResult) {
6197
+ const { passed, failed, warned } = state.verificationResult;
6198
+ response += `\n### Verification Summary\n`;
6199
+ response += `✅ ${passed} checks passed\n`;
6200
+ if (failed > 0)
6201
+ response += `❌ ${failed} checks failed\n`;
6202
+ if (warned > 0)
6203
+ response += `⚠️ ${warned} warnings\n`;
6204
+ }
6205
+ // Health score
6206
+ const issueCount = state.issues?.length || 0;
6207
+ const errorCount = state.issues?.filter((i) => i.severity === 'error').length || 0;
6208
+ const verifyFailed = state.verificationResult?.failed || 0;
6209
+ let healthScore = 100;
6210
+ healthScore -= errorCount * 20;
6211
+ healthScore -= verifyFailed * 15;
6212
+ healthScore -= (issueCount - errorCount) * 5;
6213
+ healthScore = Math.max(0, Math.min(100, healthScore));
6214
+ response += `\n---\n\n`;
6215
+ response += `## Health Score: ${healthScore}/100 ${healthScore >= 80 ? '🟢' : healthScore >= 50 ? '🟡' : '🔴'}\n`;
6216
+ }
6217
+ catch {
6218
+ response += `## ⚠️ State Corrupted\n\n`;
6219
+ response += `Could not read guardian state. Run \`guardian_analyze\` to rebuild.\n`;
6220
+ }
6221
+ return { content: [{ type: 'text', text: response }] };
6222
+ }
6223
+ /**
6224
+ * Ripple Check - Detect all files affected by a change to a type/schema/function
6225
+ * Searches the codebase for imports and usages of the entity
6226
+ */
6227
+ handleRippleCheck(args) {
6228
+ const { entityName, changeType, changeDescription } = args;
6229
+ const cwd = process.cwd();
6230
+ let response = `# 🌊 Ripple Check: \`${entityName}\`\n\n`;
6231
+ if (changeType || changeDescription) {
6232
+ response += `**Change:** ${changeDescription || changeType || 'Unknown'}\n\n`;
6233
+ }
6234
+ // Define search directories
6235
+ const searchDirs = ['src', 'app', 'lib', 'components', 'services', 'types', 'utils', 'hooks'];
6236
+ const extensions = ['.ts', '.tsx', '.js', '.jsx'];
6237
+ const matches = [];
6238
+ let definitionFile = null;
6239
+ // Helper to search a directory recursively
6240
+ const searchDir = (dir) => {
6241
+ if (!fs.existsSync(dir))
6242
+ return;
6243
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
6244
+ for (const entry of entries) {
6245
+ const fullPath = path.join(dir, entry.name);
6246
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
6247
+ searchDir(fullPath);
6248
+ }
6249
+ else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
6250
+ try {
6251
+ const content = fs.readFileSync(fullPath, 'utf-8');
6252
+ const lines = content.split('\n');
6253
+ // Check if file contains the entity
6254
+ if (!content.includes(entityName))
6255
+ continue;
6256
+ const relativePath = path.relative(cwd, fullPath);
6257
+ const match = {
6258
+ file: relativePath,
6259
+ usageLines: [],
6260
+ lineNumbers: [],
6261
+ isDefinition: false,
6262
+ };
6263
+ lines.forEach((line, i) => {
6264
+ const lineNum = i + 1;
6265
+ // Check for import statements
6266
+ if (line.includes('import') && line.includes(entityName)) {
6267
+ match.importLine = line.trim();
6268
+ }
6269
+ // Check for type/interface/function/const definition
6270
+ const defPatterns = [
6271
+ `type ${entityName}`,
6272
+ `interface ${entityName}`,
6273
+ `function ${entityName}`,
6274
+ `const ${entityName}`,
6275
+ `class ${entityName}`,
6276
+ `export type ${entityName}`,
6277
+ `export interface ${entityName}`,
6278
+ `export function ${entityName}`,
6279
+ `export const ${entityName}`,
6280
+ `export class ${entityName}`,
6281
+ ];
6282
+ if (defPatterns.some(p => line.includes(p))) {
6283
+ match.isDefinition = true;
6284
+ definitionFile = relativePath;
6285
+ }
6286
+ // Check for usage (not just import or definition)
6287
+ if (line.includes(entityName) && !line.includes('import ')) {
6288
+ match.usageLines.push(line.trim().substring(0, 100));
6289
+ match.lineNumbers.push(lineNum);
6290
+ }
6291
+ });
6292
+ // Only add if there are usages (not just definition)
6293
+ if (match.usageLines.length > 0) {
6294
+ matches.push(match);
6295
+ }
6296
+ }
6297
+ catch {
6298
+ // Skip files that can't be read
6299
+ }
6300
+ }
6301
+ }
6302
+ };
6303
+ // Search all directories
6304
+ for (const dir of searchDirs) {
6305
+ searchDir(path.join(cwd, dir));
6306
+ }
6307
+ // Sort: definition first, then by number of usages
6308
+ matches.sort((a, b) => {
6309
+ if (a.isDefinition && !b.isDefinition)
6310
+ return -1;
6311
+ if (!a.isDefinition && b.isDefinition)
6312
+ return 1;
6313
+ return b.usageLines.length - a.usageLines.length;
6314
+ });
6315
+ // Generate report
6316
+ if (matches.length === 0) {
6317
+ response += `## ✅ No usages found\n\n`;
6318
+ response += `The entity \`${entityName}\` was not found in the codebase, or has no usages.\n`;
6319
+ response += `This change is safe to make without ripple effects.\n`;
6320
+ }
6321
+ else {
6322
+ // Summary
6323
+ response += `## 📊 Impact Summary\n\n`;
6324
+ response += `| Metric | Count |\n`;
6325
+ response += `|--------|-------|\n`;
6326
+ response += `| Files affected | ${matches.length} |\n`;
6327
+ response += `| Total usages | ${matches.reduce((sum, m) => sum + m.usageLines.length, 0)} |\n`;
6328
+ if (definitionFile) {
6329
+ response += `| Definition | \`${definitionFile}\` |\n`;
6330
+ }
6331
+ response += `\n`;
6332
+ // Categorize by impact level
6333
+ const highImpact = matches.filter(m => m.usageLines.length >= 5);
6334
+ const mediumImpact = matches.filter(m => m.usageLines.length >= 2 && m.usageLines.length < 5);
6335
+ const lowImpact = matches.filter(m => m.usageLines.length === 1);
6336
+ if (highImpact.length > 0) {
6337
+ response += `## 🔴 High Impact (5+ usages)\n\n`;
6338
+ for (const m of highImpact) {
6339
+ response += `### \`${m.file}\`${m.isDefinition ? ' (DEFINITION)' : ''}\n`;
6340
+ if (m.importLine)
6341
+ response += `Import: \`${m.importLine}\`\n`;
6342
+ response += `Usages (${m.usageLines.length}):\n`;
6343
+ for (let i = 0; i < Math.min(5, m.usageLines.length); i++) {
6344
+ response += `- Line ${m.lineNumbers[i]}: \`${m.usageLines[i].substring(0, 80)}${m.usageLines[i].length > 80 ? '...' : ''}\`\n`;
6345
+ }
6346
+ if (m.usageLines.length > 5) {
6347
+ response += `- ... and ${m.usageLines.length - 5} more\n`;
6348
+ }
6349
+ response += `\n`;
6350
+ }
6351
+ }
6352
+ if (mediumImpact.length > 0) {
6353
+ response += `## 🟡 Medium Impact (2-4 usages)\n\n`;
6354
+ for (const m of mediumImpact) {
6355
+ response += `- \`${m.file}\` (${m.usageLines.length} usages)\n`;
6356
+ }
6357
+ response += `\n`;
6358
+ }
6359
+ if (lowImpact.length > 0) {
6360
+ response += `## 🟢 Low Impact (1 usage)\n\n`;
6361
+ for (const m of lowImpact) {
6362
+ response += `- \`${m.file}\`\n`;
6363
+ }
6364
+ response += `\n`;
6365
+ }
6366
+ // Recommendations
6367
+ response += `## 💡 Recommendations\n\n`;
6368
+ if (changeType === 'added_field') {
6369
+ response += `**Adding a field is usually safe.** Optional fields won't break existing code.\n`;
6370
+ response += `If the field is required, update these files to provide the new field.\n`;
6371
+ }
6372
+ else if (changeType === 'removed_field') {
6373
+ response += `**Removing a field is breaking.** Check each file above to remove usages of the deleted field.\n`;
6374
+ response += `Run \`npx tsc --noEmit\` after making changes to find remaining issues.\n`;
6375
+ }
6376
+ else if (changeType === 'renamed') {
6377
+ response += `**Renaming is breaking.** Update all files above to use the new name.\n`;
6378
+ response += `Consider using IDE "Rename Symbol" for safer refactoring.\n`;
6379
+ }
6380
+ else if (changeType === 'type_changed') {
6381
+ response += `**Type changes may break.** Review each usage to ensure compatibility.\n`;
6382
+ response += `Run \`npx tsc --noEmit\` to find type errors.\n`;
6383
+ }
6384
+ else if (changeType === 'signature_changed') {
6385
+ response += `**Signature changes are breaking.** Update all call sites with new parameters.\n`;
6386
+ }
6387
+ else {
6388
+ response += `1. Review the high-impact files first\n`;
6389
+ response += `2. Run \`npx tsc --noEmit\` after changes to catch type errors\n`;
6390
+ response += `3. Run tests to verify functionality\n`;
6391
+ }
6392
+ response += `\n---\n`;
6393
+ response += `*Run this check again after making changes to verify all ripples are addressed.*\n`;
6394
+ }
6395
+ return { content: [{ type: 'text', text: response }] };
6396
+ }
5413
6397
  async run() {
5414
6398
  const transport = new stdio_js_1.StdioServerTransport();
5415
6399
  await this.server.connect(transport);