@codebakers/cli 3.3.17 → 3.3.18

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.
@@ -815,6 +815,30 @@ class CodeBakersServer {
815
815
  required: ['feature'],
816
816
  },
817
817
  },
818
+ {
819
+ name: 'discover_patterns',
820
+ description: 'MANDATORY: Call this BEFORE writing or modifying ANY code. Searches codebase for similar implementations and identifies relevant patterns. Returns existing code patterns you MUST follow. You are NOT ALLOWED to write code without calling this first.',
821
+ inputSchema: {
822
+ type: 'object',
823
+ properties: {
824
+ task: {
825
+ type: 'string',
826
+ description: 'What you are about to do (e.g., "add signup form", "fix auth bug", "create payment endpoint")',
827
+ },
828
+ files: {
829
+ type: 'array',
830
+ items: { type: 'string' },
831
+ description: 'Files you plan to create or modify',
832
+ },
833
+ keywords: {
834
+ type: 'array',
835
+ items: { type: 'string' },
836
+ description: 'Keywords to search for in codebase (e.g., ["auth", "login", "user"])',
837
+ },
838
+ },
839
+ required: ['task'],
840
+ },
841
+ },
818
842
  {
819
843
  name: 'report_pattern_gap',
820
844
  description: 'Report when a user request cannot be fully handled by existing patterns. This helps improve CodeBakers by tracking what patterns are missing. The AI should automatically call this when it encounters something outside pattern coverage.',
@@ -1477,6 +1501,8 @@ class CodeBakersServer {
1477
1501
  return this.handleRunTests(args);
1478
1502
  case 'validate_complete':
1479
1503
  return this.handleValidateComplete(args);
1504
+ case 'discover_patterns':
1505
+ return this.handleDiscoverPatterns(args);
1480
1506
  case 'report_pattern_gap':
1481
1507
  return this.handleReportPatternGap(args);
1482
1508
  case 'track_analytics':
@@ -3359,15 +3385,51 @@ Just describe what you want to build! I'll automatically:
3359
3385
  }
3360
3386
  /**
3361
3387
  * MANDATORY: Validate that a feature is complete before AI can say "done"
3362
- * Checks: tests exist, tests pass, TypeScript compiles
3388
+ * Checks: discover_patterns was called, tests exist, tests pass, TypeScript compiles
3363
3389
  */
3364
3390
  handleValidateComplete(args) {
3365
3391
  const { feature, files = [] } = args;
3366
3392
  const cwd = process.cwd();
3367
3393
  const issues = [];
3394
+ let patternsDiscovered = false;
3368
3395
  let testsExist = false;
3369
3396
  let testsPass = false;
3370
3397
  let typescriptPass = false;
3398
+ // Step 0: Check if discover_patterns was called (compliance tracking)
3399
+ try {
3400
+ const stateFile = path.join(cwd, '.codebakers.json');
3401
+ if (fs.existsSync(stateFile)) {
3402
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3403
+ const compliance = state.compliance;
3404
+ if (compliance?.discoveries && Array.isArray(compliance.discoveries)) {
3405
+ // Check if there's a recent discovery (within last 30 minutes)
3406
+ const recentDiscovery = compliance.discoveries.find((d) => {
3407
+ const discovery = d;
3408
+ if (!discovery.timestamp)
3409
+ return false;
3410
+ const age = Date.now() - new Date(discovery.timestamp).getTime();
3411
+ return age < 30 * 60 * 1000; // 30 minutes
3412
+ });
3413
+ if (recentDiscovery) {
3414
+ patternsDiscovered = true;
3415
+ }
3416
+ else {
3417
+ issues.push('PATTERNS_NOT_CHECKED: You did not call `discover_patterns` before writing code. You MUST check existing patterns first.');
3418
+ }
3419
+ }
3420
+ else {
3421
+ issues.push('PATTERNS_NOT_CHECKED: You did not call `discover_patterns` before writing code. You MUST check existing patterns first.');
3422
+ }
3423
+ }
3424
+ else {
3425
+ // No state file - patterns weren't discovered
3426
+ issues.push('PATTERNS_NOT_CHECKED: You did not call `discover_patterns` before writing code. You MUST check existing patterns first.');
3427
+ }
3428
+ }
3429
+ catch {
3430
+ // If we can't read state, warn but don't fail
3431
+ issues.push('PATTERNS_UNKNOWN: Could not verify if `discover_patterns` was called. Please call it before continuing.');
3432
+ }
3371
3433
  // Step 1: Check if test files exist
3372
3434
  try {
3373
3435
  const testDirs = ['tests', 'test', '__tests__', 'src/__tests__', 'src/tests'];
@@ -3445,9 +3507,10 @@ Just describe what you want to build! I'll automatically:
3445
3507
  issues.push(`TYPESCRIPT_ERRORS: TypeScript compilation failed.\n${output.slice(0, 500)}`);
3446
3508
  }
3447
3509
  // Generate response
3448
- const valid = testsExist && testsPass && typescriptPass;
3510
+ const valid = patternsDiscovered && testsExist && testsPass && typescriptPass;
3449
3511
  let response = `# ✅ Feature Validation: ${feature}\n\n`;
3450
3512
  response += `| Check | Status |\n|-------|--------|\n`;
3513
+ response += `| Patterns discovered | ${patternsDiscovered ? '✅ PASS' : '❌ FAIL'} |\n`;
3451
3514
  response += `| Tests exist | ${testsExist ? '✅ PASS' : '❌ FAIL'} |\n`;
3452
3515
  response += `| Tests pass | ${testsPass ? '✅ PASS' : testsExist ? '❌ FAIL' : '⏭️ SKIP'} |\n`;
3453
3516
  response += `| TypeScript compiles | ${typescriptPass ? '✅ PASS' : '❌ FAIL'} |\n\n`;
@@ -3462,7 +3525,12 @@ Just describe what you want to build! I'll automatically:
3462
3525
  for (const issue of issues) {
3463
3526
  response += `- ${issue}\n\n`;
3464
3527
  }
3465
- response += `---\n\n**Fix these issues and call \`validate_complete\` again.**`;
3528
+ if (!patternsDiscovered) {
3529
+ response += `---\n\n**First, call \`discover_patterns\` to check existing code patterns. Then fix remaining issues and call \`validate_complete\` again.**`;
3530
+ }
3531
+ else {
3532
+ response += `---\n\n**Fix these issues and call \`validate_complete\` again.**`;
3533
+ }
3466
3534
  }
3467
3535
  return {
3468
3536
  content: [{
@@ -3473,6 +3541,227 @@ Just describe what you want to build! I'll automatically:
3473
3541
  isError: !valid,
3474
3542
  };
3475
3543
  }
3544
+ /**
3545
+ * discover_patterns - START gate for pattern compliance
3546
+ * MUST be called before writing any code
3547
+ */
3548
+ handleDiscoverPatterns(args) {
3549
+ const { task, files = [], keywords = [] } = args;
3550
+ const cwd = process.cwd();
3551
+ // Extract keywords from task if not provided
3552
+ const taskKeywords = this.extractKeywords(task);
3553
+ const allKeywords = [...new Set([...keywords, ...taskKeywords])];
3554
+ // Results to return
3555
+ const patterns = [];
3556
+ const existingCode = [];
3557
+ const mustFollow = [];
3558
+ // Step 1: Identify relevant .claude/ patterns based on keywords
3559
+ const patternMap = {
3560
+ 'auth': ['02-auth.md'],
3561
+ 'login': ['02-auth.md'],
3562
+ 'signup': ['02-auth.md'],
3563
+ 'password': ['02-auth.md'],
3564
+ 'session': ['02-auth.md'],
3565
+ 'oauth': ['02-auth.md'],
3566
+ 'payment': ['05-payments.md'],
3567
+ 'stripe': ['05-payments.md'],
3568
+ 'billing': ['05-payments.md'],
3569
+ 'subscription': ['05-payments.md'],
3570
+ 'checkout': ['05-payments.md'],
3571
+ 'database': ['01-database.md'],
3572
+ 'schema': ['01-database.md'],
3573
+ 'drizzle': ['01-database.md'],
3574
+ 'query': ['01-database.md'],
3575
+ 'api': ['03-api.md'],
3576
+ 'route': ['03-api.md'],
3577
+ 'endpoint': ['03-api.md'],
3578
+ 'form': ['04-frontend.md'],
3579
+ 'component': ['04-frontend.md'],
3580
+ 'react': ['04-frontend.md'],
3581
+ 'email': ['06b-email.md'],
3582
+ 'resend': ['06b-email.md'],
3583
+ 'voice': ['06a-voice.md'],
3584
+ 'vapi': ['06a-voice.md'],
3585
+ 'test': ['08-testing.md'],
3586
+ 'playwright': ['08-testing.md'],
3587
+ };
3588
+ for (const keyword of allKeywords) {
3589
+ const lowerKeyword = keyword.toLowerCase();
3590
+ for (const [key, patternFiles] of Object.entries(patternMap)) {
3591
+ if (lowerKeyword.includes(key) || key.includes(lowerKeyword)) {
3592
+ patterns.push(...patternFiles);
3593
+ }
3594
+ }
3595
+ }
3596
+ // Always include 00-core.md
3597
+ if (!patterns.includes('00-core.md')) {
3598
+ patterns.unshift('00-core.md');
3599
+ }
3600
+ // Deduplicate
3601
+ const uniquePatterns = [...new Set(patterns)];
3602
+ // Step 2: Search codebase for similar implementations
3603
+ const searchDirs = ['src/services', 'src/lib', 'src/app/api', 'src/components', 'lib', 'services'];
3604
+ const searchExtensions = ['.ts', '.tsx'];
3605
+ for (const keyword of allKeywords.slice(0, 5)) { // Limit to avoid too many searches
3606
+ for (const dir of searchDirs) {
3607
+ const searchDir = path.join(cwd, dir);
3608
+ if (!fs.existsSync(searchDir))
3609
+ continue;
3610
+ try {
3611
+ const files = this.findFilesRecursive(searchDir, searchExtensions);
3612
+ for (const file of files.slice(0, 20)) { // Limit files per dir
3613
+ try {
3614
+ const content = fs.readFileSync(file, 'utf-8');
3615
+ const lines = content.split('\n');
3616
+ for (let i = 0; i < lines.length; i++) {
3617
+ if (lines[i].toLowerCase().includes(keyword.toLowerCase())) {
3618
+ // Found a match - extract context
3619
+ const startLine = Math.max(0, i - 2);
3620
+ const endLine = Math.min(lines.length - 1, i + 5);
3621
+ const snippet = lines.slice(startLine, endLine + 1).join('\n');
3622
+ const relativePath = path.relative(cwd, file);
3623
+ // Avoid duplicates
3624
+ if (!existingCode.some(e => e.file === relativePath && Math.abs(parseInt(e.lines.split('-')[0]) - (startLine + 1)) < 5)) {
3625
+ existingCode.push({
3626
+ file: relativePath,
3627
+ lines: `${startLine + 1}-${endLine + 1}`,
3628
+ snippet: snippet.slice(0, 300),
3629
+ });
3630
+ }
3631
+ // Only get first match per file
3632
+ break;
3633
+ }
3634
+ }
3635
+ }
3636
+ catch {
3637
+ // Skip unreadable files
3638
+ }
3639
+ }
3640
+ }
3641
+ catch {
3642
+ // Skip inaccessible dirs
3643
+ }
3644
+ }
3645
+ }
3646
+ // Step 3: Extract patterns from existing code
3647
+ if (existingCode.length > 0) {
3648
+ // Look for common patterns in existing code
3649
+ for (const code of existingCode) {
3650
+ if (code.snippet.includes('.insert(')) {
3651
+ mustFollow.push(`Use .insert() for creating new records (found in ${code.file})`);
3652
+ }
3653
+ if (code.snippet.includes('.upsert(')) {
3654
+ mustFollow.push(`Use .upsert() for create-or-update operations (found in ${code.file})`);
3655
+ }
3656
+ if (code.snippet.includes('try {') && code.snippet.includes('catch')) {
3657
+ mustFollow.push(`Wrap database/API operations in try/catch (found in ${code.file})`);
3658
+ }
3659
+ if (code.snippet.includes('NextResponse.json')) {
3660
+ mustFollow.push(`Use NextResponse.json() for API responses (found in ${code.file})`);
3661
+ }
3662
+ if (code.snippet.includes('z.object')) {
3663
+ mustFollow.push(`Use Zod for input validation (found in ${code.file})`);
3664
+ }
3665
+ }
3666
+ }
3667
+ // Deduplicate mustFollow
3668
+ const uniqueMustFollow = [...new Set(mustFollow)];
3669
+ // Step 4: Log discovery to .codebakers.json for compliance tracking
3670
+ try {
3671
+ const stateFile = path.join(cwd, '.codebakers.json');
3672
+ let state = {};
3673
+ if (fs.existsSync(stateFile)) {
3674
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3675
+ }
3676
+ if (!state.compliance) {
3677
+ state.compliance = { discoveries: [], violations: [] };
3678
+ }
3679
+ const compliance = state.compliance;
3680
+ compliance.discoveries.push({
3681
+ task,
3682
+ patterns: uniquePatterns,
3683
+ existingCodeChecked: existingCode.map(e => e.file),
3684
+ timestamp: new Date().toISOString(),
3685
+ });
3686
+ // Keep only last 50 discoveries
3687
+ if (compliance.discoveries.length > 50) {
3688
+ compliance.discoveries = compliance.discoveries.slice(-50);
3689
+ }
3690
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
3691
+ }
3692
+ catch {
3693
+ // Ignore state file errors
3694
+ }
3695
+ // Generate response
3696
+ let response = `# 🔍 Pattern Discovery: ${task}\n\n`;
3697
+ response += `## ⛔ MANDATORY: You MUST follow these patterns before writing code\n\n`;
3698
+ response += `### 📦 Patterns to Load\n\n`;
3699
+ response += `Load these from \`.claude/\` folder:\n`;
3700
+ for (const pattern of uniquePatterns) {
3701
+ response += `- \`${pattern}\`\n`;
3702
+ }
3703
+ if (existingCode.length > 0) {
3704
+ response += `\n### 🔎 Existing Code to Follow\n\n`;
3705
+ response += `Found ${existingCode.length} relevant implementation(s):\n\n`;
3706
+ for (const code of existingCode.slice(0, 5)) { // Limit output
3707
+ response += `**${code.file}:${code.lines}**\n`;
3708
+ response += `\`\`\`typescript\n${code.snippet}\n\`\`\`\n\n`;
3709
+ }
3710
+ }
3711
+ if (uniqueMustFollow.length > 0) {
3712
+ response += `### ✅ Patterns You MUST Follow\n\n`;
3713
+ for (const rule of uniqueMustFollow) {
3714
+ response += `- ${rule}\n`;
3715
+ }
3716
+ }
3717
+ response += `\n---\n\n`;
3718
+ response += `## ⚠️ BEFORE WRITING CODE:\n\n`;
3719
+ response += `1. ✅ Read the patterns listed above\n`;
3720
+ response += `2. ✅ Check the existing code snippets\n`;
3721
+ response += `3. ✅ Follow the same patterns in your new code\n`;
3722
+ response += `4. ✅ When done, call \`validate_complete\` to verify\n\n`;
3723
+ response += `**You are NOT ALLOWED to skip these steps.**`;
3724
+ return {
3725
+ content: [{
3726
+ type: 'text',
3727
+ text: response,
3728
+ }],
3729
+ };
3730
+ }
3731
+ /**
3732
+ * Extract keywords from a task description
3733
+ */
3734
+ extractKeywords(task) {
3735
+ const words = task.toLowerCase()
3736
+ .replace(/[^a-z0-9\s]/g, ' ')
3737
+ .split(/\s+/)
3738
+ .filter(w => w.length > 2);
3739
+ // Filter out common words
3740
+ const stopWords = ['the', 'and', 'for', 'add', 'fix', 'create', 'make', 'build', 'implement', 'update', 'modify', 'change', 'new', 'with', 'from', 'this', 'that'];
3741
+ return words.filter(w => !stopWords.includes(w));
3742
+ }
3743
+ /**
3744
+ * Find files recursively with given extensions
3745
+ */
3746
+ findFilesRecursive(dir, extensions) {
3747
+ const results = [];
3748
+ try {
3749
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
3750
+ for (const entry of entries) {
3751
+ const fullPath = path.join(dir, entry.name);
3752
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
3753
+ results.push(...this.findFilesRecursive(fullPath, extensions));
3754
+ }
3755
+ else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
3756
+ results.push(fullPath);
3757
+ }
3758
+ }
3759
+ }
3760
+ catch {
3761
+ // Ignore errors
3762
+ }
3763
+ return results;
3764
+ }
3476
3765
  async handleReportPatternGap(args) {
3477
3766
  const { category, request, context, handledWith, wasSuccessful = true } = args;
3478
3767
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "3.3.17",
3
+ "version": "3.3.18",
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
@@ -896,6 +896,31 @@ class CodeBakersServer {
896
896
  required: ['feature'],
897
897
  },
898
898
  },
