@codebakers/cli 2.3.0 → 2.5.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.
- package/dist/mcp/server.js +322 -6
- package/package.json +1 -1
- package/src/mcp/server.ts +365 -6
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
}
|
|
@@ -1460,6 +1536,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
1460
1536
|
async handleUpgrade(args) {
|
|
1461
1537
|
const { areas = ['all'], severity = 'all', dryRun = false } = args;
|
|
1462
1538
|
const context = this.gatherProjectContext();
|
|
1539
|
+
const cwd = process.cwd();
|
|
1463
1540
|
let response = `# ⬆️ Project Upgrade Analysis\n\n`;
|
|
1464
1541
|
// Stack detection
|
|
1465
1542
|
response += `## Your Stack (Preserving As-Is)\n\n`;
|
|
@@ -1493,6 +1570,83 @@ Just describe what you want to build! I'll automatically:
|
|
|
1493
1570
|
const hasNext = context.dependencies.includes('next');
|
|
1494
1571
|
response += `| Framework | ${hasNext ? 'Next.js' : 'Unknown'} | ✓ Keeping |\n`;
|
|
1495
1572
|
response += `\n---\n\n`;
|
|
1573
|
+
// Git Analysis Section
|
|
1574
|
+
response += `## Deep Analysis\n\n`;
|
|
1575
|
+
// Check if git repo
|
|
1576
|
+
const isGitRepo = fs.existsSync(path.join(cwd, '.git'));
|
|
1577
|
+
if (isGitRepo) {
|
|
1578
|
+
response += `### Git Hot Spots (Most Changed Files)\n\n`;
|
|
1579
|
+
try {
|
|
1580
|
+
// Get files with most commits
|
|
1581
|
+
const gitLog = (0, child_process_1.execSync)('git log --pretty=format: --name-only --since="6 months ago" 2>/dev/null | sort | uniq -c | sort -rg | head -10', { cwd, encoding: 'utf-8', timeout: 10000 }).trim();
|
|
1582
|
+
if (gitLog) {
|
|
1583
|
+
const hotSpots = gitLog.split('\n')
|
|
1584
|
+
.map(line => line.trim())
|
|
1585
|
+
.filter(line => line && !line.includes('node_modules'))
|
|
1586
|
+
.slice(0, 5);
|
|
1587
|
+
if (hotSpots.length > 0) {
|
|
1588
|
+
hotSpots.forEach((line, i) => {
|
|
1589
|
+
const match = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
1590
|
+
if (match) {
|
|
1591
|
+
const [, count, file] = match;
|
|
1592
|
+
response += `${i + 1}. \`${file}\` - ${count} changes\n`;
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
response += `\n`;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
// Count fix commits
|
|
1599
|
+
const fixCount = (0, child_process_1.execSync)('git log --oneline --since="6 months ago" 2>/dev/null | grep -i "fix" | wc -l', { cwd, encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1600
|
+
if (parseInt(fixCount) > 0) {
|
|
1601
|
+
response += `**Bug Fix Commits:** ${fixCount} (in last 6 months)\n\n`;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
catch {
|
|
1605
|
+
response += `*(Git analysis unavailable)*\n\n`;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
response += `*(Not a git repository - skipping git analysis)*\n\n`;
|
|
1610
|
+
}
|
|
1611
|
+
// TODO/FIXME Scan
|
|
1612
|
+
response += `### Developer Notes (TODO/FIXME)\n\n`;
|
|
1613
|
+
try {
|
|
1614
|
+
const todoScan = (0, child_process_1.execSync)('grep -r "TODO\\|FIXME\\|HACK\\|XXX\\|BUG" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" -l 2>/dev/null | head -20', { cwd, encoding: 'utf-8', timeout: 10000 }).trim();
|
|
1615
|
+
if (todoScan) {
|
|
1616
|
+
const todoFiles = todoScan.split('\n').filter(f => !f.includes('node_modules'));
|
|
1617
|
+
const todoCount = todoFiles.length;
|
|
1618
|
+
if (todoCount > 0) {
|
|
1619
|
+
response += `Found developer notes in **${todoCount} files**:\n\n`;
|
|
1620
|
+
// Count by type
|
|
1621
|
+
try {
|
|
1622
|
+
const todoTypes = (0, child_process_1.execSync)('grep -roh "TODO\\|FIXME\\|HACK\\|XXX\\|BUG" --include="*.ts" --include="*.tsx" 2>/dev/null | sort | uniq -c | sort -rg', { cwd, encoding: 'utf-8', timeout: 10000 }).trim();
|
|
1623
|
+
if (todoTypes) {
|
|
1624
|
+
todoTypes.split('\n').forEach(line => {
|
|
1625
|
+
const match = line.trim().match(/^\s*(\d+)\s+(.+)$/);
|
|
1626
|
+
if (match) {
|
|
1627
|
+
const [, count, type] = match;
|
|
1628
|
+
const icon = type === 'FIXME' || type === 'BUG' ? '🔴' :
|
|
1629
|
+
type === 'HACK' ? '🟡' : '📝';
|
|
1630
|
+
response += `- ${icon} ${count} ${type}s\n`;
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
response += `\n`;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
catch {
|
|
1637
|
+
// Ignore count errors
|
|
1638
|
+
}
|
|
1639
|
+
response += `*I can implement these TODOs and fix FIXMEs during upgrade.*\n\n`;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
response += `✅ No TODO/FIXME comments found - clean codebase!\n\n`;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
catch {
|
|
1647
|
+
response += `*(TODO scan unavailable)*\n\n`;
|
|
1648
|
+
}
|
|
1649
|
+
response += `---\n\n`;
|
|
1496
1650
|
// Scan for upgrade opportunities
|
|
1497
1651
|
response += `## Upgrade Opportunities\n\n`;
|
|
1498
1652
|
const upgrades = [];
|
|
@@ -1550,18 +1704,26 @@ Just describe what you want to build! I'll automatically:
|
|
|
1550
1704
|
if (upgrades.length === 0) {
|
|
1551
1705
|
response += `✅ No major upgrade opportunities detected!\n\n`;
|
|
1552
1706
|
}
|
|
1707
|
+
// Review mode options
|
|
1708
|
+
response += `---\n\n`;
|
|
1709
|
+
response += `## Review Modes\n\n`;
|
|
1710
|
+
response += `Pick a focus for the upgrade:\n\n`;
|
|
1711
|
+
response += `- **Security Audit** - Auth, secrets, injections, OWASP top 10\n`;
|
|
1712
|
+
response += `- **Performance Review** - Bundle size, queries, caching\n`;
|
|
1713
|
+
response += `- **Code Quality** - Patterns, DRY, complexity\n`;
|
|
1714
|
+
response += `- **Pre-Launch** - Everything for production\n`;
|
|
1715
|
+
response += `- **Quick Scan** - Top 5 issues only\n`;
|
|
1716
|
+
response += `- **Comprehensive** - All of the above\n\n`;
|
|
1553
1717
|
// Recommendations
|
|
1554
1718
|
response += `---\n\n`;
|
|
1555
1719
|
response += `## Recommended Actions\n\n`;
|
|
1556
1720
|
if (dryRun) {
|
|
1557
1721
|
response += `**(Dry Run Mode - No changes will be made)**\n\n`;
|
|
1558
1722
|
}
|
|
1559
|
-
response += `1.
|
|
1560
|
-
response += `2.
|
|
1561
|
-
response += `3.
|
|
1562
|
-
response += `
|
|
1563
|
-
response += ` - "Add error boundaries to components"\n`;
|
|
1564
|
-
response += ` - "Set up Playwright testing"\n\n`;
|
|
1723
|
+
response += `1. Tell me your main concerns (security, performance, etc.)\n`;
|
|
1724
|
+
response += `2. I'll prioritize fixes based on your needs\n`;
|
|
1725
|
+
response += `3. Hot spot files get fixed first (where bugs live)\n`;
|
|
1726
|
+
response += `4. I'll implement your TODOs and fix FIXMEs along the way\n\n`;
|
|
1565
1727
|
response += `---\n\n`;
|
|
1566
1728
|
response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
|
|
1567
1729
|
return {
|
|
@@ -1676,6 +1838,160 @@ Just describe what you want to build! I'll automatically:
|
|
|
1676
1838
|
}],
|
|
1677
1839
|
};
|
|
1678
1840
|
}
|
|
1841
|
+
handleRunTests(args) {
|
|
1842
|
+
const { filter, watch = false } = args;
|
|
1843
|
+
const cwd = process.cwd();
|
|
1844
|
+
// Detect test runner from package.json
|
|
1845
|
+
let testCommand = 'npm test';
|
|
1846
|
+
try {
|
|
1847
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
1848
|
+
if (fs.existsSync(pkgPath)) {
|
|
1849
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
1850
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1851
|
+
if (deps['@playwright/test']) {
|
|
1852
|
+
testCommand = 'npx playwright test';
|
|
1853
|
+
}
|
|
1854
|
+
else if (deps['vitest']) {
|
|
1855
|
+
testCommand = 'npx vitest run';
|
|
1856
|
+
}
|
|
1857
|
+
else if (deps['jest']) {
|
|
1858
|
+
testCommand = 'npx jest';
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
catch {
|
|
1863
|
+
// Use default
|
|
1864
|
+
}
|
|
1865
|
+
// Add filter if provided
|
|
1866
|
+
if (filter) {
|
|
1867
|
+
testCommand += ` ${filter}`;
|
|
1868
|
+
}
|
|
1869
|
+
// Add watch mode
|
|
1870
|
+
if (watch) {
|
|
1871
|
+
testCommand = testCommand.replace(' run', ''); // Remove 'run' for vitest watch
|
|
1872
|
+
if (testCommand.includes('vitest')) {
|
|
1873
|
+
testCommand = testCommand.replace('vitest run', 'vitest');
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
let response = `# 🧪 Running Tests\n\n`;
|
|
1877
|
+
response += `**Command:** \`${testCommand}\`\n\n`;
|
|
1878
|
+
try {
|
|
1879
|
+
const output = (0, child_process_1.execSync)(testCommand, {
|
|
1880
|
+
cwd,
|
|
1881
|
+
encoding: 'utf-8',
|
|
1882
|
+
timeout: 120000, // 2 minute timeout
|
|
1883
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1884
|
+
});
|
|
1885
|
+
response += `## ✅ Tests Passed\n\n`;
|
|
1886
|
+
response += '```\n' + output.slice(-2000) + '\n```\n';
|
|
1887
|
+
}
|
|
1888
|
+
catch (error) {
|
|
1889
|
+
const execError = error;
|
|
1890
|
+
response += `## ❌ Tests Failed\n\n`;
|
|
1891
|
+
if (execError.stdout) {
|
|
1892
|
+
response += '### Output\n```\n' + execError.stdout.slice(-2000) + '\n```\n\n';
|
|
1893
|
+
}
|
|
1894
|
+
if (execError.stderr) {
|
|
1895
|
+
response += '### Errors\n```\n' + execError.stderr.slice(-1000) + '\n```\n\n';
|
|
1896
|
+
}
|
|
1897
|
+
response += `**Exit Code:** ${execError.status || 1}\n\n`;
|
|
1898
|
+
response += `---\n\n*Fix the failing tests and run again.*`;
|
|
1899
|
+
}
|
|
1900
|
+
return {
|
|
1901
|
+
content: [{
|
|
1902
|
+
type: 'text',
|
|
1903
|
+
text: response,
|
|
1904
|
+
}],
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
async handleReportPatternGap(args) {
|
|
1908
|
+
const { category, request, context, handledWith, wasSuccessful = true } = args;
|
|
1909
|
+
try {
|
|
1910
|
+
const response = await fetch(`${this.apiUrl}/api/pattern-gaps`, {
|
|
1911
|
+
method: 'POST',
|
|
1912
|
+
headers: {
|
|
1913
|
+
'Content-Type': 'application/json',
|
|
1914
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1915
|
+
},
|
|
1916
|
+
body: JSON.stringify({
|
|
1917
|
+
category,
|
|
1918
|
+
request,
|
|
1919
|
+
context,
|
|
1920
|
+
handledWith,
|
|
1921
|
+
wasSuccessful,
|
|
1922
|
+
}),
|
|
1923
|
+
});
|
|
1924
|
+
if (!response.ok) {
|
|
1925
|
+
const error = await response.json().catch(() => ({}));
|
|
1926
|
+
throw new Error(error.error || 'Failed to report pattern gap');
|
|
1927
|
+
}
|
|
1928
|
+
const result = await response.json();
|
|
1929
|
+
if (result.deduplicated) {
|
|
1930
|
+
return {
|
|
1931
|
+
content: [{
|
|
1932
|
+
type: 'text',
|
|
1933
|
+
text: `📊 Pattern gap already reported recently (category: ${category}). No duplicate created.`,
|
|
1934
|
+
}],
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
return {
|
|
1938
|
+
content: [{
|
|
1939
|
+
type: 'text',
|
|
1940
|
+
text: `✅ Pattern gap reported successfully.\n\n**Category:** ${category}\n**Request:** ${request}\n\nThis helps improve CodeBakers for everyone!`,
|
|
1941
|
+
}],
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
catch (error) {
|
|
1945
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1946
|
+
return {
|
|
1947
|
+
content: [{
|
|
1948
|
+
type: 'text',
|
|
1949
|
+
text: `⚠️ Could not report pattern gap: ${message}\n\n(This doesn't affect your current work)`,
|
|
1950
|
+
}],
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
async handleTrackAnalytics(args) {
|
|
1955
|
+
const { eventType, eventData, projectHash } = args;
|
|
1956
|
+
try {
|
|
1957
|
+
const response = await fetch(`${this.apiUrl}/api/cli/analytics`, {
|
|
1958
|
+
method: 'POST',
|
|
1959
|
+
headers: {
|
|
1960
|
+
'Content-Type': 'application/json',
|
|
1961
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1962
|
+
},
|
|
1963
|
+
body: JSON.stringify({
|
|
1964
|
+
eventType,
|
|
1965
|
+
eventData,
|
|
1966
|
+
projectHash,
|
|
1967
|
+
}),
|
|
1968
|
+
});
|
|
1969
|
+
if (!response.ok) {
|
|
1970
|
+
// Silently fail - analytics shouldn't interrupt user workflow
|
|
1971
|
+
return {
|
|
1972
|
+
content: [{
|
|
1973
|
+
type: 'text',
|
|
1974
|
+
text: `📊 Analytics tracked: ${eventType}`,
|
|
1975
|
+
}],
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
return {
|
|
1979
|
+
content: [{
|
|
1980
|
+
type: 'text',
|
|
1981
|
+
text: `📊 Analytics tracked: ${eventType}`,
|
|
1982
|
+
}],
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
catch {
|
|
1986
|
+
// Silently fail
|
|
1987
|
+
return {
|
|
1988
|
+
content: [{
|
|
1989
|
+
type: 'text',
|
|
1990
|
+
text: `📊 Analytics tracked: ${eventType}`,
|
|
1991
|
+
}],
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1679
1995
|
async run() {
|
|
1680
1996
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
1681
1997
|
await this.server.connect(transport);
|
package/package.json
CHANGED
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
|
}
|
|
@@ -1647,6 +1729,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
1647
1729
|
private async handleUpgrade(args: { areas?: string[]; severity?: string; dryRun?: boolean }) {
|
|
1648
1730
|
const { areas = ['all'], severity = 'all', dryRun = false } = args;
|
|
1649
1731
|
const context = this.gatherProjectContext();
|
|
1732
|
+
const cwd = process.cwd();
|
|
1650
1733
|
|
|
1651
1734
|
let response = `# ⬆️ Project Upgrade Analysis\n\n`;
|
|
1652
1735
|
|
|
@@ -1680,6 +1763,104 @@ Just describe what you want to build! I'll automatically:
|
|
|
1680
1763
|
|
|
1681
1764
|
response += `\n---\n\n`;
|
|
1682
1765
|
|
|
1766
|
+
// Git Analysis Section
|
|
1767
|
+
response += `## Deep Analysis\n\n`;
|
|
1768
|
+
|
|
1769
|
+
// Check if git repo
|
|
1770
|
+
const isGitRepo = fs.existsSync(path.join(cwd, '.git'));
|
|
1771
|
+
|
|
1772
|
+
if (isGitRepo) {
|
|
1773
|
+
response += `### Git Hot Spots (Most Changed Files)\n\n`;
|
|
1774
|
+
try {
|
|
1775
|
+
// Get files with most commits
|
|
1776
|
+
const gitLog = execSync(
|
|
1777
|
+
'git log --pretty=format: --name-only --since="6 months ago" 2>/dev/null | sort | uniq -c | sort -rg | head -10',
|
|
1778
|
+
{ cwd, encoding: 'utf-8', timeout: 10000 }
|
|
1779
|
+
).trim();
|
|
1780
|
+
|
|
1781
|
+
if (gitLog) {
|
|
1782
|
+
const hotSpots = gitLog.split('\n')
|
|
1783
|
+
.map(line => line.trim())
|
|
1784
|
+
.filter(line => line && !line.includes('node_modules'))
|
|
1785
|
+
.slice(0, 5);
|
|
1786
|
+
|
|
1787
|
+
if (hotSpots.length > 0) {
|
|
1788
|
+
hotSpots.forEach((line, i) => {
|
|
1789
|
+
const match = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
1790
|
+
if (match) {
|
|
1791
|
+
const [, count, file] = match;
|
|
1792
|
+
response += `${i + 1}. \`${file}\` - ${count} changes\n`;
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
response += `\n`;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// Count fix commits
|
|
1800
|
+
const fixCount = execSync(
|
|
1801
|
+
'git log --oneline --since="6 months ago" 2>/dev/null | grep -i "fix" | wc -l',
|
|
1802
|
+
{ cwd, encoding: 'utf-8', timeout: 5000 }
|
|
1803
|
+
).trim();
|
|
1804
|
+
|
|
1805
|
+
if (parseInt(fixCount) > 0) {
|
|
1806
|
+
response += `**Bug Fix Commits:** ${fixCount} (in last 6 months)\n\n`;
|
|
1807
|
+
}
|
|
1808
|
+
} catch {
|
|
1809
|
+
response += `*(Git analysis unavailable)*\n\n`;
|
|
1810
|
+
}
|
|
1811
|
+
} else {
|
|
1812
|
+
response += `*(Not a git repository - skipping git analysis)*\n\n`;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// TODO/FIXME Scan
|
|
1816
|
+
response += `### Developer Notes (TODO/FIXME)\n\n`;
|
|
1817
|
+
try {
|
|
1818
|
+
const todoScan = execSync(
|
|
1819
|
+
'grep -r "TODO\\|FIXME\\|HACK\\|XXX\\|BUG" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" -l 2>/dev/null | head -20',
|
|
1820
|
+
{ cwd, encoding: 'utf-8', timeout: 10000 }
|
|
1821
|
+
).trim();
|
|
1822
|
+
|
|
1823
|
+
if (todoScan) {
|
|
1824
|
+
const todoFiles = todoScan.split('\n').filter(f => !f.includes('node_modules'));
|
|
1825
|
+
const todoCount = todoFiles.length;
|
|
1826
|
+
|
|
1827
|
+
if (todoCount > 0) {
|
|
1828
|
+
response += `Found developer notes in **${todoCount} files**:\n\n`;
|
|
1829
|
+
|
|
1830
|
+
// Count by type
|
|
1831
|
+
try {
|
|
1832
|
+
const todoTypes = execSync(
|
|
1833
|
+
'grep -roh "TODO\\|FIXME\\|HACK\\|XXX\\|BUG" --include="*.ts" --include="*.tsx" 2>/dev/null | sort | uniq -c | sort -rg',
|
|
1834
|
+
{ cwd, encoding: 'utf-8', timeout: 10000 }
|
|
1835
|
+
).trim();
|
|
1836
|
+
|
|
1837
|
+
if (todoTypes) {
|
|
1838
|
+
todoTypes.split('\n').forEach(line => {
|
|
1839
|
+
const match = line.trim().match(/^\s*(\d+)\s+(.+)$/);
|
|
1840
|
+
if (match) {
|
|
1841
|
+
const [, count, type] = match;
|
|
1842
|
+
const icon = type === 'FIXME' || type === 'BUG' ? '🔴' :
|
|
1843
|
+
type === 'HACK' ? '🟡' : '📝';
|
|
1844
|
+
response += `- ${icon} ${count} ${type}s\n`;
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
response += `\n`;
|
|
1848
|
+
}
|
|
1849
|
+
} catch {
|
|
1850
|
+
// Ignore count errors
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
response += `*I can implement these TODOs and fix FIXMEs during upgrade.*\n\n`;
|
|
1854
|
+
}
|
|
1855
|
+
} else {
|
|
1856
|
+
response += `✅ No TODO/FIXME comments found - clean codebase!\n\n`;
|
|
1857
|
+
}
|
|
1858
|
+
} catch {
|
|
1859
|
+
response += `*(TODO scan unavailable)*\n\n`;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
response += `---\n\n`;
|
|
1863
|
+
|
|
1683
1864
|
// Scan for upgrade opportunities
|
|
1684
1865
|
response += `## Upgrade Opportunities\n\n`;
|
|
1685
1866
|
|
|
@@ -1745,6 +1926,17 @@ Just describe what you want to build! I'll automatically:
|
|
|
1745
1926
|
response += `✅ No major upgrade opportunities detected!\n\n`;
|
|
1746
1927
|
}
|
|
1747
1928
|
|
|
1929
|
+
// Review mode options
|
|
1930
|
+
response += `---\n\n`;
|
|
1931
|
+
response += `## Review Modes\n\n`;
|
|
1932
|
+
response += `Pick a focus for the upgrade:\n\n`;
|
|
1933
|
+
response += `- **Security Audit** - Auth, secrets, injections, OWASP top 10\n`;
|
|
1934
|
+
response += `- **Performance Review** - Bundle size, queries, caching\n`;
|
|
1935
|
+
response += `- **Code Quality** - Patterns, DRY, complexity\n`;
|
|
1936
|
+
response += `- **Pre-Launch** - Everything for production\n`;
|
|
1937
|
+
response += `- **Quick Scan** - Top 5 issues only\n`;
|
|
1938
|
+
response += `- **Comprehensive** - All of the above\n\n`;
|
|
1939
|
+
|
|
1748
1940
|
// Recommendations
|
|
1749
1941
|
response += `---\n\n`;
|
|
1750
1942
|
response += `## Recommended Actions\n\n`;
|
|
@@ -1753,12 +1945,10 @@ Just describe what you want to build! I'll automatically:
|
|
|
1753
1945
|
response += `**(Dry Run Mode - No changes will be made)**\n\n`;
|
|
1754
1946
|
}
|
|
1755
1947
|
|
|
1756
|
-
response += `1.
|
|
1757
|
-
response += `2.
|
|
1758
|
-
response += `3.
|
|
1759
|
-
response += `
|
|
1760
|
-
response += ` - "Add error boundaries to components"\n`;
|
|
1761
|
-
response += ` - "Set up Playwright testing"\n\n`;
|
|
1948
|
+
response += `1. Tell me your main concerns (security, performance, etc.)\n`;
|
|
1949
|
+
response += `2. I'll prioritize fixes based on your needs\n`;
|
|
1950
|
+
response += `3. Hot spot files get fixed first (where bugs live)\n`;
|
|
1951
|
+
response += `4. I'll implement your TODOs and fix FIXMEs along the way\n\n`;
|
|
1762
1952
|
|
|
1763
1953
|
response += `---\n\n`;
|
|
1764
1954
|
response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
|
|
@@ -1888,6 +2078,175 @@ Just describe what you want to build! I'll automatically:
|
|
|
1888
2078
|
};
|
|
1889
2079
|
}
|
|
1890
2080
|
|
|
2081
|
+
private handleRunTests(args: { filter?: string; watch?: boolean }) {
|
|
2082
|
+
const { filter, watch = false } = args;
|
|
2083
|
+
const cwd = process.cwd();
|
|
2084
|
+
|
|
2085
|
+
// Detect test runner from package.json
|
|
2086
|
+
let testCommand = 'npm test';
|
|
2087
|
+
try {
|
|
2088
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
2089
|
+
if (fs.existsSync(pkgPath)) {
|
|
2090
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
2091
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2092
|
+
|
|
2093
|
+
if (deps['@playwright/test']) {
|
|
2094
|
+
testCommand = 'npx playwright test';
|
|
2095
|
+
} else if (deps['vitest']) {
|
|
2096
|
+
testCommand = 'npx vitest run';
|
|
2097
|
+
} else if (deps['jest']) {
|
|
2098
|
+
testCommand = 'npx jest';
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
} catch {
|
|
2102
|
+
// Use default
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// Add filter if provided
|
|
2106
|
+
if (filter) {
|
|
2107
|
+
testCommand += ` ${filter}`;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// Add watch mode
|
|
2111
|
+
if (watch) {
|
|
2112
|
+
testCommand = testCommand.replace(' run', ''); // Remove 'run' for vitest watch
|
|
2113
|
+
if (testCommand.includes('vitest')) {
|
|
2114
|
+
testCommand = testCommand.replace('vitest run', 'vitest');
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
let response = `# 🧪 Running Tests\n\n`;
|
|
2119
|
+
response += `**Command:** \`${testCommand}\`\n\n`;
|
|
2120
|
+
|
|
2121
|
+
try {
|
|
2122
|
+
const output = execSync(testCommand, {
|
|
2123
|
+
cwd,
|
|
2124
|
+
encoding: 'utf-8',
|
|
2125
|
+
timeout: 120000, // 2 minute timeout
|
|
2126
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
response += `## ✅ Tests Passed\n\n`;
|
|
2130
|
+
response += '```\n' + output.slice(-2000) + '\n```\n';
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
const execError = error as { stdout?: string; stderr?: string; status?: number };
|
|
2133
|
+
response += `## ❌ Tests Failed\n\n`;
|
|
2134
|
+
|
|
2135
|
+
if (execError.stdout) {
|
|
2136
|
+
response += '### Output\n```\n' + execError.stdout.slice(-2000) + '\n```\n\n';
|
|
2137
|
+
}
|
|
2138
|
+
if (execError.stderr) {
|
|
2139
|
+
response += '### Errors\n```\n' + execError.stderr.slice(-1000) + '\n```\n\n';
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
response += `**Exit Code:** ${execError.status || 1}\n\n`;
|
|
2143
|
+
response += `---\n\n*Fix the failing tests and run again.*`;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
return {
|
|
2147
|
+
content: [{
|
|
2148
|
+
type: 'text' as const,
|
|
2149
|
+
text: response,
|
|
2150
|
+
}],
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
private async handleReportPatternGap(args: { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean }) {
|
|
2155
|
+
const { category, request, context, handledWith, wasSuccessful = true } = args;
|
|
2156
|
+
|
|
2157
|
+
try {
|
|
2158
|
+
const response = await fetch(`${this.apiUrl}/api/pattern-gaps`, {
|
|
2159
|
+
method: 'POST',
|
|
2160
|
+
headers: {
|
|
2161
|
+
'Content-Type': 'application/json',
|
|
2162
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2163
|
+
},
|
|
2164
|
+
body: JSON.stringify({
|
|
2165
|
+
category,
|
|
2166
|
+
request,
|
|
2167
|
+
context,
|
|
2168
|
+
handledWith,
|
|
2169
|
+
wasSuccessful,
|
|
2170
|
+
}),
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2173
|
+
if (!response.ok) {
|
|
2174
|
+
const error = await response.json().catch(() => ({}));
|
|
2175
|
+
throw new Error(error.error || 'Failed to report pattern gap');
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
const result = await response.json();
|
|
2179
|
+
|
|
2180
|
+
if (result.deduplicated) {
|
|
2181
|
+
return {
|
|
2182
|
+
content: [{
|
|
2183
|
+
type: 'text' as const,
|
|
2184
|
+
text: `📊 Pattern gap already reported recently (category: ${category}). No duplicate created.`,
|
|
2185
|
+
}],
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
return {
|
|
2190
|
+
content: [{
|
|
2191
|
+
type: 'text' as const,
|
|
2192
|
+
text: `✅ Pattern gap reported successfully.\n\n**Category:** ${category}\n**Request:** ${request}\n\nThis helps improve CodeBakers for everyone!`,
|
|
2193
|
+
}],
|
|
2194
|
+
};
|
|
2195
|
+
} catch (error) {
|
|
2196
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
2197
|
+
return {
|
|
2198
|
+
content: [{
|
|
2199
|
+
type: 'text' as const,
|
|
2200
|
+
text: `⚠️ Could not report pattern gap: ${message}\n\n(This doesn't affect your current work)`,
|
|
2201
|
+
}],
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
private async handleTrackAnalytics(args: { eventType: string; eventData?: Record<string, unknown>; projectHash?: string }) {
|
|
2207
|
+
const { eventType, eventData, projectHash } = args;
|
|
2208
|
+
|
|
2209
|
+
try {
|
|
2210
|
+
const response = await fetch(`${this.apiUrl}/api/cli/analytics`, {
|
|
2211
|
+
method: 'POST',
|
|
2212
|
+
headers: {
|
|
2213
|
+
'Content-Type': 'application/json',
|
|
2214
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2215
|
+
},
|
|
2216
|
+
body: JSON.stringify({
|
|
2217
|
+
eventType,
|
|
2218
|
+
eventData,
|
|
2219
|
+
projectHash,
|
|
2220
|
+
}),
|
|
2221
|
+
});
|
|
2222
|
+
|
|
2223
|
+
if (!response.ok) {
|
|
2224
|
+
// Silently fail - analytics shouldn't interrupt user workflow
|
|
2225
|
+
return {
|
|
2226
|
+
content: [{
|
|
2227
|
+
type: 'text' as const,
|
|
2228
|
+
text: `📊 Analytics tracked: ${eventType}`,
|
|
2229
|
+
}],
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
return {
|
|
2234
|
+
content: [{
|
|
2235
|
+
type: 'text' as const,
|
|
2236
|
+
text: `📊 Analytics tracked: ${eventType}`,
|
|
2237
|
+
}],
|
|
2238
|
+
};
|
|
2239
|
+
} catch {
|
|
2240
|
+
// Silently fail
|
|
2241
|
+
return {
|
|
2242
|
+
content: [{
|
|
2243
|
+
type: 'text' as const,
|
|
2244
|
+
text: `📊 Analytics tracked: ${eventType}`,
|
|
2245
|
+
}],
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
1891
2250
|
async run(): Promise<void> {
|
|
1892
2251
|
const transport = new StdioServerTransport();
|
|
1893
2252
|
await this.server.connect(transport);
|