@codebakers/cli 2.3.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.
@@ -502,6 +502,76 @@ class CodeBakersServer {
502
502
  properties: {},
503
503
  },
504
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
+ },
505
575
  ],
506
576
  }));
507
577
  // Handle tool calls
@@ -543,6 +613,12 @@ class CodeBakersServer {
543
613
  return this.handleUpgrade(args);
544
614
  case 'project_status':
545
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);
546
622
  default:
547
623
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
548
624
  }
@@ -1676,6 +1752,160 @@ Just describe what you want to build! I'll automatically:
1676
1752
  }],
1677
1753
  };
1678
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
+ }
1679
1909
  async run() {
1680
1910
  const transport = new stdio_js_1.StdioServerTransport();
1681
1911
  await this.server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.3.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
@@ -532,6 +532,79 @@ class CodeBakersServer {
532
532
  properties: {},
533
533
  },
534
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
+ },
535
608
  ],
536
609
  }));
537
610
 
@@ -595,6 +668,15 @@ class CodeBakersServer {
595
668
  case 'project_status':
596
669
  return this.handleProjectStatus();
597
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
+
598
680
  default:
599
681
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
600
682
  }
@@ -1888,6 +1970,175 @@ Just describe what you want to build! I'll automatically:
1888
1970
  };
1889
1971
  }
1890
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
+
1891
2142
  async run(): Promise<void> {
1892
2143
  const transport = new StdioServerTransport();
1893
2144
  await this.server.connect(transport);