@codebakers/cli 2.4.0 → 2.6.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 +334 -6
- package/package.json +1 -1
- package/src/mcp/server.ts +359 -6
package/dist/mcp/server.js
CHANGED
|
@@ -1536,6 +1536,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
1536
1536
|
async handleUpgrade(args) {
|
|
1537
1537
|
const { areas = ['all'], severity = 'all', dryRun = false } = args;
|
|
1538
1538
|
const context = this.gatherProjectContext();
|
|
1539
|
+
const cwd = process.cwd();
|
|
1539
1540
|
let response = `# ⬆️ Project Upgrade Analysis\n\n`;
|
|
1540
1541
|
// Stack detection
|
|
1541
1542
|
response += `## Your Stack (Preserving As-Is)\n\n`;
|
|
@@ -1569,6 +1570,325 @@ Just describe what you want to build! I'll automatically:
|
|
|
1569
1570
|
const hasNext = context.dependencies.includes('next');
|
|
1570
1571
|
response += `| Framework | ${hasNext ? 'Next.js' : 'Unknown'} | ✓ Keeping |\n`;
|
|
1571
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
|
+
// Dependency Security Scan (npm audit)
|
|
1650
|
+
response += `### 🔒 Dependency Security Scan\n\n`;
|
|
1651
|
+
try {
|
|
1652
|
+
const auditOutput = (0, child_process_1.execSync)('npm audit --json 2>/dev/null', {
|
|
1653
|
+
cwd,
|
|
1654
|
+
encoding: 'utf-8',
|
|
1655
|
+
timeout: 30000,
|
|
1656
|
+
});
|
|
1657
|
+
const audit = JSON.parse(auditOutput);
|
|
1658
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1659
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1660
|
+
if (total > 0) {
|
|
1661
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1662
|
+
if (vulns.critical > 0)
|
|
1663
|
+
response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1664
|
+
if (vulns.high > 0)
|
|
1665
|
+
response += `- 🟠 **${vulns.high} High**\n`;
|
|
1666
|
+
if (vulns.moderate > 0)
|
|
1667
|
+
response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1668
|
+
if (vulns.low > 0)
|
|
1669
|
+
response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1670
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
response += `✅ No known vulnerabilities in dependencies!\n\n`;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
catch (error) {
|
|
1677
|
+
// npm audit exits with non-zero if vulnerabilities found
|
|
1678
|
+
const execError = error;
|
|
1679
|
+
if (execError.stdout) {
|
|
1680
|
+
try {
|
|
1681
|
+
const audit = JSON.parse(execError.stdout);
|
|
1682
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1683
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1684
|
+
if (total > 0) {
|
|
1685
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1686
|
+
if (vulns.critical > 0)
|
|
1687
|
+
response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1688
|
+
if (vulns.high > 0)
|
|
1689
|
+
response += `- 🟠 **${vulns.high} High**\n`;
|
|
1690
|
+
if (vulns.moderate > 0)
|
|
1691
|
+
response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1692
|
+
if (vulns.low > 0)
|
|
1693
|
+
response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1694
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
catch {
|
|
1698
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
else {
|
|
1702
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
// TypeScript Strictness Check
|
|
1706
|
+
response += `### 📝 TypeScript Configuration\n\n`;
|
|
1707
|
+
try {
|
|
1708
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
1709
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
1710
|
+
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
|
|
1711
|
+
const compilerOptions = tsconfig.compilerOptions || {};
|
|
1712
|
+
const checks = [
|
|
1713
|
+
{ name: 'strict', value: compilerOptions.strict, recommended: true },
|
|
1714
|
+
{ name: 'noImplicitAny', value: compilerOptions.noImplicitAny, recommended: true },
|
|
1715
|
+
{ name: 'strictNullChecks', value: compilerOptions.strictNullChecks, recommended: true },
|
|
1716
|
+
{ name: 'noUnusedLocals', value: compilerOptions.noUnusedLocals, recommended: true },
|
|
1717
|
+
{ name: 'noUnusedParameters', value: compilerOptions.noUnusedParameters, recommended: true },
|
|
1718
|
+
];
|
|
1719
|
+
const enabled = checks.filter(c => c.value === true);
|
|
1720
|
+
const missing = checks.filter(c => c.value !== true && c.recommended);
|
|
1721
|
+
if (compilerOptions.strict === true) {
|
|
1722
|
+
response += `✅ **Strict mode enabled** - Good!\n\n`;
|
|
1723
|
+
}
|
|
1724
|
+
else {
|
|
1725
|
+
response += `⚠️ **Strict mode not enabled**\n\n`;
|
|
1726
|
+
if (missing.length > 0) {
|
|
1727
|
+
response += `Missing recommended options:\n`;
|
|
1728
|
+
missing.forEach(m => {
|
|
1729
|
+
response += `- \`${m.name}: true\`\n`;
|
|
1730
|
+
});
|
|
1731
|
+
response += `\n`;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
// Check for any types
|
|
1735
|
+
try {
|
|
1736
|
+
const anyCount = (0, child_process_1.execSync)('grep -r ": any" --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v node_modules | wc -l', { cwd, encoding: 'utf-8', timeout: 10000 }).trim();
|
|
1737
|
+
const count = parseInt(anyCount);
|
|
1738
|
+
if (count > 10) {
|
|
1739
|
+
response += `⚠️ Found **${count}** uses of \`: any\` - consider typing these\n\n`;
|
|
1740
|
+
}
|
|
1741
|
+
else if (count > 0) {
|
|
1742
|
+
response += `📝 Found **${count}** uses of \`: any\`\n\n`;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
catch {
|
|
1746
|
+
// Ignore
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
else {
|
|
1750
|
+
response += `⚠️ No tsconfig.json found - not a TypeScript project?\n\n`;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
catch {
|
|
1754
|
+
response += `*(TypeScript check unavailable)*\n\n`;
|
|
1755
|
+
}
|
|
1756
|
+
// Environment Variable Audit
|
|
1757
|
+
response += `### 🔐 Environment Variable Audit\n\n`;
|
|
1758
|
+
try {
|
|
1759
|
+
const envExamplePath = path.join(cwd, '.env.example');
|
|
1760
|
+
const envLocalPath = path.join(cwd, '.env.local');
|
|
1761
|
+
const envPath = path.join(cwd, '.env');
|
|
1762
|
+
const hasEnvExample = fs.existsSync(envExamplePath);
|
|
1763
|
+
const hasEnvLocal = fs.existsSync(envLocalPath);
|
|
1764
|
+
const hasEnv = fs.existsSync(envPath);
|
|
1765
|
+
if (hasEnvExample) {
|
|
1766
|
+
response += `✅ \`.env.example\` exists - good for documentation\n`;
|
|
1767
|
+
}
|
|
1768
|
+
else {
|
|
1769
|
+
response += `⚠️ No \`.env.example\` - add one for team onboarding\n`;
|
|
1770
|
+
}
|
|
1771
|
+
// Check for hardcoded secrets in code
|
|
1772
|
+
try {
|
|
1773
|
+
const secretPatterns = [
|
|
1774
|
+
'sk-[a-zA-Z0-9]{20,}', // OpenAI keys
|
|
1775
|
+
'sk_live_[a-zA-Z0-9]+', // Stripe live keys
|
|
1776
|
+
'pk_live_[a-zA-Z0-9]+', // Stripe public live keys
|
|
1777
|
+
'ghp_[a-zA-Z0-9]+', // GitHub tokens
|
|
1778
|
+
'AKIA[A-Z0-9]{16}', // AWS access keys
|
|
1779
|
+
];
|
|
1780
|
+
let secretsFound = 0;
|
|
1781
|
+
for (const pattern of secretPatterns) {
|
|
1782
|
+
try {
|
|
1783
|
+
const matches = (0, child_process_1.execSync)(`grep -rE "${pattern}" --include="*.ts" --include="*.tsx" --include="*.js" 2>/dev/null | grep -v node_modules | grep -v ".env" | wc -l`, { cwd, encoding: 'utf-8', timeout: 5000 }).trim();
|
|
1784
|
+
secretsFound += parseInt(matches) || 0;
|
|
1785
|
+
}
|
|
1786
|
+
catch {
|
|
1787
|
+
// Pattern not found
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
if (secretsFound > 0) {
|
|
1791
|
+
response += `\n🔴 **CRITICAL: Found ${secretsFound} potential hardcoded secrets in code!**\n`;
|
|
1792
|
+
response += `*These should be moved to environment variables immediately.*\n`;
|
|
1793
|
+
}
|
|
1794
|
+
else {
|
|
1795
|
+
response += `\n✅ No hardcoded secrets detected in code\n`;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
catch {
|
|
1799
|
+
// Ignore
|
|
1800
|
+
}
|
|
1801
|
+
// Check if .env is gitignored
|
|
1802
|
+
try {
|
|
1803
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
1804
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1805
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1806
|
+
if (!gitignore.includes('.env')) {
|
|
1807
|
+
response += `\n⚠️ \`.env\` not in .gitignore - secrets could be committed!\n`;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
catch {
|
|
1812
|
+
// Ignore
|
|
1813
|
+
}
|
|
1814
|
+
response += `\n`;
|
|
1815
|
+
}
|
|
1816
|
+
catch {
|
|
1817
|
+
response += `*(Environment audit unavailable)*\n\n`;
|
|
1818
|
+
}
|
|
1819
|
+
// Test Coverage Analysis
|
|
1820
|
+
response += `### 🧪 Test Coverage\n\n`;
|
|
1821
|
+
try {
|
|
1822
|
+
const hasPlaywright = context.dependencies.includes('@playwright/test');
|
|
1823
|
+
const hasVitest = context.dependencies.includes('vitest');
|
|
1824
|
+
const hasJest = context.dependencies.includes('jest');
|
|
1825
|
+
if (hasPlaywright || hasVitest || hasJest) {
|
|
1826
|
+
const framework = hasPlaywright ? 'Playwright' : hasVitest ? 'Vitest' : 'Jest';
|
|
1827
|
+
response += `✅ Test framework detected: **${framework}**\n\n`;
|
|
1828
|
+
// Count test files
|
|
1829
|
+
try {
|
|
1830
|
+
const testFiles = (0, child_process_1.execSync)('find . -name "*.test.ts" -o -name "*.test.tsx" -o -name "*.spec.ts" -o -name "*.spec.tsx" 2>/dev/null | grep -v node_modules | wc -l', { cwd, encoding: 'utf-8', timeout: 10000 }).trim();
|
|
1831
|
+
const count = parseInt(testFiles);
|
|
1832
|
+
if (count > 0) {
|
|
1833
|
+
response += `Found **${count} test files**\n\n`;
|
|
1834
|
+
}
|
|
1835
|
+
else {
|
|
1836
|
+
response += `⚠️ No test files found - add tests!\n\n`;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
catch {
|
|
1840
|
+
// Ignore
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
else {
|
|
1844
|
+
response += `⚠️ **No test framework detected**\n\n`;
|
|
1845
|
+
response += `Recommended: Add \`vitest\` or \`@playwright/test\`\n\n`;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
catch {
|
|
1849
|
+
response += `*(Test analysis unavailable)*\n\n`;
|
|
1850
|
+
}
|
|
1851
|
+
// API Endpoint Inventory
|
|
1852
|
+
response += `### 🔌 API Endpoint Inventory\n\n`;
|
|
1853
|
+
if (context.existingApiRoutes.length > 0) {
|
|
1854
|
+
response += `Found **${context.existingApiRoutes.length} API routes**:\n\n`;
|
|
1855
|
+
// Check for auth protection on routes
|
|
1856
|
+
let protectedCount = 0;
|
|
1857
|
+
let unprotectedCount = 0;
|
|
1858
|
+
for (const route of context.existingApiRoutes.slice(0, 10)) {
|
|
1859
|
+
try {
|
|
1860
|
+
const routePath = path.join(cwd, route);
|
|
1861
|
+
if (fs.existsSync(routePath)) {
|
|
1862
|
+
const content = fs.readFileSync(routePath, 'utf-8');
|
|
1863
|
+
const hasAuth = content.includes('getServerSession') ||
|
|
1864
|
+
content.includes('auth(') ||
|
|
1865
|
+
content.includes('requireAuth') ||
|
|
1866
|
+
content.includes('Authorization') ||
|
|
1867
|
+
content.includes('authenticate');
|
|
1868
|
+
if (hasAuth) {
|
|
1869
|
+
protectedCount++;
|
|
1870
|
+
}
|
|
1871
|
+
else {
|
|
1872
|
+
unprotectedCount++;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
catch {
|
|
1877
|
+
// Ignore
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (protectedCount > 0 || unprotectedCount > 0) {
|
|
1881
|
+
response += `- 🔒 **${protectedCount}** routes with auth checks\n`;
|
|
1882
|
+
response += `- 🔓 **${unprotectedCount}** routes without visible auth\n\n`;
|
|
1883
|
+
if (unprotectedCount > protectedCount) {
|
|
1884
|
+
response += `⚠️ *Many routes lack visible auth - review if intentional*\n\n`;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
else {
|
|
1889
|
+
response += `No API routes detected yet.\n\n`;
|
|
1890
|
+
}
|
|
1891
|
+
response += `---\n\n`;
|
|
1572
1892
|
// Scan for upgrade opportunities
|
|
1573
1893
|
response += `## Upgrade Opportunities\n\n`;
|
|
1574
1894
|
const upgrades = [];
|
|
@@ -1626,18 +1946,26 @@ Just describe what you want to build! I'll automatically:
|
|
|
1626
1946
|
if (upgrades.length === 0) {
|
|
1627
1947
|
response += `✅ No major upgrade opportunities detected!\n\n`;
|
|
1628
1948
|
}
|
|
1949
|
+
// Review mode options
|
|
1950
|
+
response += `---\n\n`;
|
|
1951
|
+
response += `## Review Modes\n\n`;
|
|
1952
|
+
response += `Pick a focus for the upgrade:\n\n`;
|
|
1953
|
+
response += `- **Security Audit** - Auth, secrets, injections, OWASP top 10\n`;
|
|
1954
|
+
response += `- **Performance Review** - Bundle size, queries, caching\n`;
|
|
1955
|
+
response += `- **Code Quality** - Patterns, DRY, complexity\n`;
|
|
1956
|
+
response += `- **Pre-Launch** - Everything for production\n`;
|
|
1957
|
+
response += `- **Quick Scan** - Top 5 issues only\n`;
|
|
1958
|
+
response += `- **Comprehensive** - All of the above\n\n`;
|
|
1629
1959
|
// Recommendations
|
|
1630
1960
|
response += `---\n\n`;
|
|
1631
1961
|
response += `## Recommended Actions\n\n`;
|
|
1632
1962
|
if (dryRun) {
|
|
1633
1963
|
response += `**(Dry Run Mode - No changes will be made)**\n\n`;
|
|
1634
1964
|
}
|
|
1635
|
-
response += `1.
|
|
1636
|
-
response += `2.
|
|
1637
|
-
response += `3.
|
|
1638
|
-
response += `
|
|
1639
|
-
response += ` - "Add error boundaries to components"\n`;
|
|
1640
|
-
response += ` - "Set up Playwright testing"\n\n`;
|
|
1965
|
+
response += `1. Tell me your main concerns (security, performance, etc.)\n`;
|
|
1966
|
+
response += `2. I'll prioritize fixes based on your needs\n`;
|
|
1967
|
+
response += `3. Hot spot files get fixed first (where bugs live)\n`;
|
|
1968
|
+
response += `4. I'll implement your TODOs and fix FIXMEs along the way\n\n`;
|
|
1641
1969
|
response += `---\n\n`;
|
|
1642
1970
|
response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
|
|
1643
1971
|
return {
|
package/package.json
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -1729,6 +1729,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
1729
1729
|
private async handleUpgrade(args: { areas?: string[]; severity?: string; dryRun?: boolean }) {
|
|
1730
1730
|
const { areas = ['all'], severity = 'all', dryRun = false } = args;
|
|
1731
1731
|
const context = this.gatherProjectContext();
|
|
1732
|
+
const cwd = process.cwd();
|
|
1732
1733
|
|
|
1733
1734
|
let response = `# ⬆️ Project Upgrade Analysis\n\n`;
|
|
1734
1735
|
|
|
@@ -1762,6 +1763,349 @@ Just describe what you want to build! I'll automatically:
|
|
|
1762
1763
|
|
|
1763
1764
|
response += `\n---\n\n`;
|
|
1764
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
|
+
// Dependency Security Scan (npm audit)
|
|
1863
|
+
response += `### 🔒 Dependency Security Scan\n\n`;
|
|
1864
|
+
try {
|
|
1865
|
+
const auditOutput = execSync('npm audit --json 2>/dev/null', {
|
|
1866
|
+
cwd,
|
|
1867
|
+
encoding: 'utf-8',
|
|
1868
|
+
timeout: 30000,
|
|
1869
|
+
});
|
|
1870
|
+
const audit = JSON.parse(auditOutput);
|
|
1871
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1872
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1873
|
+
|
|
1874
|
+
if (total > 0) {
|
|
1875
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1876
|
+
if (vulns.critical > 0) response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1877
|
+
if (vulns.high > 0) response += `- 🟠 **${vulns.high} High**\n`;
|
|
1878
|
+
if (vulns.moderate > 0) response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1879
|
+
if (vulns.low > 0) response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1880
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1881
|
+
} else {
|
|
1882
|
+
response += `✅ No known vulnerabilities in dependencies!\n\n`;
|
|
1883
|
+
}
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
// npm audit exits with non-zero if vulnerabilities found
|
|
1886
|
+
const execError = error as { stdout?: string };
|
|
1887
|
+
if (execError.stdout) {
|
|
1888
|
+
try {
|
|
1889
|
+
const audit = JSON.parse(execError.stdout);
|
|
1890
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1891
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1892
|
+
|
|
1893
|
+
if (total > 0) {
|
|
1894
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1895
|
+
if (vulns.critical > 0) response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1896
|
+
if (vulns.high > 0) response += `- 🟠 **${vulns.high} High**\n`;
|
|
1897
|
+
if (vulns.moderate > 0) response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1898
|
+
if (vulns.low > 0) response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1899
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1900
|
+
}
|
|
1901
|
+
} catch {
|
|
1902
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1903
|
+
}
|
|
1904
|
+
} else {
|
|
1905
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// TypeScript Strictness Check
|
|
1910
|
+
response += `### 📝 TypeScript Configuration\n\n`;
|
|
1911
|
+
try {
|
|
1912
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
1913
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
1914
|
+
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
|
|
1915
|
+
const compilerOptions = tsconfig.compilerOptions || {};
|
|
1916
|
+
|
|
1917
|
+
const checks = [
|
|
1918
|
+
{ name: 'strict', value: compilerOptions.strict, recommended: true },
|
|
1919
|
+
{ name: 'noImplicitAny', value: compilerOptions.noImplicitAny, recommended: true },
|
|
1920
|
+
{ name: 'strictNullChecks', value: compilerOptions.strictNullChecks, recommended: true },
|
|
1921
|
+
{ name: 'noUnusedLocals', value: compilerOptions.noUnusedLocals, recommended: true },
|
|
1922
|
+
{ name: 'noUnusedParameters', value: compilerOptions.noUnusedParameters, recommended: true },
|
|
1923
|
+
];
|
|
1924
|
+
|
|
1925
|
+
const enabled = checks.filter(c => c.value === true);
|
|
1926
|
+
const missing = checks.filter(c => c.value !== true && c.recommended);
|
|
1927
|
+
|
|
1928
|
+
if (compilerOptions.strict === true) {
|
|
1929
|
+
response += `✅ **Strict mode enabled** - Good!\n\n`;
|
|
1930
|
+
} else {
|
|
1931
|
+
response += `⚠️ **Strict mode not enabled**\n\n`;
|
|
1932
|
+
if (missing.length > 0) {
|
|
1933
|
+
response += `Missing recommended options:\n`;
|
|
1934
|
+
missing.forEach(m => {
|
|
1935
|
+
response += `- \`${m.name}: true\`\n`;
|
|
1936
|
+
});
|
|
1937
|
+
response += `\n`;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// Check for any types
|
|
1942
|
+
try {
|
|
1943
|
+
const anyCount = execSync(
|
|
1944
|
+
'grep -r ": any" --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v node_modules | wc -l',
|
|
1945
|
+
{ cwd, encoding: 'utf-8', timeout: 10000 }
|
|
1946
|
+
).trim();
|
|
1947
|
+
const count = parseInt(anyCount);
|
|
1948
|
+
if (count > 10) {
|
|
1949
|
+
response += `⚠️ Found **${count}** uses of \`: any\` - consider typing these\n\n`;
|
|
1950
|
+
} else if (count > 0) {
|
|
1951
|
+
response += `📝 Found **${count}** uses of \`: any\`\n\n`;
|
|
1952
|
+
}
|
|
1953
|
+
} catch {
|
|
1954
|
+
// Ignore
|
|
1955
|
+
}
|
|
1956
|
+
} else {
|
|
1957
|
+
response += `⚠️ No tsconfig.json found - not a TypeScript project?\n\n`;
|
|
1958
|
+
}
|
|
1959
|
+
} catch {
|
|
1960
|
+
response += `*(TypeScript check unavailable)*\n\n`;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// Environment Variable Audit
|
|
1964
|
+
response += `### 🔐 Environment Variable Audit\n\n`;
|
|
1965
|
+
try {
|
|
1966
|
+
const envExamplePath = path.join(cwd, '.env.example');
|
|
1967
|
+
const envLocalPath = path.join(cwd, '.env.local');
|
|
1968
|
+
const envPath = path.join(cwd, '.env');
|
|
1969
|
+
|
|
1970
|
+
const hasEnvExample = fs.existsSync(envExamplePath);
|
|
1971
|
+
const hasEnvLocal = fs.existsSync(envLocalPath);
|
|
1972
|
+
const hasEnv = fs.existsSync(envPath);
|
|
1973
|
+
|
|
1974
|
+
if (hasEnvExample) {
|
|
1975
|
+
response += `✅ \`.env.example\` exists - good for documentation\n`;
|
|
1976
|
+
} else {
|
|
1977
|
+
response += `⚠️ No \`.env.example\` - add one for team onboarding\n`;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
// Check for hardcoded secrets in code
|
|
1981
|
+
try {
|
|
1982
|
+
const secretPatterns = [
|
|
1983
|
+
'sk-[a-zA-Z0-9]{20,}', // OpenAI keys
|
|
1984
|
+
'sk_live_[a-zA-Z0-9]+', // Stripe live keys
|
|
1985
|
+
'pk_live_[a-zA-Z0-9]+', // Stripe public live keys
|
|
1986
|
+
'ghp_[a-zA-Z0-9]+', // GitHub tokens
|
|
1987
|
+
'AKIA[A-Z0-9]{16}', // AWS access keys
|
|
1988
|
+
];
|
|
1989
|
+
|
|
1990
|
+
let secretsFound = 0;
|
|
1991
|
+
for (const pattern of secretPatterns) {
|
|
1992
|
+
try {
|
|
1993
|
+
const matches = execSync(
|
|
1994
|
+
`grep -rE "${pattern}" --include="*.ts" --include="*.tsx" --include="*.js" 2>/dev/null | grep -v node_modules | grep -v ".env" | wc -l`,
|
|
1995
|
+
{ cwd, encoding: 'utf-8', timeout: 5000 }
|
|
1996
|
+
).trim();
|
|
1997
|
+
secretsFound += parseInt(matches) || 0;
|
|
1998
|
+
} catch {
|
|
1999
|
+
// Pattern not found
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
if (secretsFound > 0) {
|
|
2004
|
+
response += `\n🔴 **CRITICAL: Found ${secretsFound} potential hardcoded secrets in code!**\n`;
|
|
2005
|
+
response += `*These should be moved to environment variables immediately.*\n`;
|
|
2006
|
+
} else {
|
|
2007
|
+
response += `\n✅ No hardcoded secrets detected in code\n`;
|
|
2008
|
+
}
|
|
2009
|
+
} catch {
|
|
2010
|
+
// Ignore
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Check if .env is gitignored
|
|
2014
|
+
try {
|
|
2015
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
2016
|
+
if (fs.existsSync(gitignorePath)) {
|
|
2017
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
2018
|
+
if (!gitignore.includes('.env')) {
|
|
2019
|
+
response += `\n⚠️ \`.env\` not in .gitignore - secrets could be committed!\n`;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
} catch {
|
|
2023
|
+
// Ignore
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
response += `\n`;
|
|
2027
|
+
} catch {
|
|
2028
|
+
response += `*(Environment audit unavailable)*\n\n`;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// Test Coverage Analysis
|
|
2032
|
+
response += `### 🧪 Test Coverage\n\n`;
|
|
2033
|
+
try {
|
|
2034
|
+
const hasPlaywright = context.dependencies.includes('@playwright/test');
|
|
2035
|
+
const hasVitest = context.dependencies.includes('vitest');
|
|
2036
|
+
const hasJest = context.dependencies.includes('jest');
|
|
2037
|
+
|
|
2038
|
+
if (hasPlaywright || hasVitest || hasJest) {
|
|
2039
|
+
const framework = hasPlaywright ? 'Playwright' : hasVitest ? 'Vitest' : 'Jest';
|
|
2040
|
+
response += `✅ Test framework detected: **${framework}**\n\n`;
|
|
2041
|
+
|
|
2042
|
+
// Count test files
|
|
2043
|
+
try {
|
|
2044
|
+
const testFiles = execSync(
|
|
2045
|
+
'find . -name "*.test.ts" -o -name "*.test.tsx" -o -name "*.spec.ts" -o -name "*.spec.tsx" 2>/dev/null | grep -v node_modules | wc -l',
|
|
2046
|
+
{ cwd, encoding: 'utf-8', timeout: 10000 }
|
|
2047
|
+
).trim();
|
|
2048
|
+
const count = parseInt(testFiles);
|
|
2049
|
+
if (count > 0) {
|
|
2050
|
+
response += `Found **${count} test files**\n\n`;
|
|
2051
|
+
} else {
|
|
2052
|
+
response += `⚠️ No test files found - add tests!\n\n`;
|
|
2053
|
+
}
|
|
2054
|
+
} catch {
|
|
2055
|
+
// Ignore
|
|
2056
|
+
}
|
|
2057
|
+
} else {
|
|
2058
|
+
response += `⚠️ **No test framework detected**\n\n`;
|
|
2059
|
+
response += `Recommended: Add \`vitest\` or \`@playwright/test\`\n\n`;
|
|
2060
|
+
}
|
|
2061
|
+
} catch {
|
|
2062
|
+
response += `*(Test analysis unavailable)*\n\n`;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
// API Endpoint Inventory
|
|
2066
|
+
response += `### 🔌 API Endpoint Inventory\n\n`;
|
|
2067
|
+
if (context.existingApiRoutes.length > 0) {
|
|
2068
|
+
response += `Found **${context.existingApiRoutes.length} API routes**:\n\n`;
|
|
2069
|
+
|
|
2070
|
+
// Check for auth protection on routes
|
|
2071
|
+
let protectedCount = 0;
|
|
2072
|
+
let unprotectedCount = 0;
|
|
2073
|
+
|
|
2074
|
+
for (const route of context.existingApiRoutes.slice(0, 10)) {
|
|
2075
|
+
try {
|
|
2076
|
+
const routePath = path.join(cwd, route);
|
|
2077
|
+
if (fs.existsSync(routePath)) {
|
|
2078
|
+
const content = fs.readFileSync(routePath, 'utf-8');
|
|
2079
|
+
const hasAuth = content.includes('getServerSession') ||
|
|
2080
|
+
content.includes('auth(') ||
|
|
2081
|
+
content.includes('requireAuth') ||
|
|
2082
|
+
content.includes('Authorization') ||
|
|
2083
|
+
content.includes('authenticate');
|
|
2084
|
+
if (hasAuth) {
|
|
2085
|
+
protectedCount++;
|
|
2086
|
+
} else {
|
|
2087
|
+
unprotectedCount++;
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
} catch {
|
|
2091
|
+
// Ignore
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
if (protectedCount > 0 || unprotectedCount > 0) {
|
|
2096
|
+
response += `- 🔒 **${protectedCount}** routes with auth checks\n`;
|
|
2097
|
+
response += `- 🔓 **${unprotectedCount}** routes without visible auth\n\n`;
|
|
2098
|
+
|
|
2099
|
+
if (unprotectedCount > protectedCount) {
|
|
2100
|
+
response += `⚠️ *Many routes lack visible auth - review if intentional*\n\n`;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
} else {
|
|
2104
|
+
response += `No API routes detected yet.\n\n`;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
response += `---\n\n`;
|
|
2108
|
+
|
|
1765
2109
|
// Scan for upgrade opportunities
|
|
1766
2110
|
response += `## Upgrade Opportunities\n\n`;
|
|
1767
2111
|
|
|
@@ -1827,6 +2171,17 @@ Just describe what you want to build! I'll automatically:
|
|
|
1827
2171
|
response += `✅ No major upgrade opportunities detected!\n\n`;
|
|
1828
2172
|
}
|
|
1829
2173
|
|
|
2174
|
+
// Review mode options
|
|
2175
|
+
response += `---\n\n`;
|
|
2176
|
+
response += `## Review Modes\n\n`;
|
|
2177
|
+
response += `Pick a focus for the upgrade:\n\n`;
|
|
2178
|
+
response += `- **Security Audit** - Auth, secrets, injections, OWASP top 10\n`;
|
|
2179
|
+
response += `- **Performance Review** - Bundle size, queries, caching\n`;
|
|
2180
|
+
response += `- **Code Quality** - Patterns, DRY, complexity\n`;
|
|
2181
|
+
response += `- **Pre-Launch** - Everything for production\n`;
|
|
2182
|
+
response += `- **Quick Scan** - Top 5 issues only\n`;
|
|
2183
|
+
response += `- **Comprehensive** - All of the above\n\n`;
|
|
2184
|
+
|
|
1830
2185
|
// Recommendations
|
|
1831
2186
|
response += `---\n\n`;
|
|
1832
2187
|
response += `## Recommended Actions\n\n`;
|
|
@@ -1835,12 +2190,10 @@ Just describe what you want to build! I'll automatically:
|
|
|
1835
2190
|
response += `**(Dry Run Mode - No changes will be made)**\n\n`;
|
|
1836
2191
|
}
|
|
1837
2192
|
|
|
1838
|
-
response += `1.
|
|
1839
|
-
response += `2.
|
|
1840
|
-
response += `3.
|
|
1841
|
-
response += `
|
|
1842
|
-
response += ` - "Add error boundaries to components"\n`;
|
|
1843
|
-
response += ` - "Set up Playwright testing"\n\n`;
|
|
2193
|
+
response += `1. Tell me your main concerns (security, performance, etc.)\n`;
|
|
2194
|
+
response += `2. I'll prioritize fixes based on your needs\n`;
|
|
2195
|
+
response += `3. Hot spot files get fixed first (where bugs live)\n`;
|
|
2196
|
+
response += `4. I'll implement your TODOs and fix FIXMEs along the way\n\n`;
|
|
1844
2197
|
|
|
1845
2198
|
response += `---\n\n`;
|
|
1846
2199
|
response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
|