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,412 @@
1
+ /**
2
+ * Ghost Mode - Silent Intelligence Layer
3
+ *
4
+ * Silently tracks what files Claude reads/writes. When code is written that
5
+ * touches a file with recorded decisions, automatically checks for conflicts.
6
+ * Makes MemoryLayer feel "telepathic" by surfacing relevant context proactively.
7
+ */
8
+
9
+ import type { Decision } from '../types/index.js';
10
+ import type { Tier2Storage } from '../storage/tier2.js';
11
+ import type { EmbeddingGenerator } from '../indexing/embeddings.js';
12
+
13
+ export interface ConflictWarning {
14
+ decision: Decision;
15
+ warning: string;
16
+ severity: 'low' | 'medium' | 'high';
17
+ matchedTerms: string[];
18
+ }
19
+
20
+ export interface FileContext {
21
+ path: string;
22
+ accessedAt: Date;
23
+ relatedDecisions: Decision[];
24
+ relatedPatterns: string[];
25
+ accessCount: number;
26
+ }
27
+
28
+ export interface GhostInsight {
29
+ activeFiles: string[];
30
+ recentDecisions: Decision[];
31
+ potentialConflicts: ConflictWarning[];
32
+ suggestions: string[];
33
+ }
34
+
35
+ // Decision keywords that indicate strong stances
36
+ const DECISION_INDICATORS = [
37
+ 'use', 'always', 'never', 'prefer', 'avoid', 'must', 'should',
38
+ 'instead of', 'rather than', 'not', 'don\'t', 'do not',
39
+ ];
40
+
41
+ // Technology/pattern keywords for matching
42
+ const TECH_PATTERNS = [
43
+ // Auth
44
+ { pattern: /\b(jwt|json\s*web\s*token)\b/i, category: 'auth', term: 'JWT' },
45
+ { pattern: /\b(session|cookie)\b/i, category: 'auth', term: 'session' },
46
+ { pattern: /\b(oauth|o-?auth)\b/i, category: 'auth', term: 'OAuth' },
47
+ // Database
48
+ { pattern: /\b(sql|mysql|postgres|postgresql)\b/i, category: 'database', term: 'SQL' },
49
+ { pattern: /\b(mongo|mongodb|nosql)\b/i, category: 'database', term: 'MongoDB' },
50
+ { pattern: /\b(redis|memcache)\b/i, category: 'cache', term: 'Redis' },
51
+ // State
52
+ { pattern: /\b(redux|zustand|mobx)\b/i, category: 'state', term: 'state-management' },
53
+ { pattern: /\b(context\s*api|useContext)\b/i, category: 'state', term: 'Context API' },
54
+ // Testing
55
+ { pattern: /\b(jest|vitest|mocha)\b/i, category: 'testing', term: 'testing-framework' },
56
+ { pattern: /\b(enzyme|testing-library|rtl)\b/i, category: 'testing', term: 'testing-library' },
57
+ // API
58
+ { pattern: /\b(rest|restful)\b/i, category: 'api', term: 'REST' },
59
+ { pattern: /\b(graphql|gql)\b/i, category: 'api', term: 'GraphQL' },
60
+ { pattern: /\b(grpc|protobuf)\b/i, category: 'api', term: 'gRPC' },
61
+ // Style
62
+ { pattern: /\b(tailwind|tailwindcss)\b/i, category: 'styling', term: 'Tailwind' },
63
+ { pattern: /\b(styled-components|emotion)\b/i, category: 'styling', term: 'CSS-in-JS' },
64
+ { pattern: /\b(sass|scss|less)\b/i, category: 'styling', term: 'CSS preprocessor' },
65
+ ];
66
+
67
+ export class GhostMode {
68
+ private activeFiles: Map<string, FileContext> = new Map();
69
+ private recentDecisions: Map<string, Decision[]> = new Map();
70
+ private tier2: Tier2Storage;
71
+ private embeddingGenerator: EmbeddingGenerator;
72
+
73
+ // Ghost mode settings
74
+ private readonly MAX_ACTIVE_FILES = 20;
75
+ private readonly FILE_TTL_MS = 60 * 60 * 1000; // 1 hour
76
+ private readonly DECISION_CACHE_SIZE = 50;
77
+
78
+ constructor(tier2: Tier2Storage, embeddingGenerator: EmbeddingGenerator) {
79
+ this.tier2 = tier2;
80
+ this.embeddingGenerator = embeddingGenerator;
81
+ }
82
+
83
+ /**
84
+ * Called when any file is read - silently track and pre-fetch decisions
85
+ */
86
+ async onFileAccess(filePath: string): Promise<void> {
87
+ const now = new Date();
88
+
89
+ // Update or create file context
90
+ const existing = this.activeFiles.get(filePath);
91
+ if (existing) {
92
+ existing.accessedAt = now;
93
+ existing.accessCount++;
94
+ } else {
95
+ // Pre-fetch related decisions for this file
96
+ const relatedDecisions = await this.findRelatedDecisions(filePath);
97
+ const relatedPatterns = await this.findRelatedPatterns(filePath);
98
+
99
+ this.activeFiles.set(filePath, {
100
+ path: filePath,
101
+ accessedAt: now,
102
+ relatedDecisions,
103
+ relatedPatterns,
104
+ accessCount: 1,
105
+ });
106
+
107
+ // Cache decisions for quick conflict checking
108
+ this.recentDecisions.set(filePath, relatedDecisions);
109
+ }
110
+
111
+ // Evict stale entries
112
+ this.evictStaleFiles();
113
+ }
114
+
115
+ /**
116
+ * Called before code is written - returns potential conflicts
117
+ */
118
+ checkConflicts(code: string, targetFile?: string): ConflictWarning[] {
119
+ const warnings: ConflictWarning[] = [];
120
+ const codeTerms = this.extractTerms(code);
121
+
122
+ // Get decisions to check against
123
+ let decisionsToCheck: Decision[] = [];
124
+
125
+ if (targetFile && this.recentDecisions.has(targetFile)) {
126
+ decisionsToCheck = this.recentDecisions.get(targetFile) || [];
127
+ } else {
128
+ // Check against all cached decisions
129
+ for (const decisions of this.recentDecisions.values()) {
130
+ decisionsToCheck.push(...decisions);
131
+ }
132
+ // Deduplicate
133
+ decisionsToCheck = this.deduplicateDecisions(decisionsToCheck);
134
+ }
135
+
136
+ // Check each decision for conflicts
137
+ for (const decision of decisionsToCheck) {
138
+ const conflict = this.detectConflict(code, codeTerms, decision);
139
+ if (conflict) {
140
+ warnings.push(conflict);
141
+ }
142
+ }
143
+
144
+ // Sort by severity
145
+ warnings.sort((a, b) => {
146
+ const severityOrder = { high: 0, medium: 1, low: 2 };
147
+ return severityOrder[a.severity] - severityOrder[b.severity];
148
+ });
149
+
150
+ return warnings;
151
+ }
152
+
153
+ /**
154
+ * Get current ghost insight - what the system knows about current work
155
+ */
156
+ getInsight(): GhostInsight {
157
+ const activeFiles = Array.from(this.activeFiles.keys());
158
+ const recentDecisions = this.getRecentUniqueDecisions();
159
+ const suggestions = this.generateSuggestions();
160
+
161
+ return {
162
+ activeFiles,
163
+ recentDecisions,
164
+ potentialConflicts: [],
165
+ suggestions,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Get ghost insight with conflict check for specific code
171
+ */
172
+ getInsightForCode(code: string, targetFile?: string): GhostInsight {
173
+ const insight = this.getInsight();
174
+ insight.potentialConflicts = this.checkConflicts(code, targetFile);
175
+ return insight;
176
+ }
177
+
178
+ /**
179
+ * Clear ghost mode state
180
+ */
181
+ clear(): void {
182
+ this.activeFiles.clear();
183
+ this.recentDecisions.clear();
184
+ }
185
+
186
+ /**
187
+ * Get files most recently accessed
188
+ */
189
+ getRecentFiles(limit: number = 10): string[] {
190
+ return Array.from(this.activeFiles.entries())
191
+ .sort((a, b) => b[1].accessedAt.getTime() - a[1].accessedAt.getTime())
192
+ .slice(0, limit)
193
+ .map(([path]) => path);
194
+ }
195
+
196
+ /**
197
+ * Get decisions related to recently accessed files
198
+ */
199
+ getRecentUniqueDecisions(limit: number = 10): Decision[] {
200
+ const allDecisions: Decision[] = [];
201
+ for (const decisions of this.recentDecisions.values()) {
202
+ allDecisions.push(...decisions);
203
+ }
204
+ return this.deduplicateDecisions(allDecisions).slice(0, limit);
205
+ }
206
+
207
+ // ========== Private Methods ==========
208
+
209
+ private async findRelatedDecisions(filePath: string): Promise<Decision[]> {
210
+ try {
211
+ // Search decisions by file path
212
+ const pathParts = filePath.split(/[/\\]/);
213
+ const fileName = pathParts[pathParts.length - 1] || '';
214
+ const dirName = pathParts[pathParts.length - 2] || '';
215
+
216
+ // Create a search query from file context
217
+ const searchQuery = `${dirName} ${fileName.replace(/\.[^.]+$/, '')}`;
218
+ const embedding = await this.embeddingGenerator.embed(searchQuery);
219
+
220
+ return this.tier2.searchDecisions(embedding, 5);
221
+ } catch {
222
+ return [];
223
+ }
224
+ }
225
+
226
+ private findRelatedPatterns(filePath: string): string[] {
227
+ // Extract patterns from file extension and path
228
+ const patterns: string[] = [];
229
+
230
+ if (filePath.includes('test') || filePath.includes('spec')) {
231
+ patterns.push('testing');
232
+ }
233
+ if (filePath.includes('api') || filePath.includes('route')) {
234
+ patterns.push('api');
235
+ }
236
+ if (filePath.includes('component') || filePath.includes('ui')) {
237
+ patterns.push('ui');
238
+ }
239
+ if (filePath.includes('model') || filePath.includes('schema')) {
240
+ patterns.push('data-model');
241
+ }
242
+ if (filePath.includes('auth')) {
243
+ patterns.push('authentication');
244
+ }
245
+
246
+ return patterns;
247
+ }
248
+
249
+ private extractTerms(text: string): Set<string> {
250
+ const terms = new Set<string>();
251
+
252
+ // Extract technology/pattern terms
253
+ for (const { pattern, term } of TECH_PATTERNS) {
254
+ if (pattern.test(text)) {
255
+ terms.add(term.toLowerCase());
256
+ }
257
+ }
258
+
259
+ // Extract common programming terms
260
+ const words = text.toLowerCase().match(/\b[a-z]+\b/g) || [];
261
+ for (const word of words) {
262
+ if (word.length > 3) {
263
+ terms.add(word);
264
+ }
265
+ }
266
+
267
+ return terms;
268
+ }
269
+
270
+ private detectConflict(
271
+ code: string,
272
+ codeTerms: Set<string>,
273
+ decision: Decision
274
+ ): ConflictWarning | null {
275
+ const decisionText = `${decision.title} ${decision.description}`.toLowerCase();
276
+ const decisionTerms = this.extractTerms(decisionText);
277
+
278
+ // Check for technology conflicts
279
+ const matchedTerms: string[] = [];
280
+ for (const term of codeTerms) {
281
+ if (decisionTerms.has(term)) {
282
+ matchedTerms.push(term);
283
+ }
284
+ }
285
+
286
+ if (matchedTerms.length === 0) {
287
+ return null;
288
+ }
289
+
290
+ // Check if decision opposes this technology
291
+ const negativePattern = new RegExp(
292
+ `(don't|do not|never|avoid|not)\\s+.{0,30}\\b(${matchedTerms.join('|')})\\b`,
293
+ 'i'
294
+ );
295
+ const preferOtherPattern = new RegExp(
296
+ `(instead of|rather than)\\s+.{0,30}\\b(${matchedTerms.join('|')})\\b`,
297
+ 'i'
298
+ );
299
+ const isNegative = negativePattern.test(decisionText) || preferOtherPattern.test(decisionText);
300
+
301
+ if (!isNegative) {
302
+ // Check if decision uses a different technology in the same category
303
+ for (const { pattern, category, term } of TECH_PATTERNS) {
304
+ if (matchedTerms.some(m => m.toLowerCase() === term.toLowerCase())) {
305
+ continue; // Skip the term we're using
306
+ }
307
+
308
+ if (pattern.test(decisionText)) {
309
+ // Decision mentions a different tech in same category
310
+ const codeUsesCategory = matchedTerms.some(m => {
311
+ const match = TECH_PATTERNS.find(p => p.term.toLowerCase() === m.toLowerCase());
312
+ return match && match.category === category;
313
+ });
314
+
315
+ if (codeUsesCategory) {
316
+ return {
317
+ decision,
318
+ warning: `This code uses ${matchedTerms.join(', ')} but decision "${decision.title}" suggests using ${term}`,
319
+ severity: 'medium',
320
+ matchedTerms,
321
+ };
322
+ }
323
+ }
324
+ }
325
+
326
+ return null;
327
+ }
328
+
329
+ // Determine severity
330
+ let severity: 'low' | 'medium' | 'high' = 'low';
331
+ if (decisionText.includes('must') || decisionText.includes('never') || decisionText.includes('always')) {
332
+ severity = 'high';
333
+ } else if (decisionText.includes('should') || decisionText.includes('prefer')) {
334
+ severity = 'medium';
335
+ }
336
+
337
+ return {
338
+ decision,
339
+ warning: `This code may conflict with decision: "${decision.title}"`,
340
+ severity,
341
+ matchedTerms,
342
+ };
343
+ }
344
+
345
+ private generateSuggestions(): string[] {
346
+ const suggestions: string[] = [];
347
+
348
+ // Suggest based on active files
349
+ const recentFiles = this.getRecentFiles(5);
350
+ if (recentFiles.length > 0) {
351
+ const categories = new Set<string>();
352
+ for (const file of recentFiles) {
353
+ const ctx = this.activeFiles.get(file);
354
+ if (ctx) {
355
+ ctx.relatedPatterns.forEach(p => categories.add(p));
356
+ }
357
+ }
358
+
359
+ if (categories.size > 0) {
360
+ suggestions.push(`Working on: ${Array.from(categories).join(', ')}`);
361
+ }
362
+ }
363
+
364
+ // Suggest based on decisions
365
+ const decisions = this.getRecentUniqueDecisions(3);
366
+ if (decisions.length > 0) {
367
+ suggestions.push(`Relevant decisions: ${decisions.map(d => d.title).join(', ')}`);
368
+ }
369
+
370
+ return suggestions;
371
+ }
372
+
373
+ private deduplicateDecisions(decisions: Decision[]): Decision[] {
374
+ const seen = new Set<string>();
375
+ return decisions.filter(d => {
376
+ if (seen.has(d.id)) {
377
+ return false;
378
+ }
379
+ seen.add(d.id);
380
+ return true;
381
+ });
382
+ }
383
+
384
+ private evictStaleFiles(): void {
385
+ const now = Date.now();
386
+ const toEvict: string[] = [];
387
+
388
+ for (const [path, context] of this.activeFiles.entries()) {
389
+ if (now - context.accessedAt.getTime() > this.FILE_TTL_MS) {
390
+ toEvict.push(path);
391
+ }
392
+ }
393
+
394
+ // Evict stale files
395
+ for (const path of toEvict) {
396
+ this.activeFiles.delete(path);
397
+ this.recentDecisions.delete(path);
398
+ }
399
+
400
+ // If still too many, evict oldest
401
+ if (this.activeFiles.size > this.MAX_ACTIVE_FILES) {
402
+ const entries = Array.from(this.activeFiles.entries())
403
+ .sort((a, b) => a[1].accessedAt.getTime() - b[1].accessedAt.getTime());
404
+
405
+ const toRemove = entries.slice(0, entries.length - this.MAX_ACTIVE_FILES);
406
+ for (const [path] of toRemove) {
407
+ this.activeFiles.delete(path);
408
+ this.recentDecisions.delete(path);
409
+ }
410
+ }
411
+ }
412
+ }