@codebakers/cli 3.3.17 โ†’ 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mcp/server.ts CHANGED
@@ -68,6 +68,7 @@ class CodeBakersServer {
68
68
  private pendingUpdate: { current: string; latest: string } | null = null;
69
69
  private lastUpdateCheck = 0;
70
70
  private updateCheckInterval = 60 * 60 * 1000; // Check every hour
71
+ private currentSessionToken: string | null = null; // v6.0: Server-side enforcement session
71
72
 
72
73
  constructor() {
73
74
  this.apiKey = getApiKey();
@@ -896,6 +897,31 @@ class CodeBakersServer {
896
897
  required: ['feature'],
897
898
  },
898
899
  },
900
+ {
901
+ name: 'discover_patterns',
902
+ description:
903
+ '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.',
904
+ inputSchema: {
905
+ type: 'object' as const,
906
+ properties: {
907
+ task: {
908
+ type: 'string',
909
+ description: 'What you are about to do (e.g., "add signup form", "fix auth bug", "create payment endpoint")',
910
+ },
911
+ files: {
912
+ type: 'array',
913
+ items: { type: 'string' },
914
+ description: 'Files you plan to create or modify',
915
+ },
916
+ keywords: {
917
+ type: 'array',
918
+ items: { type: 'string' },
919
+ description: 'Keywords to search for in codebase (e.g., ["auth", "login", "user"])',
920
+ },
921
+ },
922
+ required: ['task'],
923
+ },
924
+ },
899
925
  {
900
926
  name: 'report_pattern_gap',
901
927
  description:
@@ -1619,6 +1645,9 @@ class CodeBakersServer {
1619
1645
  case 'validate_complete':
1620
1646
  return this.handleValidateComplete(args as { feature: string; files?: string[] });
1621
1647
 
1648
+ case 'discover_patterns':
1649
+ return this.handleDiscoverPatterns(args as { task: string; files?: string[]; keywords?: string[] });
1650
+
1622
1651
  case 'report_pattern_gap':
1623
1652
  return this.handleReportPatternGap(args as { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean });
1624
1653
 
@@ -3795,18 +3824,32 @@ Just describe what you want to build! I'll automatically:
3795
3824
  }
3796
3825
 
3797
3826
  /**
3798
- * MANDATORY: Validate that a feature is complete before AI can say "done"
3799
- * Checks: tests exist, tests pass, TypeScript compiles
3827
+ * MANDATORY: Validate that a feature is complete before AI can say "done" (v6.0 Server-Side)
3828
+ * Runs local checks (tests, TypeScript), then validates with server
3800
3829
  */
3801
- private handleValidateComplete(args: { feature: string; files?: string[] }) {
3830
+ private async handleValidateComplete(args: { feature: string; files?: string[] }) {
3802
3831
  const { feature, files = [] } = args;
3803
3832
  const cwd = process.cwd();
3804
- const issues: string[] = [];
3805
3833
  let testsExist = false;
3806
3834
  let testsPass = false;
3807
3835
  let typescriptPass = false;
3836
+ const testsWritten: string[] = [];
3808
3837
 
3809
- // Step 1: Check if test files exist
3838
+ // Step 1: Get session token (from memory or state file)
3839
+ let sessionToken = this.currentSessionToken;
3840
+ if (!sessionToken) {
3841
+ try {
3842
+ const stateFile = path.join(cwd, '.codebakers.json');
3843
+ if (fs.existsSync(stateFile)) {
3844
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3845
+ sessionToken = (state.currentSessionToken as string | undefined) || null;
3846
+ }
3847
+ } catch {
3848
+ // Ignore errors
3849
+ }
3850
+ }
3851
+
3852
+ // Step 2: Check if test files exist and find them
3810
3853
  try {
3811
3854
  const testDirs = ['tests', 'test', '__tests__', 'src/__tests__', 'src/tests'];
3812
3855
  const testExtensions = ['.test.ts', '.test.tsx', '.spec.ts', '.spec.tsx'];
@@ -3818,31 +3861,31 @@ Just describe what you want to build! I'll automatically:
3818
3861
  .filter((f: string | Buffer) => testExtensions.some(ext => String(f).endsWith(ext)));
3819
3862
  if (testFiles.length > 0) {
3820
3863
  testsExist = true;
3821
- break;
3864
+ testsWritten.push(...testFiles.map(f => path.join(dir, String(f))));
3822
3865
  }
3823
3866
  }
3824
3867
  }
3825
3868
 
3826
3869
  // Also check for test files adjacent to source files
3827
- if (!testsExist && files.length > 0) {
3870
+ if (files.length > 0) {
3828
3871
  for (const file of files) {
3829
3872
  const testFile = file.replace(/\.tsx?$/, '.test.ts');
3830
3873
  const specFile = file.replace(/\.tsx?$/, '.spec.ts');
3831
- if (fs.existsSync(path.join(cwd, testFile)) || fs.existsSync(path.join(cwd, specFile))) {
3874
+ if (fs.existsSync(path.join(cwd, testFile))) {
3832
3875
  testsExist = true;
3833
- break;
3876
+ testsWritten.push(testFile);
3877
+ }
3878
+ if (fs.existsSync(path.join(cwd, specFile))) {
3879
+ testsExist = true;
3880
+ testsWritten.push(specFile);
3834
3881
  }
3835
3882
  }
3836
3883
  }
3837
-
3838
- if (!testsExist) {
3839
- issues.push('NO_TESTS: No test files found. You MUST write tests before completing this feature.');
3840
- }
3841
3884
  } catch {
3842
- issues.push('NO_TESTS: Could not verify test files exist.');
3885
+ // Ignore errors
3843
3886
  }
3844
3887
 
3845
- // Step 2: Run tests
3888
+ // Step 3: Run tests locally
3846
3889
  if (testsExist) {
3847
3890
  try {
3848
3891
  let testCommand = 'npm test';
@@ -3862,13 +3905,12 @@ Just describe what you want to build! I'll automatically:
3862
3905
  stdio: ['pipe', 'pipe', 'pipe'],
3863
3906
  });
3864
3907
  testsPass = true;
3865
- } catch (error) {
3866
- const execError = error as { stderr?: string };
3867
- issues.push(`TESTS_FAIL: Tests are failing. Fix them before completing.\n${execError.stderr?.slice(0, 500) || ''}`);
3908
+ } catch {
3909
+ testsPass = false;
3868
3910
  }
3869
3911
  }
3870
3912
 
3871
- // Step 3: Run TypeScript check
3913
+ // Step 4: Run TypeScript check locally
3872
3914
  try {
3873
3915
  execSync('npx tsc --noEmit', {
3874
3916
  cwd,
@@ -3877,42 +3919,381 @@ Just describe what you want to build! I'll automatically:
3877
3919
  stdio: ['pipe', 'pipe', 'pipe'],
3878
3920
  });
3879
3921
  typescriptPass = true;
3880
- } catch (error) {
3881
- const execError = error as { stdout?: string; stderr?: string };
3882
- const output = execError.stdout || execError.stderr || '';
3883
- issues.push(`TYPESCRIPT_ERRORS: TypeScript compilation failed.\n${output.slice(0, 500)}`);
3922
+ } catch {
3923
+ typescriptPass = false;
3884
3924
  }
3885
3925
 
3886
- // Generate response
3887
- const valid = testsExist && testsPass && typescriptPass;
3926
+ // Step 5: Call server API for validation
3927
+ if (sessionToken) {
3928
+ try {
3929
+ const response = await fetch(`${this.apiUrl}/api/patterns/validate`, {
3930
+ method: 'POST',
3931
+ headers: {
3932
+ 'Content-Type': 'application/json',
3933
+ ...this.getAuthHeaders(),
3934
+ },
3935
+ body: JSON.stringify({
3936
+ sessionToken,
3937
+ featureName: feature,
3938
+ featureDescription: `Feature implementation for: ${feature}`,
3939
+ filesModified: files,
3940
+ testsWritten,
3941
+ testsRun: testsExist,
3942
+ testsPassed: testsPass,
3943
+ typescriptPassed: typescriptPass,
3944
+ }),
3945
+ });
3946
+
3947
+ const result = await response.json();
3888
3948
 
3889
- let response = `# โœ… Feature Validation: ${feature}\n\n`;
3890
- response += `| Check | Status |\n|-------|--------|\n`;
3891
- response += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
3892
- response += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
3893
- response += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
3949
+ // Save validation result for pre-commit hook
3950
+ try {
3951
+ const stateFile = path.join(cwd, '.codebakers.json');
3952
+ let state: Record<string, unknown> = {};
3953
+ if (fs.existsSync(stateFile)) {
3954
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
3955
+ }
3894
3956
 
3895
- if (valid) {
3896
- response += `## โœ… Feature is COMPLETE\n\n`;
3897
- response += `All validation checks passed. You may now mark this feature as done.\n`;
3957
+ // Save validation details for pre-commit hook
3958
+ state.lastValidation = {
3959
+ passed: result.passed,
3960
+ timestamp: new Date().toISOString(),
3961
+ feature,
3962
+ issues: result.issues || [],
3963
+ testsExist,
3964
+ testsPassed: testsPass,
3965
+ typescriptPassed: typescriptPass,
3966
+ };
3967
+
3968
+ // Clear session token only if validation passed
3969
+ if (result.passed) {
3970
+ this.currentSessionToken = null;
3971
+ delete state.currentSessionToken;
3972
+ state.lastValidationAt = new Date().toISOString();
3973
+ state.lastValidationPassed = true;
3974
+ }
3975
+
3976
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
3977
+ } catch {
3978
+ // Ignore errors
3979
+ }
3980
+
3981
+ // Generate response from server result
3982
+ let responseText = `# โœ… Feature Validation: ${feature}\n\n`;
3983
+ responseText += `## Server Validation Result\n\n`;
3984
+ responseText += `**Status:** ${result.passed ? 'โœ… PASSED' : 'โŒ FAILED'}\n\n`;
3985
+
3986
+ if (result.issues && result.issues.length > 0) {
3987
+ responseText += `### Issues:\n\n`;
3988
+ for (const issue of result.issues) {
3989
+ const icon = issue.severity === 'error' ? 'โŒ' : 'โš ๏ธ';
3990
+ responseText += `${icon} **${issue.type}**: ${issue.message}\n\n`;
3991
+ }
3992
+ }
3993
+
3994
+ responseText += `### Local Checks:\n\n`;
3995
+ responseText += `| Check | Status |\n|-------|--------|\n`;
3996
+ responseText += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
3997
+ responseText += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
3998
+ responseText += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
3999
+
4000
+ if (result.passed) {
4001
+ responseText += `## โœ… Feature is COMPLETE\n\n`;
4002
+ responseText += `Server has recorded this completion. You may now mark this feature as done.\n`;
4003
+ } else {
4004
+ responseText += `## โŒ Feature is NOT COMPLETE\n\n`;
4005
+ responseText += `**${result.nextSteps || 'Fix the issues above and try again.'}**\n`;
4006
+ }
4007
+
4008
+ return {
4009
+ content: [{
4010
+ type: 'text' as const,
4011
+ text: responseText,
4012
+ }],
4013
+ isError: !result.passed,
4014
+ };
4015
+ } catch (error) {
4016
+ // Server unreachable - fall back to local validation
4017
+ const message = error instanceof Error ? error.message : 'Unknown error';
4018
+ const valid = testsExist && testsPass && typescriptPass;
4019
+
4020
+ let responseText = `# โœ… Feature Validation: ${feature}\n\n`;
4021
+ responseText += `## โš ๏ธ OFFLINE MODE - Server Unreachable\n\n`;
4022
+ responseText += `Error: ${message}\n\n`;
4023
+ responseText += `### Local Checks Only:\n\n`;
4024
+ responseText += `| Check | Status |\n|-------|--------|\n`;
4025
+ responseText += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
4026
+ responseText += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
4027
+ responseText += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
4028
+ responseText += `**Note:** Server validation skipped due to connection error.\n`;
4029
+
4030
+ return {
4031
+ content: [{
4032
+ type: 'text' as const,
4033
+ text: responseText,
4034
+ }],
4035
+ isError: !valid,
4036
+ };
4037
+ }
3898
4038
  } else {
3899
- response += `## โŒ Feature is NOT COMPLETE\n\n`;
3900
- response += `**You are NOT ALLOWED to say "done" or "complete" until all checks pass.**\n\n`;
3901
- response += `### Issues to fix:\n\n`;
3902
- for (const issue of issues) {
3903
- response += `- ${issue}\n\n`;
4039
+ // No session token - cannot validate with server
4040
+ let responseText = `# โŒ Feature Validation: ${feature}\n\n`;
4041
+ responseText += `## โ›” NO SESSION TOKEN\n\n`;
4042
+ responseText += `You must call \`discover_patterns\` BEFORE writing code to get a session token.\n\n`;
4043
+ responseText += `### Local Checks (not sufficient for completion):\n\n`;
4044
+ responseText += `| Check | Status |\n|-------|--------|\n`;
4045
+ responseText += `| Tests exist | ${testsExist ? 'โœ… PASS' : 'โŒ FAIL'} |\n`;
4046
+ responseText += `| Tests pass | ${testsPass ? 'โœ… PASS' : testsExist ? 'โŒ FAIL' : 'โญ๏ธ SKIP'} |\n`;
4047
+ responseText += `| TypeScript compiles | ${typescriptPass ? 'โœ… PASS' : 'โŒ FAIL'} |\n\n`;
4048
+ responseText += `**You CANNOT complete this feature without a valid session.**\n`;
4049
+ responseText += `Call \`discover_patterns\` first, then implement the feature, then call \`validate_complete\` again.`;
4050
+
4051
+ return {
4052
+ content: [{
4053
+ type: 'text' as const,
4054
+ text: responseText,
4055
+ }],
4056
+ isError: true,
4057
+ };
4058
+ }
4059
+ }
4060
+
4061
+ /**
4062
+ * discover_patterns - START gate for pattern compliance (v6.0 Server-Side)
4063
+ * MUST be called before writing any code
4064
+ * Calls server API to get patterns and creates enforcement session
4065
+ */
4066
+ private async handleDiscoverPatterns(args: { task: string; files?: string[]; keywords?: string[] }) {
4067
+ const { task, files = [], keywords = [] } = args;
4068
+ const cwd = process.cwd();
4069
+
4070
+ // Generate project hash for context
4071
+ let projectHash: string | undefined;
4072
+ let projectName: string | undefined;
4073
+ try {
4074
+ const pkgPath = path.join(cwd, 'package.json');
4075
+ if (fs.existsSync(pkgPath)) {
4076
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
4077
+ projectName = pkg.name || path.basename(cwd);
4078
+ } else {
4079
+ projectName = path.basename(cwd);
3904
4080
  }
3905
- response += `---\n\n**Fix these issues and call \`validate_complete\` again.**`;
4081
+ // Simple hash of project path
4082
+ projectHash = Buffer.from(cwd).toString('base64').slice(0, 32);
4083
+ } catch {
4084
+ projectName = path.basename(cwd);
3906
4085
  }
3907
4086
 
3908
- return {
3909
- content: [{
3910
- type: 'text' as const,
3911
- text: response,
3912
- }],
3913
- // Return structured data for programmatic use
3914
- isError: !valid,
3915
- };
4087
+ try {
4088
+ // Call server API to discover patterns and create enforcement session
4089
+ const response = await fetch(`${this.apiUrl}/api/patterns/discover`, {
4090
+ method: 'POST',
4091
+ headers: {
4092
+ 'Content-Type': 'application/json',
4093
+ ...this.getAuthHeaders(),
4094
+ },
4095
+ body: JSON.stringify({
4096
+ task,
4097
+ files,
4098
+ keywords,
4099
+ projectHash,
4100
+ projectName,
4101
+ }),
4102
+ });
4103
+
4104
+ if (!response.ok) {
4105
+ const error = await response.json().catch(() => ({}));
4106
+ throw new Error(error.error || 'Server returned an error');
4107
+ }
4108
+
4109
+ const result = await response.json();
4110
+
4111
+ // Store session token for validate_complete
4112
+ this.currentSessionToken = result.sessionToken;
4113
+
4114
+ // Also store in local state file for persistence across restarts
4115
+ try {
4116
+ const stateFile = path.join(cwd, '.codebakers.json');
4117
+ let state: Record<string, unknown> = {};
4118
+ if (fs.existsSync(stateFile)) {
4119
+ state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
4120
+ }
4121
+ state.currentSessionToken = result.sessionToken;
4122
+ state.lastDiscoveryTask = task;
4123
+ state.lastDiscoveryAt = new Date().toISOString();
4124
+ // Session expires in 2 hours (server default)
4125
+ state.sessionExpiresAt = result.expiresAt || new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString();
4126
+ // Clear any previous validation (new session = new work)
4127
+ delete state.lastValidation;
4128
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
4129
+ } catch {
4130
+ // Ignore state file errors
4131
+ }
4132
+
4133
+ // Generate response with ALL instructions from server
4134
+ let responseText = `# ๐Ÿ” Pattern Discovery: ${task}\n\n`;
4135
+ responseText += `## โ›” SERVER-ENFORCED SESSION ACTIVE\n\n`;
4136
+ responseText += `**Session Token:** \`${result.sessionToken}\`\n\n`;
4137
+ responseText += `---\n\n`;
4138
+
4139
+ // Section 1: Patterns from server
4140
+ if (result.patterns && result.patterns.length > 0) {
4141
+ responseText += `## ๐Ÿ“ฆ MANDATORY PATTERNS\n\n`;
4142
+ responseText += `You MUST follow these patterns in your code:\n\n`;
4143
+ for (const pattern of result.patterns) {
4144
+ responseText += `### ${pattern.name}\n\n`;
4145
+ responseText += `**Relevance:** ${pattern.relevance}\n\n`;
4146
+ responseText += `\`\`\`typescript\n${pattern.content || ''}\n\`\`\`\n\n`;
4147
+ }
4148
+ }
4149
+
4150
+ // Section 2: Test Requirements (ALL from server, not local file)
4151
+ responseText += `---\n\n`;
4152
+ responseText += `## ๐Ÿงช MANDATORY: TESTS REQUIRED\n\n`;
4153
+ responseText += `**You MUST write and run tests. Validation will FAIL without them.**\n\n`;
4154
+ responseText += `### Test Frameworks\n\n`;
4155
+ responseText += `| Type | Framework | Command |\n`;
4156
+ responseText += `|------|-----------|--------|\n`;
4157
+ responseText += `| Unit tests | Vitest | \`npm run test\` or \`npx vitest run\` |\n`;
4158
+ responseText += `| E2E tests | Playwright | \`npx playwright test\` |\n\n`;
4159
+ responseText += `### Test File Locations\n\n`;
4160
+ responseText += `| Code Type | Test Location |\n`;
4161
+ responseText += `|-----------|---------------|\n`;
4162
+ responseText += `| API routes | \`tests/api/[route].test.ts\` |\n`;
4163
+ responseText += `| Components | \`[component].test.tsx\` |\n`;
4164
+ responseText += `| Services | \`tests/services/[service].test.ts\` |\n`;
4165
+ responseText += `| E2E flows | \`e2e/[feature].spec.ts\` |\n\n`;
4166
+ responseText += `### Minimum Test Template\n\n`;
4167
+ responseText += `\`\`\`typescript\n`;
4168
+ responseText += `// Vitest unit test\n`;
4169
+ responseText += `import { describe, it, expect } from 'vitest';\n\n`;
4170
+ responseText += `describe('FeatureName', () => {\n`;
4171
+ responseText += ` it('should handle happy path', () => {\n`;
4172
+ responseText += ` // Test success case\n`;
4173
+ responseText += ` });\n\n`;
4174
+ responseText += ` it('should handle errors', () => {\n`;
4175
+ responseText += ` // Test error case\n`;
4176
+ responseText += ` });\n`;
4177
+ responseText += `});\n`;
4178
+ responseText += `\`\`\`\n\n`;
4179
+
4180
+ // Section 3: Workflow
4181
+ responseText += `---\n\n`;
4182
+ responseText += `## ๐Ÿ“‹ REQUIRED WORKFLOW\n\n`;
4183
+ responseText += `1. โœ… **Read patterns above** - they are MANDATORY\n`;
4184
+ responseText += `2. โœ… **Write feature code** following the patterns\n`;
4185
+ responseText += `3. โœ… **Write test file(s)** - include happy path + error cases\n`;
4186
+ responseText += `4. โœ… **Run tests**: \`npm run test\`\n`;
4187
+ responseText += `5. โœ… **Fix TypeScript errors**: \`npx tsc --noEmit\`\n`;
4188
+ responseText += `6. โœ… **Call \`validate_complete\`** before saying "done"\n\n`;
4189
+
4190
+ // Section 4: Validation
4191
+ responseText += `---\n\n`;
4192
+ responseText += `## โœ… BEFORE SAYING "DONE"\n\n`;
4193
+ responseText += `You MUST call the \`validate_complete\` MCP tool:\n\n`;
4194
+ responseText += `\`\`\`\n`;
4195
+ responseText += `Tool: validate_complete\n`;
4196
+ responseText += `Args: { feature: "${task}", files: ["list of files you modified"] }\n`;
4197
+ responseText += `\`\`\`\n\n`;
4198
+ responseText += `This tool will:\n`;
4199
+ responseText += `- Verify you called discover_patterns (server checks)\n`;
4200
+ responseText += `- Check that test files exist\n`;
4201
+ responseText += `- Run \`npm test\` and verify tests pass\n`;
4202
+ responseText += `- Run TypeScript check\n`;
4203
+ responseText += `- Report PASS or FAIL from server\n\n`;
4204
+ responseText += `**You CANNOT mark this feature complete without calling validate_complete.**\n\n`;
4205
+
4206
+ // Section 5: Rules
4207
+ responseText += `---\n\n`;
4208
+ responseText += `## โš ๏ธ RULES (SERVER-ENFORCED)\n\n`;
4209
+ responseText += `1. You CANNOT skip these patterns - server tracks compliance\n`;
4210
+ responseText += `2. You CANNOT say "done" without validate_complete passing\n`;
4211
+ responseText += `3. Tests are MANDATORY - validation fails without them\n`;
4212
+ responseText += `4. TypeScript must compile - validation fails on errors\n`;
4213
+ responseText += `5. Pre-commit hook blocks commits without passed validation\n\n`;
4214
+ responseText += `**Server is tracking this session. Compliance is enforced.**`;
4215
+
4216
+ return {
4217
+ content: [{
4218
+ type: 'text' as const,
4219
+ text: responseText,
4220
+ }],
4221
+ };
4222
+ } catch (error) {
4223
+ // Fallback to local-only mode if server is unreachable
4224
+ const message = error instanceof Error ? error.message : 'Unknown error';
4225
+
4226
+ // Generate local patterns as fallback
4227
+ const taskKeywords = this.extractKeywords(task);
4228
+ const allKeywords = [...new Set([...keywords, ...taskKeywords])];
4229
+
4230
+ const patternMap: Record<string, string[]> = {
4231
+ 'auth': ['02-auth.md'], 'login': ['02-auth.md'], 'payment': ['05-payments.md'],
4232
+ 'stripe': ['05-payments.md'], 'database': ['01-database.md'], 'api': ['03-api.md'],
4233
+ 'form': ['04-frontend.md'], 'component': ['04-frontend.md'], 'test': ['08-testing.md'],
4234
+ };
4235
+
4236
+ const patterns: string[] = ['00-core.md'];
4237
+ for (const keyword of allKeywords) {
4238
+ const lowerKeyword = keyword.toLowerCase();
4239
+ for (const [key, patternFiles] of Object.entries(patternMap)) {
4240
+ if (lowerKeyword.includes(key)) patterns.push(...patternFiles);
4241
+ }
4242
+ }
4243
+ const uniquePatterns = [...new Set(patterns)];
4244
+
4245
+ let responseText = `# ๐Ÿ” Pattern Discovery: ${task}\n\n`;
4246
+ responseText += `## โš ๏ธ OFFLINE MODE - Server Unreachable\n\n`;
4247
+ responseText += `Error: ${message}\n\n`;
4248
+ responseText += `**Using local pattern suggestions (not enforced):**\n\n`;
4249
+ for (const p of uniquePatterns) {
4250
+ responseText += `- \`${p}\`\n`;
4251
+ }
4252
+ responseText += `\n**Note:** Without server connection, enforcement is not active.\n`;
4253
+ responseText += `Validation will also be limited to local checks only.`;
4254
+
4255
+ return {
4256
+ content: [{
4257
+ type: 'text' as const,
4258
+ text: responseText,
4259
+ }],
4260
+ };
4261
+ }
4262
+ }
4263
+
4264
+ /**
4265
+ * Extract keywords from a task description
4266
+ */
4267
+ private extractKeywords(task: string): string[] {
4268
+ const words = task.toLowerCase()
4269
+ .replace(/[^a-z0-9\s]/g, ' ')
4270
+ .split(/\s+/)
4271
+ .filter(w => w.length > 2);
4272
+
4273
+ // Filter out common words
4274
+ const stopWords = ['the', 'and', 'for', 'add', 'fix', 'create', 'make', 'build', 'implement', 'update', 'modify', 'change', 'new', 'with', 'from', 'this', 'that'];
4275
+ return words.filter(w => !stopWords.includes(w));
4276
+ }
4277
+
4278
+ /**
4279
+ * Find files recursively with given extensions
4280
+ */
4281
+ private findFilesRecursive(dir: string, extensions: string[]): string[] {
4282
+ const results: string[] = [];
4283
+ try {
4284
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
4285
+ for (const entry of entries) {
4286
+ const fullPath = path.join(dir, entry.name);
4287
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
4288
+ results.push(...this.findFilesRecursive(fullPath, extensions));
4289
+ } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
4290
+ results.push(fullPath);
4291
+ }
4292
+ }
4293
+ } catch {
4294
+ // Ignore errors
4295
+ }
4296
+ return results;
3916
4297
  }
3917
4298
 
3918
4299
  private async handleReportPatternGap(args: { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean }) {