@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.
@@ -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');
@@ -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'));
@@ -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
- - **Version:** 2.2.0
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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
@@ -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
- - **Version:** 2.2.0
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