@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/dist/mcp/server.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
const
|
|
3404
|
-
if (
|
|
3405
|
-
|
|
3406
|
-
|
|
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
|
-
|
|
3425
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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))
|
|
3432
|
+
if (fs.existsSync(path.join(cwd, testFile))) {
|
|
3454
3433
|
testsExist = true;
|
|
3455
|
-
|
|
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
|
-
|
|
3444
|
+
// Ignore errors
|
|
3465
3445
|
}
|
|
3466
|
-
// Step
|
|
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
|
|
3490
|
-
|
|
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
|
|
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
|
|
3505
|
-
|
|
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
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
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
|
-
|
|
3532
|
-
|
|
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
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
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
|
-
//
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3647
|
-
|
|
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
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
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
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3653
|
+
if (!response.ok) {
|
|
3654
|
+
const error = await response.json().catch(() => ({}));
|
|
3655
|
+
throw new Error(error.error || 'Server returned an error');
|
|
3689
3656
|
}
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
//
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
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
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
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
|