@codebakers/cli 2.2.0 โ†’ 2.4.0

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.
@@ -453,6 +453,125 @@ class CodeBakersServer {
453
453
  },
454
454
  },
455
455
  },
456
+ {
457
+ name: 'design',
458
+ description: 'Clone and implement designs from mockups, screenshots, or website references. Analyzes visual designs and generates pixel-perfect matching code with extracted design tokens (colors, typography, spacing). Use when user says "clone this design", "make it look like...", or "copy this UI".',
459
+ inputSchema: {
460
+ type: 'object',
461
+ properties: {
462
+ source: {
463
+ type: 'string',
464
+ description: 'Path to mockup image, folder of images, URL to clone, or reference style (e.g., "./mockups", "https://linear.app", "like Notion")',
465
+ },
466
+ outputDir: {
467
+ type: 'string',
468
+ description: 'Directory to output generated components (default: src/components)',
469
+ },
470
+ },
471
+ required: ['source'],
472
+ },
473
+ },
474
+ {
475
+ name: 'upgrade',
476
+ description: 'Upgrade an existing project to CodeBakers patterns WITHOUT changing the user\'s tech stack. Preserves their existing ORM (Prisma/Drizzle), auth (NextAuth/Clerk), UI library (Chakra/MUI), etc. Only upgrades code quality patterns like error handling, validation, tests, and security. Use when user says "upgrade this project", "improve my code", or "make this production ready".',
477
+ inputSchema: {
478
+ type: 'object',
479
+ properties: {
480
+ areas: {
481
+ type: 'array',
482
+ items: { type: 'string' },
483
+ description: 'Specific areas to upgrade: "api", "components", "testing", "security", "all" (default: all)',
484
+ },
485
+ severity: {
486
+ type: 'string',
487
+ enum: ['critical', 'high', 'medium', 'low', 'all'],
488
+ description: 'Filter upgrades by severity (default: all)',
489
+ },
490
+ dryRun: {
491
+ type: 'boolean',
492
+ description: 'Show what would be upgraded without making changes (default: false)',
493
+ },
494
+ },
495
+ },
496
+ },
497
+ {
498
+ name: 'project_status',
499
+ description: 'Show project build progress, completed features, and what\'s next. Different from get_status which shows CodeBakers connection status. Use when user asks "where am I?", "what\'s built?", "show progress", or "what\'s next?".',
500
+ inputSchema: {
501
+ type: 'object',
502
+ properties: {},
503
+ },
504
+ },
505
+ {
506
+ name: 'run_tests',
507
+ description: 'Run the project test suite (npm test or configured test command). Use after completing a feature to verify everything works. Returns test results with pass/fail status.',
508
+ inputSchema: {
509
+ type: 'object',
510
+ properties: {
511
+ filter: {
512
+ type: 'string',
513
+ description: 'Optional filter to run specific tests (passed to test runner)',
514
+ },
515
+ watch: {
516
+ type: 'boolean',
517
+ description: 'Run in watch mode (default: false)',
518
+ },
519
+ },
520
+ },
521
+ },
522
+ {
523
+ name: 'report_pattern_gap',
524
+ description: 'Report when a user request cannot be fully handled by existing patterns. This helps improve CodeBakers by tracking what patterns are missing. The AI should automatically call this when it encounters something outside pattern coverage.',
525
+ inputSchema: {
526
+ type: 'object',
527
+ properties: {
528
+ category: {
529
+ type: 'string',
530
+ description: 'Category of the gap (e.g., "third-party-apis", "mobile", "blockchain", "iot")',
531
+ },
532
+ request: {
533
+ type: 'string',
534
+ description: 'What the user asked for',
535
+ },
536
+ context: {
537
+ type: 'string',
538
+ description: 'Additional context about what was needed',
539
+ },
540
+ handledWith: {
541
+ type: 'string',
542
+ description: 'Which existing patterns were used as fallback',
543
+ },
544
+ wasSuccessful: {
545
+ type: 'boolean',
546
+ description: 'Whether the request was handled successfully despite the gap',
547
+ },
548
+ },
549
+ required: ['category', 'request'],
550
+ },
551
+ },
552
+ {
553
+ name: 'track_analytics',
554
+ description: 'Track CLI usage analytics for improving smart triggers and recommendations. Called automatically by the system for key events.',
555
+ inputSchema: {
556
+ type: 'object',
557
+ properties: {
558
+ eventType: {
559
+ type: 'string',
560
+ enum: ['trigger_fired', 'trigger_accepted', 'trigger_dismissed', 'topic_learned', 'command_used', 'pattern_fetched', 'build_started', 'build_completed', 'feature_added', 'audit_run', 'design_cloned'],
561
+ description: 'Type of event to track',
562
+ },
563
+ eventData: {
564
+ type: 'object',
565
+ description: 'Additional data specific to the event',
566
+ },
567
+ projectHash: {
568
+ type: 'string',
569
+ description: 'Hash of project path for grouping analytics',
570
+ },
571
+ },
572
+ required: ['eventType'],
573
+ },
574
+ },
456
575
  ],
457
576
  }));
458
577
  // Handle tool calls
@@ -488,6 +607,18 @@ class CodeBakersServer {
488
607
  return this.handleRunAudit();
489
608
  case 'heal':
490
609
  return this.handleHeal(args);
610
+ case 'design':
611
+ return this.handleDesign(args);
612
+ case 'upgrade':
613
+ return this.handleUpgrade(args);
614
+ case 'project_status':
615
+ return this.handleProjectStatus();
616
+ case 'run_tests':
617
+ return this.handleRunTests(args);
618
+ case 'report_pattern_gap':
619
+ return this.handleReportPatternGap(args);
620
+ case 'track_analytics':
621
+ return this.handleTrackAnalytics(args);
491
622
  default:
492
623
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
493
624
  }
@@ -1277,7 +1408,7 @@ phase: development
1277
1408
  ## Connection Status
1278
1409
  - **MCP Server:** Running
1279
1410
  - **API Connected:** Yes
1280
- - **Version:** 1.5.0
1411
+ - **Version:** 2.2.0
1281
1412
 
1282
1413
  ## Current Settings
1283
1414
  - **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
@@ -1289,6 +1420,9 @@ phase: development
1289
1420
  - ๐Ÿ” **search_patterns** - Search for specific guidance
1290
1421
  - ๐Ÿ—๏ธ **scaffold_project** - Create new projects
1291
1422
  - โš™๏ธ **init_project** - Add patterns to existing projects
1423
+ - ๐ŸŽจ **design** - Clone designs from mockups/websites
1424
+ - โฌ†๏ธ **upgrade** - Upgrade project patterns (preserves your stack)
1425
+ - ๐Ÿ“Š **project_status** - Show build progress
1292
1426
 
1293
1427
  ## How to Use
1294
1428
  Just describe what you want to build! I'll automatically:
@@ -1307,6 +1441,471 @@ Just describe what you want to build! I'll automatically:
1307
1441
  }],
1308
1442
  };
1309
1443
  }
