@theihtisham/agent-shadow-brain 5.0.0 → 5.1.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/README.md +575 -292
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/brain/auto-setup.d.ts +54 -0
- package/dist/brain/auto-setup.d.ts.map +1 -0
- package/dist/brain/auto-setup.js +660 -0
- package/dist/brain/auto-setup.js.map +1 -0
- package/dist/brain/brain-portability.d.ts +81 -0
- package/dist/brain/brain-portability.d.ts.map +1 -0
- package/dist/brain/brain-portability.js +329 -0
- package/dist/brain/brain-portability.js.map +1 -0
- package/dist/brain/code-dna.d.ts +70 -0
- package/dist/brain/code-dna.d.ts.map +1 -0
- package/dist/brain/code-dna.js +611 -0
- package/dist/brain/code-dna.js.map +1 -0
- package/dist/brain/fine-tuning-engine.d.ts +99 -0
- package/dist/brain/fine-tuning-engine.d.ts.map +1 -0
- package/dist/brain/fine-tuning-engine.js +681 -0
- package/dist/brain/fine-tuning-engine.js.map +1 -0
- package/dist/brain/intent-engine.d.ts +64 -0
- package/dist/brain/intent-engine.d.ts.map +1 -0
- package/dist/brain/intent-engine.js +374 -0
- package/dist/brain/intent-engine.js.map +1 -0
- package/dist/brain/lsp-server.d.ts +62 -0
- package/dist/brain/lsp-server.d.ts.map +1 -0
- package/dist/brain/lsp-server.js +480 -0
- package/dist/brain/lsp-server.js.map +1 -0
- package/dist/brain/mcp-server.d.ts +12 -0
- package/dist/brain/mcp-server.d.ts.map +1 -1
- package/dist/brain/mcp-server.js +396 -37
- package/dist/brain/mcp-server.js.map +1 -1
- package/dist/brain/orchestrator.d.ts +13 -0
- package/dist/brain/orchestrator.d.ts.map +1 -1
- package/dist/brain/orchestrator.js +25 -1
- package/dist/brain/orchestrator.js.map +1 -1
- package/dist/brain/plugin-system.d.ts +97 -0
- package/dist/brain/plugin-system.d.ts.map +1 -0
- package/dist/brain/plugin-system.js +404 -0
- package/dist/brain/plugin-system.js.map +1 -0
- package/dist/brain/smart-cache.d.ts +100 -0
- package/dist/brain/smart-cache.d.ts.map +1 -0
- package/dist/brain/smart-cache.js +417 -0
- package/dist/brain/smart-cache.js.map +1 -0
- package/dist/brain/temporal-intelligence.d.ts +89 -0
- package/dist/brain/temporal-intelligence.d.ts.map +1 -0
- package/dist/brain/temporal-intelligence.js +378 -0
- package/dist/brain/temporal-intelligence.js.map +1 -0
- package/dist/cli.js +444 -3
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/server.d.ts +4 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +453 -330
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +284 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +28 -4
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
// src/brain/fine-tuning-engine.ts — Custom LLM fine-tuning on codebase patterns
|
|
2
|
+
// v6.0.0 — Zero-dependency pattern learning engine
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as crypto from 'crypto';
|
|
6
|
+
// ── Pattern Extractors ─────────────────────────────────────────────────────
|
|
7
|
+
const PATTERN_EXTRACTORS = {
|
|
8
|
+
naming: {
|
|
9
|
+
camelCase: /\b[a-z][a-zA-Z0-9]*(?:get|set|is|has|can|should|will|did|on|handle|create|delete|update|fetch|load|save|parse|format|validate|convert|transform|build|render|compute|calculate|process|generate|init|start|stop|reset|clear|add|remove|find|filter|map|reduce|sort|merge|split|join|check|ensure|assert|log|debug|warn|error|throw|catch|try|retry|wait|sleep|delay|emit|listen|subscribe|observe|notify|trigger|dispatch|publish)\b/g,
|
|
10
|
+
constants: /\b[A-Z][A-Z0-9_]{2,}\b/g,
|
|
11
|
+
classNames: /\bclass\s+([A-Z][a-zA-Z0-9]+)/g,
|
|
12
|
+
interfaceNames: /\binterface\s+([A-Z][a-zA-Z0-9]+)/g,
|
|
13
|
+
typeNames: /\btype\s+([A-Z][a-zA-Z0-9]+)/g,
|
|
14
|
+
enumNames: /\benum\s+([A-Z][a-zA-Z0-9]+)/g,
|
|
15
|
+
},
|
|
16
|
+
structure: {
|
|
17
|
+
exportDefault: /export\s+default\s+(class|function|const)/g,
|
|
18
|
+
namedExport: /export\s+(class|function|const|interface|type|enum)\s+/g,
|
|
19
|
+
asyncFunction: /async\s+(function\s+\w+|\w+\s*=\s*async)/g,
|
|
20
|
+
arrowFunction: /(?:const|let)\s+\w+\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*\w+)?\s*=>/g,
|
|
21
|
+
destructuring: /(?:const|let|var)\s*\{[^}]+\}\s*=/g,
|
|
22
|
+
spreadOperator: /\.{3}\w+/g,
|
|
23
|
+
optionalChaining: /\?\./g,
|
|
24
|
+
nullishCoalescing: /\?\?/g,
|
|
25
|
+
},
|
|
26
|
+
errorHandling: {
|
|
27
|
+
tryCatch: /try\s*\{[\s\S]*?\}\s*catch\s*\(/g,
|
|
28
|
+
throwNew: /throw\s+new\s+\w+Error/g,
|
|
29
|
+
customError: /class\s+\w+Error\s+extends\s+Error/g,
|
|
30
|
+
errorFirst: /\(err(?:or)?\s*(?:,|\))/g,
|
|
31
|
+
promiseCatch: /\.catch\s*\(/g,
|
|
32
|
+
asyncCatch: /try\s*\{[\s\S]*?await[\s\S]*?\}\s*catch/g,
|
|
33
|
+
},
|
|
34
|
+
import: {
|
|
35
|
+
namedImport: /import\s*\{[^}]+\}\s*from/g,
|
|
36
|
+
defaultImport: /import\s+\w+\s+from/g,
|
|
37
|
+
sideEffect: /import\s+['"][^'"]+['"]/g,
|
|
38
|
+
dynamicImport: /import\s*\(['"]/g,
|
|
39
|
+
reExport: /export\s*\{[^}]+\}\s*from/g,
|
|
40
|
+
typeImport: /import\s+type\s*\{/g,
|
|
41
|
+
},
|
|
42
|
+
comment: {
|
|
43
|
+
jsdoc: /\/\*\*[\s\S]*?\*\//g,
|
|
44
|
+
singleLine: /\/\/\s*.{10,}/g,
|
|
45
|
+
todoFixme: /\/\/\s*(?:TODO|FIXME|HACK|XXX|BUG|NOTE|OPTIMIZE):/gi,
|
|
46
|
+
regionMarkers: /\/\/\s*(?:──|---|\*\*\*|===|###)/g,
|
|
47
|
+
},
|
|
48
|
+
test: {
|
|
49
|
+
describe: /describe\s*\(\s*['"`]/g,
|
|
50
|
+
it: /\bit\s*\(\s*['"`]/g,
|
|
51
|
+
expect: /expect\s*\(/g,
|
|
52
|
+
mock: /(?:jest\.mock|vi\.mock|sinon\.stub)\s*\(/g,
|
|
53
|
+
beforeAfter: /(?:beforeEach|afterEach|beforeAll|afterAll)\s*\(/g,
|
|
54
|
+
testUtil: /(?:render|screen|fireEvent|userEvent|waitFor)\s*[.(]/g,
|
|
55
|
+
},
|
|
56
|
+
api: {
|
|
57
|
+
expressRoute: /(?:app|router)\.\s*(?:get|post|put|patch|delete|use)\s*\(/g,
|
|
58
|
+
middleware: /\(req\s*,\s*res\s*,\s*next\)\s*=>/g,
|
|
59
|
+
statusCode: /(?:res\.status|response\.status)\s*\(\s*\d{3}\s*\)/g,
|
|
60
|
+
fetchCall: /(?:fetch|axios|got|superagent)\s*[.(]/g,
|
|
61
|
+
restPattern: /\/api\/v\d+\//g,
|
|
62
|
+
},
|
|
63
|
+
async: {
|
|
64
|
+
awaitExpression: /await\s+\w+/g,
|
|
65
|
+
promiseAll: /Promise\.(?:all|allSettled|race|any)\s*\(/g,
|
|
66
|
+
asyncIterator: /for\s+await\s+\(/g,
|
|
67
|
+
callback: /\(\s*(?:err|error|e)\s*,\s*(?:data|result|response)\s*\)/g,
|
|
68
|
+
eventEmitter: /\.(?:on|once|emit|addListener|removeListener)\s*\(/g,
|
|
69
|
+
},
|
|
70
|
+
type: {
|
|
71
|
+
genericType: /<\s*[A-Z]\w*(?:\s*(?:extends|=)\s*[^>]+)?>/g,
|
|
72
|
+
unionType: /:\s*\w+\s*\|\s*\w+/g,
|
|
73
|
+
intersectionType: /:\s*\w+\s*&\s*\w+/g,
|
|
74
|
+
typeGuard: /\w+\s+is\s+\w+/g,
|
|
75
|
+
assertion: /as\s+\w+/g,
|
|
76
|
+
recordType: /Record\s*<\s*\w+\s*,\s*\w+\s*>/g,
|
|
77
|
+
},
|
|
78
|
+
state: {
|
|
79
|
+
useState: /useState\s*<?\s*\w*>?\s*\(/g,
|
|
80
|
+
useReducer: /useReducer\s*\(/g,
|
|
81
|
+
useEffect: /useEffect\s*\(/g,
|
|
82
|
+
useMemo: /useMemo\s*\(/g,
|
|
83
|
+
useCallback: /useCallback\s*\(/g,
|
|
84
|
+
useRef: /useRef\s*\(/g,
|
|
85
|
+
zustand: /create\s*<\s*\w+\s*>\s*\(/g,
|
|
86
|
+
redux: /(?:createSlice|createAction|createReducer|createSelector)\s*\(/g,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
// ── Fine-Tuning Engine ──────────────────────────────────────────────────────
|
|
90
|
+
export class FineTuningEngine {
|
|
91
|
+
constructor(projectDir) {
|
|
92
|
+
this.patterns = new Map();
|
|
93
|
+
this.trainingData = [];
|
|
94
|
+
this.models = new Map();
|
|
95
|
+
this.styleRules = new Map();
|
|
96
|
+
this.lastTrainingRun = null;
|
|
97
|
+
this.patternIndex = new Map(); // type -> pattern IDs
|
|
98
|
+
this.projectDir = projectDir;
|
|
99
|
+
this.dataDir = path.join(projectDir, '.shadow-brain', 'fine-tuning');
|
|
100
|
+
}
|
|
101
|
+
// ── Core Training Pipeline ──────────────────────────────────────────────
|
|
102
|
+
async trainOnDirectory(dir) {
|
|
103
|
+
const targetDir = dir || this.projectDir;
|
|
104
|
+
const files = await this.collectSourceFiles(targetDir);
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
108
|
+
if (content.length > 0 && content.length < 500000) {
|
|
109
|
+
this.extractPatterns(file, content);
|
|
110
|
+
this.generateTrainingData(file, content);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Skip unreadable files
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.deriveStyleRules();
|
|
118
|
+
const model = this.buildModel();
|
|
119
|
+
this.lastTrainingRun = Date.now();
|
|
120
|
+
await this.save();
|
|
121
|
+
return model;
|
|
122
|
+
}
|
|
123
|
+
async trainOnFile(filePath, content) {
|
|
124
|
+
this.extractPatterns(filePath, content);
|
|
125
|
+
this.generateTrainingData(filePath, content);
|
|
126
|
+
this.deriveStyleRules();
|
|
127
|
+
}
|
|
128
|
+
async trainOnChange(filePath, oldContent, newContent) {
|
|
129
|
+
// Learn from the delta — what did the developer change?
|
|
130
|
+
const oldPatterns = this.extractPatternsFromContent(oldContent);
|
|
131
|
+
const newPatterns = this.extractPatternsFromContent(newContent);
|
|
132
|
+
// Find new patterns (things the developer added)
|
|
133
|
+
for (const [type, newSet] of newPatterns) {
|
|
134
|
+
const oldSet = oldPatterns.get(type) || new Set();
|
|
135
|
+
for (const p of newSet) {
|
|
136
|
+
if (!oldSet.has(p)) {
|
|
137
|
+
this.reinforcePattern(type, p, filePath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Find removed patterns (things the developer removed — negative signal)
|
|
142
|
+
for (const [type, oldSet] of oldPatterns) {
|
|
143
|
+
const newSet = newPatterns.get(type) || new Set();
|
|
144
|
+
for (const p of oldSet) {
|
|
145
|
+
if (!newSet.has(p)) {
|
|
146
|
+
this.weakenPattern(type, p);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.trainingData.push({
|
|
151
|
+
input: oldContent.slice(0, 2000),
|
|
152
|
+
output: newContent.slice(0, 2000),
|
|
153
|
+
category: 'code-change',
|
|
154
|
+
weight: 1.5, // Changes are high-value training data
|
|
155
|
+
source: filePath,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// ── Pattern Extraction ──────────────────────────────────────────────────
|
|
159
|
+
extractPatterns(filePath, content) {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
const relPath = path.relative(this.projectDir, filePath);
|
|
162
|
+
for (const [category, extractors] of Object.entries(PATTERN_EXTRACTORS)) {
|
|
163
|
+
for (const [name, regex] of Object.entries(extractors)) {
|
|
164
|
+
const matches = content.match(regex);
|
|
165
|
+
if (matches && matches.length > 0) {
|
|
166
|
+
const patternId = `${category}:${name}`;
|
|
167
|
+
const existing = this.patterns.get(patternId);
|
|
168
|
+
if (existing) {
|
|
169
|
+
existing.frequency += matches.length;
|
|
170
|
+
existing.lastSeen = now;
|
|
171
|
+
existing.examples = [...new Set([...existing.examples, ...matches.slice(0, 5)])].slice(0, 10);
|
|
172
|
+
if (!existing.files.includes(relPath)) {
|
|
173
|
+
existing.files.push(relPath);
|
|
174
|
+
if (existing.files.length > 50)
|
|
175
|
+
existing.files = existing.files.slice(-50);
|
|
176
|
+
}
|
|
177
|
+
existing.confidence = Math.min(1, existing.confidence + 0.01);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.patterns.set(patternId, {
|
|
181
|
+
id: patternId,
|
|
182
|
+
type: category,
|
|
183
|
+
pattern: name,
|
|
184
|
+
examples: matches.slice(0, 10),
|
|
185
|
+
frequency: matches.length,
|
|
186
|
+
confidence: Math.min(1, 0.3 + matches.length * 0.05),
|
|
187
|
+
context: category,
|
|
188
|
+
firstSeen: now,
|
|
189
|
+
lastSeen: now,
|
|
190
|
+
files: [relPath],
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// Update index
|
|
194
|
+
if (!this.patternIndex.has(category)) {
|
|
195
|
+
this.patternIndex.set(category, new Set());
|
|
196
|
+
}
|
|
197
|
+
this.patternIndex.get(category).add(patternId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Extract custom patterns from repeated structures
|
|
202
|
+
this.extractCustomPatterns(filePath, content);
|
|
203
|
+
}
|
|
204
|
+
extractPatternsFromContent(content) {
|
|
205
|
+
const result = new Map();
|
|
206
|
+
for (const [category, extractors] of Object.entries(PATTERN_EXTRACTORS)) {
|
|
207
|
+
const matches = new Set();
|
|
208
|
+
for (const [_name, regex] of Object.entries(extractors)) {
|
|
209
|
+
const found = content.match(regex);
|
|
210
|
+
if (found) {
|
|
211
|
+
for (const m of found)
|
|
212
|
+
matches.add(m);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (matches.size > 0) {
|
|
216
|
+
result.set(category, matches);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
extractCustomPatterns(filePath, content) {
|
|
222
|
+
const lines = content.split('\n');
|
|
223
|
+
const relPath = path.relative(this.projectDir, filePath);
|
|
224
|
+
// Detect function signature patterns
|
|
225
|
+
const funcPatterns = lines.filter(l => /^\s*(?:export\s+)?(?:async\s+)?(?:function|const|let)\s+\w+/.test(l));
|
|
226
|
+
for (const fp of funcPatterns.slice(0, 20)) {
|
|
227
|
+
const trimmed = fp.trim();
|
|
228
|
+
// Generalize: replace specific names with placeholders
|
|
229
|
+
const generalized = trimmed
|
|
230
|
+
.replace(/\b[a-z][a-zA-Z0-9]{2,}\b/g, '<name>')
|
|
231
|
+
.replace(/['"][^'"]*['"]/g, '<string>')
|
|
232
|
+
.replace(/\d+/g, '<num>');
|
|
233
|
+
const patternId = `custom:func:${this.hashString(generalized)}`;
|
|
234
|
+
const existing = this.patterns.get(patternId);
|
|
235
|
+
if (existing) {
|
|
236
|
+
existing.frequency++;
|
|
237
|
+
existing.lastSeen = Date.now();
|
|
238
|
+
if (!existing.files.includes(relPath))
|
|
239
|
+
existing.files.push(relPath);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
this.patterns.set(patternId, {
|
|
243
|
+
id: patternId,
|
|
244
|
+
type: 'structure',
|
|
245
|
+
pattern: generalized,
|
|
246
|
+
examples: [trimmed],
|
|
247
|
+
frequency: 1,
|
|
248
|
+
confidence: 0.2,
|
|
249
|
+
context: 'function-signature',
|
|
250
|
+
firstSeen: Date.now(),
|
|
251
|
+
lastSeen: Date.now(),
|
|
252
|
+
files: [relPath],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Detect import ordering patterns
|
|
257
|
+
const importBlock = [];
|
|
258
|
+
for (const line of lines) {
|
|
259
|
+
if (/^import\s/.test(line))
|
|
260
|
+
importBlock.push(line);
|
|
261
|
+
else if (importBlock.length > 0 && line.trim() !== '')
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if (importBlock.length >= 3) {
|
|
265
|
+
const hasTypeImportFirst = importBlock[0].includes('import type');
|
|
266
|
+
const hasNodeFirst = importBlock[0].includes("from 'node:") || importBlock[0].includes("from 'fs") || importBlock[0].includes("from 'path");
|
|
267
|
+
const order = hasNodeFirst ? 'node-first' : hasTypeImportFirst ? 'type-first' : 'mixed';
|
|
268
|
+
const patternId = `custom:import-order:${order}`;
|
|
269
|
+
const existing = this.patterns.get(patternId);
|
|
270
|
+
if (existing) {
|
|
271
|
+
existing.frequency++;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
this.patterns.set(patternId, {
|
|
275
|
+
id: patternId,
|
|
276
|
+
type: 'import',
|
|
277
|
+
pattern: `import-order-${order}`,
|
|
278
|
+
examples: importBlock.slice(0, 5),
|
|
279
|
+
frequency: 1,
|
|
280
|
+
confidence: 0.3,
|
|
281
|
+
context: 'import-ordering',
|
|
282
|
+
firstSeen: Date.now(),
|
|
283
|
+
lastSeen: Date.now(),
|
|
284
|
+
files: [relPath],
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Detect error handling patterns
|
|
289
|
+
const errorPatterns = content.match(/catch\s*\([^)]*\)\s*\{[^}]{0,500}\}/g);
|
|
290
|
+
if (errorPatterns) {
|
|
291
|
+
for (const ep of errorPatterns.slice(0, 5)) {
|
|
292
|
+
const hasLogging = /console\.|logger\.|log\(/.test(ep);
|
|
293
|
+
const hasRethrow = /throw\s/.test(ep);
|
|
294
|
+
const hasReturn = /return\s/.test(ep);
|
|
295
|
+
const isEmpty = /catch\s*\([^)]*\)\s*\{\s*\}/.test(ep);
|
|
296
|
+
const style = isEmpty ? 'silent' : hasRethrow ? 'rethrow' : hasLogging ? 'log' : hasReturn ? 'return' : 'custom';
|
|
297
|
+
const patternId = `custom:error-style:${style}`;
|
|
298
|
+
const existing = this.patterns.get(patternId);
|
|
299
|
+
if (existing) {
|
|
300
|
+
existing.frequency++;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.patterns.set(patternId, {
|
|
304
|
+
id: patternId,
|
|
305
|
+
type: 'error-handling',
|
|
306
|
+
pattern: `error-${style}`,
|
|
307
|
+
examples: [ep.slice(0, 200)],
|
|
308
|
+
frequency: 1,
|
|
309
|
+
confidence: 0.4,
|
|
310
|
+
context: 'error-handling-style',
|
|
311
|
+
firstSeen: Date.now(),
|
|
312
|
+
lastSeen: Date.now(),
|
|
313
|
+
files: [relPath],
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
reinforcePattern(type, pattern, filePath) {
|
|
320
|
+
const patternId = `${type}:${this.hashString(pattern).slice(0, 12)}`;
|
|
321
|
+
const existing = this.patterns.get(patternId);
|
|
322
|
+
if (existing) {
|
|
323
|
+
existing.frequency++;
|
|
324
|
+
existing.confidence = Math.min(1, existing.confidence + 0.05);
|
|
325
|
+
existing.lastSeen = Date.now();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
weakenPattern(type, pattern) {
|
|
329
|
+
const patternId = `${type}:${this.hashString(pattern).slice(0, 12)}`;
|
|
330
|
+
const existing = this.patterns.get(patternId);
|
|
331
|
+
if (existing) {
|
|
332
|
+
existing.confidence = Math.max(0, existing.confidence - 0.02);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// ── Training Data Generation ──────────────────────────────────────────
|
|
336
|
+
generateTrainingData(filePath, content) {
|
|
337
|
+
const lines = content.split('\n');
|
|
338
|
+
const relPath = path.relative(this.projectDir, filePath);
|
|
339
|
+
const ext = path.extname(filePath);
|
|
340
|
+
// Function-level training pairs (context → implementation)
|
|
341
|
+
const funcRegex = /(?:export\s+)?(?:async\s+)?(?:function\s+(\w+)|(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=]+)\s*=>)/g;
|
|
342
|
+
let match;
|
|
343
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
344
|
+
const name = match[1] || match[2];
|
|
345
|
+
const startIdx = match.index;
|
|
346
|
+
const funcBody = this.extractFunctionBody(content, startIdx);
|
|
347
|
+
if (funcBody && funcBody.length > 50 && funcBody.length < 5000) {
|
|
348
|
+
this.trainingData.push({
|
|
349
|
+
input: `// Function: ${name} in ${relPath}`,
|
|
350
|
+
output: funcBody,
|
|
351
|
+
category: 'function-implementation',
|
|
352
|
+
weight: 1.0,
|
|
353
|
+
source: relPath,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Comment → code pairs (doc comments preceding functions)
|
|
358
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
359
|
+
if (lines[i].trim().startsWith('/**') || lines[i].trim().startsWith('//')) {
|
|
360
|
+
let commentEnd = i;
|
|
361
|
+
while (commentEnd < lines.length - 1 && (lines[commentEnd].trim().startsWith('*') || lines[commentEnd].trim().startsWith('//'))) {
|
|
362
|
+
commentEnd++;
|
|
363
|
+
}
|
|
364
|
+
if (commentEnd < lines.length && /^\s*(?:export\s+)?(?:async\s+)?(?:function|const|class|interface|type)/.test(lines[commentEnd])) {
|
|
365
|
+
const comment = lines.slice(i, commentEnd).join('\n');
|
|
366
|
+
const codeLines = [];
|
|
367
|
+
for (let j = commentEnd; j < Math.min(commentEnd + 20, lines.length); j++) {
|
|
368
|
+
codeLines.push(lines[j]);
|
|
369
|
+
if (lines[j].trim() === '}' || lines[j].trim() === '};')
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
if (codeLines.length > 1) {
|
|
373
|
+
this.trainingData.push({
|
|
374
|
+
input: comment,
|
|
375
|
+
output: codeLines.join('\n'),
|
|
376
|
+
category: 'doc-to-code',
|
|
377
|
+
weight: 1.2,
|
|
378
|
+
source: relPath,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Test pattern pairs (test name → test body)
|
|
385
|
+
const testRegex = /(?:it|test)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?:async\s+)?\(\)\s*=>\s*\{/g;
|
|
386
|
+
while ((match = testRegex.exec(content)) !== null) {
|
|
387
|
+
const testName = match[1];
|
|
388
|
+
const testBody = this.extractFunctionBody(content, match.index);
|
|
389
|
+
if (testBody && testBody.length > 30) {
|
|
390
|
+
this.trainingData.push({
|
|
391
|
+
input: `// Test: ${testName}`,
|
|
392
|
+
output: testBody.slice(0, 2000),
|
|
393
|
+
category: 'test-pattern',
|
|
394
|
+
weight: 1.1,
|
|
395
|
+
source: relPath,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Cap training data size
|
|
400
|
+
if (this.trainingData.length > 10000) {
|
|
401
|
+
// Keep highest-weight items
|
|
402
|
+
this.trainingData.sort((a, b) => b.weight - a.weight);
|
|
403
|
+
this.trainingData = this.trainingData.slice(0, 8000);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
extractFunctionBody(content, startIdx) {
|
|
407
|
+
let braceCount = 0;
|
|
408
|
+
let started = false;
|
|
409
|
+
let bodyStart = startIdx;
|
|
410
|
+
for (let i = startIdx; i < Math.min(startIdx + 5000, content.length); i++) {
|
|
411
|
+
if (content[i] === '{') {
|
|
412
|
+
if (!started)
|
|
413
|
+
bodyStart = startIdx;
|
|
414
|
+
braceCount++;
|
|
415
|
+
started = true;
|
|
416
|
+
}
|
|
417
|
+
else if (content[i] === '}') {
|
|
418
|
+
braceCount--;
|
|
419
|
+
if (started && braceCount === 0) {
|
|
420
|
+
return content.slice(bodyStart, i + 1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
// ── Style Rule Derivation ────────────────────────────────────────────
|
|
427
|
+
deriveStyleRules() {
|
|
428
|
+
// Analyze patterns to create enforceable style rules
|
|
429
|
+
for (const [id, pattern] of this.patterns) {
|
|
430
|
+
if (pattern.frequency < 3 || pattern.confidence < 0.3)
|
|
431
|
+
continue;
|
|
432
|
+
const ruleId = `rule:${id}`;
|
|
433
|
+
if (this.styleRules.has(ruleId))
|
|
434
|
+
continue;
|
|
435
|
+
let rule = null;
|
|
436
|
+
// Naming conventions
|
|
437
|
+
if (pattern.type === 'naming' && pattern.pattern === 'camelCase' && pattern.frequency > 10) {
|
|
438
|
+
rule = {
|
|
439
|
+
id: ruleId,
|
|
440
|
+
rule: 'Use camelCase for function and variable names',
|
|
441
|
+
severity: 'warning',
|
|
442
|
+
examples: [{ good: 'getUserById()', bad: 'get_user_by_id()' }],
|
|
443
|
+
frequency: pattern.frequency,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
// Error handling style
|
|
447
|
+
if (pattern.type === 'error-handling' && pattern.context === 'error-handling-style') {
|
|
448
|
+
const style = pattern.pattern;
|
|
449
|
+
if (style === 'error-silent' && pattern.frequency > 5) {
|
|
450
|
+
rule = {
|
|
451
|
+
id: ruleId,
|
|
452
|
+
rule: 'Avoid empty catch blocks — log or handle errors',
|
|
453
|
+
severity: 'warning',
|
|
454
|
+
examples: [{ good: 'catch (e) { logger.error(e); }', bad: 'catch (e) { }' }],
|
|
455
|
+
frequency: pattern.frequency,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Import ordering
|
|
460
|
+
if (pattern.type === 'import' && pattern.context === 'import-ordering') {
|
|
461
|
+
rule = {
|
|
462
|
+
id: ruleId,
|
|
463
|
+
rule: `Import ordering: ${pattern.pattern}`,
|
|
464
|
+
severity: 'info',
|
|
465
|
+
examples: pattern.examples.map(e => ({ good: e, bad: '' })),
|
|
466
|
+
frequency: pattern.frequency,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
// Async patterns
|
|
470
|
+
if (pattern.type === 'async' && pattern.pattern === 'promiseAll' && pattern.frequency > 3) {
|
|
471
|
+
rule = {
|
|
472
|
+
id: ruleId,
|
|
473
|
+
rule: 'Use Promise.all/allSettled for parallel async operations',
|
|
474
|
+
severity: 'info',
|
|
475
|
+
examples: [{ good: 'await Promise.all([a(), b()])', bad: 'await a(); await b();' }],
|
|
476
|
+
frequency: pattern.frequency,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (rule) {
|
|
480
|
+
this.styleRules.set(ruleId, rule);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// ── Model Building ──────────────────────────────────────────────────
|
|
485
|
+
buildModel() {
|
|
486
|
+
const categories = {};
|
|
487
|
+
for (const td of this.trainingData) {
|
|
488
|
+
categories[td.category] = (categories[td.category] || 0) + 1;
|
|
489
|
+
}
|
|
490
|
+
const model = {
|
|
491
|
+
id: crypto.randomUUID(),
|
|
492
|
+
name: `shadow-brain-ft-${Date.now()}`,
|
|
493
|
+
createdAt: Date.now(),
|
|
494
|
+
updatedAt: Date.now(),
|
|
495
|
+
patternCount: this.patterns.size,
|
|
496
|
+
trainingPoints: this.trainingData.length,
|
|
497
|
+
accuracy: this.calculateAccuracy(),
|
|
498
|
+
version: this.models.size + 1,
|
|
499
|
+
categories,
|
|
500
|
+
};
|
|
501
|
+
this.models.set(model.id, model);
|
|
502
|
+
return model;
|
|
503
|
+
}
|
|
504
|
+
calculateAccuracy() {
|
|
505
|
+
if (this.patterns.size === 0)
|
|
506
|
+
return 0;
|
|
507
|
+
const totalConfidence = Array.from(this.patterns.values())
|
|
508
|
+
.reduce((sum, p) => sum + p.confidence, 0);
|
|
509
|
+
return Math.min(1, totalConfidence / this.patterns.size);
|
|
510
|
+
}
|
|
511
|
+
// ── Suggestion Generation ────────────────────────────────────────────
|
|
512
|
+
suggest(context, type) {
|
|
513
|
+
const suggestions = [];
|
|
514
|
+
const contextLower = context.toLowerCase();
|
|
515
|
+
for (const [_id, pattern] of this.patterns) {
|
|
516
|
+
if (pattern.confidence < 0.4 || pattern.frequency < 3)
|
|
517
|
+
continue;
|
|
518
|
+
if (type && pattern.type !== type)
|
|
519
|
+
continue;
|
|
520
|
+
// Check if context is relevant to this pattern
|
|
521
|
+
const relevant = pattern.files.some(f => contextLower.includes(path.basename(f, path.extname(f)).toLowerCase())) || pattern.examples.some(e => contextLower.includes(e.slice(0, 20).toLowerCase()));
|
|
522
|
+
if (relevant || !type) {
|
|
523
|
+
suggestions.push({
|
|
524
|
+
text: pattern.examples[0] || pattern.pattern,
|
|
525
|
+
confidence: pattern.confidence,
|
|
526
|
+
category: pattern.type,
|
|
527
|
+
basedOn: pattern.files.slice(0, 3),
|
|
528
|
+
reasoning: `Pattern "${pattern.pattern}" seen ${pattern.frequency} times across ${pattern.files.length} files`,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return suggestions
|
|
533
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
534
|
+
.slice(0, 10);
|
|
535
|
+
}
|
|
536
|
+
getStyleViolations(content) {
|
|
537
|
+
const violations = [];
|
|
538
|
+
const lines = content.split('\n');
|
|
539
|
+
for (const [_id, rule] of this.styleRules) {
|
|
540
|
+
if (rule.severity === 'info')
|
|
541
|
+
continue; // Only check warnings/errors
|
|
542
|
+
for (let i = 0; i < lines.length; i++) {
|
|
543
|
+
for (const example of rule.examples) {
|
|
544
|
+
if (example.bad && lines[i].includes(example.bad)) {
|
|
545
|
+
violations.push({ rule, line: i + 1, text: lines[i].trim() });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return violations;
|
|
551
|
+
}
|
|
552
|
+
getPatternsByType(type) {
|
|
553
|
+
const ids = this.patternIndex.get(type);
|
|
554
|
+
if (!ids)
|
|
555
|
+
return [];
|
|
556
|
+
return Array.from(ids)
|
|
557
|
+
.map(id => this.patterns.get(id))
|
|
558
|
+
.filter((p) => p !== undefined)
|
|
559
|
+
.sort((a, b) => b.frequency - a.frequency);
|
|
560
|
+
}
|
|
561
|
+
getTopPatterns(n = 20) {
|
|
562
|
+
return Array.from(this.patterns.values())
|
|
563
|
+
.sort((a, b) => b.frequency * b.confidence - a.frequency * a.confidence)
|
|
564
|
+
.slice(0, n);
|
|
565
|
+
}
|
|
566
|
+
getStyleRules() {
|
|
567
|
+
return Array.from(this.styleRules.values());
|
|
568
|
+
}
|
|
569
|
+
// ── File Collection ──────────────────────────────────────────────────
|
|
570
|
+
async collectSourceFiles(dir, maxFiles = 500) {
|
|
571
|
+
const files = [];
|
|
572
|
+
const extensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.kt', '.swift', '.rb', '.php', '.vue', '.svelte']);
|
|
573
|
+
const ignore = new Set(['node_modules', '.git', 'dist', 'build', 'out', '.next', 'coverage', '__pycache__', '.shadow-brain', 'vendor']);
|
|
574
|
+
const walk = (currentDir, depth) => {
|
|
575
|
+
if (depth > 8 || files.length >= maxFiles)
|
|
576
|
+
return;
|
|
577
|
+
try {
|
|
578
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
579
|
+
for (const entry of entries) {
|
|
580
|
+
if (files.length >= maxFiles)
|
|
581
|
+
break;
|
|
582
|
+
if (entry.isDirectory()) {
|
|
583
|
+
if (!ignore.has(entry.name) && !entry.name.startsWith('.')) {
|
|
584
|
+
walk(path.join(currentDir, entry.name), depth + 1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else if (entry.isFile()) {
|
|
588
|
+
const ext = path.extname(entry.name);
|
|
589
|
+
if (extensions.has(ext)) {
|
|
590
|
+
files.push(path.join(currentDir, entry.name));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
// Skip inaccessible directories
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
walk(dir, 0);
|
|
600
|
+
return files;
|
|
601
|
+
}
|
|
602
|
+
// ── Persistence ──────────────────────────────────────────────────────
|
|
603
|
+
async save() {
|
|
604
|
+
try {
|
|
605
|
+
if (!fs.existsSync(this.dataDir)) {
|
|
606
|
+
fs.mkdirSync(this.dataDir, { recursive: true });
|
|
607
|
+
}
|
|
608
|
+
const data = {
|
|
609
|
+
patterns: Array.from(this.patterns.entries()),
|
|
610
|
+
trainingData: this.trainingData.slice(0, 5000), // Cap for disk
|
|
611
|
+
models: Array.from(this.models.entries()),
|
|
612
|
+
styleRules: Array.from(this.styleRules.entries()),
|
|
613
|
+
lastTrainingRun: this.lastTrainingRun,
|
|
614
|
+
};
|
|
615
|
+
fs.writeFileSync(path.join(this.dataDir, 'model.json'), JSON.stringify(data, null, 2), 'utf-8');
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// Best-effort save
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
async load() {
|
|
622
|
+
try {
|
|
623
|
+
const dataPath = path.join(this.dataDir, 'model.json');
|
|
624
|
+
if (!fs.existsSync(dataPath))
|
|
625
|
+
return;
|
|
626
|
+
const raw = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
|
627
|
+
if (raw.patterns)
|
|
628
|
+
this.patterns = new Map(raw.patterns);
|
|
629
|
+
if (raw.trainingData)
|
|
630
|
+
this.trainingData = raw.trainingData;
|
|
631
|
+
if (raw.models)
|
|
632
|
+
this.models = new Map(raw.models);
|
|
633
|
+
if (raw.styleRules)
|
|
634
|
+
this.styleRules = new Map(raw.styleRules);
|
|
635
|
+
if (raw.lastTrainingRun)
|
|
636
|
+
this.lastTrainingRun = raw.lastTrainingRun;
|
|
637
|
+
// Rebuild index
|
|
638
|
+
this.patternIndex.clear();
|
|
639
|
+
for (const [id, pattern] of this.patterns) {
|
|
640
|
+
if (!this.patternIndex.has(pattern.type)) {
|
|
641
|
+
this.patternIndex.set(pattern.type, new Set());
|
|
642
|
+
}
|
|
643
|
+
this.patternIndex.get(pattern.type).add(id);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
// Start fresh
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// ── Stats ────────────────────────────────────────────────────────────
|
|
651
|
+
stats() {
|
|
652
|
+
const categories = {};
|
|
653
|
+
for (const p of this.patterns.values()) {
|
|
654
|
+
categories[p.type] = (categories[p.type] || 0) + 1;
|
|
655
|
+
}
|
|
656
|
+
const typeCounts = new Map();
|
|
657
|
+
for (const p of this.patterns.values()) {
|
|
658
|
+
typeCounts.set(p.type, (typeCounts.get(p.type) || 0) + p.frequency);
|
|
659
|
+
}
|
|
660
|
+
const topPatterns = Array.from(typeCounts.entries())
|
|
661
|
+
.map(([type, count]) => ({ type, count }))
|
|
662
|
+
.sort((a, b) => b.count - a.count)
|
|
663
|
+
.slice(0, 10);
|
|
664
|
+
const rawSize = JSON.stringify(Array.from(this.patterns.values())).length;
|
|
665
|
+
return {
|
|
666
|
+
totalPatterns: this.patterns.size,
|
|
667
|
+
totalTrainingPoints: this.trainingData.length,
|
|
668
|
+
models: this.models.size,
|
|
669
|
+
categories,
|
|
670
|
+
topPatterns,
|
|
671
|
+
lastTrainingRun: this.lastTrainingRun,
|
|
672
|
+
accuracy: this.calculateAccuracy(),
|
|
673
|
+
memoryUsageMB: Number((rawSize / (1024 * 1024)).toFixed(2)),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
677
|
+
hashString(s) {
|
|
678
|
+
return crypto.createHash('sha256').update(s).digest('hex').slice(0, 16);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
//# sourceMappingURL=fine-tuning-engine.js.map
|