@theihtisham/agent-shadow-brain 5.0.1 → 5.2.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.
Files changed (74) hide show
  1. package/README.md +1159 -888
  2. package/dist/adapters/copilot.d.ts +14 -0
  3. package/dist/adapters/copilot.d.ts.map +1 -0
  4. package/dist/adapters/copilot.js +93 -0
  5. package/dist/adapters/copilot.js.map +1 -0
  6. package/dist/adapters/cursor.d.ts +13 -0
  7. package/dist/adapters/cursor.d.ts.map +1 -0
  8. package/dist/adapters/cursor.js +96 -0
  9. package/dist/adapters/cursor.js.map +1 -0
  10. package/dist/adapters/index.d.ts +4 -1
  11. package/dist/adapters/index.d.ts.map +1 -1
  12. package/dist/adapters/index.js +7 -3
  13. package/dist/adapters/index.js.map +1 -1
  14. package/dist/adapters/windsurf.d.ts +14 -0
  15. package/dist/adapters/windsurf.d.ts.map +1 -0
  16. package/dist/adapters/windsurf.js +107 -0
  17. package/dist/adapters/windsurf.js.map +1 -0
  18. package/dist/brain/code-dna.d.ts +70 -0
  19. package/dist/brain/code-dna.d.ts.map +1 -0
  20. package/dist/brain/code-dna.js +611 -0
  21. package/dist/brain/code-dna.js.map +1 -0
  22. package/dist/brain/fine-tuning-engine.d.ts +99 -0
  23. package/dist/brain/fine-tuning-engine.d.ts.map +1 -0
  24. package/dist/brain/fine-tuning-engine.js +681 -0
  25. package/dist/brain/fine-tuning-engine.js.map +1 -0
  26. package/dist/brain/global-brain.d.ts +56 -0
  27. package/dist/brain/global-brain.d.ts.map +1 -0
  28. package/dist/brain/global-brain.js +324 -0
  29. package/dist/brain/global-brain.js.map +1 -0
  30. package/dist/brain/intent-engine.d.ts +64 -0
  31. package/dist/brain/intent-engine.d.ts.map +1 -0
  32. package/dist/brain/intent-engine.js +374 -0
  33. package/dist/brain/intent-engine.js.map +1 -0
  34. package/dist/brain/l0-cache.d.ts +40 -0
  35. package/dist/brain/l0-cache.d.ts.map +1 -0
  36. package/dist/brain/l0-cache.js +176 -0
  37. package/dist/brain/l0-cache.js.map +1 -0
  38. package/dist/brain/lsp-server.d.ts +62 -0
  39. package/dist/brain/lsp-server.d.ts.map +1 -0
  40. package/dist/brain/lsp-server.js +480 -0
  41. package/dist/brain/lsp-server.js.map +1 -0
  42. package/dist/brain/mcp-server.d.ts +4 -0
  43. package/dist/brain/mcp-server.d.ts.map +1 -1
  44. package/dist/brain/mcp-server.js +91 -0
  45. package/dist/brain/mcp-server.js.map +1 -1
  46. package/dist/brain/orchestrator.d.ts +13 -0
  47. package/dist/brain/orchestrator.d.ts.map +1 -1
  48. package/dist/brain/orchestrator.js +25 -1
  49. package/dist/brain/orchestrator.js.map +1 -1
  50. package/dist/brain/session-hooks.d.ts +28 -0
  51. package/dist/brain/session-hooks.d.ts.map +1 -0
  52. package/dist/brain/session-hooks.js +475 -0
  53. package/dist/brain/session-hooks.js.map +1 -0
  54. package/dist/brain/smart-cache.d.ts +100 -0
  55. package/dist/brain/smart-cache.d.ts.map +1 -0
  56. package/dist/brain/smart-cache.js +417 -0
  57. package/dist/brain/smart-cache.js.map +1 -0
  58. package/dist/brain/subconscious.d.ts +34 -0
  59. package/dist/brain/subconscious.d.ts.map +1 -0
  60. package/dist/brain/subconscious.js +275 -0
  61. package/dist/brain/subconscious.js.map +1 -0
  62. package/dist/brain/temporal-intelligence.d.ts +89 -0
  63. package/dist/brain/temporal-intelligence.d.ts.map +1 -0
  64. package/dist/brain/temporal-intelligence.js +378 -0
  65. package/dist/brain/temporal-intelligence.js.map +1 -0
  66. package/dist/cli.js +434 -4
  67. package/dist/cli.js.map +1 -1
  68. package/dist/index.d.ts +8 -1
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +8 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/types.d.ts +333 -1
  73. package/dist/types.d.ts.map +1 -1
  74. package/package.json +46 -10
@@ -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