1444
+ async handleDesign(args) {
1445
+ const { source, outputDir = 'src/components' } = args;
1446
+ const cwd = process.cwd();
1447
+ // Detect source type
1448
+ let sourceType = 'reference';
1449
+ if (source.startsWith('http://') || source.startsWith('https://')) {
1450
+ sourceType = 'url';
1451
+ }
1452
+ else if (source.startsWith('./') || source.startsWith('/') || source.includes('\\')) {
1453
+ const fullPath = path.join(cwd, source);
1454
+ if (fs.existsSync(fullPath)) {
1455
+ const stat = fs.statSync(fullPath);
1456
+ sourceType = stat.isDirectory() ? 'folder' : 'file';
1457
+ }
1458
+ }
1459
+ else if (source.toLowerCase().startsWith('like ')) {
1460
+ sourceType = 'reference';
1461
+ }
1462
+ // Fetch design pattern from API
1463
+ const patternResult = await this.fetchPatterns(['33-design-clone', '09-design']);
1464
+ let response = `# ๐ŸŽจ Design Clone Tool\n\n`;
1465
+ response += `**Source:** ${source}\n`;
1466
+ response += `**Type:** ${sourceType}\n`;
1467
+ response += `**Output:** ${outputDir}\n\n`;
1468
+ switch (sourceType) {
1469
+ case 'folder':
1470
+ const folderPath = path.join(cwd, source);
1471
+ const images = fs.readdirSync(folderPath).filter(f => /\.(png|jpg|jpeg|webp|svg|gif)$/i.test(f));
1472
+ response += `## Found ${images.length} Design Files\n\n`;
1473
+ images.forEach(img => {
1474
+ response += `- ${img}\n`;
1475
+ });
1476
+ response += `\n## Next Steps\n\n`;
1477
+ response += `1. I'll analyze each image for design tokens\n`;
1478
+ response += `2. Extract colors, typography, spacing\n`;
1479
+ response += `3. Generate Tailwind config with your design system\n`;
1480
+ response += `4. Create matching components\n\n`;
1481
+ response += `**Note:** For best results, provide screenshots of:\n`;
1482
+ response += `- Color palette / brand guidelines\n`;
1483
+ response += `- Typography samples\n`;
1484
+ response += `- Key UI components (buttons, cards, forms)\n`;
1485
+ break;
1486
+ case 'file':
1487
+ response += `## Analyzing Single Design\n\n`;
1488
+ response += `I'll extract design tokens from this image and generate matching components.\n\n`;
1489
+ response += `**Tip:** For a complete design system, provide a folder with multiple mockups.\n`;
1490
+ break;
1491
+ case 'url':
1492
+ response += `## Cloning Website Design\n\n`;
1493
+ response += `I'll analyze the visual design at ${source} and extract:\n`;
1494
+ response += `- Color palette\n`;
1495
+ response += `- Typography (fonts, sizes, weights)\n`;
1496
+ response += `- Spacing system\n`;
1497
+ response += `- Component patterns\n\n`;
1498
+ response += `**Note:** This creates inspired-by components, not exact copies.\n`;
1499
+ break;
1500
+ case 'reference':
1501
+ const refStyle = source.replace(/^like\s+/i, '').toLowerCase();
1502
+ const knownStyles = {
1503
+ 'linear': 'Dark theme, purple accents, minimal, clean',
1504
+ 'notion': 'Light theme, black/white, content-focused, lots of whitespace',
1505
+ 'stripe': 'Professional, purple gradients, polished shadows',
1506
+ 'vercel': 'Black/white, developer-focused, geometric',
1507
+ 'github': 'Blue accents, familiar, dev-tool aesthetic',
1508
+ 'figma': 'Purple accents, collaborative, modern',
1509
+ };
1510
+ const matchedStyle = Object.entries(knownStyles).find(([key]) => refStyle.includes(key));
1511
+ if (matchedStyle) {
1512
+ response += `## Reference Style: ${matchedStyle[0]}\n\n`;
1513
+ response += `**Characteristics:** ${matchedStyle[1]}\n\n`;
1514
+ }
1515
+ else {
1516
+ response += `## Reference Style: "${source}"\n\n`;
1517
+ response += `I'll apply a design language inspired by this reference.\n\n`;
1518
+ }
1519
+ response += `I'll generate components matching this aesthetic.\n`;
1520
+ break;
1521
+ }
1522
+ response += `\n---\n\n`;
1523
+ response += `## Design Pattern Loaded\n\n`;
1524
+ response += `The design-clone pattern (33-design-clone) is now active.\n`;
1525
+ response += `Proceed with specific component requests like:\n`;
1526
+ response += `- "Create the navigation bar"\n`;
1527
+ response += `- "Build the hero section"\n`;
1528
+ response += `- "Generate the card components"\n`;
1529
+ return {
1530
+ content: [{
1531
+ type: 'text',
1532
+ text: response,
1533
+ }],
1534
+ };
1535
+ }
1536
+ async handleUpgrade(args) {
1537
+ const { areas = ['all'], severity = 'all', dryRun = false } = args;
1538
+ const context = this.gatherProjectContext();
1539
+ let response = `# โฌ†๏ธ Project Upgrade Analysis\n\n`;
1540
+ // Stack detection
1541
+ response += `## Your Stack (Preserving As-Is)\n\n`;
1542
+ response += `| Layer | Detected | Status |\n`;
1543
+ response += `|-------|----------|--------|\n`;
1544
+ // ORM detection
1545
+ let orm = 'None';
1546
+ if (context.dependencies.includes('drizzle-orm'))
1547
+ orm = 'Drizzle';
1548
+ else if (context.dependencies.includes('@prisma/client'))
1549
+ orm = 'Prisma';
1550
+ else if (context.dependencies.includes('typeorm'))
1551
+ orm = 'TypeORM';
1552
+ else if (context.dependencies.includes('mongoose'))
1553
+ orm = 'Mongoose';
1554
+ response += `| ORM | ${orm} | โœ“ Keeping |\n`;
1555
+ // Auth detection
1556
+ let auth = 'None';
1557
+ if (context.dependencies.includes('@supabase/supabase-js'))
1558
+ auth = 'Supabase';
1559
+ else if (context.dependencies.includes('next-auth'))
1560
+ auth = 'NextAuth';
1561
+ else if (context.dependencies.includes('@clerk/nextjs'))
1562
+ auth = 'Clerk';
1563
+ else if (context.dependencies.includes('firebase'))
1564
+ auth = 'Firebase';
1565
+ response += `| Auth | ${auth} | โœ“ Keeping |\n`;
1566
+ // UI detection
1567
+ response += `| UI | ${context.uiLibrary || 'Tailwind'} | โœ“ Keeping |\n`;
1568
+ // Framework (always Next.js for now)
1569
+ const hasNext = context.dependencies.includes('next');
1570
+ response += `| Framework | ${hasNext ? 'Next.js' : 'Unknown'} | โœ“ Keeping |\n`;
1571
+ response += `\n---\n\n`;
1572
+ // Scan for upgrade opportunities
1573
+ response += `## Upgrade Opportunities\n\n`;
1574
+ const upgrades = [];
1575
+ // Check API routes
1576
+ if (context.existingApiRoutes.length > 0) {
1577
+ upgrades.push({
1578
+ area: 'API Routes',
1579
+ issue: 'Add error handling, validation, rate limiting',
1580
+ severity: 'HIGH',
1581
+ count: context.existingApiRoutes.length,
1582
+ });
1583
+ }
1584
+ // Check components
1585
+ if (context.existingComponents.length > 0) {
1586
+ upgrades.push({
1587
+ area: 'Components',
1588
+ issue: 'Add loading states, error boundaries, accessibility',
1589
+ severity: 'MEDIUM',
1590
+ count: context.existingComponents.length,
1591
+ });
1592
+ }
1593
+ // Check for tests
1594
+ const hasTests = context.dependencies.includes('@playwright/test') ||
1595
+ context.dependencies.includes('jest') ||
1596
+ context.dependencies.includes('vitest');
1597
+ if (!hasTests) {
1598
+ upgrades.push({
1599
+ area: 'Testing',
1600
+ issue: 'No test framework detected',
1601
+ severity: 'HIGH',
1602
+ count: 0,
1603
+ });
1604
+ }
1605
+ // Check for Zod
1606
+ const hasZod = context.dependencies.includes('zod');
1607
+ if (!hasZod && context.existingApiRoutes.length > 0) {
1608
+ upgrades.push({
1609
+ area: 'Validation',
1610
+ issue: 'No Zod validation detected for API routes',
1611
+ severity: 'HIGH',
1612
+ count: context.existingApiRoutes.length,
1613
+ });
1614
+ }
1615
+ // Display upgrades
1616
+ for (const upgrade of upgrades) {
1617
+ const icon = upgrade.severity === 'HIGH' ? '๐Ÿ”ด' :
1618
+ upgrade.severity === 'MEDIUM' ? '๐ŸŸก' : '๐ŸŸข';
1619
+ response += `### ${icon} ${upgrade.area}\n`;
1620
+ response += `- **Issue:** ${upgrade.issue}\n`;
1621
+ if (upgrade.count > 0) {
1622
+ response += `- **Affected:** ${upgrade.count} files\n`;
1623
+ }
1624
+ response += `- **Severity:** ${upgrade.severity}\n\n`;
1625
+ }
1626
+ if (upgrades.length === 0) {
1627
+ response += `โœ… No major upgrade opportunities detected!\n\n`;
1628
+ }
1629
+ // Recommendations
1630
+ response += `---\n\n`;
1631
+ response += `## Recommended Actions\n\n`;
1632
+ if (dryRun) {
1633
+ response += `**(Dry Run Mode - No changes will be made)**\n\n`;
1634
+ }
1635
+ response += `1. Run \`run_audit\` for detailed code quality check\n`;
1636
+ response += `2. Use \`heal\` to auto-fix common issues\n`;
1637
+ response += `3. Request specific upgrades like:\n`;
1638
+ response += ` - "Add Zod validation to my API routes"\n`;
1639
+ response += ` - "Add error boundaries to components"\n`;
1640
+ response += ` - "Set up Playwright testing"\n\n`;
1641
+ response += `---\n\n`;
1642
+ response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
1643
+ return {
1644
+ content: [{
1645
+ type: 'text',
1646
+ text: response,
1647
+ }],
1648
+ };
1649
+ }
1650
+ handleProjectStatus() {
1651
+ const cwd = process.cwd();
1652
+ const context = this.gatherProjectContext();
1653
+ let response = `# ๐Ÿ“Š Project Status\n\n`;
1654
+ response += `**Project:** ${context.projectName}\n\n`;
1655
+ // Check for .codebakers.json state
1656
+ let state = null;
1657
+ try {
1658
+ const statePath = path.join(cwd, '.codebakers.json');
1659
+ if (fs.existsSync(statePath)) {
1660
+ state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
1661
+ }
1662
+ }
1663
+ catch {
1664
+ // No state file
1665
+ }
1666
+ // Check for PRD
1667
+ const prdPath = path.join(cwd, 'PRD.md');
1668
+ const hasPrd = fs.existsSync(prdPath);
1669
+ // Check for PROJECT-STATE.md
1670
+ const projectStatePath = path.join(cwd, 'PROJECT-STATE.md');
1671
+ const hasProjectState = fs.existsSync(projectStatePath);
1672
+ if (state && typeof state === 'object') {
1673
+ const s = state;
1674
+ // Build progress
1675
+ if (s.build && typeof s.build === 'object') {
1676
+ const build = s.build;
1677
+ response += `## Build Progress\n\n`;
1678
+ const currentPhase = build.currentPhase || 0;
1679
+ const totalPhases = build.totalPhases || 1;
1680
+ const percent = Math.round((currentPhase / totalPhases) * 100);
1681
+ response += `**Phase ${currentPhase}/${totalPhases}** (${percent}%)\n\n`;
1682
+ // Progress bar
1683
+ const filled = Math.round(percent / 10);
1684
+ const empty = 10 - filled;
1685
+ response += `[${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}] ${percent}%\n\n`;
1686
+ if (build.status) {
1687
+ response += `**Status:** ${build.status}\n\n`;
1688
+ }
1689
+ }
1690
+ // Current work
1691
+ if (s.currentWork && typeof s.currentWork === 'object') {
1692
+ const work = s.currentWork;
1693
+ response += `## Current Work\n\n`;
1694
+ if (work.activeFeature) {
1695
+ response += `**In Progress:** ${work.activeFeature}\n`;
1696
+ }
1697
+ if (work.summary) {
1698
+ response += `**Summary:** ${work.summary}\n`;
1699
+ }
1700
+ if (work.lastUpdated) {
1701
+ response += `**Last Updated:** ${work.lastUpdated}\n`;
1702
+ }
1703
+ response += `\n`;
1704
+ }
1705
+ // Stack
1706
+ if (s.stack && typeof s.stack === 'object') {
1707
+ const stack = s.stack;
1708
+ response += `## Stack\n\n`;
1709
+ for (const [key, value] of Object.entries(stack)) {
1710
+ if (value) {
1711
+ response += `- **${key}:** ${value}\n`;
1712
+ }
1713
+ }
1714
+ response += `\n`;
1715
+ }
1716
+ }
1717
+ else {
1718
+ response += `## Project Overview\n\n`;
1719
+ response += `- **PRD:** ${hasPrd ? 'โœ… Found' : 'โŒ Not found'}\n`;
1720
+ response += `- **State Tracking:** ${hasProjectState ? 'โœ… Found' : 'โŒ Not found'}\n`;
1721
+ response += `- **CodeBakers State:** ${state ? 'โœ… Found' : 'โŒ Not initialized'}\n\n`;
1722
+ }
1723
+ // What's built
1724
+ response += `## What's Built\n\n`;
1725
+ response += `- **Components:** ${context.existingComponents.length}\n`;
1726
+ response += `- **API Routes:** ${context.existingApiRoutes.length}\n`;
1727
+ response += `- **Services:** ${context.existingServices.length}\n`;
1728
+ if (context.hasAuth)
1729
+ response += `- โœ… Authentication\n`;
1730
+ if (context.hasDatabase)
1731
+ response += `- โœ… Database\n`;
1732
+ if (context.hasPayments)
1733
+ response += `- โœ… Payments\n`;
1734
+ response += `\n`;
1735
+ // Recent components
1736
+ if (context.existingComponents.length > 0) {
1737
+ response += `### Recent Components\n`;
1738
+ context.existingComponents.slice(0, 10).forEach(comp => {
1739
+ response += `- ${comp}\n`;
1740
+ });
1741
+ if (context.existingComponents.length > 10) {
1742
+ response += `- ... and ${context.existingComponents.length - 10} more\n`;
1743
+ }
1744
+ response += `\n`;
1745
+ }
1746
+ response += `---\n\n`;
1747
+ response += `*Run \`upgrade\` to improve code quality or \`run_audit\` for detailed analysis.*`;
1748
+ return {
1749
+ content: [{
1750
+ type: 'text',
1751
+ text: response,
1752
+ }],
1753
+ };
1754
+ }
1755
+ handleRunTests(args) {
1756
+ const { filter, watch = false } = args;
1757
+ const cwd = process.cwd();
1758
+ // Detect test runner from package.json
1759
+ let testCommand = 'npm test';
1760
+ try {
1761
+ const pkgPath = path.join(cwd, 'package.json');
1762
+ if (fs.existsSync(pkgPath)) {
1763
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
1764
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1765
+ if (deps['@playwright/test']) {
1766
+ testCommand = 'npx playwright test';
1767
+ }
1768
+ else if (deps['vitest']) {
1769
+ testCommand = 'npx vitest run';
1770
+ }
1771
+ else if (deps['jest']) {
1772
+ testCommand = 'npx jest';
1773
+ }
1774
+ }
1775
+ }
1776
+ catch {
1777
+ // Use default
1778
+ }
1779
+ // Add filter if provided
1780
+ if (filter) {
1781
+ testCommand += ` ${filter}`;
1782
+ }
1783
+ // Add watch mode
1784
+ if (watch) {
1785
+ testCommand = testCommand.replace(' run', ''); // Remove 'run' for vitest watch
1786
+ if (testCommand.includes('vitest')) {
1787
+ testCommand = testCommand.replace('vitest run', 'vitest');
1788
+ }
1789
+ }
1790
+ let response = `# ๐Ÿงช Running Tests\n\n`;
1791
+ response += `**Command:** \`${testCommand}\`\n\n`;
1792
+ try {
1793
+ const output = (0, child_process_1.execSync)(testCommand, {
1794
+ cwd,
1795
+ encoding: 'utf-8',
1796
+ timeout: 120000, // 2 minute timeout
1797
+ stdio: ['pipe', 'pipe', 'pipe'],
1798
+ });
1799
+ response += `## โœ… Tests Passed\n\n`;
1800
+ response += '```\n' + output.slice(-2000) + '\n```\n';
1801
+ }
1802
+ catch (error) {
1803
+ const execError = error;
1804
+ response += `## โŒ Tests Failed\n\n`;
1805
+ if (execError.stdout) {
1806
+ response += '### Output\n```\n' + execError.stdout.slice(-2000) + '\n```\n\n';
1807
+ }
1808
+ if (execError.stderr) {
1809
+ response += '### Errors\n```\n' + execError.stderr.slice(-1000) + '\n```\n\n';
1810
+ }
1811
+ response += `**Exit Code:** ${execError.status || 1}\n\n`;
1812
+ response += `---\n\n*Fix the failing tests and run again.*`;
1813
+ }
1814
+ return {
1815
+ content: [{
1816
+ type: 'text',
1817
+ text: response,
1818
+ }],
1819
+ };
1820
+ }
1821
+ async handleReportPatternGap(args) {
1822
+ const { category, request, context, handledWith, wasSuccessful = true } = args;
1823
+ try {
1824
+ const response = await fetch(`${this.apiUrl}/api/pattern-gaps`, {
1825
+ method: 'POST',
1826
+ headers: {
1827
+ 'Content-Type': 'application/json',
1828
+ Authorization: `Bearer ${this.apiKey}`,
1829
+ },
1830
+ body: JSON.stringify({
1831
+ category,
1832
+ request,
1833
+ context,
1834
+ handledWith,
1835
+ wasSuccessful,
1836
+ }),
1837
+ });
1838
+ if (!response.ok) {
1839
+ const error = await response.json().catch(() => ({}));
1840
+ throw new Error(error.error || 'Failed to report pattern gap');
1841
+ }
1842
+ const result = await response.json();
1843
+ if (result.deduplicated) {
1844
+ return {
1845
+ content: [{
1846
+ type: 'text',
1847
+ text: `๐Ÿ“Š Pattern gap already reported recently (category: ${category}). No duplicate created.`,
1848
+ }],
1849
+ };
1850
+ }
1851
+ return {
1852
+ content: [{
1853
+ type: 'text',
1854
+ text: `โœ… Pattern gap reported successfully.\n\n**Category:** ${category}\n**Request:** ${request}\n\nThis helps improve CodeBakers for everyone!`,
1855
+ }],
1856
+ };
1857
+ }
1858
+ catch (error) {
1859
+ const message = error instanceof Error ? error.message : 'Unknown error';
1860
+ return {
1861
+ content: [{
1862
+ type: 'text',
1863
+ text: `โš ๏ธ Could not report pattern gap: ${message}\n\n(This doesn't affect your current work)`,
1864
+ }],
1865
+ };
1866
+ }
1867
+ }
1868
+ async handleTrackAnalytics(args) {
1869
+ const { eventType, eventData, projectHash } = args;
1870
+ try {
1871
+ const response = await fetch(`${this.apiUrl}/api/cli/analytics`, {
1872
+ method: 'POST',
1873
+ headers: {
1874
+ 'Content-Type': 'application/json',
1875
+ Authorization: `Bearer ${this.apiKey}`,
1876
+ },
1877
+ body: JSON.stringify({
1878
+ eventType,
1879
+ eventData,
1880
+ projectHash,
1881
+ }),
1882
+ });
1883
+ if (!response.ok) {
1884
+ // Silently fail - analytics shouldn't interrupt user workflow
1885
+ return {
1886
+ content: [{
1887
+ type: 'text',
1888
+ text: `๐Ÿ“Š Analytics tracked: ${eventType}`,
1889
+ }],
1890
+ };
1891
+ }
1892
+ return {
1893
+ content: [{
1894
+ type: 'text',
1895
+ text: `๐Ÿ“Š Analytics tracked: ${eventType}`,
1896
+ }],
1897
+ };
1898
+ }
1899
+ catch {
1900
+ // Silently fail
1901
+ return {
1902
+ content: [{
1903
+ type: 'text',
1904
+ text: `๐Ÿ“Š Analytics tracked: ${eventType}`,
1905
+ }],
1906
+ };
1907
+ }
1908
+ }
1310
1909
  async run() {
1311
1910
  const transport = new stdio_js_1.StdioServerTransport();
1312
1911
  await this.server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/mcp/server.ts CHANGED
@@ -480,6 +480,131 @@ class CodeBakersServer {
480
480
  },
481
481
  },
