@codebakers/cli 2.5.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.
@@ -1646,6 +1646,248 @@ Just describe what you want to build! I'll automatically:
1646
1646
  catch {
1647
1647
  response += `*(TODO scan unavailable)*\n\n`;
1648
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
+ }
1649
1891
  response += `---\n\n`;
1650
1892
  // Scan for upgrade opportunities
1651
1893
  response += `## Upgrade Opportunities\n\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.5.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
@@ -1859,6 +1859,251 @@ Just describe what you want to build! I'll automatically:
1859
1859
  response += `*(TODO scan unavailable)*\n\n`;
1860
1860
  }
1861
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
+
1862
2107
  response += `---\n\n`;
1863
2108
 
1864
2109
  // Scan for upgrade opportunities