@codebakers/cli 3.3.18 โ†’ 3.4.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.
@@ -60,6 +60,7 @@ class CodeBakersServer {
60
60
  pendingUpdate = null;
61
61
  lastUpdateCheck = 0;
62
62
  updateCheckInterval = 60 * 60 * 1000; // Check every hour
63
+ currentSessionToken = null; // v6.0: Server-side enforcement session
63
64
  constructor() {
64
65
  this.apiKey = (0, config_js_1.getApiKey)();
65
66
  this.apiUrl = (0, config_js_1.getApiUrl)();
@@ -3384,53 +3385,31 @@ Just describe what you want to build! I'll automatically:
3384
3385
  };
3385
3386
  }
3386
3387
  /**
3387
- * MANDATORY: Validate that a feature is complete before AI can say "done"
3388
- * Checks: discover_patterns was called, tests exist, tests pass, TypeScript compiles
3388
+ * MANDATORY: Validate that a feature is complete before AI can say "done" (v6.0 Server-Side)
3389
+ * Runs local checks (tests, TypeScript), then validates with server
3389
3390
  */
3390
- handleValidateComplete(args) {
3391
+ async handleValidateComplete(args) {
3391
3392
  const { feature, files = [] } = args;
3392
3393
  const cwd = process.cwd();
3393
- const issues = [];
3394
- let patternsDiscovered = false;
3395
3394
  let testsExist = false;
3396
3395
  let testsPass = false;
3397
3396
  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.');
3397
+ const testsWritten = [];
3398
+ // Step 1: Get session token (from memory or state file)
3399
+ let sessionToken = this.currentSessionToken;
3400
+ if (!sessionToken) {
3401
+ try {
3402
+ const stateFile = path.join(cwd, '.codebakers.json');
3403
+ if (fs.existsSync(stateFile)) {
3404
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3405
+ sessionToken = state.currentSessionToken || null;
3422
3406
  }
3423
3407
  }
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.');
3408
+ catch {
3409
+ // Ignore errors
3427
3410
  }
3428
3411
  }
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
- }
3433
- // Step 1: Check if test files exist
3412
+ // Step 2: Check if test files exist and find them
3434
3413
  try {
3435
3414
  const testDirs = ['tests', 'test', '__tests__', 'src/__tests__', 'src/tests'];
3436
3415
  const testExtensions = ['.test.ts', '.test.tsx', '.spec.ts', '.spec.tsx'];
@@ -3441,29 +3420,30 @@ Just describe what you want to build! I'll automatically:
3441
3420
  .filter((f) => testExtensions.some(ext => String(f).endsWith(ext)));
3442
3421
  if (testFiles.length > 0) {
3443
3422
  testsExist = true;
3444
- break;
3423
+ testsWritten.push(...testFiles.map(f => path.join(dir, String(f))));
3445
3424
  }
3446
3425
  }
3447
3426
  }
3448
3427
  // Also check for test files adjacent to source files
3449
- if (!testsExist && files.length > 0) {
3428
+ if (files.length > 0) {
3450
3429
  for (const file of files) {
3451
3430
  const testFile = file.replace(/\.tsx?$/, '.test.ts');
3452
3431
  const specFile = file.replace(/\.tsx?$/, '.spec.ts');
3453
- if (fs.existsSync(path.join(cwd, testFile)) || fs.existsSync(path.join(cwd, specFile))) {
3432
+ if (fs.existsSync(path.join(cwd, testFile))) {
3454
3433
  testsExist = true;
3455
- break;
3434
+ testsWritten.push(testFile);
3435
+ }
3436
+ if (fs.existsSync(path.join(cwd, specFile))) {
3437
+ testsExist = true;
3438
+ testsWritten.push(specFile);
3456
3439
  }
3457
3440
  }
3458
3441
  }
3459
- if (!testsExist) {
3460
- issues.push('NO_TESTS: No test files found. You MUST write tests before completing this feature.');
3461
- }
3462
3442
  }
3463
3443
  catch {
3464
- issues.push('NO_TESTS: Could not verify test files exist.');
3444
+ // Ignore errors
3465
3445
  }
3466
- // Step 2: Run tests
3446
+ // Step 3: Run tests locally
3467
3447
  if (testsExist) {
3468
3448
  try {
3469
3449
  let testCommand = 'npm test';
@@ -3486,12 +3466,11 @@ Just describe what you want to build! I'll automatically:
3486
3466
  });
3487
3467
  testsPass = true;
3488
3468
  }