482
482
  },
483
+ {
484
+ name: 'design',
485
+ description:
486
+ 'Clone and implement designs from mockups, screenshots, or website references. Analyzes visual designs and generates pixel-perfect matching code with extracted design tokens (colors, typography, spacing). Use when user says "clone this design", "make it look like...", or "copy this UI".',
487
+ inputSchema: {
488
+ type: 'object' as const,
489
+ properties: {
490
+ source: {
491
+ type: 'string',
492
+ description: 'Path to mockup image, folder of images, URL to clone, or reference style (e.g., "./mockups", "https://linear.app", "like Notion")',
493
+ },
494
+ outputDir: {
495
+ type: 'string',
496
+ description: 'Directory to output generated components (default: src/components)',
497
+ },
498
+ },
499
+ required: ['source'],
500
+ },
501
+ },
502
+ {
503
+ name: 'upgrade',
504
+ description:
505
+ 'Upgrade an existing project to CodeBakers patterns WITHOUT changing the user\'s tech stack. Preserves their existing ORM (Prisma/Drizzle), auth (NextAuth/Clerk), UI library (Chakra/MUI), etc. Only upgrades code quality patterns like error handling, validation, tests, and security. Use when user says "upgrade this project", "improve my code", or "make this production ready".',
506
+ inputSchema: {
507
+ type: 'object' as const,
508
+ properties: {
509
+ areas: {
510
+ type: 'array',
511
+ items: { type: 'string' },
512
+ description: 'Specific areas to upgrade: "api", "components", "testing", "security", "all" (default: all)',
513
+ },
514
+ severity: {
515
+ type: 'string',
516
+ enum: ['critical', 'high', 'medium', 'low', 'all'],
517
+ description: 'Filter upgrades by severity (default: all)',
518
+ },
519
+ dryRun: {
520
+ type: 'boolean',
521
+ description: 'Show what would be upgraded without making changes (default: false)',
522
+ },
523
+ },
524
+ },
525
+ },
526
+ {
527
+ name: 'project_status',
528
+ description:
529
+ 'Show project build progress, completed features, and what\'s next. Different from get_status which shows CodeBakers connection status. Use when user asks "where am I?", "what\'s built?", "show progress", or "what\'s next?".',
530
+ inputSchema: {
531
+ type: 'object' as const,
532
+ properties: {},
533
+ },
534
+ },
535
+ {
536
+ name: 'run_tests',
537
+ description:
538
+ 'Run the project test suite (npm test or configured test command). Use after completing a feature to verify everything works. Returns test results with pass/fail status.',
539
+ inputSchema: {
540
+ type: 'object' as const,
541
+ properties: {
542
+ filter: {
543
+ type: 'string',
544
+ description: 'Optional filter to run specific tests (passed to test runner)',
545
+ },
546
+ watch: {
547
+ type: 'boolean',
548
+ description: 'Run in watch mode (default: false)',
549
+ },
550
+ },
551
+ },
552
+ },
553
+ {
554
+ name: 'report_pattern_gap',
555
+ description:
556
+ 'Report when a user request cannot be fully handled by existing patterns. This helps improve CodeBakers by tracking what patterns are missing. The AI should automatically call this when it encounters something outside pattern coverage.',
557
+ inputSchema: {
558
+ type: 'object' as const,
559
+ properties: {
560
+ category: {
561
+ type: 'string',
562
+ description: 'Category of the gap (e.g., "third-party-apis", "mobile", "blockchain", "iot")',
563
+ },
564
+ request: {
565
+ type: 'string',
566
+ description: 'What the user asked for',
567
+ },
568
+ context: {
569
+ type: 'string',
570
+ description: 'Additional context about what was needed',
571
+ },
572
+ handledWith: {
573
+ type: 'string',
574
+ description: 'Which existing patterns were used as fallback',
575
+ },
576
+ wasSuccessful: {
577
+ type: 'boolean',
578
+ description: 'Whether the request was handled successfully despite the gap',
579
+ },
580
+ },
581
+ required: ['category', 'request'],
582
+ },
583
+ },
584
+ {
585
+ name: 'track_analytics',
586
+ description:
587
+ 'Track CLI usage analytics for improving smart triggers and recommendations. Called automatically by the system for key events.',
588
+ inputSchema: {
589
+ type: 'object' as const,
590
+ properties: {
591
+ eventType: {
592
+ type: 'string',
593
+ enum: ['trigger_fired', 'trigger_accepted', 'trigger_dismissed', 'topic_learned', 'command_used', 'pattern_fetched', 'build_started', 'build_completed', 'feature_added', 'audit_run', 'design_cloned'],
594
+ description: 'Type of event to track',
595
+ },
596
+ eventData: {
597
+ type: 'object',
598
+ description: 'Additional data specific to the event',
599
+ },
600
+ projectHash: {
601
+ type: 'string',
602
+ description: 'Hash of project path for grouping analytics',
603
+ },
604
+ },
605
+ required: ['eventType'],
606
+ },
607
+ },
483
608
  ],
