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,499 @@
|
|
|
1
|
+
import type { TestInfo, ChangeAnalysis, TestValidationResult, PredictedFailure, Assertion } from '../../types/documentation.js';
|
|
2
|
+
import type { TestIndexer } from './test-indexer.js';
|
|
3
|
+
import type { Tier2Storage } from '../../storage/tier2.js';
|
|
4
|
+
|
|
5
|
+
interface FunctionChange {
|
|
6
|
+
name: string;
|
|
7
|
+
changeType: 'added' | 'removed' | 'modified' | 'signature_changed';
|
|
8
|
+
oldSignature?: string;
|
|
9
|
+
newSignature?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ParsedChange {
|
|
13
|
+
addedLines: string[];
|
|
14
|
+
removedLines: string[];
|
|
15
|
+
modifiedFunctions: FunctionChange[];
|
|
16
|
+
exportChanges: { added: string[]; removed: string[] };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ChangeValidator {
|
|
20
|
+
private testIndexer: TestIndexer;
|
|
21
|
+
private tier2: Tier2Storage;
|
|
22
|
+
|
|
23
|
+
constructor(testIndexer: TestIndexer, tier2: Tier2Storage) {
|
|
24
|
+
this.testIndexer = testIndexer;
|
|
25
|
+
this.tier2 = tier2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
analyzeChange(code: string, file: string): ChangeAnalysis {
|
|
29
|
+
// Get existing file content from tier2
|
|
30
|
+
const existingFile = this.tier2.getFile(file);
|
|
31
|
+
const existingContent = existingFile?.preview || '';
|
|
32
|
+
|
|
33
|
+
// Parse changes
|
|
34
|
+
const parsed = this.parseCodeChange(existingContent, code);
|
|
35
|
+
|
|
36
|
+
// Find affected functions
|
|
37
|
+
const functions = parsed.modifiedFunctions.map(f => f.name);
|
|
38
|
+
|
|
39
|
+
// Get tests that cover this file
|
|
40
|
+
const testsForFile = this.testIndexer.getTestsForFile(file);
|
|
41
|
+
|
|
42
|
+
// Get tests that cover any of the modified functions
|
|
43
|
+
const testsForFunctions = functions.flatMap(fn => this.testIndexer.getTestsForFunction(fn));
|
|
44
|
+
|
|
45
|
+
// Combine and dedupe affected tests
|
|
46
|
+
const affectedTestIds = new Set<string>();
|
|
47
|
+
const affectedTests: TestInfo[] = [];
|
|
48
|
+
|
|
49
|
+
for (const test of [...testsForFile, ...testsForFunctions]) {
|
|
50
|
+
if (!affectedTestIds.has(test.id)) {
|
|
51
|
+
affectedTestIds.add(test.id);
|
|
52
|
+
affectedTests.push(test);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Determine change type
|
|
57
|
+
const changeType = this.determineChangeType(parsed);
|
|
58
|
+
|
|
59
|
+
// Calculate risk level
|
|
60
|
+
const risk = this.calculateRisk(parsed, affectedTests);
|
|
61
|
+
|
|
62
|
+
// Calculate test coverage percentage
|
|
63
|
+
const coveredFunctions = new Set<string>();
|
|
64
|
+
for (const test of affectedTests) {
|
|
65
|
+
for (const fn of test.coversFunctions) {
|
|
66
|
+
coveredFunctions.add(fn);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const testCoverage = functions.length > 0
|
|
70
|
+
? Math.round((coveredFunctions.size / functions.length) * 100)
|
|
71
|
+
: affectedTests.length > 0 ? 100 : 0;
|
|
72
|
+
|
|
73
|
+
// Generate reasoning
|
|
74
|
+
const reasoning = this.generateReasoning(parsed, affectedTests, risk);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
file,
|
|
78
|
+
functions,
|
|
79
|
+
type: changeType,
|
|
80
|
+
affectedTests,
|
|
81
|
+
testCoverage,
|
|
82
|
+
risk,
|
|
83
|
+
reasoning
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
predictFailures(analysis: ChangeAnalysis, newCode: string): PredictedFailure[] {
|
|
88
|
+
const failures: PredictedFailure[] = [];
|
|
89
|
+
|
|
90
|
+
for (const test of analysis.affectedTests) {
|
|
91
|
+
// Check each assertion in the test
|
|
92
|
+
for (const assertion of test.assertions) {
|
|
93
|
+
const prediction = this.predictAssertionFailure(assertion, newCode, analysis);
|
|
94
|
+
if (prediction) {
|
|
95
|
+
failures.push(prediction);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If no specific assertion failures, check for general issues
|
|
100
|
+
if (!failures.some(f => f.test.id === test.id)) {
|
|
101
|
+
const generalPrediction = this.predictGeneralFailure(test, analysis, newCode);
|
|
102
|
+
if (generalPrediction) {
|
|
103
|
+
failures.push(generalPrediction);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return failures;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
validateChange(code: string, file: string): TestValidationResult {
|
|
112
|
+
const analysis = this.analyzeChange(code, file);
|
|
113
|
+
const predictedFailures = this.predictFailures(analysis, code);
|
|
114
|
+
|
|
115
|
+
// Categorize tests
|
|
116
|
+
const wouldPass: TestInfo[] = [];
|
|
117
|
+
const wouldFail: PredictedFailure[] = predictedFailures;
|
|
118
|
+
const uncertain: TestInfo[] = [];
|
|
119
|
+
|
|
120
|
+
const failingTestIds = new Set(predictedFailures.map(f => f.test.id));
|
|
121
|
+
|
|
122
|
+
for (const test of analysis.affectedTests) {
|
|
123
|
+
if (failingTestIds.has(test.id)) {
|
|
124
|
+
continue; // Already in wouldFail
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Determine if test would likely pass or is uncertain
|
|
128
|
+
const coverage = this.assessTestCoverage(test, code, file);
|
|
129
|
+
if (coverage.confident) {
|
|
130
|
+
wouldPass.push(test);
|
|
131
|
+
} else {
|
|
132
|
+
uncertain.push(test);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Generate suggested test updates
|
|
137
|
+
const suggestedTestUpdates = this.generateTestUpdates(predictedFailures, analysis);
|
|
138
|
+
|
|
139
|
+
// Determine if change is safe
|
|
140
|
+
const safe = predictedFailures.length === 0 &&
|
|
141
|
+
analysis.risk !== 'high' &&
|
|
142
|
+
(analysis.testCoverage >= 50 || analysis.affectedTests.length === 0);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
safe,
|
|
146
|
+
relatedTests: analysis.affectedTests,
|
|
147
|
+
wouldPass,
|
|
148
|
+
wouldFail: predictedFailures,
|
|
149
|
+
uncertain,
|
|
150
|
+
suggestedTestUpdates,
|
|
151
|
+
coveragePercent: analysis.testCoverage
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private parseCodeChange(oldCode: string, newCode: string): ParsedChange {
|
|
156
|
+
const oldLines = oldCode.split('\n');
|
|
157
|
+
const newLines = newCode.split('\n');
|
|
158
|
+
|
|
159
|
+
const addedLines: string[] = [];
|
|
160
|
+
const removedLines: string[] = [];
|
|
161
|
+
|
|
162
|
+
// Simple diff: lines in new but not in old
|
|
163
|
+
const oldSet = new Set(oldLines.map(l => l.trim()));
|
|
164
|
+
const newSet = new Set(newLines.map(l => l.trim()));
|
|
165
|
+
|
|
166
|
+
for (const line of newLines) {
|
|
167
|
+
if (!oldSet.has(line.trim()) && line.trim()) {
|
|
168
|
+
addedLines.push(line);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const line of oldLines) {
|
|
173
|
+
if (!newSet.has(line.trim()) && line.trim()) {
|
|
174
|
+
removedLines.push(line);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Detect function changes
|
|
179
|
+
const oldFunctions = this.extractFunctions(oldCode);
|
|
180
|
+
const newFunctions = this.extractFunctions(newCode);
|
|
181
|
+
const modifiedFunctions: FunctionChange[] = [];
|
|
182
|
+
|
|
183
|
+
const oldFuncMap = new Map(oldFunctions.map(f => [f.name, f]));
|
|
184
|
+
const newFuncMap = new Map(newFunctions.map(f => [f.name, f]));
|
|
185
|
+
|
|
186
|
+
// Find removed functions
|
|
187
|
+
for (const [name, func] of oldFuncMap) {
|
|
188
|
+
if (!newFuncMap.has(name)) {
|
|
189
|
+
modifiedFunctions.push({
|
|
190
|
+
name,
|
|
191
|
+
changeType: 'removed',
|
|
192
|
+
oldSignature: func.signature
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Find added and modified functions
|
|
198
|
+
for (const [name, func] of newFuncMap) {
|
|
199
|
+
const oldFunc = oldFuncMap.get(name);
|
|
200
|
+
if (!oldFunc) {
|
|
201
|
+
modifiedFunctions.push({
|
|
202
|
+
name,
|
|
203
|
+
changeType: 'added',
|
|
204
|
+
newSignature: func.signature
|
|
205
|
+
});
|
|
206
|
+
} else if (oldFunc.signature !== func.signature) {
|
|
207
|
+
modifiedFunctions.push({
|
|
208
|
+
name,
|
|
209
|
+
changeType: 'signature_changed',
|
|
210
|
+
oldSignature: oldFunc.signature,
|
|
211
|
+
newSignature: func.signature
|
|
212
|
+
});
|
|
213
|
+
} else if (oldFunc.body !== func.body) {
|
|
214
|
+
modifiedFunctions.push({
|
|
215
|
+
name,
|
|
216
|
+
changeType: 'modified',
|
|
217
|
+
oldSignature: oldFunc.signature,
|
|
218
|
+
newSignature: func.signature
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Detect export changes
|
|
224
|
+
const oldExports = this.extractExports(oldCode);
|
|
225
|
+
const newExports = this.extractExports(newCode);
|
|
226
|
+
|
|
227
|
+
const addedExports = newExports.filter(e => !oldExports.includes(e));
|
|
228
|
+
const removedExports = oldExports.filter(e => !newExports.includes(e));
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
addedLines,
|
|
232
|
+
removedLines,
|
|
233
|
+
modifiedFunctions,
|
|
234
|
+
exportChanges: { added: addedExports, removed: removedExports }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private extractFunctions(code: string): Array<{ name: string; signature: string; body: string }> {
|
|
239
|
+
const functions: Array<{ name: string; signature: string; body: string }> = [];
|
|
240
|
+
|
|
241
|
+
// JavaScript/TypeScript function patterns
|
|
242
|
+
const patterns = [
|
|
243
|
+
// Function declaration
|
|
244
|
+
/function\s+(\w+)\s*\(([^)]*)\)[^{]*\{/g,
|
|
245
|
+
// Arrow function
|
|
246
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*(?::\s*\w+)?\s*=>/g,
|
|
247
|
+
// Method
|
|
248
|
+
/(\w+)\s*\(([^)]*)\)\s*(?::\s*\w+)?\s*\{/g,
|
|
249
|
+
// Python function
|
|
250
|
+
/def\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*\w+)?\s*:/g,
|
|
251
|
+
// Go function
|
|
252
|
+
/func\s+(\w+)\s*\(([^)]*)\)/g
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
for (const pattern of patterns) {
|
|
256
|
+
let match;
|
|
257
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
258
|
+
const name = match[1];
|
|
259
|
+
const params = match[2];
|
|
260
|
+
const signature = `${name}(${params})`;
|
|
261
|
+
|
|
262
|
+
// Extract a simple body representation (first 200 chars after signature)
|
|
263
|
+
const bodyStart = match.index + match[0].length;
|
|
264
|
+
const body = code.slice(bodyStart, bodyStart + 200);
|
|
265
|
+
|
|
266
|
+
functions.push({ name: name ?? '', signature, body });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return functions;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private extractExports(code: string): string[] {
|
|
274
|
+
const exports: string[] = [];
|
|
275
|
+
|
|
276
|
+
// ES6 exports
|
|
277
|
+
const exportPatterns = [
|
|
278
|
+
/export\s+(?:default\s+)?(?:function|class|const|let|var)\s+(\w+)/g,
|
|
279
|
+
/export\s*\{\s*([^}]+)\s*\}/g,
|
|
280
|
+
/module\.exports\s*=\s*\{\s*([^}]+)\s*\}/g,
|
|
281
|
+
/module\.exports\.(\w+)\s*=/g
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
for (const pattern of exportPatterns) {
|
|
285
|
+
let match;
|
|
286
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
287
|
+
const captured = match[1];
|
|
288
|
+
if (!captured) continue;
|
|
289
|
+
if (captured.includes(',')) {
|
|
290
|
+
// Multiple exports
|
|
291
|
+
const names = captured.split(',').map(s => {
|
|
292
|
+
const parts = s.trim().split(/\s+as\s+/);
|
|
293
|
+
return parts[0] ?? '';
|
|
294
|
+
}).filter(Boolean);
|
|
295
|
+
exports.push(...names);
|
|
296
|
+
} else {
|
|
297
|
+
exports.push(captured.trim());
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return exports;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private determineChangeType(parsed: ParsedChange): 'refactor' | 'add' | 'delete' | 'modify' {
|
|
306
|
+
const hasAdditions = parsed.addedLines.length > 0 || parsed.modifiedFunctions.some(f => f.changeType === 'added');
|
|
307
|
+
const hasDeletions = parsed.removedLines.length > 0 || parsed.modifiedFunctions.some(f => f.changeType === 'removed');
|
|
308
|
+
const hasModifications = parsed.modifiedFunctions.some(f => f.changeType === 'modified' || f.changeType === 'signature_changed');
|
|
309
|
+
|
|
310
|
+
if (hasDeletions && !hasAdditions) return 'delete';
|
|
311
|
+
if (hasAdditions && !hasDeletions && !hasModifications) return 'add';
|
|
312
|
+
if (hasModifications || (hasAdditions && hasDeletions)) return 'modify';
|
|
313
|
+
|
|
314
|
+
return 'refactor';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private calculateRisk(parsed: ParsedChange, affectedTests: TestInfo[]): 'low' | 'medium' | 'high' {
|
|
318
|
+
let riskScore = 0;
|
|
319
|
+
|
|
320
|
+
// Removed exports are high risk
|
|
321
|
+
if (parsed.exportChanges.removed.length > 0) {
|
|
322
|
+
riskScore += 30;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Signature changes are medium-high risk
|
|
326
|
+
const signatureChanges = parsed.modifiedFunctions.filter(f => f.changeType === 'signature_changed');
|
|
327
|
+
riskScore += signatureChanges.length * 20;
|
|
328
|
+
|
|
329
|
+
// Removed functions are high risk
|
|
330
|
+
const removedFunctions = parsed.modifiedFunctions.filter(f => f.changeType === 'removed');
|
|
331
|
+
riskScore += removedFunctions.length * 25;
|
|
332
|
+
|
|
333
|
+
// No test coverage is risky
|
|
334
|
+
if (affectedTests.length === 0 && parsed.modifiedFunctions.length > 0) {
|
|
335
|
+
riskScore += 20;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Many affected tests might indicate broad impact
|
|
339
|
+
if (affectedTests.length > 10) {
|
|
340
|
+
riskScore += 15;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (riskScore >= 50) return 'high';
|
|
344
|
+
if (riskScore >= 25) return 'medium';
|
|
345
|
+
return 'low';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private generateReasoning(parsed: ParsedChange, affectedTests: TestInfo[], risk: 'low' | 'medium' | 'high'): string {
|
|
349
|
+
const reasons: string[] = [];
|
|
350
|
+
|
|
351
|
+
if (parsed.modifiedFunctions.length > 0) {
|
|
352
|
+
const types = parsed.modifiedFunctions.map(f => f.changeType);
|
|
353
|
+
if (types.includes('removed')) {
|
|
354
|
+
reasons.push(`${types.filter(t => t === 'removed').length} function(s) removed`);
|
|
355
|
+
}
|
|
356
|
+
if (types.includes('signature_changed')) {
|
|
357
|
+
reasons.push(`${types.filter(t => t === 'signature_changed').length} function signature(s) changed`);
|
|
358
|
+
}
|
|
359
|
+
if (types.includes('modified')) {
|
|
360
|
+
reasons.push(`${types.filter(t => t === 'modified').length} function body(ies) modified`);
|
|
361
|
+
}
|
|
362
|
+
if (types.includes('added')) {
|
|
363
|
+
reasons.push(`${types.filter(t => t === 'added').length} function(s) added`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (parsed.exportChanges.removed.length > 0) {
|
|
368
|
+
reasons.push(`${parsed.exportChanges.removed.length} export(s) removed: ${parsed.exportChanges.removed.join(', ')}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (affectedTests.length > 0) {
|
|
372
|
+
reasons.push(`${affectedTests.length} test(s) may be affected`);
|
|
373
|
+
} else {
|
|
374
|
+
reasons.push('No tests cover this code');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return reasons.join('. ') + '.';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private predictAssertionFailure(
|
|
381
|
+
assertion: Assertion,
|
|
382
|
+
newCode: string,
|
|
383
|
+
analysis: ChangeAnalysis
|
|
384
|
+
): PredictedFailure | null {
|
|
385
|
+
// Check if the assertion subject is affected by the change
|
|
386
|
+
const subject = assertion.subject;
|
|
387
|
+
|
|
388
|
+
// Check if subject function was removed or signature changed
|
|
389
|
+
for (const func of analysis.functions) {
|
|
390
|
+
if (subject.includes(func)) {
|
|
391
|
+
// Check if this function had breaking changes
|
|
392
|
+
const funcInfo = this.tier2.searchSymbols(func, undefined, 1);
|
|
393
|
+
if (funcInfo.length === 0) {
|
|
394
|
+
// Function might have been removed
|
|
395
|
+
return {
|
|
396
|
+
test: analysis.affectedTests.find(t => t.assertions.some(a => a.code === assertion.code))!,
|
|
397
|
+
assertion,
|
|
398
|
+
reason: `Function '${func}' appears to be removed or renamed`,
|
|
399
|
+
confidence: 80,
|
|
400
|
+
suggestedFix: `Update test to use the new function name or remove the test if functionality is removed`
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Check if expected value might have changed
|
|
407
|
+
if (assertion.expected) {
|
|
408
|
+
// Look for the expected value in the old code but not in new
|
|
409
|
+
const expectedInCode = newCode.includes(assertion.expected);
|
|
410
|
+
if (!expectedInCode && assertion.type === 'equality') {
|
|
411
|
+
return {
|
|
412
|
+
test: analysis.affectedTests.find(t => t.assertions.some(a => a.code === assertion.code))!,
|
|
413
|
+
assertion,
|
|
414
|
+
reason: `Expected value '${assertion.expected}' may have changed`,
|
|
415
|
+
confidence: 60,
|
|
416
|
+
suggestedFix: `Review and update the expected value in the assertion`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private predictGeneralFailure(
|
|
425
|
+
test: TestInfo,
|
|
426
|
+
analysis: ChangeAnalysis,
|
|
427
|
+
newCode: string
|
|
428
|
+
): PredictedFailure | null {
|
|
429
|
+
// Check if any covered functions were removed
|
|
430
|
+
for (const fn of test.coversFunctions) {
|
|
431
|
+
const inNewCode = newCode.includes(fn);
|
|
432
|
+
if (!inNewCode) {
|
|
433
|
+
return {
|
|
434
|
+
test,
|
|
435
|
+
reason: `Test calls '${fn}' which may have been removed or renamed`,
|
|
436
|
+
confidence: 70,
|
|
437
|
+
suggestedFix: `Update test to use the new function name or remove the test`
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Check for signature changes affecting this test
|
|
443
|
+
for (const fn of test.coversFunctions) {
|
|
444
|
+
if (analysis.functions.includes(fn)) {
|
|
445
|
+
// Function was modified
|
|
446
|
+
return {
|
|
447
|
+
test,
|
|
448
|
+
reason: `Test uses '${fn}' which has been modified`,
|
|
449
|
+
confidence: 40,
|
|
450
|
+
suggestedFix: `Review test assertions after code changes`
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private assessTestCoverage(test: TestInfo, newCode: string, file: string): { confident: boolean } {
|
|
459
|
+
// Check if all covered functions still exist
|
|
460
|
+
let allFunctionsExist = true;
|
|
461
|
+
for (const fn of test.coversFunctions) {
|
|
462
|
+
if (!newCode.includes(fn)) {
|
|
463
|
+
allFunctionsExist = false;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if file is still covered
|
|
469
|
+
const coversFile = test.coversFiles.some(f =>
|
|
470
|
+
file.includes(f) || f.includes(file.replace(/\.[^.]+$/, ''))
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
confident: allFunctionsExist && coversFile
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private generateTestUpdates(
|
|
479
|
+
failures: PredictedFailure[],
|
|
480
|
+
analysis: ChangeAnalysis
|
|
481
|
+
): Array<{ file: string; testName: string; line: number; before: string; after: string; reason: string }> {
|
|
482
|
+
const updates: Array<{ file: string; testName: string; line: number; before: string; after: string; reason: string }> = [];
|
|
483
|
+
|
|
484
|
+
for (const failure of failures) {
|
|
485
|
+
if (failure.assertion && failure.suggestedFix) {
|
|
486
|
+
updates.push({
|
|
487
|
+
file: failure.test.file,
|
|
488
|
+
testName: failure.test.name,
|
|
489
|
+
line: failure.assertion.line,
|
|
490
|
+
before: failure.assertion.code,
|
|
491
|
+
after: `// TODO: ${failure.suggestedFix}\n${failure.assertion.code}`,
|
|
492
|
+
reason: failure.reason
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return updates;
|
|
498
|
+
}
|
|
499
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { MCPServer } from './server/mcp.js';
|
|
2
|
+
import { getDefaultConfig, parseArgs } from './utils/config.js';
|
|
3
|
+
import { executeCLI, printHelp } from './cli/commands.js';
|
|
4
|
+
|
|
5
|
+
async function main(): Promise<void> {
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
// Check for CLI commands first
|
|
9
|
+
const firstArg = args[0];
|
|
10
|
+
const cliCommands = ['projects', 'export', 'help', '--help', '-h'];
|
|
11
|
+
|
|
12
|
+
if (firstArg && cliCommands.includes(firstArg)) {
|
|
13
|
+
// Handle CLI commands
|
|
14
|
+
executeCLI(args);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// No arguments and not piped - show help
|
|
19
|
+
if (args.length === 0 && process.stdin.isTTY) {
|
|
20
|
+
printHelp();
|
|
21
|
+
console.log('\nTo start as MCP server, use: memorylayer --project <path>\n');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Parse command line arguments for MCP server mode
|
|
26
|
+
const { projectPath } = parseArgs(args);
|
|
27
|
+
|
|
28
|
+
// Get configuration
|
|
29
|
+
const config = getDefaultConfig(projectPath);
|
|
30
|
+
|
|
31
|
+
console.error('MemoryLayer starting...');
|
|
32
|
+
console.error(`Project: ${config.projectPath}`);
|
|
33
|
+
console.error(`Data directory: ${config.dataDir}`);
|
|
34
|
+
|
|
35
|
+
// Create and start MCP server
|
|
36
|
+
const server = new MCPServer(config);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await server.start();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Failed to start MemoryLayer:', error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
main().catch((error) => {
|
|
47
|
+
console.error('Unhandled error:', error);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|