3489
- catch (error) {
3490
- const execError = error;
3491
- issues.push(`TESTS_FAIL: Tests are failing. Fix them before completing.\n${execError.stderr?.slice(0, 500) || ''}`);
3469
+ catch {
3470
+ testsPass = false;
3492
3471
  }
3493
3472
  }
3494
- // Step 3: Run TypeScript check
3473
+ // Step 4: Run TypeScript check locally
3495
3474
  try {
3496
3475
  (0, child_process_1.execSync)('npx tsc --noEmit', {
3497
3476
  cwd,
@@ -3501,232 +3480,322 @@ Just describe what you want to build! I'll automatically:
3501
3480
  });
3502
3481
  typescriptPass = true;
3503
3482
  }
3504
- catch (error) {
3505
- const execError = error;
3506
- const output = execError.stdout || execError.stderr || '';
3507
- issues.push(`TYPESCRIPT_ERRORS: TypeScript compilation failed.\n${output.slice(0, 500)}`);
3508
- }
3509
- // Generate response
3510
- const valid = patternsDiscovered && testsExist && testsPass && typescriptPass;
3511
- let response = `# โœ… Feature Validation: ${feature}\n\n`;
3512
- response += `| Check | Status |\n|-------|--------|\n`;
3513
- response += `| Patterns discovered | ${patternsDiscovered ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
3514
- response += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
3515
- response += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
3516
- response += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
3517
- if (valid) {
3518
- response += `## โœ… Feature is COMPLETE\n\n`;
3519
- response += `All validation checks passed. You may now mark this feature as done.\n`;
3483
+ catch {
3484
+ typescriptPass = false;
3520
3485
  }