484
609
  }));
485
610
 
@@ -534,6 +659,24 @@ class CodeBakersServer {
534
659
  case 'heal':
535
660
  return this.handleHeal(args as { auto?: boolean; dryRun?: boolean; severity?: string });
536
661
 
662
+ case 'design':
663
+ return this.handleDesign(args as { source: string; outputDir?: string });
664
+
665
+ case 'upgrade':
666
+ return this.handleUpgrade(args as { areas?: string[]; severity?: string; dryRun?: boolean });
667
+
668
+ case 'project_status':
669
+ return this.handleProjectStatus();
670
+
671
+ case 'run_tests':
672
+ return this.handleRunTests(args as { filter?: string; watch?: boolean });
673
+
674
+ case 'report_pattern_gap':
675
+ return this.handleReportPatternGap(args as { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean });
676
+
677
+ case 'track_analytics':
678
+ return this.handleTrackAnalytics(args as { eventType: string; eventData?: Record<string, unknown>; projectHash?: string });
679
+
537
680
  default:
538
681
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
539
682
  }
@@ -1445,7 +1588,7 @@ phase: development
1445
1588
  ## Connection Status
1446
1589
  - **MCP Server:** Running
1447
1590
  - **API Connected:** Yes
1448
- - **Version:** 1.5.0
1591
+ - **Version:** 2.2.0
1449
1592
 
