neuronlayer 0.1.9 → 0.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.

Potentially problematic release.


This version of neuronlayer might be problematic. Click here for more details.

Files changed (81) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +172 -90
  3. package/dist/index.js.map +7 -0
  4. package/package.json +6 -1
  5. package/esbuild.config.js +0 -26
  6. package/src/cli/commands.ts +0 -573
  7. package/src/core/adr-exporter.ts +0 -253
  8. package/src/core/architecture/architecture-enforcement.ts +0 -228
  9. package/src/core/architecture/duplicate-detector.ts +0 -288
  10. package/src/core/architecture/index.ts +0 -6
  11. package/src/core/architecture/pattern-learner.ts +0 -306
  12. package/src/core/architecture/pattern-library.ts +0 -403
  13. package/src/core/architecture/pattern-validator.ts +0 -324
  14. package/src/core/change-intelligence/bug-correlator.ts +0 -544
  15. package/src/core/change-intelligence/change-intelligence.ts +0 -264
  16. package/src/core/change-intelligence/change-tracker.ts +0 -334
  17. package/src/core/change-intelligence/fix-suggester.ts +0 -340
  18. package/src/core/change-intelligence/index.ts +0 -5
  19. package/src/core/code-verifier.ts +0 -843
  20. package/src/core/confidence/confidence-scorer.ts +0 -251
  21. package/src/core/confidence/conflict-checker.ts +0 -289
  22. package/src/core/confidence/index.ts +0 -5
  23. package/src/core/confidence/source-tracker.ts +0 -263
  24. package/src/core/confidence/warning-detector.ts +0 -241
  25. package/src/core/context-rot/compaction.ts +0 -284
  26. package/src/core/context-rot/context-health.ts +0 -243
  27. package/src/core/context-rot/context-rot-prevention.ts +0 -213
  28. package/src/core/context-rot/critical-context.ts +0 -221
  29. package/src/core/context-rot/drift-detector.ts +0 -255
  30. package/src/core/context-rot/index.ts +0 -7
  31. package/src/core/context.ts +0 -263
  32. package/src/core/decision-extractor.ts +0 -339
  33. package/src/core/decisions.ts +0 -69
  34. package/src/core/deja-vu.ts +0 -421
  35. package/src/core/engine.ts +0 -1646
  36. package/src/core/feature-context.ts +0 -726
  37. package/src/core/ghost-mode.ts +0 -465
  38. package/src/core/learning.ts +0 -519
  39. package/src/core/living-docs/activity-tracker.ts +0 -296
  40. package/src/core/living-docs/architecture-generator.ts +0 -428
  41. package/src/core/living-docs/changelog-generator.ts +0 -348
  42. package/src/core/living-docs/component-generator.ts +0 -230
  43. package/src/core/living-docs/doc-engine.ts +0 -134
  44. package/src/core/living-docs/doc-validator.ts +0 -282
  45. package/src/core/living-docs/index.ts +0 -8
  46. package/src/core/project-manager.ts +0 -301
  47. package/src/core/refresh/activity-gate.ts +0 -256
  48. package/src/core/refresh/git-staleness-checker.ts +0 -108
  49. package/src/core/refresh/index.ts +0 -27
  50. package/src/core/summarizer.ts +0 -290
  51. package/src/core/test-awareness/change-validator.ts +0 -499
  52. package/src/core/test-awareness/index.ts +0 -5
  53. package/src/index.ts +0 -90
  54. package/src/indexing/ast.ts +0 -868
  55. package/src/indexing/embeddings.ts +0 -85
  56. package/src/indexing/indexer.ts +0 -270
  57. package/src/indexing/watcher.ts +0 -78
  58. package/src/server/gateways/aggregator.ts +0 -374
  59. package/src/server/gateways/index.ts +0 -473
  60. package/src/server/gateways/memory-ghost.ts +0 -343
  61. package/src/server/gateways/memory-query.ts +0 -452
  62. package/src/server/gateways/memory-record.ts +0 -346
  63. package/src/server/gateways/memory-review.ts +0 -410
  64. package/src/server/gateways/memory-status.ts +0 -517
  65. package/src/server/gateways/memory-verify.ts +0 -392
  66. package/src/server/gateways/router.ts +0 -434
  67. package/src/server/gateways/types.ts +0 -610
  68. package/src/server/http.ts +0 -228
  69. package/src/server/mcp.ts +0 -154
  70. package/src/server/resources.ts +0 -85
  71. package/src/server/tools.ts +0 -2460
  72. package/src/storage/database.ts +0 -271
  73. package/src/storage/tier1.ts +0 -135
  74. package/src/storage/tier2.ts +0 -972
  75. package/src/storage/tier3.ts +0 -123
  76. package/src/types/documentation.ts +0 -619
  77. package/src/types/index.ts +0 -222
  78. package/src/utils/config.ts +0 -194
  79. package/src/utils/files.ts +0 -117
  80. package/src/utils/time.ts +0 -37
  81. package/src/utils/tokens.ts +0 -52