3521
- else {
3522
- response += `## โŒ Feature is NOT COMPLETE\n\n`;
3523
- response += `**You are NOT ALLOWED to say "done" or "complete" until all checks pass.**\n\n`;
3524
- response += `### Issues to fix:\n\n`;
3525
- for (const issue of issues) {
3526
- response += `- ${issue}\n\n`;
3527
- }
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.**`;
3486
+ // Step 5: Call server API for validation
3487
+ if (sessionToken) {
3488
+ try {
3489
+ const response = await fetch(`${this.apiUrl}/api/patterns/validate`, {
3490
+ method: 'POST',
3491
+ headers: {
3492
+ 'Content-Type': 'application/json',
3493
+ ...this.getAuthHeaders(),
3494
+ },
3495
+ body: JSON.stringify({
3496
+ sessionToken,
3497
+ featureName: feature,
3498
+ featureDescription: `Feature implementation for: ${feature}`,
3499
+ filesModified: files,
3500
+ testsWritten,
3501
+ testsRun: testsExist,
3502
+ testsPassed: testsPass,
3503
+ typescriptPassed: typescriptPass,
3504
+ }),
3505
+ });
3506
+ const result = await response.json();
3507
+ // Save validation result for pre-commit hook
3508
+ try {
3509
+ const stateFile = path.join(cwd, '.codebakers.json');
3510
+ let state = {};
3511
+ if (fs.existsSync(stateFile)) {
3512
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3513
+ }
3514
+ // Save validation details for pre-commit hook
3515
+ state.lastValidation = {
3516
+ passed: result.passed,
3517
+ timestamp: new Date().toISOString(),
3518
+ feature,
3519
+ issues: result.issues || [],
3520
+ testsExist,
3521
+ testsPassed: testsPass,
3522
+ typescriptPassed: typescriptPass,
3523
+ };
3524
+ // Clear session token only if validation passed
3525
+ if (result.passed) {
3526
+ this.currentSessionToken = null;
3527
+ delete state.currentSessionToken;
3528
+ state.lastValidationAt = new Date().toISOString();
3529
+ state.lastValidationPassed = true;
3530
+ }
3531
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
3532
+ }
3533
+ catch {
3534
+ // Ignore errors
3535
+ }
3536
+ // Generate response from server result
3537
+ let responseText = `# โœ… Feature Validation: ${feature}\n\n`;
3538
+ responseText += `## Server Validation Result\n\n`;
3539
+ responseText += `**Status:** ${result.passed ? 'โœ… PASSED' : 'โŒ FAILED'}\n\n`;
3540
+ if (result.issues && result.issues.length > 0) {
3541
+ responseText += `### Issues:\n\n`;
3542
+ for (const issue of result.issues) {
3543
+ const icon = issue.severity === 'error' ? 'โŒ' : 'โš ๏ธ';
3544
+ responseText += `${icon} **${issue.type}**: ${issue.message}\n\n`;
3545
+ }
3546
+ }
3547
+ responseText += `### Local Checks:\n\n`;
3548
+ responseText += `| Check | Status |\n|-------|--------|\n`;
3549
+ responseText += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
3550
+ responseText += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
3551
+ responseText += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
3552
+ if (result.passed) {
3553
+ responseText += `## โœ… Feature is COMPLETE\n\n`;
3554
+ responseText += `Server has recorded this completion. You may now mark this feature as done.\n`;
3555
+ }
3556
+ else {
3557
+ responseText += `## โŒ Feature is NOT COMPLETE\n\n`;
3558
+ responseText += `**${result.nextSteps || 'Fix the issues above and try again.'}**\n`;
3559
+ }
3560
+ return {
3561
+ content: [{
3562
+ type: 'text',
3563
+ text: responseText,
3564
+ }],
3565
+ isError: !result.passed,
3566
+ };
3530
3567
  }
3531
- else {
3532
- response += `---\n\n**Fix these issues and call \`validate_complete\` again.**`;
3568
+ catch (error) {
3569
+ // Server unreachable - fall back to local validation
3570
+ const message = error instanceof Error ? error.message : 'Unknown error';
3571
+ const valid = testsExist && testsPass && typescriptPass;
3572
+ let responseText = `# โœ… Feature Validation: ${feature}\n\n`;
3573
+ responseText += `## โš ๏ธ OFFLINE MODE - Server Unreachable\n\n`;
3574
+ responseText += `Error: ${message}\n\n`;
3575
+ responseText += `### Local Checks Only:\n\n`;
3576
+ responseText += `| Check | Status |\n|-------|--------|\n`;
3577
+ responseText += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
3578
+ responseText += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
3579
+ responseText += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
3580
+ responseText += `**Note:** Server validation skipped due to connection error.\n`;
3581
+ return {
3582
+ content: [{
3583
+ type: 'text',
3584
+ text: responseText,
3585
+ }],
3586
+ isError: !valid,
3587
+ };
3533
3588
  }
3534
3589
  }
3535
- return {
3536
- content: [{
3537
- type: 'text',
3538
- text: response,
3539
- }],
3540
- // Return structured data for programmatic use
3541
- isError: !valid,
3542
- };
3590
+ else {
3591
+ // No session token - cannot validate with server
3592
+ let responseText = `# โŒ Feature Validation: ${feature}\n\n`;
3593
+ responseText += `## โ›” NO SESSION TOKEN\n\n`;
3594
+ responseText += `You must call \`discover_patterns\` BEFORE writing code to get a session token.\n\n`;
3595
+ responseText += `### Local Checks (not sufficient for completion):\n\n`;
3596
+ responseText += `| Check | Status |\n|-------|--------|\n`;
3597
+ responseText += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
3598
+ responseText += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
3599
+ responseText += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
3600
+ responseText += `**You CANNOT complete this feature without a valid session.**\n`;
3601
+ responseText += `Call \`discover_patterns\` first, then implement the feature, then call \`validate_complete\` again.`;
3602
+ return {
3603
+ content: [{
3604
+ type: 'text',
3605
+ text: responseText,
3606
+ }],
3607
+ isError: true,
3608
+ };
3609
+ }
3543
3610
  }