1450
1593
  ## Current Settings
1451
1594
  - **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
@@ -1457,6 +1600,9 @@ phase: development
1457
1600
  - ๐Ÿ” **search_patterns** - Search for specific guidance
1458
1601
  - ๐Ÿ—๏ธ **scaffold_project** - Create new projects
1459
1602
  - โš™๏ธ **init_project** - Add patterns to existing projects
1603
+ - ๐ŸŽจ **design** - Clone designs from mockups/websites
1604
+ - โฌ†๏ธ **upgrade** - Upgrade project patterns (preserves your stack)
1605
+ - ๐Ÿ“Š **project_status** - Show build progress
1460
1606
 
1461
1607
  ## How to Use
1462
1608
  Just describe what you want to build! I'll automatically:
@@ -1477,6 +1623,522 @@ Just describe what you want to build! I'll automatically:
1477
1623
  };
1478
1624
  }
1479
1625
 
1626
+ private async handleDesign(args: { source: string; outputDir?: string }) {
1627
+ const { source, outputDir = 'src/components' } = args;
1628
+ const cwd = process.cwd();
1629
+
1630
+ // Detect source type
1631
+ let sourceType: 'folder' | 'file' | 'url' | 'reference' = 'reference';
1632
+ if (source.startsWith('http://') || source.startsWith('https://')) {
1633
+ sourceType = 'url';
1634
+ } else if (source.startsWith('./') || source.startsWith('/') || source.includes('\\')) {
1635
+ const fullPath = path.join(cwd, source);
1636
+ if (fs.existsSync(fullPath)) {
1637
+ const stat = fs.statSync(fullPath);
1638
+ sourceType = stat.isDirectory() ? 'folder' : 'file';
1639
+ }
1640
+ } else if (source.toLowerCase().startsWith('like ')) {
1641
+ sourceType = 'reference';
1642
+ }
1643
+
1644
+ // Fetch design pattern from API
1645
+ const patternResult = await this.fetchPatterns(['33-design-clone', '09-design']);
1646
+
1647
+ let response = `# ๐ŸŽจ Design Clone Tool\n\n`;
1648
+ response += `**Source:** ${source}\n`;
1649
+ response += `**Type:** ${sourceType}\n`;
1650
+ response += `**Output:** ${outputDir}\n\n`;
1651
+
1652
+ switch (sourceType) {
1653
+ case 'folder':
1654
+ const folderPath = path.join(cwd, source);
1655
+ const images = fs.readdirSync(folderPath).filter(f =>
1656
+ /\.(png|jpg|jpeg|webp|svg|gif)$/i.test(f)
1657
+ );
1658
+ response += `## Found ${images.length} Design Files\n\n`;
1659
+ images.forEach(img => {
1660
+ response += `- ${img}\n`;
1661
+ });
1662
+ response += `\n## Next Steps\n\n`;
1663
+ response += `1. I'll analyze each image for design tokens\n`;
1664
+ response += `2. Extract colors, typography, spacing\n`;
1665
+ response += `3. Generate Tailwind config with your design system\n`;
1666
+ response += `4. Create matching components\n\n`;
1667
+ response += `**Note:** For best results, provide screenshots of:\n`;
1668
+ response += `- Color palette / brand guidelines\n`;
1669
+ response += `- Typography samples\n`;
1670
+ response += `- Key UI components (buttons, cards, forms)\n`;
1671
+ break;
1672
+
1673
+ case 'file':
1674
+ response += `## Analyzing Single Design\n\n`;
1675
+ response += `I'll extract design tokens from this image and generate matching components.\n\n`;
1676
+ response += `**Tip:** For a complete design system, provide a folder with multiple mockups.\n`;
1677
+ break;
1678
+
1679
+ case 'url':
1680
+ response += `## Cloning Website Design\n\n`;
1681
+ response += `I'll analyze the visual design at ${source} and extract:\n`;
1682
+ response += `- Color palette\n`;
1683
+ response += `- Typography (fonts, sizes, weights)\n`;
1684
+ response += `- Spacing system\n`;
1685
+ response += `- Component patterns\n\n`;
1686
+ response += `**Note:** This creates inspired-by components, not exact copies.\n`;
1687
+ break;
1688
+
1689
+ case 'reference':
1690
+ const refStyle = source.replace(/^like\s+/i, '').toLowerCase();
1691
+ const knownStyles: Record<string, string> = {
1692
+ 'linear': 'Dark theme, purple accents, minimal, clean',
1693
+ 'notion': 'Light theme, black/white, content-focused, lots of whitespace',
1694
+ 'stripe': 'Professional, purple gradients, polished shadows',
1695
+ 'vercel': 'Black/white, developer-focused, geometric',
1696
+ 'github': 'Blue accents, familiar, dev-tool aesthetic',
1697
+ 'figma': 'Purple accents, collaborative, modern',
1698
+ };
1699
+ const matchedStyle = Object.entries(knownStyles).find(([key]) =>
1700
+ refStyle.includes(key)
1701
+ );
1702
+ if (matchedStyle) {
1703
+ response += `## Reference Style: ${matchedStyle[0]}\n\n`;
1704
+ response += `**Characteristics:** ${matchedStyle[1]}\n\n`;
1705
+ } else {
1706
+ response += `## Reference Style: "${source}"\n\n`;
1707
+ response += `I'll apply a design language inspired by this reference.\n\n`;
1708
+ }
1709
+ response += `I'll generate components matching this aesthetic.\n`;
1710
+ break;
1711
+ }
1712
+
1713
+ response += `\n---\n\n`;
1714
+ response += `## Design Pattern Loaded\n\n`;
1715
+ response += `The design-clone pattern (33-design-clone) is now active.\n`;
1716
+ response += `Proceed with specific component requests like:\n`;
1717
+ response += `- "Create the navigation bar"\n`;
1718
+ response += `- "Build the hero section"\n`;
1719
+ response += `- "Generate the card components"\n`;
1720
+
1721
+ return {
1722
+ content: [{
1723
+ type: 'text' as const,
1724
+ text: response,
1725
+ }],
1726
+ };
1727
+ }
1728
+
1729
+ private async handleUpgrade(args: { areas?: string[]; severity?: string; dryRun?: boolean }) {
1730
+ const { areas = ['all'], severity = 'all', dryRun = false } = args;
1731
+ const context = this.gatherProjectContext();
1732
+
1733
+ let response = `# โฌ†๏ธ Project Upgrade Analysis\n\n`;
1734
+
1735
+ // Stack detection
1736
+ response += `## Your Stack (Preserving As-Is)\n\n`;
1737
+ response += `| Layer | Detected | Status |\n`;
1738
+ response += `|-------|----------|--------|\n`;
1739
+
1740
+ // ORM detection
1741
+ let orm = 'None';
1742
+ if (context.dependencies.includes('drizzle-orm')) orm = 'Drizzle';
1743
+ else if (context.dependencies.includes('@prisma/client')) orm = 'Prisma';
1744
+ else if (context.dependencies.includes('typeorm')) orm = 'TypeORM';
1745
+ else if (context.dependencies.includes('mongoose')) orm = 'Mongoose';
1746
+ response += `| ORM | ${orm} | โœ“ Keeping |\n`;
1747
+
1748
+ // Auth detection
1749
+ let auth = 'None';
1750
+ if (context.dependencies.includes('@supabase/supabase-js')) auth = 'Supabase';
1751
+ else if (context.dependencies.includes('next-auth')) auth = 'NextAuth';
1752
+ else if (context.dependencies.includes('@clerk/nextjs')) auth = 'Clerk';
1753
+ else if (context.dependencies.includes('firebase')) auth = 'Firebase';
1754
+ response += `| Auth | ${auth} | โœ“ Keeping |\n`;
1755
+
1756
+ // UI detection
1757
+ response += `| UI | ${context.uiLibrary || 'Tailwind'} | โœ“ Keeping |\n`;
1758
+
1759
+ // Framework (always Next.js for now)
1760
+ const hasNext = context.dependencies.includes('next');
1761
+ response += `| Framework | ${hasNext ? 'Next.js' : 'Unknown'} | โœ“ Keeping |\n`;
1762
+
1763
+ response += `\n---\n\n`;
1764
+
1765
+ // Scan for upgrade opportunities
1766
+ response += `## Upgrade Opportunities\n\n`;
1767
+
1768
+ const upgrades: Array<{ area: string; issue: string; severity: string; count: number }> = [];
1769
+
1770
+ // Check API routes
1771
+ if (context.existingApiRoutes.length > 0) {
1772
+ upgrades.push({
1773
+ area: 'API Routes',
1774
+ issue: 'Add error handling, validation, rate limiting',
1775
+ severity: 'HIGH',
1776
+ count: context.existingApiRoutes.length,
1777
+ });
1778
+ }
1779
+
1780
+ // Check components
1781
+ if (context.existingComponents.length > 0) {
1782
+ upgrades.push({
1783
+ area: 'Components',
1784
+ issue: 'Add loading states, error boundaries, accessibility',
1785
+ severity: 'MEDIUM',
1786
+ count: context.existingComponents.length,
1787
+ });
1788
+ }
1789
+
1790
+ // Check for tests
1791
+ const hasTests = context.dependencies.includes('@playwright/test') ||
1792
+ context.dependencies.includes('jest') ||
1793
+ context.dependencies.includes('vitest');
1794
+ if (!hasTests) {
1795
+ upgrades.push({
1796
+ area: 'Testing',
1797
+ issue: 'No test framework detected',
1798
+ severity: 'HIGH',
1799
+ count: 0,
1800
+ });
1801
+ }
1802
+
1803
+ // Check for Zod
1804
+ const hasZod = context.dependencies.includes('zod');
1805
+ if (!hasZod && context.existingApiRoutes.length > 0) {
1806
+ upgrades.push({
1807
+ area: 'Validation',
1808
+ issue: 'No Zod validation detected for API routes',
1809
+ severity: 'HIGH',
1810
+ count: context.existingApiRoutes.length,
1811
+ });
1812
+ }
1813
+
1814
+ // Display upgrades
1815
+ for (const upgrade of upgrades) {
1816
+ const icon = upgrade.severity === 'HIGH' ? '๐Ÿ”ด' :
1817
+ upgrade.severity === 'MEDIUM' ? '๐ŸŸก' : '๐ŸŸข';
1818
+ response += `### ${icon} ${upgrade.area}\n`;
1819
+ response += `- **Issue:** ${upgrade.issue}\n`;
1820
+ if (upgrade.count > 0) {
1821
+ response += `- **Affected:** ${upgrade.count} files\n`;
1822
+ }
1823
+ response += `- **Severity:** ${upgrade.severity}\n\n`;
1824
+ }
1825
+
1826
+ if (upgrades.length === 0) {
1827
+ response += `โœ… No major upgrade opportunities detected!\n\n`;
1828
+ }
1829
+
1830
+ // Recommendations
1831
+ response += `---\n\n`;
1832
+ response += `## Recommended Actions\n\n`;
1833
+
1834
+ if (dryRun) {
1835
+ response += `**(Dry Run Mode - No changes will be made)**\n\n`;
1836
+ }
1837
+
1838
+ response += `1. Run \`run_audit\` for detailed code quality check\n`;
1839
+ response += `2. Use \`heal\` to auto-fix common issues\n`;
1840
+ response += `3. Request specific upgrades like:\n`;
1841
+ response += ` - "Add Zod validation to my API routes"\n`;
1842
+ response += ` - "Add error boundaries to components"\n`;
1843
+ response += ` - "Set up Playwright testing"\n\n`;
1844
+
1845
+ response += `---\n\n`;
1846
+ response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
1847
+
1848
+ return {
1849
+ content: [{
1850
+ type: 'text' as const,
1851
+ text: response,
1852
+ }],
1853
+ };
1854
+ }
1855
+
1856
+ private handleProjectStatus() {
1857
+ const cwd = process.cwd();
1858
+ const context = this.gatherProjectContext();
1859
+
1860
+ let response = `# ๐Ÿ“Š Project Status\n\n`;
1861
+ response += `**Project:** ${context.projectName}\n\n`;
1862
+
1863
+ // Check for .codebakers.json state
1864
+ let state: Record<string, unknown> | null = null;
1865
+ try {
1866
+ const statePath = path.join(cwd, '.codebakers.json');
1867
+ if (fs.existsSync(statePath)) {
1868
+ state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
1869
+ }
1870
+ } catch {
1871
+ // No state file
1872
+ }
1873
+
1874
+ // Check for PRD
1875
+ const prdPath = path.join(cwd, 'PRD.md');
1876
+ const hasPrd = fs.existsSync(prdPath);
1877
+
1878
+ // Check for PROJECT-STATE.md
1879
+ const projectStatePath = path.join(cwd, 'PROJECT-STATE.md');
1880
+ const hasProjectState = fs.existsSync(projectStatePath);
1881
+
1882
+ if (state && typeof state === 'object') {
1883
+ const s = state as Record<string, unknown>;
1884
+
1885
+ // Build progress
1886
+ if (s.build && typeof s.build === 'object') {
1887
+ const build = s.build as Record<string, unknown>;
1888
+ response += `## Build Progress\n\n`;
1889
+ const currentPhase = build.currentPhase as number || 0;
1890
+ const totalPhases = build.totalPhases as number || 1;
1891
+ const percent = Math.round((currentPhase / totalPhases) * 100);
1892
+ response += `**Phase ${currentPhase}/${totalPhases}** (${percent}%)\n\n`;
1893
+
1894
+ // Progress bar
1895
+ const filled = Math.round(percent / 10);
1896
+ const empty = 10 - filled;
1897
+ response += `[${'โ–ˆ'.repeat(filled)}${'โ–‘'.repeat(empty)}] ${percent}%\n\n`;
1898
+
1899
+ if (build.status) {
1900
+ response += `**Status:** ${build.status}\n\n`;
1901
+ }
1902
+ }
1903
+
1904
+ // Current work
1905
+ if (s.currentWork && typeof s.currentWork === 'object') {
1906
+ const work = s.currentWork as Record<string, unknown>;
1907
+ response += `## Current Work\n\n`;
1908
+ if (work.activeFeature) {
1909
+ response += `**In Progress:** ${work.activeFeature}\n`;
1910
+ }
1911
+ if (work.summary) {
1912
+ response += `**Summary:** ${work.summary}\n`;
1913
+ }
1914
+ if (work.lastUpdated) {
1915
+ response += `**Last Updated:** ${work.lastUpdated}\n`;
1916
+ }
1917
+ response += `\n`;
1918
+ }
1919
+
1920
+ // Stack
1921
+ if (s.stack && typeof s.stack === 'object') {
1922
+ const stack = s.stack as Record<string, unknown>;
1923
+ response += `## Stack\n\n`;
1924
+ for (const [key, value] of Object.entries(stack)) {
1925
+ if (value) {
1926
+ response += `- **${key}:** ${value}\n`;
1927
+ }
1928
+ }
1929
+ response += `\n`;
1930
+ }
1931
+ } else {
1932
+ response += `## Project Overview\n\n`;
1933
+ response += `- **PRD:** ${hasPrd ? 'โœ… Found' : 'โŒ Not found'}\n`;
1934
+ response += `- **State Tracking:** ${hasProjectState ? 'โœ… Found' : 'โŒ Not found'}\n`;
1935
+ response += `- **CodeBakers State:** ${state ? 'โœ… Found' : 'โŒ Not initialized'}\n\n`;
1936
+ }
1937
+
1938
+ // What's built
1939
+ response += `## What's Built\n\n`;
1940
+ response += `- **Components:** ${context.existingComponents.length}\n`;
1941
+ response += `- **API Routes:** ${context.existingApiRoutes.length}\n`;
1942
+ response += `- **Services:** ${context.existingServices.length}\n`;
1943
+
1944
+ if (context.hasAuth) response += `- โœ… Authentication\n`;
1945
+ if (context.hasDatabase) response += `- โœ… Database\n`;
1946
+ if (context.hasPayments) response += `- โœ… Payments\n`;
1947
+
1948
+ response += `\n`;
1949
+
1950
+ // Recent components
1951
+ if (context.existingComponents.length > 0) {
1952
+ response += `### Recent Components\n`;
1953
+ context.existingComponents.slice(0, 10).forEach(comp => {
1954
+ response += `- ${comp}\n`;
1955
+ });
1956
+ if (context.existingComponents.length > 10) {
1957
+ response += `- ... and ${context.existingComponents.length - 10} more\n`;
1958
+ }
1959
+ response += `\n`;
1960
+ }
1961
+
1962
+ response += `---\n\n`;
1963
+ response += `*Run \`upgrade\` to improve code quality or \`run_audit\` for detailed analysis.*`;
1964
+
1965
+ return {
1966
+ content: [{
1967
+ type: 'text' as const,
1968
+ text: response,
1969
+ }],
1970
+ };
1971
+ }
1972
+
1973
+ private handleRunTests(args: { filter?: string; watch?: boolean }) {
1974
+ const { filter, watch = false } = args;
1975
+ const cwd = process.cwd();
1976
+
1977
+ // Detect test runner from package.json
1978
+ let testCommand = 'npm test';
1979
+ try {
1980
+ const pkgPath = path.join(cwd, 'package.json');
1981
+ if (fs.existsSync(pkgPath)) {
1982
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
1983
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1984
+
1985
+ if (deps['@playwright/test']) {
1986
+ testCommand = 'npx playwright test';
1987
+ } else if (deps['vitest']) {
1988
+ testCommand = 'npx vitest run';
1989
+ } else if (deps['jest']) {
1990
+ testCommand = 'npx jest';
1991
+ }
1992
+ }
1993
+ } catch {
1994
+ // Use default
1995
+ }
1996
+
1997
+ // Add filter if provided
1998
+ if (filter) {
1999
+ testCommand += ` ${filter}`;
2000
+ }
2001
+
2002
+ // Add watch mode
2003
+ if (watch) {
2004
+ testCommand = testCommand.replace(' run', ''); // Remove 'run' for vitest watch
2005
+ if (testCommand.includes('vitest')) {
2006
+ testCommand = testCommand.replace('vitest run', 'vitest');
2007
+ }
2008
+ }
2009
+
2010
+ let response = `# ๐Ÿงช Running Tests\n\n`;
2011
+ response += `**Command:** \`${testCommand}\`\n\n`;
2012
+
2013
+ try {
2014
+ const output = execSync(testCommand, {
2015
+ cwd,
2016
+ encoding: 'utf-8',
2017
+ timeout: 120000, // 2 minute timeout
2018
+ stdio: ['pipe', 'pipe', 'pipe'],
2019
+ });
2020
+
2021
+ response += `## โœ… Tests Passed\n\n`;
2022
+ response += '```\n' + output.slice(-2000) + '\n```\n';
2023
+ } catch (error) {
2024
+ const execError = error as { stdout?: string; stderr?: string; status?: number };
2025
+ response += `## โŒ Tests Failed\n\n`;
2026
+
2027
+ if (execError.stdout) {
2028
+ response += '### Output\n```\n' + execError.stdout.slice(-2000) + '\n```\n\n';
2029
+ }
2030
+ if (execError.stderr) {
2031
+ response += '### Errors\n```\n' + execError.stderr.slice(-1000) + '\n```\n\n';
2032
+ }
2033
+
2034
+ response += `**Exit Code:** ${execError.status || 1}\n\n`;
2035
+ response += `---\n\n*Fix the failing tests and run again.*`;
2036
+ }
2037
+
2038
+ return {
2039
+ content: [{
2040
+ type: 'text' as const,
2041
+ text: response,
2042
+ }],
2043
+ };
2044
+ }
2045
+
2046
+ private async handleReportPatternGap(args: { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean }) {
2047
+ const { category, request, context, handledWith, wasSuccessful = true } = args;
2048
+
2049
+ try {
2050
+ const response = await fetch(`${this.apiUrl}/api/pattern-gaps`, {
2051
+ method: 'POST',
2052
+ headers: {
2053
+ 'Content-Type': 'application/json',
2054
+ Authorization: `Bearer ${this.apiKey}`,
2055
+ },
2056
+ body: JSON.stringify({
2057
+ category,
2058
+ request,
2059
+ context,
2060
+ handledWith,
2061
+ wasSuccessful,
2062
+ }),
2063
+ });
2064
+
2065
+ if (!response.ok) {
2066
+ const error = await response.json().catch(() => ({}));
2067
+ throw new Error(error.error || 'Failed to report pattern gap');
2068
+ }
2069
+
2070
+ const result = await response.json();
2071
+
2072
+ if (result.deduplicated) {
2073
+ return {
2074
+ content: [{
2075
+ type: 'text' as const,
2076
+ text: `๐Ÿ“Š Pattern gap already reported recently (category: ${category}). No duplicate created.`,
2077
+ }],
2078
+ };
2079
+ }
2080
+
2081
+ return {
2082
+ content: [{
2083
+ type: 'text' as const,
2084
+ text: `โœ… Pattern gap reported successfully.\n\n**Category:** ${category}\n**Request:** ${request}\n\nThis helps improve CodeBakers for everyone!`,
2085
+ }],
2086
+ };
2087
+ } catch (error) {
2088
+ const message = error instanceof Error ? error.message : 'Unknown error';
2089
+ return {
2090
+ content: [{
2091
+ type: 'text' as const,
2092
+ text: `โš ๏ธ Could not report pattern gap: ${message}\n\n(This doesn't affect your current work)`,
2093
+ }],
2094
+ };
2095
+ }
2096
+ }
2097
+
2098
+ private async handleTrackAnalytics(args: { eventType: string; eventData?: Record<string, unknown>; projectHash?: string }) {
2099
+ const { eventType, eventData, projectHash } = args;
2100
+
2101
+ try {
2102
+ const response = await fetch(`${this.apiUrl}/api/cli/analytics`, {
2103
+ method: 'POST',
2104
+ headers: {
2105
+ 'Content-Type': 'application/json',
2106
+ Authorization: `Bearer ${this.apiKey}`,
2107
+ },
2108
+ body: JSON.stringify({
2109
+ eventType,
2110
+ eventData,
2111
+ projectHash,
2112
+ }),
2113
+ });
2114
+
2115
+ if (!response.ok) {
2116
+ // Silently fail - analytics shouldn't interrupt user workflow
2117
+ return {
2118
+ content: [{
2119
+ type: 'text' as const,
2120
+ text: `๐Ÿ“Š Analytics tracked: ${eventType}`,
2121
+ }],
2122
+ };
2123
+ }
2124
+
2125
+ return {
2126
+ content: [{
2127
+ type: 'text' as const,
2128
+ text: `๐Ÿ“Š Analytics tracked: ${eventType}`,
2129
+ }],
2130
+ };
2131
+ } catch {
2132
+ // Silently fail
2133
+ return {
2134
+ content: [{
2135
+ type: 'text' as const,
2136
+ text: `๐Ÿ“Š Analytics tracked: ${eventType}`,
2137
+ }],
2138
+ };
2139
+ }
2140
+ }
2141
+
1480
2142
  async run(): Promise<void> {
1481
2143
  const transport = new StdioServerTransport();
1482
2144
  await this.server.connect(transport);