@codebakers/cli 2.5.0 → 2.6.1
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/commands/install.js +10 -1
- package/dist/commands/upgrade.js +9 -0
- package/dist/mcp/server.js +299 -4
- package/package.json +1 -1
- package/src/commands/install.ts +11 -1
- package/src/commands/upgrade.ts +10 -0
- package/src/mcp/server.ts +321 -4
package/dist/commands/install.js
CHANGED
|
@@ -9,6 +9,7 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
9
9
|
const fs_1 = require("fs");
|
|
10
10
|
const path_1 = require("path");
|
|
11
11
|
const config_js_1 = require("../config.js");
|
|
12
|
+
const api_js_1 = require("../lib/api.js");
|
|
12
13
|
async function install() {
|
|
13
14
|
console.log(chalk_1.default.blue('\n CodeBakers Install\n'));
|
|
14
15
|
const apiKey = (0, config_js_1.getApiKey)();
|
|
@@ -41,14 +42,22 @@ async function install() {
|
|
|
41
42
|
(0, fs_1.writeFileSync)((0, path_1.join)(cwd, 'CLAUDE.md'), content.router);
|
|
42
43
|
}
|
|
43
44
|
// Write modules
|
|
45
|
+
const modulesDir = (0, path_1.join)(cwd, '.claude');
|
|
44
46
|
if (content.modules && Object.keys(content.modules).length > 0) {
|
|
45
|
-
const modulesDir = (0, path_1.join)(cwd, '.claude');
|
|
46
47
|
if (!(0, fs_1.existsSync)(modulesDir)) {
|
|
47
48
|
(0, fs_1.mkdirSync)(modulesDir, { recursive: true });
|
|
48
49
|
}
|
|
49
50
|
for (const [name, data] of Object.entries(content.modules)) {
|
|
50
51
|
(0, fs_1.writeFileSync)((0, path_1.join)(modulesDir, name), data);
|
|
51
52
|
}
|
|
53
|
+
// Write version file for tracking
|
|
54
|
+
const versionInfo = {
|
|
55
|
+
version: content.version,
|
|
56
|
+
moduleCount: Object.keys(content.modules).length,
|
|
57
|
+
installedAt: new Date().toISOString(),
|
|
58
|
+
cliVersion: (0, api_js_1.getCliVersion)(),
|
|
59
|
+
};
|
|
60
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(modulesDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
|
|
52
61
|
}
|
|
53
62
|
// Add to .gitignore if not present
|
|
54
63
|
const gitignorePath = (0, path_1.join)(cwd, '.gitignore');
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -74,6 +74,15 @@ async function upgrade() {
|
|
|
74
74
|
}
|
|
75
75
|
console.log(chalk_1.default.green(` ✓ Updated ${moduleCount} modules in .claude/`));
|
|
76
76
|
}
|
|
77
|
+
// Write version file for tracking
|
|
78
|
+
const versionInfo = {
|
|
79
|
+
version: content.version,
|
|
80
|
+
moduleCount,
|
|
81
|
+
updatedAt: new Date().toISOString(),
|
|
82
|
+
cliVersion: (0, api_js_1.getCliVersion)(),
|
|
83
|
+
};
|
|
84
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
|
|
85
|
+
console.log(chalk_1.default.green(' ✓ Version info saved'));
|
|
77
86
|
console.log(chalk_1.default.green(`\n ✅ Upgraded to patterns v${content.version}!\n`));
|
|
78
87
|
// Show what's new if available
|
|
79
88
|
console.log(chalk_1.default.gray(' Changes take effect in your next AI session.\n'));
|
package/dist/mcp/server.js
CHANGED
|
@@ -41,6 +41,7 @@ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
|
41
41
|
const config_js_1 = require("../config.js");
|
|
42
42
|
const audit_js_1 = require("../commands/audit.js");
|
|
43
43
|
const heal_js_1 = require("../commands/heal.js");
|
|
44
|
+
const api_js_1 = require("../lib/api.js");
|
|
44
45
|
const fs = __importStar(require("fs"));
|
|
45
46
|
const path = __importStar(require("path"));
|
|
46
47
|
const child_process_1 = require("child_process");
|
|
@@ -239,6 +240,46 @@ class CodeBakersServer {
|
|
|
239
240
|
}
|
|
240
241
|
return context;
|
|
241
242
|
}
|
|
243
|
+
async checkPatternVersion() {
|
|
244
|
+
const cwd = process.cwd();
|
|
245
|
+
const versionPath = path.join(cwd, '.claude', '.version.json');
|
|
246
|
+
// Read local version
|
|
247
|
+
let installed = null;
|
|
248
|
+
if (fs.existsSync(versionPath)) {
|
|
249
|
+
try {
|
|
250
|
+
installed = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Ignore parse errors
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Fetch latest version from API
|
|
257
|
+
let latest = null;
|
|
258
|
+
try {
|
|
259
|
+
const response = await fetch(`${this.apiUrl}/api/content/version`, {
|
|
260
|
+
headers: this.apiKey ? { 'Authorization': `Bearer ${this.apiKey}` } : {},
|
|
261
|
+
});
|
|
262
|
+
if (response.ok) {
|
|
263
|
+
latest = await response.json();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Ignore fetch errors
|
|
268
|
+
}
|
|
269
|
+
// Compare versions
|
|
270
|
+
let updateAvailable = false;
|
|
271
|
+
let message = null;
|
|
272
|
+
if (installed && latest) {
|
|
273
|
+
if (installed.version !== latest.version) {
|
|
274
|
+
updateAvailable = true;
|
|
275
|
+
message = `⚠️ Pattern update available: v${installed.version} → v${latest.version} (${latest.moduleCount - installed.moduleCount} new modules)\n Run \`codebakers upgrade\` to update`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else if (!installed && latest) {
|
|
279
|
+
message = `ℹ️ No version tracking found. Run \`codebakers upgrade\` to sync patterns`;
|
|
280
|
+
}
|
|
281
|
+
return { installed, latest, updateAvailable, message };
|
|
282
|
+
}
|
|
242
283
|
formatContextForPrompt(context) {
|
|
243
284
|
const lines = [];
|
|
244
285
|
lines.push(`Project: ${context.projectName}`);
|
|
@@ -602,7 +643,7 @@ class CodeBakersServer {
|
|
|
602
643
|
case 'get_experience_level':
|
|
603
644
|
return this.handleGetExperienceLevel();
|
|
604
645
|
case 'get_status':
|
|
605
|
-
return this.handleGetStatus();
|
|
646
|
+
return await this.handleGetStatus();
|
|
606
647
|
case 'run_audit':
|
|
607
648
|
return this.handleRunAudit();
|
|
608
649
|
case 'heal':
|
|
@@ -1400,16 +1441,28 @@ phase: development
|
|
|
1400
1441
|
};
|
|
1401
1442
|
}
|
|
1402
1443
|
}
|
|
1403
|
-
handleGetStatus() {
|
|
1444
|
+
async handleGetStatus() {
|
|
1404
1445
|
const level = (0, config_js_1.getExperienceLevel)();
|
|
1405
1446
|
const context = this.gatherProjectContext();
|
|
1447
|
+
const versionCheck = await this.checkPatternVersion();
|
|
1448
|
+
const cliVersion = (0, api_js_1.getCliVersion)();
|
|
1449
|
+
// Build version status section
|
|
1450
|
+
let versionSection = `- **CLI Version:** ${cliVersion}`;
|
|
1451
|
+
if (versionCheck.installed) {
|
|
1452
|
+
versionSection += `\n- **Patterns Version:** ${versionCheck.installed.version} (${versionCheck.installed.moduleCount} modules)`;
|
|
1453
|
+
}
|
|
1454
|
+
// Build update alert if needed
|
|
1455
|
+
let updateAlert = '';
|
|
1456
|
+
if (versionCheck.message) {
|
|
1457
|
+
updateAlert = `\n\n## ${versionCheck.updateAvailable ? '⚠️ Update Available' : 'ℹ️ Version Info'}\n${versionCheck.message}\n`;
|
|
1458
|
+
}
|
|
1406
1459
|
const statusText = `# ✅ CodeBakers is Active!
|
|
1407
1460
|
|
|
1408
1461
|
## Connection Status
|
|
1409
1462
|
- **MCP Server:** Running
|
|
1410
1463
|
- **API Connected:** Yes
|
|
1411
|
-
|
|
1412
|
-
|
|
1464
|
+
${versionSection}
|
|
1465
|
+
${updateAlert}
|
|
1413
1466
|
## Current Settings
|
|
1414
1467
|
- **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
|
|
1415
1468
|
- **Project:** ${context.projectName}
|
|
@@ -1646,6 +1699,248 @@ Just describe what you want to build! I'll automatically:
|
|
|
1646
1699
|
catch {
|
|
1647
1700
|
response += `*(TODO scan unavailable)*\n\n`;
|
|
1648
1701
|
}
|
|
1702
|
+
// Dependency Security Scan (npm audit)
|
|
1703
|
+
response += `### 🔒 Dependency Security Scan\n\n`;
|
|
1704
|
+
try {
|
|
1705
|
+
const auditOutput = (0, child_process_1.execSync)('npm audit --json 2>/dev/null', {
|
|
1706
|
+
cwd,
|
|
1707
|
+
encoding: 'utf-8',
|
|
1708
|
+
timeout: 30000,
|
|
1709
|
+
});
|
|
1710
|
+
const audit = JSON.parse(auditOutput);
|
|
1711
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1712
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1713
|
+
if (total > 0) {
|
|
1714
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1715
|
+
if (vulns.critical > 0)
|
|
1716
|
+
response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1717
|
+
if (vulns.high > 0)
|
|
1718
|
+
response += `- 🟠 **${vulns.high} High**\n`;
|
|
1719
|
+
if (vulns.moderate > 0)
|
|
1720
|
+
response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1721
|
+
if (vulns.low > 0)
|
|
1722
|
+
response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1723
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1724
|
+
}
|
|
1725
|
+
else {
|
|
1726
|
+
response += `✅ No known vulnerabilities in dependencies!\n\n`;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
catch (error) {
|
|
1730
|
+
// npm audit exits with non-zero if vulnerabilities found
|
|
1731
|
+
const execError = error;
|
|
1732
|
+
if (execError.stdout) {
|
|
1733
|
+
try {
|
|
1734
|
+
const audit = JSON.parse(execError.stdout);
|
|
1735
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1736
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1737
|
+
if (total > 0) {
|
|
1738
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1739
|
+
if (vulns.critical > 0)
|
|
1740
|
+
response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1741
|
+
if (vulns.high > 0)
|
|
1742
|
+
response += `- 🟠 **${vulns.high} High**\n`;
|
|
1743
|
+
if (vulns.moderate > 0)
|
|
1744
|
+
response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1745
|
+
if (vulns.low > 0)
|
|
1746
|
+
response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1747
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
catch {
|
|
1751
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
else {
|
|
1755
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
// TypeScript Strictness Check
|
|
1759
|
+
response += `### 📝 TypeScript Configuration\n\n`;
|
|
1760
|
+
try {
|
|
1761
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
1762
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
1763
|
+
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
|
|
1764
|
+
const compilerOptions = tsconfig.compilerOptions || {};
|
|
1765
|
+
const checks = [
|
|
1766
|
+
{ name: 'strict', value: compilerOptions.strict, recommended: true },
|
|
1767
|
+
{ name: 'noImplicitAny', value: compilerOptions.noImplicitAny, recommended: true },
|
|
1768
|
+
{ name: 'strictNullChecks', value: compilerOptions.strictNullChecks, recommended: true },
|
|
1769
|
+
{ name: 'noUnusedLocals', value: compilerOptions.noUnusedLocals, recommended: true },
|
|
1770
|
+
{ name: 'noUnusedParameters', value: compilerOptions.noUnusedParameters, recommended: true },
|
|
1771
|
+
];
|
|
1772
|
+
const enabled = checks.filter(c => c.value === true);
|
|
1773
|
+
const missing = checks.filter(c => c.value !== true && c.recommended);
|
|
1774
|
+
if (compilerOptions.strict === true) {
|
|
1775
|
+
response += `✅ **Strict mode enabled** - Good!\n\n`;
|
|
1776
|
+
}
|
|
1777
|
+
else {
|
|
1778
|
+
response += `⚠️ **Strict mode not enabled**\n\n`;
|
|
1779
|
+
if (missing.length > 0) {
|
|
1780
|
+
response += `Missing recommended options:\n`;
|
|
1781
|
+
missing.forEach(m => {
|
|
1782
|
+
response += `- \`${m.name}: true\`\n`;
|
|
1783
|
+
});
|
|
1784
|
+
response += `\n`;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
// Check for any types
|
|
1788
|
+
try {
|
|
1789
|
+
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();
|
|
1790
|
+
const count = parseInt(anyCount);
|
|
1791
|
+
if (count > 10) {
|
|
1792
|
+
response += `⚠️ Found **${count}** uses of \`: any\` - consider typing these\n\n`;
|
|
1793
|
+
}
|
|
1794
|
+
else if (count > 0) {
|
|
1795
|
+
response += `📝 Found **${count}** uses of \`: any\`\n\n`;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
catch {
|
|
1799
|
+
// Ignore
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
else {
|
|
1803
|
+
response += `⚠️ No tsconfig.json found - not a TypeScript project?\n\n`;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
catch {
|
|
1807
|
+
response += `*(TypeScript check unavailable)*\n\n`;
|
|
1808
|
+
}
|
|
1809
|
+
// Environment Variable Audit
|
|
1810
|
+
response += `### 🔐 Environment Variable Audit\n\n`;
|
|
1811
|
+
try {
|
|
1812
|
+
const envExamplePath = path.join(cwd, '.env.example');
|
|
1813
|
+
const envLocalPath = path.join(cwd, '.env.local');
|
|
1814
|
+
const envPath = path.join(cwd, '.env');
|
|
1815
|
+
const hasEnvExample = fs.existsSync(envExamplePath);
|
|
1816
|
+
const hasEnvLocal = fs.existsSync(envLocalPath);
|
|
1817
|
+
const hasEnv = fs.existsSync(envPath);
|
|
1818
|
+
if (hasEnvExample) {
|
|
1819
|
+
response += `✅ \`.env.example\` exists - good for documentation\n`;
|
|
1820
|
+
}
|
|
1821
|
+
else {
|
|
1822
|
+
response += `⚠️ No \`.env.example\` - add one for team onboarding\n`;
|
|
1823
|
+
}
|
|
1824
|
+
// Check for hardcoded secrets in code
|
|
1825
|
+
try {
|
|
1826
|
+
const secretPatterns = [
|
|
1827
|
+
'sk-[a-zA-Z0-9]{20,}', // OpenAI keys
|
|
1828
|
+
'sk_live_[a-zA-Z0-9]+', // Stripe live keys
|
|
1829
|
+
'pk_live_[a-zA-Z0-9]+', // Stripe public live keys
|
|
1830
|
+
'ghp_[a-zA-Z0-9]+', // GitHub tokens
|
|
1831
|
+
'AKIA[A-Z0-9]{16}', // AWS access keys
|
|
1832
|
+
];
|
|
1833
|
+
let secretsFound = 0;
|
|
1834
|
+
for (const pattern of secretPatterns) {
|
|
1835
|
+
try {
|
|
1836
|
+
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();
|
|
1837
|
+
secretsFound += parseInt(matches) || 0;
|
|
1838
|
+
}
|
|
1839
|
+
catch {
|
|
1840
|
+
// Pattern not found
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (secretsFound > 0) {
|
|
1844
|
+
response += `\n🔴 **CRITICAL: Found ${secretsFound} potential hardcoded secrets in code!**\n`;
|
|
1845
|
+
response += `*These should be moved to environment variables immediately.*\n`;
|
|
1846
|
+
}
|
|
1847
|
+
else {
|
|
1848
|
+
response += `\n✅ No hardcoded secrets detected in code\n`;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
catch {
|
|
1852
|
+
// Ignore
|
|
1853
|
+
}
|
|
1854
|
+
// Check if .env is gitignored
|
|
1855
|
+
try {
|
|
1856
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
1857
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1858
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1859
|
+
if (!gitignore.includes('.env')) {
|
|
1860
|
+
response += `\n⚠️ \`.env\` not in .gitignore - secrets could be committed!\n`;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
catch {
|
|
1865
|
+
// Ignore
|
|
1866
|
+
}
|
|
1867
|
+
response += `\n`;
|
|
1868
|
+
}
|
|
1869
|
+
catch {
|
|
1870
|
+
response += `*(Environment audit unavailable)*\n\n`;
|
|
1871
|
+
}
|
|
1872
|
+
// Test Coverage Analysis
|
|
1873
|
+
response += `### 🧪 Test Coverage\n\n`;
|
|
1874
|
+
try {
|
|
1875
|
+
const hasPlaywright = context.dependencies.includes('@playwright/test');
|
|
1876
|
+
const hasVitest = context.dependencies.includes('vitest');
|
|
1877
|
+
const hasJest = context.dependencies.includes('jest');
|
|
1878
|
+
if (hasPlaywright || hasVitest || hasJest) {
|
|
1879
|
+
const framework = hasPlaywright ? 'Playwright' : hasVitest ? 'Vitest' : 'Jest';
|
|
1880
|
+
response += `✅ Test framework detected: **${framework}**\n\n`;
|
|
1881
|
+
// Count test files
|
|
1882
|
+
try {
|
|
1883
|
+
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();
|
|
1884
|
+
const count = parseInt(testFiles);
|
|
1885
|
+
if (count > 0) {
|
|
1886
|
+
response += `Found **${count} test files**\n\n`;
|
|
1887
|
+
}
|
|
1888
|
+
else {
|
|
1889
|
+
response += `⚠️ No test files found - add tests!\n\n`;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
catch {
|
|
1893
|
+
// Ignore
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
else {
|
|
1897
|
+
response += `⚠️ **No test framework detected**\n\n`;
|
|
1898
|
+
response += `Recommended: Add \`vitest\` or \`@playwright/test\`\n\n`;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
catch {
|
|
1902
|
+
response += `*(Test analysis unavailable)*\n\n`;
|
|
1903
|
+
}
|
|
1904
|
+
// API Endpoint Inventory
|
|
1905
|
+
response += `### 🔌 API Endpoint Inventory\n\n`;
|
|
1906
|
+
if (context.existingApiRoutes.length > 0) {
|
|
1907
|
+
response += `Found **${context.existingApiRoutes.length} API routes**:\n\n`;
|
|
1908
|
+
// Check for auth protection on routes
|
|
1909
|
+
let protectedCount = 0;
|
|
1910
|
+
let unprotectedCount = 0;
|
|
1911
|
+
for (const route of context.existingApiRoutes.slice(0, 10)) {
|
|
1912
|
+
try {
|
|
1913
|
+
const routePath = path.join(cwd, route);
|
|
1914
|
+
if (fs.existsSync(routePath)) {
|
|
1915
|
+
const content = fs.readFileSync(routePath, 'utf-8');
|
|
1916
|
+
const hasAuth = content.includes('getServerSession') ||
|
|
1917
|
+
content.includes('auth(') ||
|
|
1918
|
+
content.includes('requireAuth') ||
|
|
1919
|
+
content.includes('Authorization') ||
|
|
1920
|
+
content.includes('authenticate');
|
|
1921
|
+
if (hasAuth) {
|
|
1922
|
+
protectedCount++;
|
|
1923
|
+
}
|
|
1924
|
+
else {
|
|
1925
|
+
unprotectedCount++;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
catch {
|
|
1930
|
+
// Ignore
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (protectedCount > 0 || unprotectedCount > 0) {
|
|
1934
|
+
response += `- 🔒 **${protectedCount}** routes with auth checks\n`;
|
|
1935
|
+
response += `- 🔓 **${unprotectedCount}** routes without visible auth\n\n`;
|
|
1936
|
+
if (unprotectedCount > protectedCount) {
|
|
1937
|
+
response += `⚠️ *Many routes lack visible auth - review if intentional*\n\n`;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
else {
|
|
1942
|
+
response += `No API routes detected yet.\n\n`;
|
|
1943
|
+
}
|
|
1649
1944
|
response += `---\n\n`;
|
|
1650
1945
|
// Scan for upgrade opportunities
|
|
1651
1946
|
response += `## Upgrade Opportunities\n\n`;
|
package/package.json
CHANGED
package/src/commands/install.ts
CHANGED
|
@@ -3,6 +3,7 @@ import ora from 'ora';
|
|
|
3
3
|
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { getApiKey, getApiUrl } from '../config.js';
|
|
6
|
+
import { getCliVersion } from '../lib/api.js';
|
|
6
7
|
|
|
7
8
|
interface ContentResponse {
|
|
8
9
|
version: string;
|
|
@@ -51,8 +52,8 @@ export async function install(): Promise<void> {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// Write modules
|
|
55
|
+
const modulesDir = join(cwd, '.claude');
|
|
54
56
|
if (content.modules && Object.keys(content.modules).length > 0) {
|
|
55
|
-
const modulesDir = join(cwd, '.claude');
|
|
56
57
|
if (!existsSync(modulesDir)) {
|
|
57
58
|
mkdirSync(modulesDir, { recursive: true });
|
|
58
59
|
}
|
|
@@ -60,6 +61,15 @@ export async function install(): Promise<void> {
|
|
|
60
61
|
for (const [name, data] of Object.entries(content.modules)) {
|
|
61
62
|
writeFileSync(join(modulesDir, name), data);
|
|
62
63
|
}
|
|
64
|
+
|
|
65
|
+
// Write version file for tracking
|
|
66
|
+
const versionInfo = {
|
|
67
|
+
version: content.version,
|
|
68
|
+
moduleCount: Object.keys(content.modules).length,
|
|
69
|
+
installedAt: new Date().toISOString(),
|
|
70
|
+
cliVersion: getCliVersion(),
|
|
71
|
+
};
|
|
72
|
+
writeFileSync(join(modulesDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
// Add to .gitignore if not present
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -91,6 +91,16 @@ export async function upgrade(): Promise<void> {
|
|
|
91
91
|
console.log(chalk.green(` ✓ Updated ${moduleCount} modules in .claude/`));
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Write version file for tracking
|
|
95
|
+
const versionInfo = {
|
|
96
|
+
version: content.version,
|
|
97
|
+
moduleCount,
|
|
98
|
+
updatedAt: new Date().toISOString(),
|
|
99
|
+
cliVersion: getCliVersion(),
|
|
100
|
+
};
|
|
101
|
+
writeFileSync(join(claudeDir, '.version.json'), JSON.stringify(versionInfo, null, 2));
|
|
102
|
+
console.log(chalk.green(' ✓ Version info saved'));
|
|
103
|
+
|
|
94
104
|
console.log(chalk.green(`\n ✅ Upgraded to patterns v${content.version}!\n`));
|
|
95
105
|
|
|
96
106
|
// Show what's new if available
|
package/src/mcp/server.ts
CHANGED
|
@@ -11,11 +11,21 @@ import {
|
|
|
11
11
|
import { getApiKey, getApiUrl, getExperienceLevel, setExperienceLevel, type ExperienceLevel } from '../config.js';
|
|
12
12
|
import { audit as runAudit } from '../commands/audit.js';
|
|
13
13
|
import { heal as runHeal } from '../commands/heal.js';
|
|
14
|
+
import { getCliVersion } from '../lib/api.js';
|
|
14
15
|
import * as fs from 'fs';
|
|
15
16
|
import * as path from 'path';
|
|
16
17
|
import { execSync } from 'child_process';
|
|
17
18
|
import * as templates from '../templates/nextjs-supabase.js';
|
|
18
19
|
|
|
20
|
+
// Version info type
|
|
21
|
+
interface VersionInfo {
|
|
22
|
+
version: string;
|
|
23
|
+
moduleCount: number;
|
|
24
|
+
installedAt?: string;
|
|
25
|
+
updatedAt?: string;
|
|
26
|
+
cliVersion: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
// Pattern cache to avoid repeated API calls
|
|
20
30
|
const patternCache = new Map<string, { content: string; timestamp: number }>();
|
|
21
31
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
@@ -243,6 +253,54 @@ class CodeBakersServer {
|
|
|
243
253
|
return context;
|
|
244
254
|
}
|
|
245
255
|
|
|
256
|
+
private async checkPatternVersion(): Promise<{
|
|
257
|
+
installed: VersionInfo | null;
|
|
258
|
+
latest: { version: string; moduleCount: number } | null;
|
|
259
|
+
updateAvailable: boolean;
|
|
260
|
+
message: string | null;
|
|
261
|
+
}> {
|
|
262
|
+
const cwd = process.cwd();
|
|
263
|
+
const versionPath = path.join(cwd, '.claude', '.version.json');
|
|
264
|
+
|
|
265
|
+
// Read local version
|
|
266
|
+
let installed: VersionInfo | null = null;
|
|
267
|
+
if (fs.existsSync(versionPath)) {
|
|
268
|
+
try {
|
|
269
|
+
installed = JSON.parse(fs.readFileSync(versionPath, 'utf-8'));
|
|
270
|
+
} catch {
|
|
271
|
+
// Ignore parse errors
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Fetch latest version from API
|
|
276
|
+
let latest: { version: string; moduleCount: number } | null = null;
|
|
277
|
+
try {
|
|
278
|
+
const response = await fetch(`${this.apiUrl}/api/content/version`, {
|
|
279
|
+
headers: this.apiKey ? { 'Authorization': `Bearer ${this.apiKey}` } : {},
|
|
280
|
+
});
|
|
281
|
+
if (response.ok) {
|
|
282
|
+
latest = await response.json();
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
// Ignore fetch errors
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Compare versions
|
|
289
|
+
let updateAvailable = false;
|
|
290
|
+
let message: string | null = null;
|
|
291
|
+
|
|
292
|
+
if (installed && latest) {
|
|
293
|
+
if (installed.version !== latest.version) {
|
|
294
|
+
updateAvailable = true;
|
|
295
|
+
message = `⚠️ Pattern update available: v${installed.version} → v${latest.version} (${latest.moduleCount - installed.moduleCount} new modules)\n Run \`codebakers upgrade\` to update`;
|
|
296
|
+
}
|
|
297
|
+
} else if (!installed && latest) {
|
|
298
|
+
message = `ℹ️ No version tracking found. Run \`codebakers upgrade\` to sync patterns`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { installed, latest, updateAvailable, message };
|
|
302
|
+
}
|
|
303
|
+
|
|
246
304
|
private formatContextForPrompt(context: ProjectContext): string {
|
|
247
305
|
const lines: string[] = [];
|
|
248
306
|
|
|
@@ -651,7 +709,7 @@ class CodeBakersServer {
|
|
|
651
709
|
return this.handleGetExperienceLevel();
|
|
652
710
|
|
|
653
711
|
case 'get_status':
|
|
654
|
-
return this.handleGetStatus();
|
|
712
|
+
return await this.handleGetStatus();
|
|
655
713
|
|
|
656
714
|
case 'run_audit':
|
|
657
715
|
return this.handleRunAudit();
|
|
@@ -1579,17 +1637,31 @@ phase: development
|
|
|
1579
1637
|
}
|
|
1580
1638
|
}
|
|
1581
1639
|
|
|
1582
|
-
private handleGetStatus() {
|
|
1640
|
+
private async handleGetStatus() {
|
|
1583
1641
|
const level = getExperienceLevel();
|
|
1584
1642
|
const context = this.gatherProjectContext();
|
|
1643
|
+
const versionCheck = await this.checkPatternVersion();
|
|
1644
|
+
const cliVersion = getCliVersion();
|
|
1645
|
+
|
|
1646
|
+
// Build version status section
|
|
1647
|
+
let versionSection = `- **CLI Version:** ${cliVersion}`;
|
|
1648
|
+
if (versionCheck.installed) {
|
|
1649
|
+
versionSection += `\n- **Patterns Version:** ${versionCheck.installed.version} (${versionCheck.installed.moduleCount} modules)`;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// Build update alert if needed
|
|
1653
|
+
let updateAlert = '';
|
|
1654
|
+
if (versionCheck.message) {
|
|
1655
|
+
updateAlert = `\n\n## ${versionCheck.updateAvailable ? '⚠️ Update Available' : 'ℹ️ Version Info'}\n${versionCheck.message}\n`;
|
|
1656
|
+
}
|
|
1585
1657
|
|
|
1586
1658
|
const statusText = `# ✅ CodeBakers is Active!
|
|
1587
1659
|
|
|
1588
1660
|
## Connection Status
|
|
1589
1661
|
- **MCP Server:** Running
|
|
1590
1662
|
- **API Connected:** Yes
|
|
1591
|
-
|
|
1592
|
-
|
|
1663
|
+
${versionSection}
|
|
1664
|
+
${updateAlert}
|
|
1593
1665
|
## Current Settings
|
|
1594
1666
|
- **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
|
|
1595
1667
|
- **Project:** ${context.projectName}
|
|
@@ -1859,6 +1931,251 @@ Just describe what you want to build! I'll automatically:
|
|
|
1859
1931
|
response += `*(TODO scan unavailable)*\n\n`;
|
|
1860
1932
|
}
|
|
1861
1933
|
|
|
1934
|
+
// Dependency Security Scan (npm audit)
|
|
1935
|
+
response += `### 🔒 Dependency Security Scan\n\n`;
|
|
1936
|
+
try {
|
|
1937
|
+
const auditOutput = execSync('npm audit --json 2>/dev/null', {
|
|
1938
|
+
cwd,
|
|
1939
|
+
encoding: 'utf-8',
|
|
1940
|
+
timeout: 30000,
|
|
1941
|
+
});
|
|
1942
|
+
const audit = JSON.parse(auditOutput);
|
|
1943
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1944
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1945
|
+
|
|
1946
|
+
if (total > 0) {
|
|
1947
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1948
|
+
if (vulns.critical > 0) response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1949
|
+
if (vulns.high > 0) response += `- 🟠 **${vulns.high} High**\n`;
|
|
1950
|
+
if (vulns.moderate > 0) response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1951
|
+
if (vulns.low > 0) response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1952
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1953
|
+
} else {
|
|
1954
|
+
response += `✅ No known vulnerabilities in dependencies!\n\n`;
|
|
1955
|
+
}
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
// npm audit exits with non-zero if vulnerabilities found
|
|
1958
|
+
const execError = error as { stdout?: string };
|
|
1959
|
+
if (execError.stdout) {
|
|
1960
|
+
try {
|
|
1961
|
+
const audit = JSON.parse(execError.stdout);
|
|
1962
|
+
const vulns = audit.metadata?.vulnerabilities || {};
|
|
1963
|
+
const total = (vulns.critical || 0) + (vulns.high || 0) + (vulns.moderate || 0) + (vulns.low || 0);
|
|
1964
|
+
|
|
1965
|
+
if (total > 0) {
|
|
1966
|
+
response += `Found **${total} vulnerabilities**:\n\n`;
|
|
1967
|
+
if (vulns.critical > 0) response += `- 🔴 **${vulns.critical} Critical**\n`;
|
|
1968
|
+
if (vulns.high > 0) response += `- 🟠 **${vulns.high} High**\n`;
|
|
1969
|
+
if (vulns.moderate > 0) response += `- 🟡 **${vulns.moderate} Moderate**\n`;
|
|
1970
|
+
if (vulns.low > 0) response += `- 🟢 **${vulns.low} Low**\n`;
|
|
1971
|
+
response += `\n*Run \`npm audit fix\` to auto-fix, or \`npm audit\` for details.*\n\n`;
|
|
1972
|
+
}
|
|
1973
|
+
} catch {
|
|
1974
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1975
|
+
}
|
|
1976
|
+
} else {
|
|
1977
|
+
response += `*(Dependency scan unavailable)*\n\n`;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
// TypeScript Strictness Check
|
|
1982
|
+
response += `### 📝 TypeScript Configuration\n\n`;
|
|
1983
|
+
try {
|
|
1984
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
1985
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
1986
|
+
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8'));
|
|
1987
|
+
const compilerOptions = tsconfig.compilerOptions || {};
|
|
1988
|
+
|
|
1989
|
+
const checks = [
|
|
1990
|
+
{ name: 'strict', value: compilerOptions.strict, recommended: true },
|
|
1991
|
+
{ name: 'noImplicitAny', value: compilerOptions.noImplicitAny, recommended: true },
|
|
1992
|
+
{ name: 'strictNullChecks', value: compilerOptions.strictNullChecks, recommended: true },
|
|
1993
|
+
{ name: 'noUnusedLocals', value: compilerOptions.noUnusedLocals, recommended: true },
|
|
1994
|
+
{ name: 'noUnusedParameters', value: compilerOptions.noUnusedParameters, recommended: true },
|
|
1995
|
+
];
|
|
1996
|
+
|
|
1997
|
+
const enabled = checks.filter(c => c.value === true);
|
|
1998
|
+
const missing = checks.filter(c => c.value !== true && c.recommended);
|
|
1999
|
+
|
|
2000
|
+
if (compilerOptions.strict === true) {
|
|
2001
|
+
response += `✅ **Strict mode enabled** - Good!\n\n`;
|
|
2002
|
+
} else {
|
|
2003
|
+
response += `⚠️ **Strict mode not enabled**\n\n`;
|
|
2004
|
+
if (missing.length > 0) {
|
|
2005
|
+
response += `Missing recommended options:\n`;
|
|
2006
|
+
missing.forEach(m => {
|
|
2007
|
+
response += `- \`${m.name}: true\`\n`;
|
|
2008
|
+
});
|
|
2009
|
+
response += `\n`;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Check for any types
|
|
2014
|
+
try {
|
|
2015
|
+
const anyCount = execSync(
|
|
2016
|
+
'grep -r ": any" --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v node_modules | wc -l',
|
|
2017
|
+
{ cwd, encoding: 'utf-8', timeout: 10000 }
|
|
2018
|
+
).trim();
|
|
2019
|
+
const count = parseInt(anyCount);
|
|
2020
|
+
if (count > 10) {
|
|
2021
|
+
response += `⚠️ Found **${count}** uses of \`: any\` - consider typing these\n\n`;
|
|
2022
|
+
} else if (count > 0) {
|
|
2023
|
+
response += `📝 Found **${count}** uses of \`: any\`\n\n`;
|
|
2024
|
+
}
|
|
2025
|
+
} catch {
|
|
2026
|
+
// Ignore
|
|
2027
|
+
}
|
|
2028
|
+
} else {
|
|
2029
|
+
response += `⚠️ No tsconfig.json found - not a TypeScript project?\n\n`;
|
|
2030
|
+
}
|
|
2031
|
+
} catch {
|
|
2032
|
+
response += `*(TypeScript check unavailable)*\n\n`;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
// Environment Variable Audit
|
|
2036
|
+
response += `### 🔐 Environment Variable Audit\n\n`;
|
|
2037
|
+
try {
|
|
2038
|
+
const envExamplePath = path.join(cwd, '.env.example');
|
|
2039
|
+
const envLocalPath = path.join(cwd, '.env.local');
|
|
2040
|
+
const envPath = path.join(cwd, '.env');
|
|
2041
|
+
|
|
2042
|
+
const hasEnvExample = fs.existsSync(envExamplePath);
|
|
2043
|
+
const hasEnvLocal = fs.existsSync(envLocalPath);
|
|
2044
|
+
const hasEnv = fs.existsSync(envPath);
|
|
2045
|
+
|
|
2046
|
+
if (hasEnvExample) {
|
|
2047
|
+
response += `✅ \`.env.example\` exists - good for documentation\n`;
|
|
2048
|
+
} else {
|
|
2049
|
+
response += `⚠️ No \`.env.example\` - add one for team onboarding\n`;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
// Check for hardcoded secrets in code
|
|
2053
|
+
try {
|
|
2054
|
+
const secretPatterns = [
|
|
2055
|
+
'sk-[a-zA-Z0-9]{20,}', // OpenAI keys
|
|
2056
|
+
'sk_live_[a-zA-Z0-9]+', // Stripe live keys
|
|
2057
|
+
'pk_live_[a-zA-Z0-9]+', // Stripe public live keys
|
|
2058
|
+
'ghp_[a-zA-Z0-9]+', // GitHub tokens
|
|
2059
|
+
'AKIA[A-Z0-9]{16}', // AWS access keys
|
|
2060
|
+
];
|
|
2061
|
+
|
|
2062
|
+
let secretsFound = 0;
|
|
2063
|
+
for (const pattern of secretPatterns) {
|
|
2064
|
+
try {
|
|
2065
|
+
const matches = execSync(
|
|
2066
|
+
`grep -rE "${pattern}" --include="*.ts" --include="*.tsx" --include="*.js" 2>/dev/null | grep -v node_modules | grep -v ".env" | wc -l`,
|
|
2067
|
+
{ cwd, encoding: 'utf-8', timeout: 5000 }
|
|
2068
|
+
).trim();
|
|
2069
|
+
secretsFound += parseInt(matches) || 0;
|
|
2070
|
+
} catch {
|
|
2071
|
+
// Pattern not found
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
if (secretsFound > 0) {
|
|
2076
|
+
response += `\n🔴 **CRITICAL: Found ${secretsFound} potential hardcoded secrets in code!**\n`;
|
|
2077
|
+
response += `*These should be moved to environment variables immediately.*\n`;
|
|
2078
|
+
} else {
|
|
2079
|
+
response += `\n✅ No hardcoded secrets detected in code\n`;
|
|
2080
|
+
}
|
|
2081
|
+
} catch {
|
|
2082
|
+
// Ignore
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// Check if .env is gitignored
|
|
2086
|
+
try {
|
|
2087
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
2088
|
+
if (fs.existsSync(gitignorePath)) {
|
|
2089
|
+
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
2090
|
+
if (!gitignore.includes('.env')) {
|
|
2091
|
+
response += `\n⚠️ \`.env\` not in .gitignore - secrets could be committed!\n`;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
} catch {
|
|
2095
|
+
// Ignore
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
response += `\n`;
|
|
2099
|
+
} catch {
|
|
2100
|
+
response += `*(Environment audit unavailable)*\n\n`;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// Test Coverage Analysis
|
|
2104
|
+
response += `### 🧪 Test Coverage\n\n`;
|
|
2105
|
+
try {
|
|
2106
|
+
const hasPlaywright = context.dependencies.includes('@playwright/test');
|
|
2107
|
+
const hasVitest = context.dependencies.includes('vitest');
|
|
2108
|
+
const hasJest = context.dependencies.includes('jest');
|
|
2109
|
+
|
|
2110
|
+
if (hasPlaywright || hasVitest || hasJest) {
|
|
2111
|
+
const framework = hasPlaywright ? 'Playwright' : hasVitest ? 'Vitest' : 'Jest';
|
|
2112
|
+
response += `✅ Test framework detected: **${framework}**\n\n`;
|
|
2113
|
+
|
|
2114
|
+
// Count test files
|
|
2115
|
+
try {
|
|
2116
|
+
const testFiles = execSync(
|
|
2117
|
+
'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',
|
|
2118
|
+
{ cwd, encoding: 'utf-8', timeout: 10000 }
|
|
2119
|
+
).trim();
|
|
2120
|
+
const count = parseInt(testFiles);
|
|
2121
|
+
if (count > 0) {
|
|
2122
|
+
response += `Found **${count} test files**\n\n`;
|
|
2123
|
+
} else {
|
|
2124
|
+
response += `⚠️ No test files found - add tests!\n\n`;
|
|
2125
|
+
}
|
|
2126
|
+
} catch {
|
|
2127
|
+
// Ignore
|
|
2128
|
+
}
|
|
2129
|
+
} else {
|
|
2130
|
+
response += `⚠️ **No test framework detected**\n\n`;
|
|
2131
|
+
response += `Recommended: Add \`vitest\` or \`@playwright/test\`\n\n`;
|
|
2132
|
+
}
|
|
2133
|
+
} catch {
|
|
2134
|
+
response += `*(Test analysis unavailable)*\n\n`;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// API Endpoint Inventory
|
|
2138
|
+
response += `### 🔌 API Endpoint Inventory\n\n`;
|
|
2139
|
+
if (context.existingApiRoutes.length > 0) {
|
|
2140
|
+
response += `Found **${context.existingApiRoutes.length} API routes**:\n\n`;
|
|
2141
|
+
|
|
2142
|
+
// Check for auth protection on routes
|
|
2143
|
+
let protectedCount = 0;
|
|
2144
|
+
let unprotectedCount = 0;
|
|
2145
|
+
|
|
2146
|
+
for (const route of context.existingApiRoutes.slice(0, 10)) {
|
|
2147
|
+
try {
|
|
2148
|
+
const routePath = path.join(cwd, route);
|
|
2149
|
+
if (fs.existsSync(routePath)) {
|
|
2150
|
+
const content = fs.readFileSync(routePath, 'utf-8');
|
|
2151
|
+
const hasAuth = content.includes('getServerSession') ||
|
|
2152
|
+
content.includes('auth(') ||
|
|
2153
|
+
content.includes('requireAuth') ||
|
|
2154
|
+
content.includes('Authorization') ||
|
|
2155
|
+
content.includes('authenticate');
|
|
2156
|
+
if (hasAuth) {
|
|
2157
|
+
protectedCount++;
|
|
2158
|
+
} else {
|
|
2159
|
+
unprotectedCount++;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
} catch {
|
|
2163
|
+
// Ignore
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
if (protectedCount > 0 || unprotectedCount > 0) {
|
|
2168
|
+
response += `- 🔒 **${protectedCount}** routes with auth checks\n`;
|
|
2169
|
+
response += `- 🔓 **${unprotectedCount}** routes without visible auth\n\n`;
|
|
2170
|
+
|
|
2171
|
+
if (unprotectedCount > protectedCount) {
|
|
2172
|
+
response += `⚠️ *Many routes lack visible auth - review if intentional*\n\n`;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
} else {
|
|
2176
|
+
response += `No API routes detected yet.\n\n`;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
1862
2179
|
response += `---\n\n`;
|
|
1863
2180
|
|
|
1864
2181
|
// Scan for upgrade opportunities
|