3544
3611
  /**
3545
- * discover_patterns - START gate for pattern compliance
3612
+ * discover_patterns - START gate for pattern compliance (v6.0 Server-Side)
3546
3613
  * MUST be called before writing any code
3614
+ * Calls server API to get patterns and creates enforcement session
3547
3615
  */
3548
- handleDiscoverPatterns(args) {
3616
+ async handleDiscoverPatterns(args) {
3549
3617
  const { task, files = [], keywords = [] } = args;
3550
3618
  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
- }
3619
+ // Generate project hash for context
3620
+ let projectHash;
3621
+ let projectName;
3622
+ try {
3623
+ const pkgPath = path.join(cwd, 'package.json');
3624
+ if (fs.existsSync(pkgPath)) {
3625
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
3626
+ projectName = pkg.name || path.basename(cwd);
3594
3627
  }
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
- }
3628
+ else {
3629
+ projectName = path.basename(cwd);
3644
3630
  }
3631
+ // Simple hash of project path
3632
+ projectHash = Buffer.from(cwd).toString('base64').slice(0, 32);
3645
3633
  }
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
- }
3634
+ catch {
3635
+ projectName = path.basename(cwd);
3666
3636
  }
3667
- // Deduplicate mustFollow
3668
- const uniqueMustFollow = [...new Set(mustFollow)];
3669
- // Step 4: Log discovery to .codebakers.json for compliance tracking
3670
3637
  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(),
3638
+ // Call server API to discover patterns and create enforcement session
3639
+ const response = await fetch(`${this.apiUrl}/api/patterns/discover`, {
3640
+ method: 'POST',
3641
+ headers: {
3642
+ 'Content-Type': 'application/json',
3643
+ ...this.getAuthHeaders(),
3644
+ },
3645
+ body: JSON.stringify({
3646
+ task,
3647
+ files,
3648
+ keywords,
3649
+ projectHash,
3650
+ projectName,
3651
+ }),
3685
3652
  });
