neuronlayer 0.1.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/CONTRIBUTING.md +127 -0
- package/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/index.js +38016 -0
- package/esbuild.config.js +26 -0
- package/package.json +63 -0
- package/src/cli/commands.ts +382 -0
- package/src/core/adr-exporter.ts +253 -0
- package/src/core/architecture/architecture-enforcement.ts +228 -0
- package/src/core/architecture/duplicate-detector.ts +288 -0
- package/src/core/architecture/index.ts +6 -0
- package/src/core/architecture/pattern-learner.ts +306 -0
- package/src/core/architecture/pattern-library.ts +403 -0
- package/src/core/architecture/pattern-validator.ts +324 -0
- package/src/core/change-intelligence/bug-correlator.ts +444 -0
- package/src/core/change-intelligence/change-intelligence.ts +221 -0
- package/src/core/change-intelligence/change-tracker.ts +334 -0
- package/src/core/change-intelligence/fix-suggester.ts +340 -0
- package/src/core/change-intelligence/index.ts +5 -0
- package/src/core/code-verifier.ts +843 -0
- package/src/core/confidence/confidence-scorer.ts +251 -0
- package/src/core/confidence/conflict-checker.ts +289 -0
- package/src/core/confidence/index.ts +5 -0
- package/src/core/confidence/source-tracker.ts +263 -0
- package/src/core/confidence/warning-detector.ts +241 -0
- package/src/core/context-rot/compaction.ts +284 -0
- package/src/core/context-rot/context-health.ts +243 -0
- package/src/core/context-rot/context-rot-prevention.ts +213 -0
- package/src/core/context-rot/critical-context.ts +221 -0
- package/src/core/context-rot/drift-detector.ts +255 -0
- package/src/core/context-rot/index.ts +7 -0
- package/src/core/context.ts +263 -0
- package/src/core/decision-extractor.ts +339 -0
- package/src/core/decisions.ts +69 -0
- package/src/core/deja-vu.ts +421 -0
- package/src/core/engine.ts +1455 -0
- package/src/core/feature-context.ts +726 -0
- package/src/core/ghost-mode.ts +412 -0
- package/src/core/learning.ts +485 -0
- package/src/core/living-docs/activity-tracker.ts +296 -0
- package/src/core/living-docs/architecture-generator.ts +428 -0
- package/src/core/living-docs/changelog-generator.ts +348 -0
- package/src/core/living-docs/component-generator.ts +230 -0
- package/src/core/living-docs/doc-engine.ts +110 -0
- package/src/core/living-docs/doc-validator.ts +282 -0
- package/src/core/living-docs/index.ts +8 -0
- package/src/core/project-manager.ts +297 -0
- package/src/core/summarizer.ts +267 -0
- package/src/core/test-awareness/change-validator.ts +499 -0
- package/src/core/test-awareness/index.ts +5 -0
- package/src/index.ts +49 -0
- package/src/indexing/ast.ts +563 -0
- package/src/indexing/embeddings.ts +85 -0
- package/src/indexing/indexer.ts +245 -0
- package/src/indexing/watcher.ts +78 -0
- package/src/server/gateways/aggregator.ts +374 -0
- package/src/server/gateways/index.ts +473 -0
- package/src/server/gateways/memory-ghost.ts +343 -0
- package/src/server/gateways/memory-query.ts +452 -0
- package/src/server/gateways/memory-record.ts +346 -0
- package/src/server/gateways/memory-review.ts +410 -0
- package/src/server/gateways/memory-status.ts +517 -0
- package/src/server/gateways/memory-verify.ts +392 -0
- package/src/server/gateways/router.ts +434 -0
- package/src/server/gateways/types.ts +610 -0
- package/src/server/mcp.ts +154 -0
- package/src/server/resources.ts +85 -0
- package/src/server/tools.ts +2261 -0
- package/src/storage/database.ts +262 -0
- package/src/storage/tier1.ts +135 -0
- package/src/storage/tier2.ts +764 -0
- package/src/storage/tier3.ts +123 -0
- package/src/types/documentation.ts +619 -0
- package/src/types/index.ts +222 -0
- package/src/utils/config.ts +193 -0
- package/src/utils/files.ts +117 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/tokens.ts +52 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { Tier2Storage } from '../../storage/tier2.js';
|
|
2
|
+
import type { EmbeddingGenerator } from '../../indexing/embeddings.js';
|
|
3
|
+
import type {
|
|
4
|
+
ConfidenceResult,
|
|
5
|
+
ConfidenceLevel,
|
|
6
|
+
ConfidenceSources,
|
|
7
|
+
ConfidenceWarning,
|
|
8
|
+
CodebaseMatch,
|
|
9
|
+
DecisionMatch,
|
|
10
|
+
PatternMatch
|
|
11
|
+
} from '../../types/documentation.js';
|
|
12
|
+
import { SourceTracker } from './source-tracker.js';
|
|
13
|
+
import { WarningDetector } from './warning-detector.js';
|
|
14
|
+
import { ConflictChecker } from './conflict-checker.js';
|
|
15
|
+
|
|
16
|
+
// Weights for confidence calculation
|
|
17
|
+
const WEIGHTS = {
|
|
18
|
+
codebase: 0.5, // 50% weight from codebase matches
|
|
19
|
+
decision: 0.3, // 30% weight from decision alignment
|
|
20
|
+
pattern: 0.2 // 20% weight from pattern matching
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Thresholds for confidence levels
|
|
24
|
+
const THRESHOLDS = {
|
|
25
|
+
high: 80,
|
|
26
|
+
medium: 50,
|
|
27
|
+
low: 20
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export class ConfidenceScorer {
|
|
31
|
+
private sourceTracker: SourceTracker;
|
|
32
|
+
private warningDetector: WarningDetector;
|
|
33
|
+
private conflictChecker: ConflictChecker;
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
tier2: Tier2Storage,
|
|
37
|
+
embeddingGenerator: EmbeddingGenerator
|
|
38
|
+
) {
|
|
39
|
+
this.sourceTracker = new SourceTracker(tier2, embeddingGenerator);
|
|
40
|
+
this.warningDetector = new WarningDetector(tier2);
|
|
41
|
+
this.conflictChecker = new ConflictChecker(tier2, embeddingGenerator);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getConfidence(code: string, context?: string): Promise<ConfidenceResult> {
|
|
45
|
+
// 1. Track sources
|
|
46
|
+
const sources = await this.sourceTracker.trackSources(code, context);
|
|
47
|
+
|
|
48
|
+
// 2. Calculate scores for each component
|
|
49
|
+
const codeScore = this.calculateCodeScore(sources.codebase);
|
|
50
|
+
const decisionScore = this.calculateDecisionScore(sources.decisions);
|
|
51
|
+
const patternScore = this.calculatePatternScore(sources.patterns);
|
|
52
|
+
|
|
53
|
+
// 3. Calculate composite score
|
|
54
|
+
const compositeScore = this.calculateCompositeScore(codeScore, decisionScore, patternScore);
|
|
55
|
+
|
|
56
|
+
// 4. Determine confidence level
|
|
57
|
+
const confidence = this.determineLevel(compositeScore);
|
|
58
|
+
|
|
59
|
+
// 5. Detect warnings
|
|
60
|
+
const warnings = await this.warningDetector.detectWarnings(code, sources);
|
|
61
|
+
|
|
62
|
+
// 6. Check for decision conflicts
|
|
63
|
+
const conflicts = await this.conflictChecker.checkConflicts(code);
|
|
64
|
+
if (conflicts.hasConflicts) {
|
|
65
|
+
for (const conflict of conflicts.conflicts) {
|
|
66
|
+
warnings.push({
|
|
67
|
+
type: 'conflicts_with_decision',
|
|
68
|
+
message: `Conflicts with decision: "${conflict.decisionTitle}"`,
|
|
69
|
+
severity: conflict.severity === 'high' ? 'critical' : 'warning',
|
|
70
|
+
suggestion: conflict.conflictDescription,
|
|
71
|
+
relatedDecision: conflict.decisionId
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 7. Generate reasoning
|
|
77
|
+
const reasoning = this.generateReasoning(confidence, sources, warnings);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
confidence,
|
|
81
|
+
score: Math.round(compositeScore),
|
|
82
|
+
reasoning,
|
|
83
|
+
sources,
|
|
84
|
+
warnings
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private calculateCodeScore(matches: CodebaseMatch[]): number {
|
|
89
|
+
if (matches.length === 0) return 0;
|
|
90
|
+
|
|
91
|
+
// Weight by similarity and usage count
|
|
92
|
+
let totalWeight = 0;
|
|
93
|
+
let weightedSum = 0;
|
|
94
|
+
|
|
95
|
+
for (const match of matches) {
|
|
96
|
+
const weight = 1 + Math.log10(1 + (match.usageCount || 1));
|
|
97
|
+
weightedSum += match.similarity * weight;
|
|
98
|
+
totalWeight += weight;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private calculateDecisionScore(matches: DecisionMatch[]): number {
|
|
105
|
+
if (matches.length === 0) return 50; // Neutral if no decisions
|
|
106
|
+
|
|
107
|
+
// Average relevance of matched decisions
|
|
108
|
+
const totalRelevance = matches.reduce((sum, m) => sum + m.relevance, 0);
|
|
109
|
+
return totalRelevance / matches.length;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private calculatePatternScore(matches: PatternMatch[]): number {
|
|
113
|
+
if (matches.length === 0) return 30; // Low baseline if no patterns
|
|
114
|
+
|
|
115
|
+
// Average confidence of matched patterns
|
|
116
|
+
const totalConfidence = matches.reduce((sum, m) => sum + m.confidence, 0);
|
|
117
|
+
return totalConfidence / matches.length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private calculateCompositeScore(codeScore: number, decisionScore: number, patternScore: number): number {
|
|
121
|
+
return (
|
|
122
|
+
codeScore * WEIGHTS.codebase +
|
|
123
|
+
decisionScore * WEIGHTS.decision +
|
|
124
|
+
patternScore * WEIGHTS.pattern
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private determineLevel(score: number): ConfidenceLevel {
|
|
129
|
+
if (score >= THRESHOLDS.high) return 'high';
|
|
130
|
+
if (score >= THRESHOLDS.medium) return 'medium';
|
|
131
|
+
if (score >= THRESHOLDS.low) return 'low';
|
|
132
|
+
return 'guessing';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private generateReasoning(
|
|
136
|
+
level: ConfidenceLevel,
|
|
137
|
+
sources: ConfidenceSources,
|
|
138
|
+
warnings: ConfidenceWarning[]
|
|
139
|
+
): string {
|
|
140
|
+
const parts: string[] = [];
|
|
141
|
+
|
|
142
|
+
// Confidence level explanation
|
|
143
|
+
switch (level) {
|
|
144
|
+
case 'high':
|
|
145
|
+
parts.push('High confidence');
|
|
146
|
+
break;
|
|
147
|
+
case 'medium':
|
|
148
|
+
parts.push('Medium confidence');
|
|
149
|
+
break;
|
|
150
|
+
case 'low':
|
|
151
|
+
parts.push('Low confidence');
|
|
152
|
+
break;
|
|
153
|
+
case 'guessing':
|
|
154
|
+
parts.push('Best guess');
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Source attribution
|
|
159
|
+
if (sources.codebase.length > 0) {
|
|
160
|
+
const topMatch = sources.codebase[0];
|
|
161
|
+
parts.push(`found similar code in ${topMatch.file}${topMatch.function ? `:${topMatch.function}` : ''} (${topMatch.similarity}% match)`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (sources.decisions.length > 0) {
|
|
165
|
+
parts.push(`aligns with ${sources.decisions.length} recorded decision(s)`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (sources.patterns.length > 0) {
|
|
169
|
+
parts.push(`matches ${sources.patterns.length} established pattern(s)`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (sources.usedGeneralKnowledge) {
|
|
173
|
+
parts.push('based partly on general knowledge');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Warnings
|
|
177
|
+
const criticalWarnings = warnings.filter(w => w.severity === 'critical');
|
|
178
|
+
if (criticalWarnings.length > 0) {
|
|
179
|
+
parts.push(`${criticalWarnings.length} critical warning(s)`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return parts.join(', ');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async listSources(code: string, context?: string, includeSnippets: boolean = false): Promise<ConfidenceSources> {
|
|
186
|
+
const sources = await this.sourceTracker.trackSources(code, context);
|
|
187
|
+
|
|
188
|
+
if (!includeSnippets) {
|
|
189
|
+
// Remove snippets for cleaner output
|
|
190
|
+
for (const match of sources.codebase) {
|
|
191
|
+
delete match.snippet;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return sources;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async checkConflicts(code: string) {
|
|
199
|
+
return this.conflictChecker.checkConflicts(code);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Get confidence level indicator emoji
|
|
203
|
+
static getIndicator(level: ConfidenceLevel): string {
|
|
204
|
+
switch (level) {
|
|
205
|
+
case 'high': return '\u{1F7E2}'; // Green circle
|
|
206
|
+
case 'medium': return '\u{1F7E1}'; // Yellow circle
|
|
207
|
+
case 'low': return '\u{1F7E0}'; // Orange circle
|
|
208
|
+
case 'guessing': return '\u{1F534}'; // Red circle
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Format confidence result for display
|
|
213
|
+
static formatResult(result: ConfidenceResult): string {
|
|
214
|
+
const indicator = ConfidenceScorer.getIndicator(result.confidence);
|
|
215
|
+
const lines: string[] = [];
|
|
216
|
+
|
|
217
|
+
lines.push(`${indicator} ${result.confidence.toUpperCase()} Confidence (${result.score}%)`);
|
|
218
|
+
lines.push(result.reasoning);
|
|
219
|
+
|
|
220
|
+
if (result.sources.codebase.length > 0) {
|
|
221
|
+
lines.push('');
|
|
222
|
+
lines.push('Codebase Sources:');
|
|
223
|
+
for (const match of result.sources.codebase.slice(0, 3)) {
|
|
224
|
+
lines.push(` - ${match.file}${match.line ? `:${match.line}` : ''} (${match.similarity}% similar)`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (result.sources.decisions.length > 0) {
|
|
229
|
+
lines.push('');
|
|
230
|
+
lines.push('Related Decisions:');
|
|
231
|
+
for (const decision of result.sources.decisions.slice(0, 3)) {
|
|
232
|
+
lines.push(` - "${decision.title}" (${decision.relevance}% relevant)`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (result.warnings.length > 0) {
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push('Warnings:');
|
|
239
|
+
for (const warning of result.warnings) {
|
|
240
|
+
const icon = warning.severity === 'critical' ? '\u{1F534}' :
|
|
241
|
+
warning.severity === 'warning' ? '\u{1F7E1}' : '\u{2139}\u{FE0F}';
|
|
242
|
+
lines.push(` ${icon} ${warning.message}`);
|
|
243
|
+
if (warning.suggestion) {
|
|
244
|
+
lines.push(` \u{2192} ${warning.suggestion}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import type { Tier2Storage } from '../../storage/tier2.js';
|
|
2
|
+
import type { EmbeddingGenerator } from '../../indexing/embeddings.js';
|
|
3
|
+
import type { ConflictResult } from '../../types/documentation.js';
|
|
4
|
+
|
|
5
|
+
// Common conflict patterns - decisions that might conflict with code patterns
|
|
6
|
+
const CONFLICT_PATTERNS = [
|
|
7
|
+
// Authentication patterns
|
|
8
|
+
{
|
|
9
|
+
decisionKeywords: ['jwt', 'json web token'],
|
|
10
|
+
conflictingCode: /session\s*=|cookie.*auth|express-session/i,
|
|
11
|
+
conflict: 'Code uses session-based auth, but decision specifies JWT'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
decisionKeywords: ['session', 'cookie auth'],
|
|
15
|
+
conflictingCode: /jwt\.sign|jsonwebtoken/i,
|
|
16
|
+
conflict: 'Code uses JWT, but decision specifies session-based auth'
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
// Database patterns
|
|
20
|
+
{
|
|
21
|
+
decisionKeywords: ['parameterized queries', 'prepared statements', 'no string concatenation'],
|
|
22
|
+
conflictingCode: /\$\{.*\}.*(?:SELECT|INSERT|UPDATE|DELETE)|`.*\+.*\+.*(?:SELECT|INSERT)/i,
|
|
23
|
+
conflict: 'Code uses string concatenation for SQL, but decision requires parameterized queries'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
decisionKeywords: ['orm', 'prisma', 'sequelize', 'typeorm'],
|
|
27
|
+
conflictingCode: /db\.query\s*\(|execute\s*\(\s*['"`]SELECT/i,
|
|
28
|
+
conflict: 'Code uses raw SQL, but decision specifies ORM usage'
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// Error handling patterns
|
|
32
|
+
{
|
|
33
|
+
decisionKeywords: ['custom error classes', 'error types'],
|
|
34
|
+
conflictingCode: /throw\s+new\s+Error\s*\(/i,
|
|
35
|
+
conflict: 'Code throws generic Error, but decision specifies custom error classes'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
decisionKeywords: ['always log errors', 'error logging'],
|
|
39
|
+
conflictingCode: /catch\s*\([^)]*\)\s*\{\s*\}/i,
|
|
40
|
+
conflict: 'Code has empty catch block, but decision requires error logging'
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// API patterns
|
|
44
|
+
{
|
|
45
|
+
decisionKeywords: ['rest api', 'restful'],
|
|
46
|
+
conflictingCode: /graphql|gql`|ApolloServer/i,
|
|
47
|
+
conflict: 'Code uses GraphQL, but decision specifies REST API'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
decisionKeywords: ['graphql'],
|
|
51
|
+
conflictingCode: /app\.(get|post|put|delete|patch)\s*\(/i,
|
|
52
|
+
conflict: 'Code uses REST endpoints, but decision specifies GraphQL'
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Testing patterns
|
|
56
|
+
{
|
|
57
|
+
decisionKeywords: ['jest', 'use jest'],
|
|
58
|
+
conflictingCode: /import.*mocha|describe.*chai|from\s+['"]vitest/i,
|
|
59
|
+
conflict: 'Code uses different testing framework, but decision specifies Jest'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
decisionKeywords: ['vitest'],
|
|
63
|
+
conflictingCode: /import.*jest|from\s+['"]@jest/i,
|
|
64
|
+
conflict: 'Code uses Jest, but decision specifies Vitest'
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// State management patterns
|
|
68
|
+
{
|
|
69
|
+
decisionKeywords: ['redux', 'use redux'],
|
|
70
|
+
conflictingCode: /useContext|createContext|zustand|mobx/i,
|
|
71
|
+
conflict: 'Code uses different state management, but decision specifies Redux'
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Styling patterns
|
|
75
|
+
{
|
|
76
|
+
decisionKeywords: ['tailwind', 'tailwindcss'],
|
|
77
|
+
conflictingCode: /styled-components|emotion|\.module\.css/i,
|
|
78
|
+
conflict: 'Code uses different styling approach, but decision specifies Tailwind'
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Async patterns
|
|
82
|
+
{
|
|
83
|
+
decisionKeywords: ['async/await', 'always use async'],
|
|
84
|
+
conflictingCode: /\.then\s*\([^)]*=>/i,
|
|
85
|
+
conflict: 'Code uses promise chains, but decision specifies async/await'
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
export class ConflictChecker {
|
|
90
|
+
private tier2: Tier2Storage;
|
|
91
|
+
private embeddingGenerator: EmbeddingGenerator;
|
|
92
|
+
|
|
93
|
+
constructor(tier2: Tier2Storage, embeddingGenerator: EmbeddingGenerator) {
|
|
94
|
+
this.tier2 = tier2;
|
|
95
|
+
this.embeddingGenerator = embeddingGenerator;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async checkConflicts(code: string): Promise<ConflictResult> {
|
|
99
|
+
const conflicts: ConflictResult['conflicts'] = [];
|
|
100
|
+
|
|
101
|
+
// Get all decisions
|
|
102
|
+
const decisions = this.tier2.getAllDecisions();
|
|
103
|
+
|
|
104
|
+
if (decisions.length === 0) {
|
|
105
|
+
return { hasConflicts: false, conflicts: [] };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check each decision for conflicts
|
|
109
|
+
for (const decision of decisions) {
|
|
110
|
+
const conflict = this.checkDecisionConflict(code, decision);
|
|
111
|
+
if (conflict) {
|
|
112
|
+
conflicts.push(conflict);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Also check using semantic similarity for decisions that might conflict
|
|
117
|
+
const semanticConflicts = await this.checkSemanticConflicts(code, decisions);
|
|
118
|
+
for (const conflict of semanticConflicts) {
|
|
119
|
+
// Avoid duplicates
|
|
120
|
+
if (!conflicts.some(c => c.decisionId === conflict.decisionId)) {
|
|
121
|
+
conflicts.push(conflict);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Sort by severity
|
|
126
|
+
conflicts.sort((a, b) => {
|
|
127
|
+
const severityOrder = { high: 0, medium: 1, low: 2 };
|
|
128
|
+
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
hasConflicts: conflicts.length > 0,
|
|
133
|
+
conflicts
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private checkDecisionConflict(
|
|
138
|
+
code: string,
|
|
139
|
+
decision: { id: string; title: string; description: string; createdAt: Date }
|
|
140
|
+
): ConflictResult['conflicts'][0] | null {
|
|
141
|
+
const decisionText = `${decision.title} ${decision.description}`.toLowerCase();
|
|
142
|
+
|
|
143
|
+
for (const pattern of CONFLICT_PATTERNS) {
|
|
144
|
+
// Check if this decision matches the pattern keywords
|
|
145
|
+
const matchesKeywords = pattern.decisionKeywords.some(keyword =>
|
|
146
|
+
decisionText.includes(keyword.toLowerCase())
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (matchesKeywords && pattern.conflictingCode.test(code)) {
|
|
150
|
+
return {
|
|
151
|
+
decisionId: decision.id,
|
|
152
|
+
decisionTitle: decision.title,
|
|
153
|
+
decisionDate: decision.createdAt,
|
|
154
|
+
conflictDescription: pattern.conflict,
|
|
155
|
+
severity: this.determineSeverity(pattern.conflict)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async checkSemanticConflicts(
|
|
164
|
+
code: string,
|
|
165
|
+
decisions: Array<{ id: string; title: string; description: string; createdAt: Date }>
|
|
166
|
+
): Promise<ConflictResult['conflicts']> {
|
|
167
|
+
const conflicts: ConflictResult['conflicts'] = [];
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// Extract key concepts from the code
|
|
171
|
+
const codeApproach = this.extractApproach(code);
|
|
172
|
+
if (!codeApproach) return [];
|
|
173
|
+
|
|
174
|
+
// Generate embedding for the code approach
|
|
175
|
+
const codeEmbedding = await this.embeddingGenerator.embed(codeApproach);
|
|
176
|
+
|
|
177
|
+
// Search for related decisions
|
|
178
|
+
const relatedDecisions = this.tier2.searchDecisions(codeEmbedding, 5);
|
|
179
|
+
|
|
180
|
+
for (const related of relatedDecisions) {
|
|
181
|
+
// Check if the decision contradicts the approach
|
|
182
|
+
const contradiction = this.findContradiction(codeApproach, related.description);
|
|
183
|
+
if (contradiction) {
|
|
184
|
+
// Avoid duplicates
|
|
185
|
+
if (!conflicts.some(c => c.decisionId === related.id)) {
|
|
186
|
+
conflicts.push({
|
|
187
|
+
decisionId: related.id,
|
|
188
|
+
decisionTitle: related.title,
|
|
189
|
+
decisionDate: related.createdAt,
|
|
190
|
+
conflictDescription: contradiction,
|
|
191
|
+
severity: 'medium'
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error('Error checking semantic conflicts:', error);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return conflicts;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private extractApproach(code: string): string | null {
|
|
204
|
+
// Extract what the code is doing
|
|
205
|
+
const patterns: Array<{ regex: RegExp; approach: string }> = [
|
|
206
|
+
{ regex: /async\s+function\s+(\w+)/, approach: 'async function' },
|
|
207
|
+
{ regex: /class\s+(\w+)\s+extends/, approach: 'class inheritance' },
|
|
208
|
+
{ regex: /interface\s+(\w+)/, approach: 'interface definition' },
|
|
209
|
+
{ regex: /import\s+.*from\s+['"](\w+)/, approach: 'using library' },
|
|
210
|
+
{ regex: /\.then\s*\(/, approach: 'promise chains' },
|
|
211
|
+
{ regex: /await\s+/, approach: 'async/await' },
|
|
212
|
+
{ regex: /db\.(query|execute)/i, approach: 'direct database queries' },
|
|
213
|
+
{ regex: /prisma\.\w+/, approach: 'Prisma ORM' },
|
|
214
|
+
{ regex: /jwt\.(sign|verify)/i, approach: 'JWT authentication' },
|
|
215
|
+
{ regex: /session\s*\[/i, approach: 'session-based authentication' }
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (const { regex, approach } of patterns) {
|
|
219
|
+
if (regex.test(code)) {
|
|
220
|
+
return approach;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private findContradiction(codeApproach: string, decisionDescription: string): string | null {
|
|
228
|
+
const desc = decisionDescription.toLowerCase();
|
|
229
|
+
const approach = codeApproach.toLowerCase();
|
|
230
|
+
|
|
231
|
+
// Define contradictory pairs
|
|
232
|
+
const contradictions: Array<{ approaches: string[]; keywords: string[]; message: string }> = [
|
|
233
|
+
{
|
|
234
|
+
approaches: ['promise chains'],
|
|
235
|
+
keywords: ['async/await', 'always use async'],
|
|
236
|
+
message: 'Code uses promise chains, but decision prefers async/await'
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
approaches: ['direct database queries'],
|
|
240
|
+
keywords: ['orm', 'prisma', 'typeorm'],
|
|
241
|
+
message: 'Code uses direct queries, but decision specifies ORM'
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
approaches: ['class inheritance'],
|
|
245
|
+
keywords: ['composition', 'prefer composition'],
|
|
246
|
+
message: 'Code uses inheritance, but decision prefers composition'
|
|
247
|
+
}
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
for (const { approaches, keywords, message } of contradictions) {
|
|
251
|
+
if (approaches.some(a => approach.includes(a)) &&
|
|
252
|
+
keywords.some(k => desc.includes(k))) {
|
|
253
|
+
return message;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private determineSeverity(conflictDescription: string): 'low' | 'medium' | 'high' {
|
|
261
|
+
const highSeverityKeywords = ['sql', 'security', 'authentication', 'injection', 'password'];
|
|
262
|
+
const mediumSeverityKeywords = ['orm', 'framework', 'testing', 'api'];
|
|
263
|
+
|
|
264
|
+
const lower = conflictDescription.toLowerCase();
|
|
265
|
+
|
|
266
|
+
if (highSeverityKeywords.some(k => lower.includes(k))) {
|
|
267
|
+
return 'high';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (mediumSeverityKeywords.some(k => lower.includes(k))) {
|
|
271
|
+
return 'medium';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return 'low';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Quick check without full analysis
|
|
278
|
+
quickConflictCheck(code: string): boolean {
|
|
279
|
+
const decisions = this.tier2.getAllDecisions();
|
|
280
|
+
|
|
281
|
+
for (const decision of decisions) {
|
|
282
|
+
if (this.checkDecisionConflict(code, decision)) {
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Confidence Scoring Module - Phase 8
|
|
2
|
+
export { ConfidenceScorer } from './confidence-scorer.js';
|
|
3
|
+
export { SourceTracker } from './source-tracker.js';
|
|
4
|
+
export { WarningDetector } from './warning-detector.js';
|
|
5
|
+
export { ConflictChecker } from './conflict-checker.js';
|