@codebakers/cli 3.3.6 → 3.3.8

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