3686
- // Keep only last 50 discoveries
3687
- if (compliance.discoveries.length > 50) {
3688
- compliance.discoveries = compliance.discoveries.slice(-50);
3653
+ if (!response.ok) {
3654
+ const error = await response.json().catch(() => ({}));
3655
+ throw new Error(error.error || 'Server returned an error');
3689
3656
  }
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`;
3657
+ const result = await response.json();
3658
+ // Store session token for validate_complete
3659
+ this.currentSessionToken = result.sessionToken;
3660
+ // Also store in local state file for persistence across restarts
3661
+ try {
3662
+ const stateFile = path.join(cwd, '.codebakers.json');
3663
+ let state = {};
3664
+ if (fs.existsSync(stateFile)) {
3665
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3666
+ }
3667
+ state.currentSessionToken = result.sessionToken;
3668
+ state.lastDiscoveryTask = task;
3669
+ state.lastDiscoveryAt = new Date().toISOString();
3670
+ // Session expires in 2 hours (server default)
3671
+ state.sessionExpiresAt = result.expiresAt || new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString();
3672
+ // Clear any previous validation (new session = new work)
3673
+ delete state.lastValidation;
3674
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
3709
3675
  }
3676
+ catch {
3677
+ // Ignore state file errors
3678
+ }
3679
+ // Generate response with ALL instructions from server
3680
+ let responseText = `# ๐Ÿ” Pattern Discovery: ${task}\n\n`;
3681
+ responseText += `## โ›” SERVER-ENFORCED SESSION ACTIVE\n\n`;
3682
+ responseText += `**Session Token:** \`${result.sessionToken}\`\n\n`;
3683
+ responseText += `---\n\n`;
3684
+ // Section 1: Patterns from server
3685
+ if (result.patterns && result.patterns.length > 0) {
3686
+ responseText += `## ๐Ÿ“ฆ MANDATORY PATTERNS\n\n`;
3687
+ responseText += `You MUST follow these patterns in your code:\n\n`;
3688
+ for (const pattern of result.patterns) {
3689
+ responseText += `### ${pattern.name}\n\n`;
3690
+ responseText += `**Relevance:** ${pattern.relevance}\n\n`;
3691
+ responseText += `\`\`\`typescript\n${pattern.content || ''}\n\`\`\`\n\n`;
3692
+ }
3693
+ }
3694
+ // Section 2: Test Requirements (ALL from server, not local file)
3695
+ responseText += `---\n\n`;
3696
+ responseText += `## ๐Ÿงช MANDATORY: TESTS REQUIRED\n\n`;
3697
+ responseText += `**You MUST write and run tests. Validation will FAIL without them.**\n\n`;
3698
+ responseText += `### Test Frameworks\n\n`;
3699
+ responseText += `| Type | Framework | Command |\n`;
3700
+ responseText += `|------|-----------|--------|\n`;
3701
+ responseText += `| Unit tests | Vitest | \`npm run test\` or \`npx vitest run\` |\n`;
3702
+ responseText += `| E2E tests | Playwright | \`npx playwright test\` |\n\n`;
3703
+ responseText += `### Test File Locations\n\n`;
3704
+ responseText += `| Code Type | Test Location |\n`;
3705
+ responseText += `|-----------|---------------|\n`;
3706
+ responseText += `| API routes | \`tests/api/[route].test.ts\` |\n`;
3707
+ responseText += `| Components | \`[component].test.tsx\` |\n`;
3708
+ responseText += `| Services | \`tests/services/[service].test.ts\` |\n`;
3709
+ responseText += `| E2E flows | \`e2e/[feature].spec.ts\` |\n\n`;
3710
+ responseText += `### Minimum Test Template\n\n`;
3711
+ responseText += `\`\`\`typescript\n`;
3712
+ responseText += `// Vitest unit test\n`;
3713
+ responseText += `import { describe, it, expect } from 'vitest';\n\n`;
3714
+ responseText += `describe('FeatureName', () => {\n`;
3715
+ responseText += ` it('should handle happy path', () => {\n`;
3716
+ responseText += ` // Test success case\n`;
3717
+ responseText += ` });\n\n`;
3718
+ responseText += ` it('should handle errors', () => {\n`;
3719
+ responseText += ` // Test error case\n`;
3720
+ responseText += ` });\n`;
3721
+ responseText += `});\n`;
3722
+ responseText += `\`\`\`\n\n`;
3723
+ // Section 3: Workflow
3724
+ responseText += `---\n\n`;
3725
+ responseText += `## ๐Ÿ“‹ REQUIRED WORKFLOW\n\n`;
3726
+ responseText += `1. โœ… **Read patterns above** - they are MANDATORY\n`;
3727
+ responseText += `2. โœ… **Write feature code** following the patterns\n`;
3728
+ responseText += `3. โœ… **Write test file(s)** - include happy path + error cases\n`;
3729
+ responseText += `4. โœ… **Run tests**: \`npm run test\`\n`;
3730
+ responseText += `5. โœ… **Fix TypeScript errors**: \`npx tsc --noEmit\`\n`;
3731
+ responseText += `6. โœ… **Call \`validate_complete\`** before saying "done"\n\n`;
3732
+ // Section 4: Validation
3733
+ responseText += `---\n\n`;
3734
+ responseText += `## โœ… BEFORE SAYING "DONE"\n\n`;
3735
+ responseText += `You MUST call the \`validate_complete\` MCP tool:\n\n`;
3736
+ responseText += `\`\`\`\n`;
3737
+ responseText += `Tool: validate_complete\n`;
3738
+ responseText += `Args: { feature: "${task}", files: ["list of files you modified"] }\n`;
3739
+ responseText += `\`\`\`\n\n`;
3740
+ responseText += `This tool will:\n`;
3741
+ responseText += `- Verify you called discover_patterns (server checks)\n`;
3742
+ responseText += `- Check that test files exist\n`;
3743
+ responseText += `- Run \`npm test\` and verify tests pass\n`;
3744
+ responseText += `- Run TypeScript check\n`;
3745
+ responseText += `- Report PASS or FAIL from server\n\n`;
3746
+ responseText += `**You CANNOT mark this feature complete without calling validate_complete.**\n\n`;
3747
+ // Section 5: Rules
3748
+ responseText += `---\n\n`;
3749
+ responseText += `## โš ๏ธ RULES (SERVER-ENFORCED)\n\n`;
3750
+ responseText += `1. You CANNOT skip these patterns - server tracks compliance\n`;
3751
+ responseText += `2. You CANNOT say "done" without validate_complete passing\n`;
3752
+ responseText += `3. Tests are MANDATORY - validation fails without them\n`;
3753
+ responseText += `4. TypeScript must compile - validation fails on errors\n`;
3754
+ responseText += `5. Pre-commit hook blocks commits without passed validation\n\n`;
3755
+ responseText += `**Server is tracking this session. Compliance is enforced.**`;
3756
+ return {
3757
+ content: [{
3758
+ type: 'text',
3759
+ text: responseText,
3760
+ }],
3761
+ };
3710
3762
  }
