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.
Files changed (78) hide show
  1. package/CONTRIBUTING.md +127 -0
  2. package/LICENSE +21 -0
  3. package/README.md +305 -0
  4. package/dist/index.js +38016 -0
  5. package/esbuild.config.js +26 -0
  6. package/package.json +63 -0
  7. package/src/cli/commands.ts +382 -0
  8. package/src/core/adr-exporter.ts +253 -0
  9. package/src/core/architecture/architecture-enforcement.ts +228 -0
  10. package/src/core/architecture/duplicate-detector.ts +288 -0
  11. package/src/core/architecture/index.ts +6 -0
  12. package/src/core/architecture/pattern-learner.ts +306 -0
  13. package/src/core/architecture/pattern-library.ts +403 -0
  14. package/src/core/architecture/pattern-validator.ts +324 -0
  15. package/src/core/change-intelligence/bug-correlator.ts +444 -0
  16. package/src/core/change-intelligence/change-intelligence.ts +221 -0
  17. package/src/core/change-intelligence/change-tracker.ts +334 -0
  18. package/src/core/change-intelligence/fix-suggester.ts +340 -0
  19. package/src/core/change-intelligence/index.ts +5 -0
  20. package/src/core/code-verifier.ts +843 -0
  21. package/src/core/confidence/confidence-scorer.ts +251 -0
  22. package/src/core/confidence/conflict-checker.ts +289 -0
  23. package/src/core/confidence/index.ts +5 -0
  24. package/src/core/confidence/source-tracker.ts +263 -0
  25. package/src/core/confidence/warning-detector.ts +241 -0
  26. package/src/core/context-rot/compaction.ts +284 -0
  27. package/src/core/context-rot/context-health.ts +243 -0
  28. package/src/core/context-rot/context-rot-prevention.ts +213 -0
  29. package/src/core/context-rot/critical-context.ts +221 -0
  30. package/src/core/context-rot/drift-detector.ts +255 -0
  31. package/src/core/context-rot/index.ts +7 -0
  32. package/src/core/context.ts +263 -0
  33. package/src/core/decision-extractor.ts +339 -0
  34. package/src/core/decisions.ts +69 -0
  35. package/src/core/deja-vu.ts +421 -0
  36. package/src/core/engine.ts +1455 -0
  37. package/src/core/feature-context.ts +726 -0
  38. package/src/core/ghost-mode.ts +412 -0
  39. package/src/core/learning.ts +485 -0
  40. package/src/core/living-docs/activity-tracker.ts +296 -0
  41. package/src/core/living-docs/architecture-generator.ts +428 -0
  42. package/src/core/living-docs/changelog-generator.ts +348 -0
  43. package/src/core/living-docs/component-generator.ts +230 -0
  44. package/src/core/living-docs/doc-engine.ts +110 -0
  45. package/src/core/living-docs/doc-validator.ts +282 -0
  46. package/src/core/living-docs/index.ts +8 -0
  47. package/src/core/project-manager.ts +297 -0
  48. package/src/core/summarizer.ts +267 -0
  49. package/src/core/test-awareness/change-validator.ts +499 -0
  50. package/src/core/test-awareness/index.ts +5 -0
  51. package/src/index.ts +49 -0
  52. package/src/indexing/ast.ts +563 -0
  53. package/src/indexing/embeddings.ts +85 -0
  54. package/src/indexing/indexer.ts +245 -0
  55. package/src/indexing/watcher.ts +78 -0
  56. package/src/server/gateways/aggregator.ts +374 -0
  57. package/src/server/gateways/index.ts +473 -0
  58. package/src/server/gateways/memory-ghost.ts +343 -0
  59. package/src/server/gateways/memory-query.ts +452 -0
  60. package/src/server/gateways/memory-record.ts +346 -0
  61. package/src/server/gateways/memory-review.ts +410 -0
  62. package/src/server/gateways/memory-status.ts +517 -0
  63. package/src/server/gateways/memory-verify.ts +392 -0
  64. package/src/server/gateways/router.ts +434 -0
  65. package/src/server/gateways/types.ts +610 -0
  66. package/src/server/mcp.ts +154 -0
  67. package/src/server/resources.ts +85 -0
  68. package/src/server/tools.ts +2261 -0
  69. package/src/storage/database.ts +262 -0
  70. package/src/storage/tier1.ts +135 -0
  71. package/src/storage/tier2.ts +764 -0
  72. package/src/storage/tier3.ts +123 -0
  73. package/src/types/documentation.ts +619 -0
  74. package/src/types/index.ts +222 -0
  75. package/src/utils/config.ts +193 -0
  76. package/src/utils/files.ts +117 -0
  77. package/src/utils/time.ts +37 -0
  78. package/src/utils/tokens.ts +52 -0
