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,267 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { CodeSymbol } from '../types/index.js';
|
|
3
|
+
import { estimateTokens } from '../utils/tokens.js';
|
|
4
|
+
|
|
5
|
+
interface FileSummary {
|
|
6
|
+
fileId: number;
|
|
7
|
+
summary: string;
|
|
8
|
+
tokens: number;
|
|
9
|
+
generatedAt: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class FileSummarizer {
|
|
13
|
+
private db: Database.Database;
|
|
14
|
+
|
|
15
|
+
constructor(db: Database.Database) {
|
|
16
|
+
this.db = db;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Generate a compressed summary of a file
|
|
20
|
+
generateSummary(
|
|
21
|
+
filePath: string,
|
|
22
|
+
content: string,
|
|
23
|
+
symbols: CodeSymbol[],
|
|
24
|
+
imports: Array<{ importedFrom: string; importedSymbols: string[] }>,
|
|
25
|
+
exports: Array<{ exportedName: string }>
|
|
26
|
+
): string {
|
|
27
|
+
const lines: string[] = [];
|
|
28
|
+
|
|
29
|
+
// File name and type
|
|
30
|
+
const fileName = filePath.split(/[/\\]/).pop() || filePath;
|
|
31
|
+
const extension = fileName.split('.').pop() || '';
|
|
32
|
+
lines.push(`**${fileName}** (${this.getFileType(extension)})`);
|
|
33
|
+
|
|
34
|
+
// Purpose (inferred from file name and content)
|
|
35
|
+
const purpose = this.inferPurpose(fileName, content, symbols);
|
|
36
|
+
if (purpose) {
|
|
37
|
+
lines.push(`Purpose: ${purpose}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Exports summary
|
|
41
|
+
if (exports.length > 0) {
|
|
42
|
+
const exportNames = exports.map(e => e.exportedName).slice(0, 10);
|
|
43
|
+
lines.push(`Exports: ${exportNames.join(', ')}${exports.length > 10 ? ` (+${exports.length - 10} more)` : ''}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Key symbols
|
|
47
|
+
const functions = symbols.filter(s => s.kind === 'function' && s.exported);
|
|
48
|
+
const classes = symbols.filter(s => s.kind === 'class');
|
|
49
|
+
const interfaces = symbols.filter(s => s.kind === 'interface');
|
|
50
|
+
|
|
51
|
+
if (classes.length > 0) {
|
|
52
|
+
lines.push(`Classes: ${classes.map(c => c.name).join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (interfaces.length > 0) {
|
|
56
|
+
lines.push(`Interfaces: ${interfaces.map(i => i.name).slice(0, 5).join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (functions.length > 0) {
|
|
60
|
+
const funcNames = functions.map(f => f.signature || f.name).slice(0, 5);
|
|
61
|
+
lines.push(`Functions: ${funcNames.join(', ')}${functions.length > 5 ? ` (+${functions.length - 5} more)` : ''}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Dependencies
|
|
65
|
+
if (imports.length > 0) {
|
|
66
|
+
const deps = imports
|
|
67
|
+
.filter(i => !i.importedFrom.startsWith('.'))
|
|
68
|
+
.map(i => i.importedFrom.split('/')[0])
|
|
69
|
+
.filter((v, i, a) => a.indexOf(v) === i)
|
|
70
|
+
.slice(0, 5);
|
|
71
|
+
|
|
72
|
+
if (deps.length > 0) {
|
|
73
|
+
lines.push(`Uses: ${deps.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Local imports (internal dependencies)
|
|
78
|
+
const localImports = imports
|
|
79
|
+
.filter(i => i.importedFrom.startsWith('.'))
|
|
80
|
+
.map(i => i.importedFrom.replace(/^\.\//, '').replace(/\.[^.]+$/, ''))
|
|
81
|
+
.slice(0, 5);
|
|
82
|
+
|
|
83
|
+
if (localImports.length > 0) {
|
|
84
|
+
lines.push(`Imports from: ${localImports.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Key patterns detected
|
|
88
|
+
const patterns = this.detectPatterns(content);
|
|
89
|
+
if (patterns.length > 0) {
|
|
90
|
+
lines.push(`Patterns: ${patterns.join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private getFileType(extension: string): string {
|
|
97
|
+
const types: Record<string, string> = {
|
|
98
|
+
'ts': 'TypeScript',
|
|
99
|
+
'tsx': 'React Component',
|
|
100
|
+
'js': 'JavaScript',
|
|
101
|
+
'jsx': 'React Component',
|
|
102
|
+
'py': 'Python',
|
|
103
|
+
'go': 'Go',
|
|
104
|
+
'rs': 'Rust',
|
|
105
|
+
'java': 'Java',
|
|
106
|
+
'rb': 'Ruby',
|
|
107
|
+
'vue': 'Vue Component',
|
|
108
|
+
'svelte': 'Svelte Component',
|
|
109
|
+
};
|
|
110
|
+
return types[extension] || extension.toUpperCase();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private inferPurpose(fileName: string, content: string, symbols: CodeSymbol[]): string {
|
|
114
|
+
const lowerName = fileName.toLowerCase();
|
|
115
|
+
const lowerContent = content.toLowerCase();
|
|
116
|
+
|
|
117
|
+
// Common file patterns
|
|
118
|
+
if (lowerName.includes('middleware')) return 'Request middleware/interceptor';
|
|
119
|
+
if (lowerName.includes('route') || lowerName.includes('router')) return 'API route definitions';
|
|
120
|
+
if (lowerName.includes('controller')) return 'Request handler/controller';
|
|
121
|
+
if (lowerName.includes('service')) return 'Business logic service';
|
|
122
|
+
if (lowerName.includes('model')) return 'Data model definitions';
|
|
123
|
+
if (lowerName.includes('schema')) return 'Schema/validation definitions';
|
|
124
|
+
if (lowerName.includes('util') || lowerName.includes('helper')) return 'Utility functions';
|
|
125
|
+
if (lowerName.includes('config')) return 'Configuration';
|
|
126
|
+
if (lowerName.includes('constant')) return 'Constants/enums';
|
|
127
|
+
if (lowerName.includes('type') || lowerName.includes('interface')) return 'Type definitions';
|
|
128
|
+
if (lowerName.includes('test') || lowerName.includes('spec')) return 'Tests';
|
|
129
|
+
if (lowerName.includes('hook')) return 'React hooks';
|
|
130
|
+
if (lowerName.includes('context')) return 'React context provider';
|
|
131
|
+
if (lowerName.includes('store')) return 'State management';
|
|
132
|
+
if (lowerName.includes('api')) return 'API client/definitions';
|
|
133
|
+
if (lowerName.includes('auth')) return 'Authentication';
|
|
134
|
+
if (lowerName.includes('db') || lowerName.includes('database')) return 'Database operations';
|
|
135
|
+
|
|
136
|
+
// Content-based inference
|
|
137
|
+
if (lowerContent.includes('express') && lowerContent.includes('router')) return 'Express router';
|
|
138
|
+
if (lowerContent.includes('mongoose') || lowerContent.includes('prisma')) return 'Database model';
|
|
139
|
+
if (lowerContent.includes('usestate') || lowerContent.includes('useeffect')) return 'React component';
|
|
140
|
+
if (lowerContent.includes('describe(') && lowerContent.includes('it(')) return 'Test suite';
|
|
141
|
+
|
|
142
|
+
// Symbol-based inference
|
|
143
|
+
const hasClasses = symbols.some(s => s.kind === 'class');
|
|
144
|
+
const hasInterfaces = symbols.some(s => s.kind === 'interface');
|
|
145
|
+
const hasFunctions = symbols.some(s => s.kind === 'function');
|
|
146
|
+
|
|
147
|
+
if (hasInterfaces && !hasClasses && !hasFunctions) return 'Type definitions';
|
|
148
|
+
if (hasClasses && symbols.filter(s => s.kind === 'class').length === 1) {
|
|
149
|
+
const className = symbols.find(s => s.kind === 'class')?.name;
|
|
150
|
+
return `${className} implementation`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return '';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private detectPatterns(content: string): string[] {
|
|
157
|
+
const patterns: string[] = [];
|
|
158
|
+
const lower = content.toLowerCase();
|
|
159
|
+
|
|
160
|
+
// Architectural patterns
|
|
161
|
+
if (lower.includes('singleton') || /private\s+static\s+instance/i.test(content)) {
|
|
162
|
+
patterns.push('Singleton');
|
|
163
|
+
}
|
|
164
|
+
if (lower.includes('factory') || /create\w+\s*\(/i.test(content)) {
|
|
165
|
+
patterns.push('Factory');
|
|
166
|
+
}
|
|
167
|
+
if (lower.includes('observer') || lower.includes('subscriber') || lower.includes('eventemitter')) {
|
|
168
|
+
patterns.push('Observer');
|
|
169
|
+
}
|
|
170
|
+
if (/async\s+\*|yield\s+/i.test(content)) {
|
|
171
|
+
patterns.push('Generator');
|
|
172
|
+
}
|
|
173
|
+
if (lower.includes('decorator') || /@\w+\s*\(/i.test(content)) {
|
|
174
|
+
patterns.push('Decorator');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Code patterns
|
|
178
|
+
if (lower.includes('try') && lower.includes('catch')) {
|
|
179
|
+
patterns.push('Error handling');
|
|
180
|
+
}
|
|
181
|
+
if (lower.includes('async') && lower.includes('await')) {
|
|
182
|
+
patterns.push('Async/await');
|
|
183
|
+
}
|
|
184
|
+
if (/\.then\s*\(/i.test(content)) {
|
|
185
|
+
patterns.push('Promise chain');
|
|
186
|
+
}
|
|
187
|
+
if (lower.includes('middleware')) {
|
|
188
|
+
patterns.push('Middleware');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return patterns.slice(0, 4);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Store summary in database
|
|
195
|
+
storeSummary(fileId: number, summary: string): void {
|
|
196
|
+
const tokens = estimateTokens(summary);
|
|
197
|
+
|
|
198
|
+
const stmt = this.db.prepare(`
|
|
199
|
+
INSERT INTO file_summaries (file_id, summary, summary_tokens, generated_at)
|
|
200
|
+
VALUES (?, ?, ?, unixepoch())
|
|
201
|
+
ON CONFLICT(file_id) DO UPDATE SET
|
|
202
|
+
summary = excluded.summary,
|
|
203
|
+
summary_tokens = excluded.summary_tokens,
|
|
204
|
+
generated_at = unixepoch()
|
|
205
|
+
`);
|
|
206
|
+
stmt.run(fileId, summary, tokens);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Get summary from database
|
|
210
|
+
getSummary(fileId: number): FileSummary | null {
|
|
211
|
+
const stmt = this.db.prepare(`
|
|
212
|
+
SELECT file_id as fileId, summary, summary_tokens as tokens, generated_at as generatedAt
|
|
213
|
+
FROM file_summaries
|
|
214
|
+
WHERE file_id = ?
|
|
215
|
+
`);
|
|
216
|
+
return stmt.get(fileId) as FileSummary | null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Get summaries for multiple files
|
|
220
|
+
getSummaries(fileIds: number[]): Map<number, string> {
|
|
221
|
+
const result = new Map<number, string>();
|
|
222
|
+
|
|
223
|
+
if (fileIds.length === 0) return result;
|
|
224
|
+
|
|
225
|
+
const placeholders = fileIds.map(() => '?').join(',');
|
|
226
|
+
const stmt = this.db.prepare(`
|
|
227
|
+
SELECT file_id, summary
|
|
228
|
+
FROM file_summaries
|
|
229
|
+
WHERE file_id IN (${placeholders})
|
|
230
|
+
`);
|
|
231
|
+
const rows = stmt.all(...fileIds) as Array<{ file_id: number; summary: string }>;
|
|
232
|
+
|
|
233
|
+
for (const row of rows) {
|
|
234
|
+
result.set(row.file_id, row.summary);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check if summary needs regeneration
|
|
241
|
+
needsRegeneration(fileId: number, fileLastModified: number): boolean {
|
|
242
|
+
const summary = this.getSummary(fileId);
|
|
243
|
+
if (!summary) return true;
|
|
244
|
+
|
|
245
|
+
// Regenerate if file was modified after summary was generated
|
|
246
|
+
return fileLastModified > summary.generatedAt;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Get compression ratio stats
|
|
250
|
+
getCompressionStats(): { totalFiles: number; avgCompression: number; totalTokensSaved: number } {
|
|
251
|
+
const stmt = this.db.prepare(`
|
|
252
|
+
SELECT
|
|
253
|
+
COUNT(*) as total_files,
|
|
254
|
+
AVG(f.size_bytes / 4.0 / NULLIF(fs.summary_tokens, 0)) as avg_compression,
|
|
255
|
+
SUM(f.size_bytes / 4.0 - fs.summary_tokens) as tokens_saved
|
|
256
|
+
FROM file_summaries fs
|
|
257
|
+
JOIN files f ON fs.file_id = f.id
|
|
258
|
+
`);
|
|
259
|
+
const result = stmt.get() as { total_files: number; avg_compression: number; tokens_saved: number };
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
totalFiles: result.total_files || 0,
|
|
263
|
+
avgCompression: result.avg_compression || 1,
|
|
264
|
+
totalTokensSaved: result.tokens_saved || 0
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|