3711
- if (uniqueMustFollow.length > 0) {
3712
- response += `### โœ… Patterns You MUST Follow\n\n`;
3713
- for (const rule of uniqueMustFollow) {
3714
- response += `- ${rule}\n`;
3715
- }
3763
+ catch (error) {
3764
+ // Fallback to local-only mode if server is unreachable
3765
+ const message = error instanceof Error ? error.message : 'Unknown error';
3766
+ // Generate local patterns as fallback
3767
+ const taskKeywords = this.extractKeywords(task);
3768
+ const allKeywords = [...new Set([...keywords, ...taskKeywords])];
3769
+ const patternMap = {
3770
+ 'auth': ['02-auth.md'], 'login': ['02-auth.md'], 'payment': ['05-payments.md'],
3771
+ 'stripe': ['05-payments.md'], 'database': ['01-database.md'], 'api': ['03-api.md'],
3772
+ 'form': ['04-frontend.md'], 'component': ['04-frontend.md'], 'test': ['08-testing.md'],
3773
+ };
3774
+ const patterns = ['00-core.md'];
3775
+ for (const keyword of allKeywords) {
3776
+ const lowerKeyword = keyword.toLowerCase();
3777
+ for (const [key, patternFiles] of Object.entries(patternMap)) {
3778
+ if (lowerKeyword.includes(key))
3779
+ patterns.push(...patternFiles);
3780
+ }
3781
+ }
3782
+ const uniquePatterns = [...new Set(patterns)];
3783
+ let responseText = `# ๐Ÿ” Pattern Discovery: ${task}\n\n`;
3784
+ responseText += `## โš ๏ธ OFFLINE MODE - Server Unreachable\n\n`;
3785
+ responseText += `Error: ${message}\n\n`;
3786
+ responseText += `**Using local pattern suggestions (not enforced):**\n\n`;
3787
+ for (const p of uniquePatterns) {
3788
+ responseText += `- \`${p}\`\n`;
3789
+ }
3790
+ responseText += `\n**Note:** Without server connection, enforcement is not active.\n`;
3791
+ responseText += `Validation will also be limited to local checks only.`;
3792
+ return {
3793
+ content: [{
3794
+ type: 'text',
3795
+ text: responseText,
3796
+ }],
3797
+ };
3716
3798
  }
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
3799
  }
3731
3800
  /**
3732
3801
  * Extract keywords from a task description