@@ -0,0 +1,253 @@
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import type { Decision } from '../types/index.js';
4
+
5
+ export interface ADRExportOptions {
6
+ outputDir?: string;
7
+ format?: 'madr' | 'nygard' | 'simple';
8
+ includeIndex?: boolean;
9
+ }
10
+
11
+ export class ADRExporter {
12
+ private projectPath: string;
13
+
14
+ constructor(projectPath: string) {
15
+ this.projectPath = projectPath;
16
+ }
17
+
18
+ // Export a single decision to ADR file
19
+ exportDecision(decision: Decision, options: ADRExportOptions = {}): string {
20
+ const outputDir = options.outputDir || join(this.projectPath, 'docs', 'decisions');
21
+ const format = options.format || 'madr';
22
+
23
+ // Ensure output directory exists
24
+ if (!existsSync(outputDir)) {
25
+ mkdirSync(outputDir, { recursive: true });
26
+ }
27
+
28
+ // Generate filename
29
+ const existingFiles = this.getExistingADRFiles(outputDir);
30
+ const nextNumber = this.getNextADRNumber(existingFiles);
31
+ const slug = this.slugify(decision.title);
32
+ const filename = `${String(nextNumber).padStart(4, '0')}-${slug}.md`;
33
+ const filePath = join(outputDir, filename);
34
+
35
+ // Generate content based on format
36
+ let content: string;
37
+ switch (format) {
38
+ case 'madr':
39
+ content = this.formatMADR(decision, nextNumber);
40
+ break;
41
+ case 'nygard':
42
+ content = this.formatNygard(decision, nextNumber);
43
+ break;
44
+ case 'simple':
45
+ default:
46
+ content = this.formatSimple(decision, nextNumber);
47
+ break;
48
+ }
49
+
50
+ // Write file
51
+ writeFileSync(filePath, content);
52
+
53
+ return filePath;
54
+ }
55
+
56
+ // Export all decisions
57
+ exportAllDecisions(decisions: Decision[], options: ADRExportOptions = {}): string[] {
58
+ const exportedFiles: string[] = [];
59
+ const outputDir = options.outputDir || join(this.projectPath, 'docs', 'decisions');
60
+
61
+ // Sort by creation date
62
+ const sorted = [...decisions].sort((a, b) =>
63
+ a.createdAt.getTime() - b.createdAt.getTime()
64
+ );
65
+
66
+ for (const decision of sorted) {
67
+ const filePath = this.exportDecision(decision, { ...options, outputDir });
68
+ exportedFiles.push(filePath);
69
+ }
70
+
71
+ // Generate index if requested
72
+ if (options.includeIndex !== false) {
73
+ const indexPath = this.generateIndex(decisions, outputDir);
74
+ exportedFiles.push(indexPath);
75
+ }
76
+
77
+ return exportedFiles;
78
+ }
79
+
80
+ // Generate index file
81
+ private generateIndex(decisions: Decision[], outputDir: string): string {
82
+ const indexPath = join(outputDir, 'README.md');
83
+
84
+ const lines: string[] = [
85
+ '# Architecture Decision Records',
86
+ '',
87
+ 'This directory contains Architecture Decision Records (ADRs) for this project.',
88
+ '',
89
+ '## Decisions',
90
+ '',
91
+ '| # | Title | Status | Date |',
92
+ '|---|-------|--------|------|'
93
+ ];
94
+
95
+ const sorted = [...decisions].sort((a, b) =>
96
+ a.createdAt.getTime() - b.createdAt.getTime()
97
+ );
98
+
99
+ sorted.forEach((d, i) => {
100
+ const num = String(i + 1).padStart(4, '0');
101
+ const slug = this.slugify(d.title);
102
+ const status = d.status || 'accepted';
103
+ const date = d.createdAt.toISOString().split('T')[0];
104
+ lines.push(`| ${num} | [${d.title}](./${num}-${slug}.md) | ${status} | ${date} |`);
105
+ });
106
+
107
+ lines.push('');
108
+ lines.push('---');
109
+ lines.push('');
110
+ lines.push('*Generated by MemoryLayer*');
111
+
112
+ writeFileSync(indexPath, lines.join('\n'));
113
+ return indexPath;
114
+ }
115
+
116
+ // MADR format (Markdown Any Decision Records)
117
+ private formatMADR(decision: Decision, number: number): string {
118
+ const date = decision.createdAt.toISOString().split('T')[0];
119
+ const status = decision.status || 'accepted';
120
+
121
+ return `# ${decision.title}
122
+
123
+ * Status: ${status}
124
+ * Date: ${date}
125
+ ${decision.author ? `* Author: ${decision.author}` : ''}
126
+ ${decision.tags.length > 0 ? `* Tags: ${decision.tags.join(', ')}` : ''}
127
+
128
+ ## Context and Problem Statement
129
+
130
+ ${decision.description}
131
+
132
+ ## Decision Drivers
133
+
134
+ * [List the main factors that influenced this decision]
135
+
136
+ ## Considered Options
137
+
138
+ * [Option 1]
139
+ * [Option 2]
140
+ * [Option 3]
141
+
142
+ ## Decision Outcome
143
+
144
+ Chosen option: "${decision.title}"
145
+
146
+ ### Consequences
147
+
148
+ * Good, because [positive consequence]
149
+ * Bad, because [negative consequence]
150
+
151
+ ${decision.files.length > 0 ? `## Related Files
152
+
153
+ ${decision.files.map(f => `* \`${f}\``).join('\n')}
154
+ ` : ''}
155
+ ${decision.supersededBy ? `## Superseded By
156
+
157
+ This decision has been superseded by [ADR ${decision.supersededBy}](./${decision.supersededBy}.md).
158
+ ` : ''}
159
+ ---
160
+ *Exported from MemoryLayer*
161
+ `;
162
+ }
163
+
164
+ // Nygard format (original ADR format)
165
+ private formatNygard(decision: Decision, number: number): string {
166
+ const date = decision.createdAt.toISOString().split('T')[0];
167
+ const status = decision.status || 'Accepted';
168
+
169
+ return `# ${number}. ${decision.title}
170
+
171
+ Date: ${date}
172
+
173
+ ## Status
174
+
175
+ ${status.charAt(0).toUpperCase() + status.slice(1)}
176
+ ${decision.supersededBy ? `\nSuperseded by [ADR ${decision.supersededBy}](./${decision.supersededBy}.md)` : ''}
177
+
178
+ ## Context
179
+
180
+ ${decision.description}
181
+
182
+ ## Decision
183
+
184
+ We will ${decision.title.toLowerCase()}.
185
+
186
+ ## Consequences
187
+
188
+ [Describe the consequences of this decision]
189
+
190
+ ${decision.files.length > 0 ? `## Related Files
191
+
192
+ ${decision.files.map(f => `- ${f}`).join('\n')}
193
+ ` : ''}
194
+ ---
195
+ *Exported from MemoryLayer*
196
+ `;
197
+ }
198
+
199
+ // Simple format
200
+ private formatSimple(decision: Decision, number: number): string {
201
+ const date = decision.createdAt.toISOString().split('T')[0];
202
+
203
+ return `# ${decision.title}
204
+
205
+ **Date:** ${date}
206
+ ${decision.author ? `**Author:** ${decision.author}` : ''}
207
+ **Status:** ${decision.status || 'accepted'}
208
+ ${decision.tags.length > 0 ? `**Tags:** ${decision.tags.join(', ')}` : ''}
209
+
210
+ ## Description
211
+
212
+ ${decision.description}
213
+
214
+ ${decision.files.length > 0 ? `## Related Files
215
+
216
+ ${decision.files.map(f => `- \`${f}\``).join('\n')}
217
+ ` : ''}
218
+ ---
219
+ *Exported from MemoryLayer*
220
+ `;
221
+ }
222
+
223
+ private getExistingADRFiles(dir: string): string[] {
224
+ if (!existsSync(dir)) return [];
225
+
226
+ try {
227
+ const { readdirSync } = require('fs');
228
+ return readdirSync(dir)
229
+ .filter((f: string) => /^\d{4}-.*\.md$/.test(f))
230
+ .sort();
231
+ } catch {
232
+ return [];
233
+ }
234
+ }
235
+
236
+ private getNextADRNumber(existingFiles: string[]): number {
237
+ if (existingFiles.length === 0) return 1;
238
+
239
+ const numbers = existingFiles
240
+ .map(f => parseInt(f.split('-')[0] || '0', 10))
241
+ .filter(n => !isNaN(n));
242
+
243
+ return Math.max(...numbers, 0) + 1;
244
+ }
245
+
246
+ private slugify(text: string): string {
247
+ return text
248
+ .toLowerCase()
249
+ .replace(/[^a-z0-9]+/g, '-')
250
+ .replace(/^-|-$/g, '')
251
+ .slice(0, 50);
252
+ }
253
+ }
@@ -0,0 +1,228 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { Tier2Storage } from '../../storage/tier2.js';
3
+ import type { EmbeddingGenerator } from '../../indexing/embeddings.js';
4
+ import type {
5
+ Pattern,
6
+ PatternCategory,
7
+ PatternValidationResult,
8
+ ExistingFunction,
9
+ CodeExample,
10
+ PatternRule
11
+ } from '../../types/documentation.js';
12
+ import { PatternLibrary } from './pattern-library.js';
13
+ import { PatternLearner } from './pattern-learner.js';
14
+ import { PatternValidator } from './pattern-validator.js';
15
+ import { DuplicateDetector } from './duplicate-detector.js';
16
+
17
+ export class ArchitectureEnforcement {
18
+ private patternLibrary: PatternLibrary;
19
+ private patternLearner: PatternLearner;
20
+ private patternValidator: PatternValidator;
21
+ private duplicateDetector: DuplicateDetector;
22
+ private initialized = false;
23
+
24
+ constructor(
25
+ db: Database.Database,
26
+ tier2: Tier2Storage,
27
+ embeddingGenerator: EmbeddingGenerator
28
+ ) {
29
+ this.patternLibrary = new PatternLibrary(db);
30
+ this.duplicateDetector = new DuplicateDetector(tier2, embeddingGenerator);
31
+ this.patternLearner = new PatternLearner(tier2, this.patternLibrary);
32
+ this.patternValidator = new PatternValidator(this.patternLibrary, this.duplicateDetector, tier2);
33
+ }
34
+
35
+ // Initialize by learning patterns from codebase
36
+ initialize(): { patternsLearned: number; examplesAdded: number } {
37
+ if (this.initialized) return { patternsLearned: 0, examplesAdded: 0 };
38
+
39
+ const result = this.patternLearner.learnFromCodebase();
40
+ this.initialized = true;
41
+
42
+ return {
43
+ patternsLearned: result.patternsLearned,
44
+ examplesAdded: result.examplesAdded
45
+ };
46
+ }
47
+
48
+ // Validate code against patterns
49
+ validatePattern(code: string, type?: PatternCategory | 'auto'): PatternValidationResult {
50
+ return this.patternValidator.validate(code, type);
51
+ }
52
+
53
+ // Suggest existing functions for an intent
54
+ suggestExisting(intent: string, limit?: number): ExistingFunction[] {
55
+ return this.duplicateDetector.suggestExisting(intent, limit);
56
+ }
57
+
58
+ // Learn a new pattern
59
+ learnPattern(
60
+ code: string,
61
+ name: string,
62
+ description?: string,
63
+ category?: PatternCategory
64
+ ): { success: boolean; patternId?: string; message: string } {
65
+ return this.patternLearner.learnPattern(code, name, description, category);
66
+ }
67
+
68
+ // List all patterns
69
+ listPatterns(category?: PatternCategory): Pattern[] {
70
+ if (category) {
71
+ return this.patternLibrary.getPatternsByCategory(category);
72
+ }
73
+ return this.patternLibrary.getAllPatterns();
74
+ }
75
+
76
+ // Get a specific pattern
77
+ getPattern(id: string): Pattern | null {
78
+ return this.patternLibrary.getPattern(id);
79
+ }
80
+
81
+ // Add example to existing pattern
82
+ addExample(patternId: string, code: string, explanation: string, isAntiPattern: boolean = false): boolean {
83
+ const example: CodeExample = { code, explanation };
84
+ return this.patternLibrary.addExample(patternId, example, isAntiPattern);
85
+ }
86
+
87
+ // Add rule to existing pattern
88
+ addRule(patternId: string, rule: string, severity: 'info' | 'warning' | 'critical'): boolean {
89
+ const patternRule: PatternRule = { rule, severity };
90
+ return this.patternLibrary.addRule(patternId, patternRule);
91
+ }
92
+
93
+ // Search patterns
94
+ searchPatterns(query: string): Pattern[] {
95
+ return this.patternLibrary.searchPatterns(query);
96
+ }
97
+
98
+ // Delete a pattern
99
+ deletePattern(id: string): boolean {
100
+ return this.patternLibrary.deletePattern(id);
101
+ }
102
+
103
+ // Refresh duplicate detector index
104
+ refreshIndex(): void {
105
+ this.duplicateDetector.refresh();
106
+ }
107
+
108
+ // Get statistics
109
+ getStats(): {
110
+ patterns: {
111
+ total: number;
112
+ byCategory: Record<string, number>;
113
+ topPatterns: Array<{ name: string; usageCount: number }>;
114
+ };
115
+ functions: {
116
+ total: number;
117
+ exported: number;
118
+ byPurpose: Record<string, number>;
119
+ };
120
+ } {
121
+ return {
122
+ patterns: this.patternLearner.getStats(),
123
+ functions: this.duplicateDetector.getStats()
124
+ };
125
+ }
126
+
127
+ // Format validation result for display
128
+ static formatValidationResult(result: PatternValidationResult): string {
129
+ const lines: string[] = [];
130
+
131
+ const scoreIcon = result.score >= 80 ? '\u{1F7E2}' :
132
+ result.score >= 50 ? '\u{1F7E1}' : '\u{1F534}';
133
+
134
+ lines.push(`\u{1F50D} Pattern Validation\n`);
135
+ lines.push(`${scoreIcon} Score: ${result.score}/100`);
136
+
137
+ if (result.matchedPattern) {
138
+ lines.push(`Matched Pattern: ${result.matchedPattern}`);
139
+ }
140
+
141
+ if (result.violations.length > 0) {
142
+ lines.push('\n\u274C Violations:');
143
+ for (const v of result.violations) {
144
+ const icon = v.severity === 'critical' ? '\u{1F534}' :
145
+ v.severity === 'warning' ? '\u{1F7E1}' : '\u{2139}\u{FE0F}';
146
+ lines.push(` ${icon} ${v.message}`);
147
+ if (v.suggestion) {
148
+ lines.push(` \u2192 ${v.suggestion}`);
149
+ }
150
+ }
151
+ }
152
+
153
+ if (result.existingAlternatives.length > 0) {
154
+ lines.push('\n\u{1F4A1} Existing Alternatives:');
155
+ for (const alt of result.existingAlternatives) {
156
+ lines.push(` - ${alt.name}() in ${alt.file} (${alt.similarity}% similar)`);
157
+ }
158
+ }
159
+
160
+ if (result.suggestions.length > 0) {
161
+ lines.push('\n\u{1F4DD} Suggestions:');
162
+ for (const s of result.suggestions) {
163
+ const icon = s.priority === 'high' ? '\u{1F534}' :
164
+ s.priority === 'medium' ? '\u{1F7E1}' : '\u{1F7E2}';
165
+ lines.push(` ${icon} ${s.description}`);
166
+ if (s.code) {
167
+ lines.push(` ${s.code}`);
168
+ }
169
+ }
170
+ }
171
+
172
+ return lines.join('\n');
173
+ }
174
+
175
+ // Format pattern list for display
176
+ static formatPatternList(patterns: Pattern[]): string {
177
+ if (patterns.length === 0) {
178
+ return 'No patterns found.';
179
+ }
180
+
181
+ const lines: string[] = [];
182
+ lines.push(`\u{1F4DA} Patterns (${patterns.length})\n`);
183
+
184
+ const byCategory: Record<string, Pattern[]> = {};
185
+ for (const p of patterns) {
186
+ if (!byCategory[p.category]) {
187
+ byCategory[p.category] = [];
188
+ }
189
+ byCategory[p.category].push(p);
190
+ }
191
+
192
+ for (const [category, categoryPatterns] of Object.entries(byCategory)) {
193
+ lines.push(`\n${category.toUpperCase()}:`);
194
+ for (const p of categoryPatterns) {
195
+ lines.push(` \u251C\u2500 ${p.name} (${p.usageCount} uses)`);
196
+ if (p.description) {
197
+ lines.push(` \u2502 ${p.description.slice(0, 50)}...`);
198
+ }
199
+ lines.push(` \u2502 Rules: ${p.rules.length}, Examples: ${p.examples.length}`);
200
+ }
201
+ }
202
+
203
+ return lines.join('\n');
204
+ }
205
+
206
+ // Format existing function suggestions for display
207
+ static formatSuggestions(suggestions: ExistingFunction[]): string {
208
+ if (suggestions.length === 0) {
209
+ return 'No existing functions found for this intent.';
210
+ }
211
+
212
+ const lines: string[] = [];
213
+ lines.push(`\u{1F4A1} Existing Functions\n`);
214
+
215
+ for (const s of suggestions) {
216
+ lines.push(`\u251C\u2500 ${s.name}()`);
217
+ lines.push(`\u2502 File: ${s.file}:${s.line}`);
218
+ lines.push(`\u2502 Signature: ${s.signature}`);
219
+ lines.push(`\u2502 Used: ${s.usageCount} times, Relevance: ${s.similarity}%`);
220
+ if (s.description) {
221
+ lines.push(`\u2502 ${s.description}`);
222
+ }
223
+ lines.push('');
224
+ }
225
+
226
+ return lines.join('\n');
227
+ }
228
+ }