@codebakers/cli 3.5.1 → 3.6.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/dist/mcp/server.js +260 -1
- package/package.json +1 -1
- package/src/mcp/server.ts +261 -1
package/dist/mcp/server.js
CHANGED
|
@@ -3416,6 +3416,8 @@ Just describe what you want to build! I'll automatically:
|
|
|
3416
3416
|
let testsPass = false;
|
|
3417
3417
|
let typescriptPass = false;
|
|
3418
3418
|
const testsWritten = [];
|
|
3419
|
+
// v6.1: Code analysis for compliance scoring
|
|
3420
|
+
const codeAnalysis = {};
|
|
3419
3421
|
// Step 1: Get session token (from memory or state file)
|
|
3420
3422
|
let sessionToken = this.currentSessionToken;
|
|
3421
3423
|
if (!sessionToken) {
|
|
@@ -3464,6 +3466,74 @@ Just describe what you want to build! I'll automatically:
|
|
|
3464
3466
|
catch {
|
|
3465
3467
|
// Ignore errors
|
|
3466
3468
|
}
|
|
3469
|
+
// Step 2.5: v6.1 - Analyze code for compliance scoring
|
|
3470
|
+
try {
|
|
3471
|
+
let totalLines = 0;
|
|
3472
|
+
let hasErrorHandling = false;
|
|
3473
|
+
let hasLoadingStates = false;
|
|
3474
|
+
let hasConsoleLog = false;
|
|
3475
|
+
let hasAnyType = false;
|
|
3476
|
+
// Analyze provided files
|
|
3477
|
+
const filesToAnalyze = files.length > 0 ? files : [];
|
|
3478
|
+
// Also scan for recently modified .ts/.tsx files if no files provided
|
|
3479
|
+
if (filesToAnalyze.length === 0) {
|
|
3480
|
+
const srcDir = path.join(cwd, 'src');
|
|
3481
|
+
if (fs.existsSync(srcDir)) {
|
|
3482
|
+
const recentFiles = fs.readdirSync(srcDir, { recursive: true })
|
|
3483
|
+
.filter((f) => {
|
|
3484
|
+
const name = String(f);
|
|
3485
|
+
return (name.endsWith('.ts') || name.endsWith('.tsx')) &&
|
|
3486
|
+
!name.includes('.test.') && !name.includes('.spec.');
|
|
3487
|
+
})
|
|
3488
|
+
.slice(0, 20) // Limit to 20 files for performance
|
|
3489
|
+
.map(f => path.join('src', String(f)));
|
|
3490
|
+
filesToAnalyze.push(...recentFiles);
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
for (const file of filesToAnalyze) {
|
|
3494
|
+
try {
|
|
3495
|
+
const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
|
|
3496
|
+
if (!fs.existsSync(filePath))
|
|
3497
|
+
continue;
|
|
3498
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
3499
|
+
const lines = content.split('\n').length;
|
|
3500
|
+
totalLines += lines;
|
|
3501
|
+
// Check for error handling patterns
|
|
3502
|
+
if (content.includes('try {') || content.includes('catch (') ||
|
|
3503
|
+
content.includes('.catch(') || content.includes('onError') ||
|
|
3504
|
+
content.includes('error:') || content.includes('handleError')) {
|
|
3505
|
+
hasErrorHandling = true;
|
|
3506
|
+
}
|
|
3507
|
+
// Check for loading states
|
|
3508
|
+
if (content.includes('isLoading') || content.includes('loading:') ||
|
|
3509
|
+
content.includes('isPending') || content.includes('Skeleton') ||
|
|
3510
|
+
content.includes('Spinner') || content.includes('Loading')) {
|
|
3511
|
+
hasLoadingStates = true;
|
|
3512
|
+
}
|
|
3513
|
+
// Check for console.log (bad in production)
|
|
3514
|
+
if (content.includes('console.log') || content.includes('console.warn') ||
|
|
3515
|
+
content.includes('console.error')) {
|
|
3516
|
+
hasConsoleLog = true;
|
|
3517
|
+
}
|
|
3518
|
+
// Check for any type (should be avoided)
|
|
3519
|
+
if (content.includes(': any') || content.includes(':any') ||
|
|
3520
|
+
content.includes('as any') || content.includes('<any>')) {
|
|
3521
|
+
hasAnyType = true;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
catch {
|
|
3525
|
+
// Skip files that can't be read
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
codeAnalysis.linesOfCode = totalLines;
|
|
3529
|
+
codeAnalysis.hasErrorHandling = hasErrorHandling;
|
|
3530
|
+
codeAnalysis.hasLoadingStates = hasLoadingStates;
|
|
3531
|
+
codeAnalysis.hasConsoleLog = hasConsoleLog;
|
|
3532
|
+
codeAnalysis.hasAnyType = hasAnyType;
|
|
3533
|
+
}
|
|
3534
|
+
catch {
|
|
3535
|
+
// Ignore code analysis errors
|
|
3536
|
+
}
|
|
3467
3537
|
// Step 3: Run tests locally
|
|
3468
3538
|
if (testsExist) {
|
|
3469
3539
|
try {
|
|
@@ -3522,6 +3592,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
3522
3592
|
testsRun: testsExist,
|
|
3523
3593
|
testsPassed: testsPass,
|
|
3524
3594
|
typescriptPassed: typescriptPass,
|
|
3595
|
+
codeAnalysis, // v6.1: Send code analysis for compliance scoring
|
|
3525
3596
|
}),
|
|
3526
3597
|
});
|
|
3527
3598
|
const result = await response.json();
|
|
@@ -3558,6 +3629,52 @@ Just describe what you want to build! I'll automatically:
|
|
|
3558
3629
|
let responseText = `# ✅ Feature Validation: ${feature}\n\n`;
|
|
3559
3630
|
responseText += `## Server Validation Result\n\n`;
|
|
3560
3631
|
responseText += `**Status:** ${result.passed ? '✅ PASSED' : '❌ FAILED'}\n\n`;
|
|
3632
|
+
// v6.1: Show compliance score
|
|
3633
|
+
if (result.compliance) {
|
|
3634
|
+
const score = result.compliance.score || 0;
|
|
3635
|
+
const scoreEmoji = score >= 90 ? '🏆' : score >= 70 ? '👍' : score >= 50 ? '⚠️' : '❌';
|
|
3636
|
+
responseText += `## ${scoreEmoji} Compliance Score: ${score}/100\n\n`;
|
|
3637
|
+
if (result.compliance.patternScores) {
|
|
3638
|
+
responseText += `### Pattern Scores:\n`;
|
|
3639
|
+
responseText += `| Pattern | Score |\n|---------|-------|\n`;
|
|
3640
|
+
for (const [pattern, patternScore] of Object.entries(result.compliance.patternScores)) {
|
|
3641
|
+
const emoji = patternScore >= 80 ? '✅' : patternScore >= 50 ? '⚠️' : '❌';
|
|
3642
|
+
responseText += `| ${pattern} | ${emoji} ${patternScore}/100 |\n`;
|
|
3643
|
+
}
|
|
3644
|
+
responseText += `\n`;
|
|
3645
|
+
}
|
|
3646
|
+
if (result.compliance.deductions && result.compliance.deductions.length > 0) {
|
|
3647
|
+
responseText += `### Deductions:\n`;
|
|
3648
|
+
for (const deduction of result.compliance.deductions) {
|
|
3649
|
+
responseText += `- ❌ **${deduction.rule}**: ${deduction.issue} (-${deduction.points} pts)\n`;
|
|
3650
|
+
}
|
|
3651
|
+
responseText += `\n`;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
// v6.1: Show test quality metrics
|
|
3655
|
+
if (result.testQuality) {
|
|
3656
|
+
const tq = result.testQuality;
|
|
3657
|
+
responseText += `## 🧪 Test Quality Score: ${tq.overallScore}/100\n\n`;
|
|
3658
|
+
responseText += `| Metric | Status |\n|--------|--------|\n`;
|
|
3659
|
+
responseText += `| Coverage | ${tq.coverage || 0}% |\n`;
|
|
3660
|
+
responseText += `| Happy Path Tests | ${tq.hasHappyPath ? '✅' : '❌'} |\n`;
|
|
3661
|
+
responseText += `| Error Case Tests | ${tq.hasErrorCases ? '✅' : '❌'} |\n`;
|
|
3662
|
+
responseText += `| Boundary Cases | ${tq.hasBoundaryCases ? '✅' : '❌'} |\n\n`;
|
|
3663
|
+
if (tq.missingTests && tq.missingTests.length > 0) {
|
|
3664
|
+
responseText += `### Missing Tests:\n`;
|
|
3665
|
+
for (const missing of tq.missingTests) {
|
|
3666
|
+
responseText += `- ⚠️ ${missing}\n`;
|
|
3667
|
+
}
|
|
3668
|
+
responseText += `\n`;
|
|
3669
|
+
}
|
|
3670
|
+
if (tq.recommendations && tq.recommendations.length > 0) {
|
|
3671
|
+
responseText += `### Recommendations:\n`;
|
|
3672
|
+
for (const rec of tq.recommendations) {
|
|
3673
|
+
responseText += `- 💡 ${rec}\n`;
|
|
3674
|
+
}
|
|
3675
|
+
responseText += `\n`;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3561
3678
|
if (result.issues && result.issues.length > 0) {
|
|
3562
3679
|
responseText += `### Issues:\n\n`;
|
|
3563
3680
|
for (const issue of result.issues) {
|
|
@@ -3572,7 +3689,10 @@ Just describe what you want to build! I'll automatically:
|
|
|
3572
3689
|
responseText += `| TypeScript compiles | ${typescriptPass ? '✅ PASS' : '❌ FAIL'} |\n\n`;
|
|
3573
3690
|
if (result.passed) {
|
|
3574
3691
|
responseText += `## ✅ Feature is COMPLETE\n\n`;
|
|
3575
|
-
|
|
3692
|
+
const completionMsg = result.compliance && result.compliance.score >= 90
|
|
3693
|
+
? 'Excellent work! High compliance score achieved.'
|
|
3694
|
+
: 'Server has recorded this completion. You may now mark this feature as done.';
|
|
3695
|
+
responseText += `${completionMsg}\n`;
|
|
3576
3696
|
}
|
|
3577
3697
|
else {
|
|
3578
3698
|
responseText += `## ❌ Feature is NOT COMPLETE\n\n`;
|
|
@@ -3640,11 +3760,92 @@ Just describe what you want to build! I'll automatically:
|
|
|
3640
3760
|
// Generate project hash for context
|
|
3641
3761
|
let projectHash;
|
|
3642
3762
|
let projectName;
|
|
3763
|
+
let detectedStack = {};
|
|
3643
3764
|
try {
|
|
3644
3765
|
const pkgPath = path.join(cwd, 'package.json');
|
|
3645
3766
|
if (fs.existsSync(pkgPath)) {
|
|
3646
3767
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
3647
3768
|
projectName = pkg.name || path.basename(cwd);
|
|
3769
|
+
// v6.1: Extract detected stack from dependencies for conflict detection
|
|
3770
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3771
|
+
const depNames = Object.keys(allDeps);
|
|
3772
|
+
// Detect framework
|
|
3773
|
+
if (depNames.includes('next'))
|
|
3774
|
+
detectedStack.framework = 'nextjs';
|
|
3775
|
+
else if (depNames.includes('remix'))
|
|
3776
|
+
detectedStack.framework = 'remix';
|
|
3777
|
+
else if (depNames.includes('gatsby'))
|
|
3778
|
+
detectedStack.framework = 'gatsby';
|
|
3779
|
+
else if (depNames.includes('react'))
|
|
3780
|
+
detectedStack.framework = 'react';
|
|
3781
|
+
else if (depNames.includes('vue'))
|
|
3782
|
+
detectedStack.framework = 'vue';
|
|
3783
|
+
// Detect ORM/database
|
|
3784
|
+
const orms = [];
|
|
3785
|
+
if (depNames.includes('drizzle-orm'))
|
|
3786
|
+
orms.push('drizzle');
|
|
3787
|
+
if (depNames.includes('prisma') || depNames.includes('@prisma/client'))
|
|
3788
|
+
orms.push('prisma');
|
|
3789
|
+
if (depNames.includes('typeorm'))
|
|
3790
|
+
orms.push('typeorm');
|
|
3791
|
+
if (depNames.includes('mongoose'))
|
|
3792
|
+
orms.push('mongoose');
|
|
3793
|
+
if (depNames.includes('sequelize'))
|
|
3794
|
+
orms.push('sequelize');
|
|
3795
|
+
if (orms.length > 0)
|
|
3796
|
+
detectedStack.orm = orms.length === 1 ? orms[0] : orms;
|
|
3797
|
+
// Detect state management
|
|
3798
|
+
const stateLibs = [];
|
|
3799
|
+
if (depNames.includes('@reduxjs/toolkit') || depNames.includes('redux'))
|
|
3800
|
+
stateLibs.push('redux');
|
|
3801
|
+
if (depNames.includes('zustand'))
|
|
3802
|
+
stateLibs.push('zustand');
|
|
3803
|
+
if (depNames.includes('jotai'))
|
|
3804
|
+
stateLibs.push('jotai');
|
|
3805
|
+
if (depNames.includes('recoil'))
|
|
3806
|
+
stateLibs.push('recoil');
|
|
3807
|
+
if (depNames.includes('mobx'))
|
|
3808
|
+
stateLibs.push('mobx');
|
|
3809
|
+
if (stateLibs.length > 0)
|
|
3810
|
+
detectedStack.stateManagement = stateLibs.length === 1 ? stateLibs[0] : stateLibs;
|
|
3811
|
+
// Detect styling
|
|
3812
|
+
const styleLibs = [];
|
|
3813
|
+
if (depNames.includes('tailwindcss'))
|
|
3814
|
+
styleLibs.push('tailwind');
|
|
3815
|
+
if (depNames.includes('@emotion/react') || depNames.includes('@emotion/styled'))
|
|
3816
|
+
styleLibs.push('emotion');
|
|
3817
|
+
if (depNames.includes('styled-components'))
|
|
3818
|
+
styleLibs.push('styled-components');
|
|
3819
|
+
if (depNames.includes('@mui/material'))
|
|
3820
|
+
styleLibs.push('mui');
|
|
3821
|
+
if (depNames.includes('@chakra-ui/react'))
|
|
3822
|
+
styleLibs.push('chakra');
|
|
3823
|
+
if (styleLibs.length > 0)
|
|
3824
|
+
detectedStack.styling = styleLibs.length === 1 ? styleLibs[0] : styleLibs;
|
|
3825
|
+
// Detect form libraries
|
|
3826
|
+
const formLibs = [];
|
|
3827
|
+
if (depNames.includes('react-hook-form'))
|
|
3828
|
+
formLibs.push('react-hook-form');
|
|
3829
|
+
if (depNames.includes('formik'))
|
|
3830
|
+
formLibs.push('formik');
|
|
3831
|
+
if (depNames.includes('react-final-form'))
|
|
3832
|
+
formLibs.push('react-final-form');
|
|
3833
|
+
if (formLibs.length > 0)
|
|
3834
|
+
detectedStack.forms = formLibs.length === 1 ? formLibs[0] : formLibs;
|
|
3835
|
+
// Detect auth
|
|
3836
|
+
if (depNames.includes('@supabase/supabase-js'))
|
|
3837
|
+
detectedStack.auth = 'supabase';
|
|
3838
|
+
else if (depNames.includes('next-auth') || depNames.includes('@auth/core'))
|
|
3839
|
+
detectedStack.auth = 'next-auth';
|
|
3840
|
+
else if (depNames.includes('@clerk/nextjs'))
|
|
3841
|
+
detectedStack.auth = 'clerk';
|
|
3842
|
+
else if (depNames.includes('firebase'))
|
|
3843
|
+
detectedStack.auth = 'firebase';
|
|
3844
|
+
// Detect payments
|
|
3845
|
+
if (depNames.includes('stripe'))
|
|
3846
|
+
detectedStack.payments = 'stripe';
|
|
3847
|
+
else if (depNames.includes('@paypal/react-paypal-js'))
|
|
3848
|
+
detectedStack.payments = 'paypal';
|
|
3648
3849
|
}
|
|
3649
3850
|
else {
|
|
3650
3851
|
projectName = path.basename(cwd);
|
|
@@ -3669,6 +3870,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
3669
3870
|
keywords,
|
|
3670
3871
|
projectHash,
|
|
3671
3872
|
projectName,
|
|
3873
|
+
detectedStack, // v6.1: Send stack for conflict detection
|
|
3672
3874
|
}),
|
|
3673
3875
|
});
|
|
3674
3876
|
if (!response.ok) {
|
|
@@ -3701,6 +3903,63 @@ Just describe what you want to build! I'll automatically:
|
|
|
3701
3903
|
let responseText = `# 🔍 Pattern Discovery: ${task}\n\n`;
|
|
3702
3904
|
responseText += `## ⛔ SERVER-ENFORCED SESSION ACTIVE\n\n`;
|
|
3703
3905
|
responseText += `**Session Token:** \`${result.sessionToken}\`\n\n`;
|
|
3906
|
+
// v6.1: Show detected conflicts (high priority warning)
|
|
3907
|
+
if (result.detectedConflicts && result.detectedConflicts.length > 0) {
|
|
3908
|
+
responseText += `---\n\n`;
|
|
3909
|
+
responseText += `## ⚠️ ARCHITECTURE CONFLICTS DETECTED\n\n`;
|
|
3910
|
+
responseText += `The following conflicts were found in your project:\n\n`;
|
|
3911
|
+
for (const conflict of result.detectedConflicts) {
|
|
3912
|
+
responseText += `### ${conflict.type}\n`;
|
|
3913
|
+
responseText += `**Conflicting:** ${conflict.items.join(' + ')}\n`;
|
|
3914
|
+
responseText += `**Recommendation:** ${conflict.recommendation}\n`;
|
|
3915
|
+
responseText += `**Reason:** ${conflict.reason}\n\n`;
|
|
3916
|
+
}
|
|
3917
|
+
responseText += `**Please resolve these conflicts before proceeding.**\n\n`;
|
|
3918
|
+
}
|
|
3919
|
+
// v6.1: Show team profile settings if configured
|
|
3920
|
+
if (result.teamProfile) {
|
|
3921
|
+
responseText += `---\n\n`;
|
|
3922
|
+
responseText += `## 🏢 TEAM PROFILE\n\n`;
|
|
3923
|
+
responseText += `| Setting | Value |\n|---------|-------|\n`;
|
|
3924
|
+
responseText += `| Industry | ${result.teamProfile.industryProfile || 'general'} |\n`;
|
|
3925
|
+
responseText += `| Strictness | ${result.teamProfile.strictnessLevel || 'standard'} |\n`;
|
|
3926
|
+
if (result.teamProfile.requireHipaa)
|
|
3927
|
+
responseText += `| HIPAA | ✅ Required |\n`;
|
|
3928
|
+
if (result.teamProfile.requirePci)
|
|
3929
|
+
responseText += `| PCI-DSS | ✅ Required |\n`;
|
|
3930
|
+
if (result.teamProfile.requireSoc2)
|
|
3931
|
+
responseText += `| SOC2 | ✅ Required |\n`;
|
|
3932
|
+
if (result.teamProfile.requireGdpr)
|
|
3933
|
+
responseText += `| GDPR | ✅ Required |\n`;
|
|
3934
|
+
responseText += `\n`;
|
|
3935
|
+
}
|
|
3936
|
+
// v6.1: Show project memory if available
|
|
3937
|
+
if (result.projectMemory) {
|
|
3938
|
+
responseText += `---\n\n`;
|
|
3939
|
+
responseText += `## 🧠 PROJECT MEMORY\n\n`;
|
|
3940
|
+
responseText += `Server has remembered your project's architectural decisions:\n\n`;
|
|
3941
|
+
const memory = result.projectMemory;
|
|
3942
|
+
if (memory.stackDecisions) {
|
|
3943
|
+
const stack = typeof memory.stackDecisions === 'string'
|
|
3944
|
+
? JSON.parse(memory.stackDecisions)
|
|
3945
|
+
: memory.stackDecisions;
|
|
3946
|
+
if (Object.keys(stack).length > 0) {
|
|
3947
|
+
responseText += `### Stack Decisions\n`;
|
|
3948
|
+
responseText += `| Category | Choice |\n|----------|--------|\n`;
|
|
3949
|
+
for (const [key, value] of Object.entries(stack)) {
|
|
3950
|
+
responseText += `| ${key} | ${value} |\n`;
|
|
3951
|
+
}
|
|
3952
|
+
responseText += `\n`;
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
if (memory.namingConventions) {
|
|
3956
|
+
responseText += `### Naming Conventions\n\`\`\`\n${memory.namingConventions}\n\`\`\`\n\n`;
|
|
3957
|
+
}
|
|
3958
|
+
if (memory.projectRules) {
|
|
3959
|
+
responseText += `### Project Rules\n${memory.projectRules}\n\n`;
|
|
3960
|
+
}
|
|
3961
|
+
responseText += `**Follow these established patterns for consistency.**\n\n`;
|
|
3962
|
+
}
|
|
3704
3963
|
responseText += `---\n\n`;
|
|
3705
3964
|
// Section 1: Patterns from server
|
|
3706
3965
|
if (result.patterns && result.patterns.length > 0) {
|
package/package.json
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -3853,6 +3853,16 @@ Just describe what you want to build! I'll automatically:
|
|
|
3853
3853
|
let typescriptPass = false;
|
|
3854
3854
|
const testsWritten: string[] = [];
|
|
3855
3855
|
|
|
3856
|
+
// v6.1: Code analysis for compliance scoring
|
|
3857
|
+
const codeAnalysis: {
|
|
3858
|
+
hasErrorHandling?: boolean;
|
|
3859
|
+
hasLoadingStates?: boolean;
|
|
3860
|
+
hasTypeAnnotations?: boolean;
|
|
3861
|
+
hasConsoleLog?: boolean;
|
|
3862
|
+
hasAnyType?: boolean;
|
|
3863
|
+
linesOfCode?: number;
|
|
3864
|
+
} = {};
|
|
3865
|
+
|
|
3856
3866
|
// Step 1: Get session token (from memory or state file)
|
|
3857
3867
|
let sessionToken = this.currentSessionToken;
|
|
3858
3868
|
if (!sessionToken) {
|
|
@@ -3903,6 +3913,81 @@ Just describe what you want to build! I'll automatically:
|
|
|
3903
3913
|
// Ignore errors
|
|
3904
3914
|
}
|
|
3905
3915
|
|
|
3916
|
+
// Step 2.5: v6.1 - Analyze code for compliance scoring
|
|
3917
|
+
try {
|
|
3918
|
+
let totalLines = 0;
|
|
3919
|
+
let hasErrorHandling = false;
|
|
3920
|
+
let hasLoadingStates = false;
|
|
3921
|
+
let hasConsoleLog = false;
|
|
3922
|
+
let hasAnyType = false;
|
|
3923
|
+
|
|
3924
|
+
// Analyze provided files
|
|
3925
|
+
const filesToAnalyze = files.length > 0 ? files : [];
|
|
3926
|
+
|
|
3927
|
+
// Also scan for recently modified .ts/.tsx files if no files provided
|
|
3928
|
+
if (filesToAnalyze.length === 0) {
|
|
3929
|
+
const srcDir = path.join(cwd, 'src');
|
|
3930
|
+
if (fs.existsSync(srcDir)) {
|
|
3931
|
+
const recentFiles = fs.readdirSync(srcDir, { recursive: true })
|
|
3932
|
+
.filter((f: string | Buffer) => {
|
|
3933
|
+
const name = String(f);
|
|
3934
|
+
return (name.endsWith('.ts') || name.endsWith('.tsx')) &&
|
|
3935
|
+
!name.includes('.test.') && !name.includes('.spec.');
|
|
3936
|
+
})
|
|
3937
|
+
.slice(0, 20) // Limit to 20 files for performance
|
|
3938
|
+
.map(f => path.join('src', String(f)));
|
|
3939
|
+
filesToAnalyze.push(...recentFiles);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
for (const file of filesToAnalyze) {
|
|
3944
|
+
try {
|
|
3945
|
+
const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
|
|
3946
|
+
if (!fs.existsSync(filePath)) continue;
|
|
3947
|
+
|
|
3948
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
3949
|
+
const lines = content.split('\n').length;
|
|
3950
|
+
totalLines += lines;
|
|
3951
|
+
|
|
3952
|
+
// Check for error handling patterns
|
|
3953
|
+
if (content.includes('try {') || content.includes('catch (') ||
|
|
3954
|
+
content.includes('.catch(') || content.includes('onError') ||
|
|
3955
|
+
content.includes('error:') || content.includes('handleError')) {
|
|
3956
|
+
hasErrorHandling = true;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
// Check for loading states
|
|
3960
|
+
if (content.includes('isLoading') || content.includes('loading:') ||
|
|
3961
|
+
content.includes('isPending') || content.includes('Skeleton') ||
|
|
3962
|
+
content.includes('Spinner') || content.includes('Loading')) {
|
|
3963
|
+
hasLoadingStates = true;
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
// Check for console.log (bad in production)
|
|
3967
|
+
if (content.includes('console.log') || content.includes('console.warn') ||
|
|
3968
|
+
content.includes('console.error')) {
|
|
3969
|
+
hasConsoleLog = true;
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
// Check for any type (should be avoided)
|
|
3973
|
+
if (content.includes(': any') || content.includes(':any') ||
|
|
3974
|
+
content.includes('as any') || content.includes('<any>')) {
|
|
3975
|
+
hasAnyType = true;
|
|
3976
|
+
}
|
|
3977
|
+
} catch {
|
|
3978
|
+
// Skip files that can't be read
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
codeAnalysis.linesOfCode = totalLines;
|
|
3983
|
+
codeAnalysis.hasErrorHandling = hasErrorHandling;
|
|
3984
|
+
codeAnalysis.hasLoadingStates = hasLoadingStates;
|
|
3985
|
+
codeAnalysis.hasConsoleLog = hasConsoleLog;
|
|
3986
|
+
codeAnalysis.hasAnyType = hasAnyType;
|
|
3987
|
+
} catch {
|
|
3988
|
+
// Ignore code analysis errors
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3906
3991
|
// Step 3: Run tests locally
|
|
3907
3992
|
if (testsExist) {
|
|
3908
3993
|
try {
|
|
@@ -3959,6 +4044,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
3959
4044
|
testsRun: testsExist,
|
|
3960
4045
|
testsPassed: testsPass,
|
|
3961
4046
|
typescriptPassed: typescriptPass,
|
|
4047
|
+
codeAnalysis, // v6.1: Send code analysis for compliance scoring
|
|
3962
4048
|
}),
|
|
3963
4049
|
});
|
|
3964
4050
|
|
|
@@ -4001,6 +4087,58 @@ Just describe what you want to build! I'll automatically:
|
|
|
4001
4087
|
responseText += `## Server Validation Result\n\n`;
|
|
4002
4088
|
responseText += `**Status:** ${result.passed ? '✅ PASSED' : '❌ FAILED'}\n\n`;
|
|
4003
4089
|
|
|
4090
|
+
// v6.1: Show compliance score
|
|
4091
|
+
if (result.compliance) {
|
|
4092
|
+
const score = result.compliance.score || 0;
|
|
4093
|
+
const scoreEmoji = score >= 90 ? '🏆' : score >= 70 ? '👍' : score >= 50 ? '⚠️' : '❌';
|
|
4094
|
+
responseText += `## ${scoreEmoji} Compliance Score: ${score}/100\n\n`;
|
|
4095
|
+
|
|
4096
|
+
if (result.compliance.patternScores) {
|
|
4097
|
+
responseText += `### Pattern Scores:\n`;
|
|
4098
|
+
responseText += `| Pattern | Score |\n|---------|-------|\n`;
|
|
4099
|
+
for (const [pattern, patternScore] of Object.entries(result.compliance.patternScores)) {
|
|
4100
|
+
const emoji = (patternScore as number) >= 80 ? '✅' : (patternScore as number) >= 50 ? '⚠️' : '❌';
|
|
4101
|
+
responseText += `| ${pattern} | ${emoji} ${patternScore}/100 |\n`;
|
|
4102
|
+
}
|
|
4103
|
+
responseText += `\n`;
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
if (result.compliance.deductions && result.compliance.deductions.length > 0) {
|
|
4107
|
+
responseText += `### Deductions:\n`;
|
|
4108
|
+
for (const deduction of result.compliance.deductions) {
|
|
4109
|
+
responseText += `- ❌ **${deduction.rule}**: ${deduction.issue} (-${deduction.points} pts)\n`;
|
|
4110
|
+
}
|
|
4111
|
+
responseText += `\n`;
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
// v6.1: Show test quality metrics
|
|
4116
|
+
if (result.testQuality) {
|
|
4117
|
+
const tq = result.testQuality;
|
|
4118
|
+
responseText += `## 🧪 Test Quality Score: ${tq.overallScore}/100\n\n`;
|
|
4119
|
+
responseText += `| Metric | Status |\n|--------|--------|\n`;
|
|
4120
|
+
responseText += `| Coverage | ${tq.coverage || 0}% |\n`;
|
|
4121
|
+
responseText += `| Happy Path Tests | ${tq.hasHappyPath ? '✅' : '❌'} |\n`;
|
|
4122
|
+
responseText += `| Error Case Tests | ${tq.hasErrorCases ? '✅' : '❌'} |\n`;
|
|
4123
|
+
responseText += `| Boundary Cases | ${tq.hasBoundaryCases ? '✅' : '❌'} |\n\n`;
|
|
4124
|
+
|
|
4125
|
+
if (tq.missingTests && tq.missingTests.length > 0) {
|
|
4126
|
+
responseText += `### Missing Tests:\n`;
|
|
4127
|
+
for (const missing of tq.missingTests) {
|
|
4128
|
+
responseText += `- ⚠️ ${missing}\n`;
|
|
4129
|
+
}
|
|
4130
|
+
responseText += `\n`;
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
if (tq.recommendations && tq.recommendations.length > 0) {
|
|
4134
|
+
responseText += `### Recommendations:\n`;
|
|
4135
|
+
for (const rec of tq.recommendations) {
|
|
4136
|
+
responseText += `- 💡 ${rec}\n`;
|
|
4137
|
+
}
|
|
4138
|
+
responseText += `\n`;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4004
4142
|
if (result.issues && result.issues.length > 0) {
|
|
4005
4143
|
responseText += `### Issues:\n\n`;
|
|
4006
4144
|
for (const issue of result.issues) {
|
|
@@ -4017,7 +4155,10 @@ Just describe what you want to build! I'll automatically:
|
|
|
4017
4155
|
|
|
4018
4156
|
if (result.passed) {
|
|
4019
4157
|
responseText += `## ✅ Feature is COMPLETE\n\n`;
|
|
4020
|
-
|
|
4158
|
+
const completionMsg = result.compliance && result.compliance.score >= 90
|
|
4159
|
+
? 'Excellent work! High compliance score achieved.'
|
|
4160
|
+
: 'Server has recorded this completion. You may now mark this feature as done.';
|
|
4161
|
+
responseText += `${completionMsg}\n`;
|
|
4021
4162
|
} else {
|
|
4022
4163
|
responseText += `## ❌ Feature is NOT COMPLETE\n\n`;
|
|
4023
4164
|
responseText += `**${result.nextSteps || 'Fix the issues above and try again.'}**\n`;
|
|
@@ -4088,11 +4229,68 @@ Just describe what you want to build! I'll automatically:
|
|
|
4088
4229
|
// Generate project hash for context
|
|
4089
4230
|
let projectHash: string | undefined;
|
|
4090
4231
|
let projectName: string | undefined;
|
|
4232
|
+
let detectedStack: Record<string, string | string[]> = {};
|
|
4233
|
+
|
|
4091
4234
|
try {
|
|
4092
4235
|
const pkgPath = path.join(cwd, 'package.json');
|
|
4093
4236
|
if (fs.existsSync(pkgPath)) {
|
|
4094
4237
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
4095
4238
|
projectName = pkg.name || path.basename(cwd);
|
|
4239
|
+
|
|
4240
|
+
// v6.1: Extract detected stack from dependencies for conflict detection
|
|
4241
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4242
|
+
const depNames = Object.keys(allDeps);
|
|
4243
|
+
|
|
4244
|
+
// Detect framework
|
|
4245
|
+
if (depNames.includes('next')) detectedStack.framework = 'nextjs';
|
|
4246
|
+
else if (depNames.includes('remix')) detectedStack.framework = 'remix';
|
|
4247
|
+
else if (depNames.includes('gatsby')) detectedStack.framework = 'gatsby';
|
|
4248
|
+
else if (depNames.includes('react')) detectedStack.framework = 'react';
|
|
4249
|
+
else if (depNames.includes('vue')) detectedStack.framework = 'vue';
|
|
4250
|
+
|
|
4251
|
+
// Detect ORM/database
|
|
4252
|
+
const orms: string[] = [];
|
|
4253
|
+
if (depNames.includes('drizzle-orm')) orms.push('drizzle');
|
|
4254
|
+
if (depNames.includes('prisma') || depNames.includes('@prisma/client')) orms.push('prisma');
|
|
4255
|
+
if (depNames.includes('typeorm')) orms.push('typeorm');
|
|
4256
|
+
if (depNames.includes('mongoose')) orms.push('mongoose');
|
|
4257
|
+
if (depNames.includes('sequelize')) orms.push('sequelize');
|
|
4258
|
+
if (orms.length > 0) detectedStack.orm = orms.length === 1 ? orms[0] : orms;
|
|
4259
|
+
|
|
4260
|
+
// Detect state management
|
|
4261
|
+
const stateLibs: string[] = [];
|
|
4262
|
+
if (depNames.includes('@reduxjs/toolkit') || depNames.includes('redux')) stateLibs.push('redux');
|
|
4263
|
+
if (depNames.includes('zustand')) stateLibs.push('zustand');
|
|
4264
|
+
if (depNames.includes('jotai')) stateLibs.push('jotai');
|
|
4265
|
+
if (depNames.includes('recoil')) stateLibs.push('recoil');
|
|
4266
|
+
if (depNames.includes('mobx')) stateLibs.push('mobx');
|
|
4267
|
+
if (stateLibs.length > 0) detectedStack.stateManagement = stateLibs.length === 1 ? stateLibs[0] : stateLibs;
|
|
4268
|
+
|
|
4269
|
+
// Detect styling
|
|
4270
|
+
const styleLibs: string[] = [];
|
|
4271
|
+
if (depNames.includes('tailwindcss')) styleLibs.push('tailwind');
|
|
4272
|
+
if (depNames.includes('@emotion/react') || depNames.includes('@emotion/styled')) styleLibs.push('emotion');
|
|
4273
|
+
if (depNames.includes('styled-components')) styleLibs.push('styled-components');
|
|
4274
|
+
if (depNames.includes('@mui/material')) styleLibs.push('mui');
|
|
4275
|
+
if (depNames.includes('@chakra-ui/react')) styleLibs.push('chakra');
|
|
4276
|
+
if (styleLibs.length > 0) detectedStack.styling = styleLibs.length === 1 ? styleLibs[0] : styleLibs;
|
|
4277
|
+
|
|
4278
|
+
// Detect form libraries
|
|
4279
|
+
const formLibs: string[] = [];
|
|
4280
|
+
if (depNames.includes('react-hook-form')) formLibs.push('react-hook-form');
|
|
4281
|
+
if (depNames.includes('formik')) formLibs.push('formik');
|
|
4282
|
+
if (depNames.includes('react-final-form')) formLibs.push('react-final-form');
|
|
4283
|
+
if (formLibs.length > 0) detectedStack.forms = formLibs.length === 1 ? formLibs[0] : formLibs;
|
|
4284
|
+
|
|
4285
|
+
// Detect auth
|
|
4286
|
+
if (depNames.includes('@supabase/supabase-js')) detectedStack.auth = 'supabase';
|
|
4287
|
+
else if (depNames.includes('next-auth') || depNames.includes('@auth/core')) detectedStack.auth = 'next-auth';
|
|
4288
|
+
else if (depNames.includes('@clerk/nextjs')) detectedStack.auth = 'clerk';
|
|
4289
|
+
else if (depNames.includes('firebase')) detectedStack.auth = 'firebase';
|
|
4290
|
+
|
|
4291
|
+
// Detect payments
|
|
4292
|
+
if (depNames.includes('stripe')) detectedStack.payments = 'stripe';
|
|
4293
|
+
else if (depNames.includes('@paypal/react-paypal-js')) detectedStack.payments = 'paypal';
|
|
4096
4294
|
} else {
|
|
4097
4295
|
projectName = path.basename(cwd);
|
|
4098
4296
|
}
|
|
@@ -4116,6 +4314,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
4116
4314
|
keywords,
|
|
4117
4315
|
projectHash,
|
|
4118
4316
|
projectName,
|
|
4317
|
+
detectedStack, // v6.1: Send stack for conflict detection
|
|
4119
4318
|
}),
|
|
4120
4319
|
});
|
|
4121
4320
|
|
|
@@ -4152,6 +4351,67 @@ Just describe what you want to build! I'll automatically:
|
|
|
4152
4351
|
let responseText = `# 🔍 Pattern Discovery: ${task}\n\n`;
|
|
4153
4352
|
responseText += `## ⛔ SERVER-ENFORCED SESSION ACTIVE\n\n`;
|
|
4154
4353
|
responseText += `**Session Token:** \`${result.sessionToken}\`\n\n`;
|
|
4354
|
+
|
|
4355
|
+
// v6.1: Show detected conflicts (high priority warning)
|
|
4356
|
+
if (result.detectedConflicts && result.detectedConflicts.length > 0) {
|
|
4357
|
+
responseText += `---\n\n`;
|
|
4358
|
+
responseText += `## ⚠️ ARCHITECTURE CONFLICTS DETECTED\n\n`;
|
|
4359
|
+
responseText += `The following conflicts were found in your project:\n\n`;
|
|
4360
|
+
for (const conflict of result.detectedConflicts) {
|
|
4361
|
+
responseText += `### ${conflict.type}\n`;
|
|
4362
|
+
responseText += `**Conflicting:** ${conflict.items.join(' + ')}\n`;
|
|
4363
|
+
responseText += `**Recommendation:** ${conflict.recommendation}\n`;
|
|
4364
|
+
responseText += `**Reason:** ${conflict.reason}\n\n`;
|
|
4365
|
+
}
|
|
4366
|
+
responseText += `**Please resolve these conflicts before proceeding.**\n\n`;
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
// v6.1: Show team profile settings if configured
|
|
4370
|
+
if (result.teamProfile) {
|
|
4371
|
+
responseText += `---\n\n`;
|
|
4372
|
+
responseText += `## 🏢 TEAM PROFILE\n\n`;
|
|
4373
|
+
responseText += `| Setting | Value |\n|---------|-------|\n`;
|
|
4374
|
+
responseText += `| Industry | ${result.teamProfile.industryProfile || 'general'} |\n`;
|
|
4375
|
+
responseText += `| Strictness | ${result.teamProfile.strictnessLevel || 'standard'} |\n`;
|
|
4376
|
+
if (result.teamProfile.requireHipaa) responseText += `| HIPAA | ✅ Required |\n`;
|
|
4377
|
+
if (result.teamProfile.requirePci) responseText += `| PCI-DSS | ✅ Required |\n`;
|
|
4378
|
+
if (result.teamProfile.requireSoc2) responseText += `| SOC2 | ✅ Required |\n`;
|
|
4379
|
+
if (result.teamProfile.requireGdpr) responseText += `| GDPR | ✅ Required |\n`;
|
|
4380
|
+
responseText += `\n`;
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
// v6.1: Show project memory if available
|
|
4384
|
+
if (result.projectMemory) {
|
|
4385
|
+
responseText += `---\n\n`;
|
|
4386
|
+
responseText += `## 🧠 PROJECT MEMORY\n\n`;
|
|
4387
|
+
responseText += `Server has remembered your project's architectural decisions:\n\n`;
|
|
4388
|
+
|
|
4389
|
+
const memory = result.projectMemory;
|
|
4390
|
+
if (memory.stackDecisions) {
|
|
4391
|
+
const stack = typeof memory.stackDecisions === 'string'
|
|
4392
|
+
? JSON.parse(memory.stackDecisions)
|
|
4393
|
+
: memory.stackDecisions;
|
|
4394
|
+
if (Object.keys(stack).length > 0) {
|
|
4395
|
+
responseText += `### Stack Decisions\n`;
|
|
4396
|
+
responseText += `| Category | Choice |\n|----------|--------|\n`;
|
|
4397
|
+
for (const [key, value] of Object.entries(stack)) {
|
|
4398
|
+
responseText += `| ${key} | ${value} |\n`;
|
|
4399
|
+
}
|
|
4400
|
+
responseText += `\n`;
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
if (memory.namingConventions) {
|
|
4405
|
+
responseText += `### Naming Conventions\n\`\`\`\n${memory.namingConventions}\n\`\`\`\n\n`;
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
if (memory.projectRules) {
|
|
4409
|
+
responseText += `### Project Rules\n${memory.projectRules}\n\n`;
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
responseText += `**Follow these established patterns for consistency.**\n\n`;
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4155
4415
|
responseText += `---\n\n`;
|
|
4156
4416
|
|
|
4157
4417
|
// Section 1: Patterns from server
|