@@ -1,519 +0,0 @@
1
- import { createHash } from 'crypto';
2
- import type Database from 'better-sqlite3';
3
- import type { SearchResult } from '../types/index.js';
4
-
5
- export type UsageEventType =
6
- | 'query' // User made a query
7
- | 'file_view' // File was viewed
8
- | 'context_used' // Context was included in response
9
- | 'context_ignored' // Context was retrieved but not used
10
- | 'decision_made' // User made a decision
11
- | 'file_edit'; // User edited a file
12
-
13
- interface UsageEvent {
14
- eventType: UsageEventType;
15
- filePath?: string;
16
- query?: string;
17
- contextUsed?: boolean;
18
- }
19
-
20
- interface FileAccessStats {
21
- fileId: number;
22
- accessCount: number;
23
- lastAccessed: number;
24
- relevanceScore: number;
25
- }
26
-
27
- interface QueryPattern {
28
- queryHash: string;
29
- queryText: string;
30
- resultFiles: string[];
31
- hitCount: number;
32
- avgUsefulness: number;
33
- }
34
-
35
- export class LearningEngine {
36
- private db: Database.Database;
37
- private hotCache: Map<string, { content: string; accessCount: number; lastAccessed: number }> = new Map();
38
- private readonly MAX_CACHE_SIZE = 50;
39
- private readonly CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
40
- private lastImportanceUpdate: number = 0;
41
- private readonly IMPORTANCE_UPDATE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
42
-
43
- constructor(db: Database.Database) {
44
- this.db = db;
45
- }
46
-
47
- // Track usage events
48
- trackEvent(event: UsageEvent): void {
49
- const stmt = this.db.prepare(`
50
- INSERT INTO usage_events (event_type, file_path, query, context_used, timestamp)
51
- VALUES (?, ?, ?, ?, unixepoch())
52
- `);
53
- stmt.run(
54
- event.eventType,
55
- event.filePath || null,
56
- event.query || null,
57
- event.contextUsed ? 1 : 0
58
- );
59
-
60
- // Update file access stats if file was accessed
61
- if (event.filePath && (event.eventType === 'file_view' || event.eventType === 'context_used')) {
62
- this.updateFileAccess(event.filePath);
63
- }
64
- }
65
-
66
- // Track a query and its results for pattern learning
67
- trackQuery(query: string, resultFiles: string[]): void {
68
- const queryHash = this.hashQuery(query);
69
-
70
- const stmt = this.db.prepare(`
71
- INSERT INTO query_patterns (query_hash, query_text, result_files, hit_count, last_used)
72
- VALUES (?, ?, ?, 1, unixepoch())
73
- ON CONFLICT(query_hash) DO UPDATE SET
74
- hit_count = hit_count + 1,
75
- last_used = unixepoch()
76
- `);
77
- stmt.run(queryHash, query, JSON.stringify(resultFiles));
78
- }
79
-
80
- // Update usefulness score for a query pattern
81
- updateQueryUsefulness(query: string, wasUseful: boolean): void {
82
- const queryHash = this.hashQuery(query);
83
-
84
- // Exponential moving average for usefulness
85
- const alpha = 0.3;
86
- const newScore = wasUseful ? 1.0 : 0.0;
87
-
88
- const stmt = this.db.prepare(`
89
- UPDATE query_patterns
90
- SET avg_usefulness = avg_usefulness * (1 - ?) + ? * ?
91
- WHERE query_hash = ?
92
- `);
93
- stmt.run(alpha, alpha, newScore, queryHash);
94
- }
95
-
96
- // Update file access statistics
97
- private updateFileAccess(filePath: string): void {
98
- // Get file ID
99
- const fileStmt = this.db.prepare('SELECT id FROM files WHERE path = ?');
100
- const file = fileStmt.get(filePath) as { id: number } | undefined;
101
-
102
- if (!file) return;
103
-
104
- const stmt = this.db.prepare(`
105
- INSERT INTO file_access (file_id, access_count, last_accessed, relevance_score)
106
- VALUES (?, 1, unixepoch(), 0.5)
107
- ON CONFLICT(file_id) DO UPDATE SET
108
- access_count = access_count + 1,
109
- last_accessed = unixepoch(),
110
- relevance_score = MIN(1.0, relevance_score + 0.05)
111
- `);
112
- stmt.run(file.id);
113
-
114
- // Update hot cache
115
- this.updateHotCache(filePath);
116
- }
117
-
118
- // Get personalized boost for a file based on usage patterns
119
- getPersonalizedBoost(filePath: string): number {
120
- const fileStmt = this.db.prepare('SELECT id FROM files WHERE path = ?');
121
- const file = fileStmt.get(filePath) as { id: number } | undefined;
122
-
123
- if (!file) return 1.0;
124
-
125
- const stmt = this.db.prepare(`
126
- SELECT access_count, last_accessed, relevance_score
127
- FROM file_access
128
- WHERE file_id = ?
129
- `);
130
- const stats = stmt.get(file.id) as FileAccessStats | undefined;
131
-
132
- if (!stats) return 1.0;
133
-
134
- // Calculate boost based on:
135
- // 1. Access frequency (log scale to avoid extreme boosts)
136
- // 2. Recency (decay over time)
137
- // 3. Learned relevance score
138
-
139
- const frequencyBoost = 1 + Math.log10(1 + stats.accessCount) * 0.2;
140
-
141
- const hoursSinceAccess = (Date.now() / 1000 - stats.lastAccessed) / 3600;
142
- const recencyBoost = Math.exp(-hoursSinceAccess / 168); // Decay over 1 week
143
-
144
- const relevanceBoost = 0.5 + stats.relevanceScore;
145
-
146
- return frequencyBoost * (0.7 + 0.3 * recencyBoost) * relevanceBoost;
147
- }
148
-
149
- // Apply personalized ranking to search results
150
- applyPersonalizedRanking(results: SearchResult[]): SearchResult[] {
151
- return results
152
- .map(r => ({
153
- ...r,
154
- score: (r.score || r.similarity) * this.getPersonalizedBoost(r.file)
155
- }))
156
- .sort((a, b) => (b.score || 0) - (a.score || 0));
157
- }
158
-
159
- // Get frequently accessed files for pre-fetching
160
- getFrequentFiles(limit: number = 20): string[] {
161
- const stmt = this.db.prepare(`
162
- SELECT f.path
163
- FROM file_access fa
164
- JOIN files f ON fa.file_id = f.id
165
- ORDER BY fa.access_count DESC, fa.last_accessed DESC
166
- LIMIT ?
167
- `);
168
- const rows = stmt.all(limit) as { path: string }[];
169
- return rows.map(r => r.path);
170
- }
171
-
172
- // Get recently accessed files
173
- getRecentFiles(limit: number = 10): string[] {
174
- const stmt = this.db.prepare(`
175
- SELECT f.path
176
- FROM file_access fa
177
- JOIN files f ON fa.file_id = f.id
178
- ORDER BY fa.last_accessed DESC
179
- LIMIT ?
180
- `);
181
- const rows = stmt.all(limit) as { path: string }[];
182
- return rows.map(r => r.path);
183
- }
184
-
185
- // Predict likely needed files based on current context
186
- predictNeededFiles(currentFile: string, query: string): string[] {
187
- const predictions: Set<string> = new Set();
188
-
189
- // 1. Files frequently accessed together with current file
190
- const coAccessStmt = this.db.prepare(`
191
- SELECT DISTINCT ue2.file_path
192
- FROM usage_events ue1
193
- JOIN usage_events ue2 ON ABS(ue1.timestamp - ue2.timestamp) < 300
194
- WHERE ue1.file_path = ?
195
- AND ue2.file_path != ?
196
- AND ue2.file_path IS NOT NULL
197
- GROUP BY ue2.file_path
198
- ORDER BY COUNT(*) DESC
199
- LIMIT 5
200
- `);
201
- const coAccessed = coAccessStmt.all(currentFile, currentFile) as { file_path: string }[];
202
- coAccessed.forEach(r => predictions.add(r.file_path));
203
-
204
- // 2. Files from similar past queries
205
- const queryHash = this.hashQuery(query);
206
- const similarQueryStmt = this.db.prepare(`
207
- SELECT result_files
208
- FROM query_patterns
209
- WHERE query_hash = ?
210
- OR query_text LIKE ?
211
- ORDER BY avg_usefulness DESC, hit_count DESC
212
- LIMIT 3
213
- `);
214
- const keywords = query.split(/\s+/).slice(0, 3).join('%');
215
- const similarQueries = similarQueryStmt.all(queryHash, `%${keywords}%`) as { result_files: string }[];
216
-
217
- for (const q of similarQueries) {
218
- try {
219
- const files = JSON.parse(q.result_files) as string[];
220
- files.slice(0, 3).forEach(f => predictions.add(f));
221
- } catch {
222
- // Invalid JSON, skip
223
- }
224
- }
225
-
226
- // 3. Files that import/are imported by current file
227
- const depStmt = this.db.prepare(`
228
- SELECT DISTINCT f2.path
229
- FROM files f1
230
- JOIN imports i ON i.file_id = f1.id
231
- JOIN files f2 ON f2.path LIKE '%' || REPLACE(i.imported_from, './', '') || '%'
232
- WHERE f1.path = ?
233
- LIMIT 5
234
- `);
235
- const deps = depStmt.all(currentFile) as { path: string }[];
236
- deps.forEach(r => predictions.add(r.path));
237
-
238
- return Array.from(predictions).slice(0, 10);
239
- }
240
-
241
- // Hot cache management
242
- private updateHotCache(filePath: string): void {
243
- const existing = this.hotCache.get(filePath);
244
-
245
- if (existing) {
246
- existing.accessCount++;
247
- existing.lastAccessed = Date.now();
248
- }
249
-
250
- // Evict old entries if cache is full
251
- if (this.hotCache.size >= this.MAX_CACHE_SIZE) {
252
- this.evictCacheEntries();
253
- }
254
- }
255
-
256
- private evictCacheEntries(): void {
257
- const now = Date.now();
258
- const entries = Array.from(this.hotCache.entries());
259
-
260
- // Remove expired entries
261
- for (const [key, value] of entries) {
262
- if (now - value.lastAccessed > this.CACHE_TTL_MS) {
263
- this.hotCache.delete(key);
264
- }
265
- }
266
-
267
- // If still too full, remove least accessed
268
- if (this.hotCache.size >= this.MAX_CACHE_SIZE) {
269
- entries
270
- .sort((a, b) => a[1].accessCount - b[1].accessCount)
271
- .slice(0, 10)
272
- .forEach(([key]) => this.hotCache.delete(key));
273
- }
274
- }
275
-
276
- addToHotCache(filePath: string, content: string): void {
277
- this.hotCache.set(filePath, {
278
- content,
279
- accessCount: 1,
280
- lastAccessed: Date.now()
281
- });
282
- this.updateHotCache(filePath);
283
- }
284
-
285
- getFromHotCache(filePath: string): string | null {
286
- const entry = this.hotCache.get(filePath);
287
- if (entry) {
288
- entry.accessCount++;
289
- entry.lastAccessed = Date.now();
290
- return entry.content;
291
- }
292
- return null;
293
- }
294
-
295
- isInHotCache(filePath: string): boolean {
296
- return this.hotCache.has(filePath);
297
- }
298
-
299
- getHotCacheStats(): { size: number; files: string[] } {
300
- return {
301
- size: this.hotCache.size,
302
- files: Array.from(this.hotCache.keys())
303
- };
304
- }
305
-
306
- // Query expansion for better retrieval
307
- expandQuery(query: string): string[] {
308
- const expansions: string[] = [query];
309
- const words = query.toLowerCase().split(/\s+/);
310
-
311
- // Add variations based on common patterns
312
- const synonyms: Record<string, string[]> = {
313
- 'auth': ['authentication', 'login', 'authorization'],
314
- 'db': ['database', 'sql', 'storage'],
315
- 'api': ['endpoint', 'route', 'handler'],
316
- 'ui': ['component', 'view', 'frontend'],
317
- 'error': ['exception', 'failure', 'bug'],
318
- 'test': ['spec', 'unit', 'integration'],
319
- 'config': ['configuration', 'settings', 'options'],
320
- 'user': ['account', 'profile', 'member'],
321
- 'fix': ['bug', 'issue', 'problem'],
322
- 'add': ['create', 'implement', 'new'],
323
- };
324
-
325
- // Expand synonyms
326
- for (const word of words) {
327
- const syns = synonyms[word];
328
- if (syns) {
329
- for (const syn of syns) {
330
- expansions.push(query.replace(new RegExp(word, 'gi'), syn));
331
- }
332
- }
333
- }
334
-
335
- // Add partial queries for broader search
336
- if (words.length > 2) {
337
- expansions.push(words.slice(0, 2).join(' '));
338
- expansions.push(words.slice(-2).join(' '));
339
- }
340
-
341
- return [...new Set(expansions)].slice(0, 5);
342
- }
343
-
344
- // Get usage statistics
345
- getUsageStats(): {
346
- totalQueries: number;
347
- totalFileViews: number;
348
- topFiles: Array<{ path: string; count: number }>;
349
- recentActivity: number;
350
- } {
351
- const queryCountStmt = this.db.prepare(`
352
- SELECT COUNT(*) as count FROM usage_events WHERE event_type = 'query'
353
- `);
354
- const totalQueries = (queryCountStmt.get() as { count: number }).count;
355
-
356
- const viewCountStmt = this.db.prepare(`
357
- SELECT COUNT(*) as count FROM usage_events WHERE event_type = 'file_view'
358
- `);
359
- const totalFileViews = (viewCountStmt.get() as { count: number }).count;
360
-
361
- const topFilesStmt = this.db.prepare(`
362
- SELECT f.path, fa.access_count as count
363
- FROM file_access fa
364
- JOIN files f ON fa.file_id = f.id
365
- ORDER BY fa.access_count DESC
366
- LIMIT 10
367
- `);
368
- const topFiles = topFilesStmt.all() as Array<{ path: string; count: number }>;
369
-
370
- const recentStmt = this.db.prepare(`
371
- SELECT COUNT(*) as count
372
- FROM usage_events
373
- WHERE timestamp > unixepoch() - 3600
374
- `);
375
- const recentActivity = (recentStmt.get() as { count: number }).count;
376
-
377
- return { totalQueries, totalFileViews, topFiles, recentActivity };
378
- }
379
-
380
- // Clean up old usage data
381
- cleanup(daysToKeep: number = 30): number {
382
- const stmt = this.db.prepare(`
383
- DELETE FROM usage_events
384
- WHERE timestamp < unixepoch() - ? * 86400
385
- `);
386
- const result = stmt.run(daysToKeep);
387
- return result.changes;
388
- }
389
-
390
- /**
391
- * Check if importance update should be skipped
392
- * Returns true if we've already updated within the last 5 minutes
393
- */
394
- shouldSkipImportanceUpdate(): boolean {
395
- const now = Date.now();
396
- return now - this.lastImportanceUpdate < this.IMPORTANCE_UPDATE_INTERVAL_MS;
397
- }
398
-
399
- /**
400
- * Get time since last importance update in milliseconds
401
- */
402
- getTimeSinceLastImportanceUpdate(): number {
403
- return Date.now() - this.lastImportanceUpdate;
404
- }
405
-
406
- /**
407
- * Get the last importance update timestamp
408
- */
409
- getLastImportanceUpdateTime(): number {
410
- return this.lastImportanceUpdate;
411
- }
412
-
413
- /**
414
- * Update importance scores for all files based on usage patterns
415
- * Called by background intelligence loop
416
- * Gated to run at most once per 5 minutes
417
- */
418
- updateImportanceScores(): void {
419
- // Gate: skip if we've already updated recently
420
- if (this.shouldSkipImportanceUpdate()) {
421
- return;
422
- }
423
-
424
- try {
425
- // Get all files with access stats
426
- const stmt = this.db.prepare(`
427
- SELECT f.id, f.path, fa.access_count, fa.last_accessed, fa.relevance_score
428
- FROM files f
429
- LEFT JOIN file_access fa ON fa.file_id = f.id
430
- `);
431
-
432
- const files = stmt.all() as Array<{
433
- id: number;
434
- path: string;
435
- access_count: number | null;
436
- last_accessed: number | null;
437
- relevance_score: number | null;
438
- }>;
439
-
440
- // Calculate and update importance scores
441
- const updateStmt = this.db.prepare(`
442
- INSERT INTO file_access (file_id, access_count, last_accessed, relevance_score)
443
- VALUES (?, COALESCE(?, 0), COALESCE(?, unixepoch()), ?)
444
- ON CONFLICT(file_id) DO UPDATE SET
445
- relevance_score = excluded.relevance_score
446
- `);
447
-
448
- for (const file of files) {
449
- const importance = this.calculateImportance(
450
- file.access_count || 0,
451
- file.last_accessed || Math.floor(Date.now() / 1000),
452
- file.path
453
- );
454
-
455
- updateStmt.run(
456
- file.id,
457
- file.access_count,
458
- file.last_accessed,
459
- importance
460
- );
461
- }
462
-
463
- // Update the timestamp to prevent running again within the interval
464
- this.lastImportanceUpdate = Date.now();
465
- } catch (error) {
466
- console.error('Error updating importance scores:', error);
467
- }
468
- }
469
-
470
- /**
471
- * Calculate importance score for a file based on multiple factors
472
- */
473
- calculateImportance(accessCount: number, lastAccessed: number, filePath: string): number {
474
- // Factor 1: Access frequency (log scale to avoid extreme boosts)
475
- const frequencyScore = Math.log10(1 + accessCount) * 0.4;
476
-
477
- // Factor 2: Recency (how recently was it accessed)
478
- const hoursSinceAccess = (Date.now() / 1000 - lastAccessed) / 3600;
479
- const recencyScore = Math.exp(-hoursSinceAccess / 168) * 0.3; // Decay over 1 week
480
-
481
- // Factor 3: File importance heuristics
482
- let fileImportance = 0.3; // Default
483
-
484
- // Boost important file types
485
- if (filePath.includes('index.') || filePath.includes('main.')) {
486
- fileImportance = 0.5;
487
- } else if (filePath.includes('.config.') || filePath.includes('config/')) {
488
- fileImportance = 0.45;
489
- } else if (filePath.includes('.test.') || filePath.includes('.spec.')) {
490
- fileImportance = 0.25; // Tests slightly less important for context
491
- } else if (filePath.includes('/types/') || filePath.includes('.d.ts')) {
492
- fileImportance = 0.4; // Type definitions are often helpful
493
- }
494
-
495
- // Combine factors (max 1.0)
496
- return Math.min(1.0, frequencyScore + recencyScore + fileImportance);
497
- }
498
-
499
- /**
500
- * Get importance score for a specific file
501
- */
502
- getFileImportance(filePath: string): number {
503
- const stmt = this.db.prepare(`
504
- SELECT fa.relevance_score
505
- FROM files f
506
- JOIN file_access fa ON fa.file_id = f.id
507
- WHERE f.path = ?
508
- `);
509
-
510
- const result = stmt.get(filePath) as { relevance_score: number } | undefined;
511
- return result?.relevance_score || 0.5;
512
- }
513
-
514
- private hashQuery(query: string): string {
515
- // Normalize query for matching
516
- const normalized = query.toLowerCase().trim().replace(/\s+/g, ' ');
517
- return createHash('md5').update(normalized).digest('hex').slice(0, 16);
518
- }
519
- }