@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.
- package/dist/commands/go.d.ts +1 -0
- package/dist/commands/go.js +75 -33
- package/dist/commands/install-precommit.d.ts +1 -0
- package/dist/commands/install-precommit.js +229 -0
- package/dist/commands/upgrade.js +284 -4
- package/dist/index.js +5 -0
- package/dist/mcp/server.js +325 -256
- package/package.json +1 -1
- package/src/commands/go.ts +91 -36
- package/src/commands/install-precommit.ts +234 -0
- package/src/commands/upgrade.ts +314 -5
- package/src/index.ts +6 -0
- package/src/mcp/server.ts +335 -266
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();
|
|
@@ -3823,52 +3824,32 @@ Just describe what you want to build! I'll automatically:
|
|
|
3823
3824
|
}
|
|
3824
3825
|
|
|
3825
3826
|
/**
|
|
3826
|
-
* MANDATORY: Validate that a feature is complete before AI can say "done"
|
|
3827
|
-
*
|
|
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
|
|
3828
3829
|
*/
|
|
3829
|
-
private handleValidateComplete(args: { feature: string; files?: string[] }) {
|
|
3830
|
+
private async handleValidateComplete(args: { feature: string; files?: string[] }) {
|
|
3830
3831
|
const { feature, files = [] } = args;
|
|
3831
3832
|
const cwd = process.cwd();
|
|
3832
|
-
const issues: string[] = [];
|
|
3833
|
-
let patternsDiscovered = false;
|
|
3834
3833
|
let testsExist = false;
|
|
3835
3834
|
let testsPass = false;
|
|
3836
3835
|
let typescriptPass = false;
|
|
3836
|
+
const testsWritten: string[] = [];
|
|
3837
3837
|
|
|
3838
|
-
// Step
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
const
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
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.');
|
|
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;
|
|
3861
3846
|
}
|
|
3862
|
-
}
|
|
3863
|
-
//
|
|
3864
|
-
issues.push('PATTERNS_NOT_CHECKED: You did not call `discover_patterns` before writing code. You MUST check existing patterns first.');
|
|
3847
|
+
} catch {
|
|
3848
|
+
// Ignore errors
|
|
3865
3849
|
}
|
|
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
3850
|
}
|
|
3870
3851
|
|
|
3871
|
-
// Step
|
|
3852
|
+
// Step 2: Check if test files exist and find them
|
|
3872
3853
|
try {
|
|
3873
3854
|
const testDirs = ['tests', 'test', '__tests__', 'src/__tests__', 'src/tests'];
|
|
3874
3855
|
const testExtensions = ['.test.ts', '.test.tsx', '.spec.ts', '.spec.tsx'];
|
|
@@ -3880,31 +3861,31 @@ Just describe what you want to build! I'll automatically:
|
|
|
3880
3861
|
.filter((f: string | Buffer) => testExtensions.some(ext => String(f).endsWith(ext)));
|
|
3881
3862
|
if (testFiles.length > 0) {
|
|
3882
3863
|
testsExist = true;
|
|
3883
|
-
|
|
3864
|
+
testsWritten.push(...testFiles.map(f => path.join(dir, String(f))));
|
|
3884
3865
|
}
|
|
3885
3866
|
}
|
|
3886
3867
|
}
|
|
3887
3868
|
|
|
3888
3869
|
// Also check for test files adjacent to source files
|
|
3889
|
-
if (
|
|
3870
|
+
if (files.length > 0) {
|
|
3890
3871
|
for (const file of files) {
|
|
3891
3872
|
const testFile = file.replace(/\.tsx?$/, '.test.ts');
|
|
3892
3873
|
const specFile = file.replace(/\.tsx?$/, '.spec.ts');
|
|
3893
|
-
if (fs.existsSync(path.join(cwd, testFile))
|
|
3874
|
+
if (fs.existsSync(path.join(cwd, testFile))) {
|
|
3894
3875
|
testsExist = true;
|
|
3895
|
-
|
|
3876
|
+
testsWritten.push(testFile);
|
|
3877
|
+
}
|
|
3878
|
+
if (fs.existsSync(path.join(cwd, specFile))) {
|
|
3879
|
+
testsExist = true;
|
|
3880
|
+
testsWritten.push(specFile);
|
|
3896
3881
|
}
|
|
3897
3882
|
}
|
|
3898
3883
|
}
|
|
3899
|
-
|
|
3900
|
-
if (!testsExist) {
|
|
3901
|
-
issues.push('NO_TESTS: No test files found. You MUST write tests before completing this feature.');
|
|
3902
|
-
}
|
|
3903
3884
|
} catch {
|
|
3904
|
-
|
|
3885
|
+
// Ignore errors
|
|
3905
3886
|
}
|
|
3906
3887
|
|
|
3907
|
-
// Step
|
|
3888
|
+
// Step 3: Run tests locally
|
|
3908
3889
|
if (testsExist) {
|
|
3909
3890
|
try {
|
|
3910
3891
|
let testCommand = 'npm test';
|
|
@@ -3924,13 +3905,12 @@ Just describe what you want to build! I'll automatically:
|
|
|
3924
3905
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3925
3906
|
});
|
|
3926
3907
|
testsPass = true;
|
|
3927
|
-
} catch
|
|
3928
|
-
|
|
3929
|
-
issues.push(`TESTS_FAIL: Tests are failing. Fix them before completing.\n${execError.stderr?.slice(0, 500) || ''}`);
|
|
3908
|
+
} catch {
|
|
3909
|
+
testsPass = false;
|
|
3930
3910
|
}
|
|
3931
3911
|
}
|
|
3932
3912
|
|
|
3933
|
-
// Step
|
|
3913
|
+
// Step 4: Run TypeScript check locally
|
|
3934
3914
|
try {
|
|
3935
3915
|
execSync('npx tsc --noEmit', {
|
|
3936
3916
|
cwd,
|
|
@@ -3939,257 +3919,346 @@ Just describe what you want to build! I'll automatically:
|
|
|
3939
3919
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
3940
3920
|
});
|
|
3941
3921
|
typescriptPass = true;
|
|
3942
|
-
} catch
|
|
3943
|
-
|
|
3944
|
-
const output = execError.stdout || execError.stderr || '';
|
|
3945
|
-
issues.push(`TYPESCRIPT_ERRORS: TypeScript compilation failed.\n${output.slice(0, 500)}`);
|
|
3922
|
+
} catch {
|
|
3923
|
+
typescriptPass = false;
|
|
3946
3924
|
}
|
|
3947
3925
|
|
|
3948
|
-
//
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
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
|
+
});
|
|
3957
3946
|
|
|
3958
|
-
|
|
3959
|
-
response += `## ✅ Feature is COMPLETE\n\n`;
|
|
3960
|
-
response += `All validation checks passed. You may now mark this feature as done.\n`;
|
|
3961
|
-
} else {
|
|
3962
|
-
response += `## ❌ Feature is NOT COMPLETE\n\n`;
|
|
3963
|
-
response += `**You are NOT ALLOWED to say "done" or "complete" until all checks pass.**\n\n`;
|
|
3964
|
-
response += `### Issues to fix:\n\n`;
|
|
3965
|
-
for (const issue of issues) {
|
|
3966
|
-
response += `- ${issue}\n\n`;
|
|
3967
|
-
}
|
|
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
|
-
}
|
|
3973
|
-
}
|
|
3947
|
+
const result = await response.json();
|
|
3974
3948
|
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
};
|
|
3983
|
-
}
|
|
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
|
+
}
|
|
3984
3956
|
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
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
|
+
};
|
|
3992
3967
|
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
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
|
-
};
|
|
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
|
+
}
|
|
4032
3975
|
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
if (lowerKeyword.includes(key) || key.includes(lowerKeyword)) {
|
|
4037
|
-
patterns.push(...patternFiles);
|
|
3976
|
+
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
3977
|
+
} catch {
|
|
3978
|
+
// Ignore errors
|
|
4038
3979
|
}
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
3980
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
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`;
|
|
4046
3985
|
|
|
4047
|
-
|
|
4048
|
-
|
|
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
|
+
}
|
|
4049
3993
|
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
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`;
|
|
4053
3999
|
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
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
|
+
}
|
|
4058
4007
|
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
snippet: snippet.slice(0, 300),
|
|
4081
|
-
});
|
|
4082
|
-
}
|
|
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`;
|
|
4083
4029
|
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
}
|
|
4092
|
-
} catch {
|
|
4093
|
-
// Skip inaccessible dirs
|
|
4094
|
-
}
|
|
4030
|
+
return {
|
|
4031
|
+
content: [{
|
|
4032
|
+
type: 'text' as const,
|
|
4033
|
+
text: responseText,
|
|
4034
|
+
}],
|
|
4035
|
+
isError: !valid,
|
|
4036
|
+
};
|
|
4095
4037
|
}
|
|
4096
|
-
}
|
|
4038
|
+
} else {
|
|
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.`;
|
|
4097
4050
|
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
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
|
-
}
|
|
4051
|
+
return {
|
|
4052
|
+
content: [{
|
|
4053
|
+
type: 'text' as const,
|
|
4054
|
+
text: responseText,
|
|
4055
|
+
}],
|
|
4056
|
+
isError: true,
|
|
4057
|
+
};
|
|
4118
4058
|
}
|
|
4059
|
+
}
|
|
4119
4060
|
|
|
4120
|
-
|
|
4121
|
-
|
|
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();
|
|
4122
4069
|
|
|
4123
|
-
//
|
|
4070
|
+
// Generate project hash for context
|
|
4071
|
+
let projectHash: string | undefined;
|
|
4072
|
+
let projectName: string | undefined;
|
|
4124
4073
|
try {
|
|
4125
|
-
const
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
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);
|
|
4129
4080
|
}
|
|
4081
|
+
// Simple hash of project path
|
|
4082
|
+
projectHash = Buffer.from(cwd).toString('base64').slice(0, 32);
|
|
4083
|
+
} catch {
|
|
4084
|
+
projectName = path.basename(cwd);
|
|
4085
|
+
}
|
|
4130
4086
|
|
|
4131
|
-
|
|
4132
|
-
|
|
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');
|
|
4133
4107
|
}
|
|
4134
4108
|
|
|
4135
|
-
const
|
|
4136
|
-
compliance.discoveries.push({
|
|
4137
|
-
task,
|
|
4138
|
-
patterns: uniquePatterns,
|
|
4139
|
-
existingCodeChecked: existingCode.map(e => e.file),
|
|
4140
|
-
timestamp: new Date().toISOString(),
|
|
4141
|
-
});
|
|
4109
|
+
const result = await response.json();
|
|
4142
4110
|
|
|
4143
|
-
//
|
|
4144
|
-
|
|
4145
|
-
|
|
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
|
+
}
|
|
4146
4148
|
}
|
|
4147
4149
|
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
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.**`;
|
|
4152
4215
|
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
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';
|
|
4156
4225
|
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
response += `- \`${pattern}\`\n`;
|
|
4161
|
-
}
|
|
4226
|
+
// Generate local patterns as fallback
|
|
4227
|
+
const taskKeywords = this.extractKeywords(task);
|
|
4228
|
+
const allKeywords = [...new Set([...keywords, ...taskKeywords])];
|
|
4162
4229
|
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
response += `\`\`\`typescript\n${code.snippet}\n\`\`\`\n\n`;
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
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
|
+
};
|
|
4171
4235
|
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
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
|
+
}
|
|
4176
4242
|
}
|
|
4177
|
-
|
|
4243
|
+
const uniquePatterns = [...new Set(patterns)];
|
|
4178
4244
|
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
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.`;
|
|
4186
4254
|
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4255
|
+
return {
|
|
4256
|
+
content: [{
|
|
4257
|
+
type: 'text' as const,
|
|
4258
|
+
text: responseText,
|
|
4259
|
+
}],
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4193
4262
|
}
|
|
4194
4263
|
|
|
4195
4264
|
/**
|