899
+ {
900
+ name: 'discover_patterns',
901
+ description:
902
+ 'MANDATORY: Call this BEFORE writing or modifying ANY code. Searches codebase for similar implementations and identifies relevant patterns. Returns existing code patterns you MUST follow. You are NOT ALLOWED to write code without calling this first.',
903
+ inputSchema: {
904
+ type: 'object' as const,
905
+ properties: {
906
+ task: {
907
+ type: 'string',
908
+ description: 'What you are about to do (e.g., "add signup form", "fix auth bug", "create payment endpoint")',
909
+ },
910
+ files: {
911
+ type: 'array',
912
+ items: { type: 'string' },
913
+ description: 'Files you plan to create or modify',
914
+ },
915
+ keywords: {
916
+ type: 'array',
917
+ items: { type: 'string' },
918
+ description: 'Keywords to search for in codebase (e.g., ["auth", "login", "user"])',
919
+ },
920
+ },
921
+ required: ['task'],
922
+ },
923
+ },
899
924
  {
900
925
  name: 'report_pattern_gap',
901
926
  description:
@@ -1619,6 +1644,9 @@ class CodeBakersServer {
1619
1644
  case 'validate_complete':
1620
1645
  return this.handleValidateComplete(args as { feature: string; files?: string[] });
1621
1646
 
1647
+ case 'discover_patterns':
1648
+ return this.handleDiscoverPatterns(args as { task: string; files?: string[]; keywords?: string[] });
1649
+
1622
1650
  case 'report_pattern_gap':
1623
1651
  return this.handleReportPatternGap(args as { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean });
1624
1652
 
@@ -3796,16 +3824,50 @@ Just describe what you want to build! I'll automatically:
3796
3824
 
3797
3825
  /**
3798
3826
  * MANDATORY: Validate that a feature is complete before AI can say "done"
3799
- * Checks: tests exist, tests pass, TypeScript compiles
3827
+ * Checks: discover_patterns was called, tests exist, tests pass, TypeScript compiles
3800
3828
  */
3801
3829
  private handleValidateComplete(args: { feature: string; files?: string[] }) {
3802
3830
  const { feature, files = [] } = args;
3803
3831
  const cwd = process.cwd();
3804
3832
  const issues: string[] = [];
3833
+ let patternsDiscovered = false;
3805
3834
  let testsExist = false;
3806
3835
  let testsPass = false;
3807
3836
  let typescriptPass = false;
3808
3837
 
3838
+ // Step 0: Check if discover_patterns was called (compliance tracking)
3839
+ try {
3840
+ const stateFile = path.join(cwd, '.codebakers.json');
3841
+ if (fs.existsSync(stateFile)) {
3842
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3843
+ const compliance = state.compliance as { discoveries?: unknown[] } | undefined;
3844
+
3845
+ if (compliance?.discoveries && Array.isArray(compliance.discoveries)) {
3846
+ // Check if there's a recent discovery (within last 30 minutes)
3847
+ const recentDiscovery = compliance.discoveries.find((d: unknown) => {
3848
+ const discovery = d as { timestamp?: string; task?: string };
3849
+ if (!discovery.timestamp) return false;
3850
+ const age = Date.now() - new Date(discovery.timestamp).getTime();
3851
+ return age < 30 * 60 * 1000; // 30 minutes
3852
+ });
3853
+
3854
+ if (recentDiscovery) {
3855
+ patternsDiscovered = true;
3856
+ } else {
3857
+ issues.push('PATTERNS_NOT_CHECKED: You did not call `discover_patterns` before writing code. You MUST check existing patterns first.');
3858
+ }
3859
+ } else {
3860
+ issues.push('PATTERNS_NOT_CHECKED: You did not call `discover_patterns` before writing code. You MUST check existing patterns first.');
3861
+ }
3862
+ } else {
3863
+ // No state file - patterns weren't discovered
3864
+ issues.push('PATTERNS_NOT_CHECKED: You did not call `discover_patterns` before writing code. You MUST check existing patterns first.');
3865
+ }
3866
+ } catch {
3867
+ // If we can't read state, warn but don't fail
3868
+ issues.push('PATTERNS_UNKNOWN: Could not verify if `discover_patterns` was called. Please call it before continuing.');
3869
+ }
3870
+
3809
3871
  // Step 1: Check if test files exist
3810
3872
  try {
3811
3873
  const testDirs = ['tests', 'test', '__tests__', 'src/__tests__', 'src/tests'];
@@ -3884,10 +3946,11 @@ Just describe what you want to build! I'll automatically:
3884
3946
  }
3885
3947
 
3886
3948
  // Generate response
3887
- const valid = testsExist && testsPass && typescriptPass;
3949
+ const valid = patternsDiscovered && testsExist && testsPass && typescriptPass;
3888
3950
 
3889
3951
  let response = `# ✅ Feature Validation: ${feature}\n\n`;
3890
3952
  response += `| Check | Status |\n|-------|--------|\n`;
3953
+ response += `| Patterns discovered | ${patternsDiscovered ? '✅ PASS' : '❌ FAIL'} |\n`;
3891
3954
  response += `| Tests exist | ${testsExist ? '✅ PASS' : '❌ FAIL'} |\n`;
3892
3955
  response += `| Tests pass | ${testsPass ? '✅ PASS' : testsExist ? '❌ FAIL' : '⏭️ SKIP'} |\n`;
3893
3956
  response += `| TypeScript compiles | ${typescriptPass ? '✅ PASS' : '❌ FAIL'} |\n\n`;
@@ -3902,7 +3965,11 @@ Just describe what you want to build! I'll automatically:
3902
3965
  for (const issue of issues) {
3903
3966
  response += `- ${issue}\n\n`;
3904
3967
  }
3905
- response += `---\n\n**Fix these issues and call \`validate_complete\` again.**`;
3968
+ if (!patternsDiscovered) {
3969
+ response += `---\n\n**First, call \`discover_patterns\` to check existing code patterns. Then fix remaining issues and call \`validate_complete\` again.**`;
3970
+ } else {
3971
+ response += `---\n\n**Fix these issues and call \`validate_complete\` again.**`;
3972
+ }
3906
3973
  }
3907
3974
 
3908
3975
  return {
@@ -3915,6 +3982,251 @@ Just describe what you want to build! I'll automatically:
3915
3982
  };
3916
3983
  }
3917
3984
 
3985
+ /**
3986
+ * discover_patterns - START gate for pattern compliance
3987
+ * MUST be called before writing any code
3988
+ */
3989
+ private handleDiscoverPatterns(args: { task: string; files?: string[]; keywords?: string[] }) {
3990
+ const { task, files = [], keywords = [] } = args;
3991
+ const cwd = process.cwd();
3992
+
3993
+ // Extract keywords from task if not provided
3994
+ const taskKeywords = this.extractKeywords(task);
3995
+ const allKeywords = [...new Set([...keywords, ...taskKeywords])];
3996
+
3997
+ // Results to return
3998
+ const patterns: string[] = [];
3999
+ const existingCode: { file: string; lines: string; snippet: string }[] = [];
4000
+ const mustFollow: string[] = [];
4001
+
4002
+ // Step 1: Identify relevant .claude/ patterns based on keywords
4003
+ const patternMap: Record<string, string[]> = {
4004
+ 'auth': ['02-auth.md'],
4005
+ 'login': ['02-auth.md'],
4006
+ 'signup': ['02-auth.md'],
4007
+ 'password': ['02-auth.md'],
4008
+ 'session': ['02-auth.md'],
4009
+ 'oauth': ['02-auth.md'],
4010
+ 'payment': ['05-payments.md'],
4011
+ 'stripe': ['05-payments.md'],
4012
+ 'billing': ['05-payments.md'],
4013
+ 'subscription': ['05-payments.md'],
4014
+ 'checkout': ['05-payments.md'],
4015
+ 'database': ['01-database.md'],
4016
+ 'schema': ['01-database.md'],
4017
+ 'drizzle': ['01-database.md'],
4018
+ 'query': ['01-database.md'],
4019
+ 'api': ['03-api.md'],
4020
+ 'route': ['03-api.md'],
4021
+ 'endpoint': ['03-api.md'],
4022
+ 'form': ['04-frontend.md'],
4023
+ 'component': ['04-frontend.md'],
4024
+ 'react': ['04-frontend.md'],
4025
+ 'email': ['06b-email.md'],
4026
+ 'resend': ['06b-email.md'],
4027
+ 'voice': ['06a-voice.md'],
4028
+ 'vapi': ['06a-voice.md'],
4029
+ 'test': ['08-testing.md'],
4030
+ 'playwright': ['08-testing.md'],
4031
+ };
4032
+
4033
+ for (const keyword of allKeywords) {
4034
+ const lowerKeyword = keyword.toLowerCase();
4035
+ for (const [key, patternFiles] of Object.entries(patternMap)) {
4036
+ if (lowerKeyword.includes(key) || key.includes(lowerKeyword)) {
4037
+ patterns.push(...patternFiles);
4038
+ }
4039
+ }
4040
+ }
4041
+
4042
+ // Always include 00-core.md
4043
+ if (!patterns.includes('00-core.md')) {
4044
+ patterns.unshift('00-core.md');
4045
+ }
4046
+
4047
+ // Deduplicate
4048
+ const uniquePatterns = [...new Set(patterns)];
4049
+
4050
+ // Step 2: Search codebase for similar implementations
4051
+ const searchDirs = ['src/services', 'src/lib', 'src/app/api', 'src/components', 'lib', 'services'];
4052
+ const searchExtensions = ['.ts', '.tsx'];
4053
+
4054
+ for (const keyword of allKeywords.slice(0, 5)) { // Limit to avoid too many searches
4055
+ for (const dir of searchDirs) {
4056
+ const searchDir = path.join(cwd, dir);
4057
+ if (!fs.existsSync(searchDir)) continue;
4058
+
4059
+ try {
4060
+ const files = this.findFilesRecursive(searchDir, searchExtensions);
4061
+ for (const file of files.slice(0, 20)) { // Limit files per dir
4062
+ try {
4063
+ const content = fs.readFileSync(file, 'utf-8');
4064
+ const lines = content.split('\n');
4065
+
4066
+ for (let i = 0; i < lines.length; i++) {
4067
+ if (lines[i].toLowerCase().includes(keyword.toLowerCase())) {
4068
+ // Found a match - extract context
4069
+ const startLine = Math.max(0, i - 2);
4070
+ const endLine = Math.min(lines.length - 1, i + 5);
4071
+ const snippet = lines.slice(startLine, endLine + 1).join('\n');
4072
+
4073
+ const relativePath = path.relative(cwd, file);
4074
+
4075
+ // Avoid duplicates
4076
+ if (!existingCode.some(e => e.file === relativePath && Math.abs(parseInt(e.lines.split('-')[0]) - (startLine + 1)) < 5)) {
4077
+ existingCode.push({
4078
+ file: relativePath,
4079
+ lines: `${startLine + 1}-${endLine + 1}`,
4080
+ snippet: snippet.slice(0, 300),
4081
+ });
4082
+ }
4083
+
4084
+ // Only get first match per file
4085
+ break;
4086
+ }
4087
+ }
4088
+ } catch {
4089
+ // Skip unreadable files
4090
+ }
4091
+ }
4092
+ } catch {
4093
+ // Skip inaccessible dirs
4094
+ }
4095
+ }
4096
+ }
4097
+
4098
+ // Step 3: Extract patterns from existing code
4099
+ if (existingCode.length > 0) {
4100
+ // Look for common patterns in existing code
4101
+ for (const code of existingCode) {
4102
+ if (code.snippet.includes('.insert(')) {
4103
+ mustFollow.push(`Use .insert() for creating new records (found in ${code.file})`);
4104
+ }
4105
+ if (code.snippet.includes('.upsert(')) {
4106
+ mustFollow.push(`Use .upsert() for create-or-update operations (found in ${code.file})`);
4107
+ }
4108
+ if (code.snippet.includes('try {') && code.snippet.includes('catch')) {
4109
+ mustFollow.push(`Wrap database/API operations in try/catch (found in ${code.file})`);
4110
+ }
4111
+ if (code.snippet.includes('NextResponse.json')) {
4112
+ mustFollow.push(`Use NextResponse.json() for API responses (found in ${code.file})`);
4113
+ }
4114
+ if (code.snippet.includes('z.object')) {
4115
+ mustFollow.push(`Use Zod for input validation (found in ${code.file})`);
4116
+ }
4117
+ }
4118
+ }
4119
+
4120
+ // Deduplicate mustFollow
4121
+ const uniqueMustFollow = [...new Set(mustFollow)];
4122
+
4123
+ // Step 4: Log discovery to .codebakers.json for compliance tracking
4124
+ try {
4125
+ const stateFile = path.join(cwd, '.codebakers.json');
4126
+ let state: Record<string, unknown> = {};
4127
+ if (fs.existsSync(stateFile)) {
4128
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
4129
+ }
4130
+
4131
+ if (!state.compliance) {
4132
+ state.compliance = { discoveries: [], violations: [] };
4133
+ }
4134
+
4135
+ const compliance = state.compliance as { discoveries: unknown[]; violations: unknown[] };
4136
+ compliance.discoveries.push({
4137
+ task,
4138
+ patterns: uniquePatterns,
4139
+ existingCodeChecked: existingCode.map(e => e.file),
4140
+ timestamp: new Date().toISOString(),
4141
+ });
4142
+
4143
+ // Keep only last 50 discoveries
4144
+ if (compliance.discoveries.length > 50) {
4145
+ compliance.discoveries = compliance.discoveries.slice(-50);
4146
+ }
4147
+
4148
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
4149
+ } catch {
4150
+ // Ignore state file errors
4151
+ }
4152
+
4153
+ // Generate response
4154
+ let response = `# 🔍 Pattern Discovery: ${task}\n\n`;
4155
+ response += `## ⛔ MANDATORY: You MUST follow these patterns before writing code\n\n`;
4156
+
4157
+ response += `### 📦 Patterns to Load\n\n`;
4158
+ response += `Load these from \`.claude/\` folder:\n`;
4159
+ for (const pattern of uniquePatterns) {
4160
+ response += `- \`${pattern}\`\n`;
4161
+ }
4162
+
4163
+ if (existingCode.length > 0) {
4164
+ response += `\n### 🔎 Existing Code to Follow\n\n`;
4165
+ response += `Found ${existingCode.length} relevant implementation(s):\n\n`;
4166
+ for (const code of existingCode.slice(0, 5)) { // Limit output
4167
+ response += `**${code.file}:${code.lines}**\n`;
4168
+ response += `\`\`\`typescript\n${code.snippet}\n\`\`\`\n\n`;
4169
+ }
4170
+ }
4171
+
4172
+ if (uniqueMustFollow.length > 0) {
4173
+ response += `### ✅ Patterns You MUST Follow\n\n`;
4174
+ for (const rule of uniqueMustFollow) {
4175
+ response += `- ${rule}\n`;
4176
+ }
4177
+ }
4178
+
4179
+ response += `\n---\n\n`;
4180
+ response += `## ⚠️ BEFORE WRITING CODE:\n\n`;
4181
+ response += `1. ✅ Read the patterns listed above\n`;
4182
+ response += `2. ✅ Check the existing code snippets\n`;
4183
+ response += `3. ✅ Follow the same patterns in your new code\n`;
4184
+ response += `4. ✅ When done, call \`validate_complete\` to verify\n\n`;
4185
+ response += `**You are NOT ALLOWED to skip these steps.**`;
4186
+
4187
+ return {
4188
+ content: [{
4189
+ type: 'text' as const,
4190
+ text: response,
4191
+ }],
4192
+ };
4193
+ }
4194
+
4195
+ /**
4196
+ * Extract keywords from a task description
4197
+ */
4198
+ private extractKeywords(task: string): string[] {
4199
+ const words = task.toLowerCase()
4200
+ .replace(/[^a-z0-9\s]/g, ' ')
4201
+ .split(/\s+/)
4202
+ .filter(w => w.length > 2);
4203
+
4204
+ // Filter out common words
4205
+ const stopWords = ['the', 'and', 'for', 'add', 'fix', 'create', 'make', 'build', 'implement', 'update', 'modify', 'change', 'new', 'with', 'from', 'this', 'that'];
4206
+ return words.filter(w => !stopWords.includes(w));
4207
+ }
4208
+
4209
+ /**
4210
+ * Find files recursively with given extensions
4211
+ */
4212
+ private findFilesRecursive(dir: string, extensions: string[]): string[] {
4213
+ const results: string[] = [];
4214
+ try {
4215
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
4216
+ for (const entry of entries) {
4217
+ const fullPath = path.join(dir, entry.name);
4218
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
4219
+ results.push(...this.findFilesRecursive(fullPath, extensions));
4220
+ } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
4221
+ results.push(fullPath);
4222
+ }
4223
+ }
4224
+ } catch {
4225
+ // Ignore errors
4226
+ }
4227
+ return results;
4228
+ }
4229
+
3918
4230
  private async handleReportPatternGap(args: { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean }) {
3919
4231
  const { category, request, context, handledWith, wasSuccessful = true } = args;
3920
4232