@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.
@@ -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. 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`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.4.0",
3
+ "version": "2.6.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
@@ -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. 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`;
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`;