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,213 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type {
3
+ ContextHealth,
4
+ CompactionResult,
5
+ CompactionOptions,
6
+ CompactionSuggestion,
7
+ CriticalContext,
8
+ DriftResult
9
+ } from '../../types/documentation.js';
10
+ import { ContextHealthMonitor } from './context-health.js';
11
+ import { DriftDetector } from './drift-detector.js';
12
+ import { CompactionEngine } from './compaction.js';
13
+ import { CriticalContextManager } from './critical-context.js';
14
+
15
+ interface Message {
16
+ role: 'user' | 'assistant' | 'system';
17
+ content: string;
18
+ timestamp?: Date;
19
+ }
20
+
21
+ export class ContextRotPrevention {
22
+ private healthMonitor: ContextHealthMonitor;
23
+ private driftDetector: DriftDetector;
24
+ private compactionEngine: CompactionEngine;
25
+ private criticalManager: CriticalContextManager;
26
+ private db: Database.Database;
27
+
28
+ constructor(db: Database.Database, tokenLimit?: number) {
29
+ this.db = db;
30
+ this.criticalManager = new CriticalContextManager(db);
31
+ this.healthMonitor = new ContextHealthMonitor(db, this.criticalManager, tokenLimit);
32
+ this.driftDetector = new DriftDetector(this.criticalManager);
33
+ this.compactionEngine = new CompactionEngine(this.healthMonitor, this.criticalManager);
34
+ }
35
+
36
+ // ========== Context Health ==========
37
+
38
+ getContextHealth(): ContextHealth {
39
+ const driftResult = this.driftDetector.detectDrift();
40
+ return this.healthMonitor.getHealth(driftResult.driftScore);
41
+ }
42
+
43
+ setTokenLimit(limit: number): void {
44
+ this.healthMonitor.setTokenLimit(limit);
45
+ }
46
+
47
+ setCurrentTokens(tokens: number): void {
48
+ this.healthMonitor.setCurrentTokens(tokens);
49
+ }
50
+
51
+ addContextChunk(content: string, tokens: number, type: 'message' | 'decision' | 'requirement' | 'instruction' | 'code' = 'message'): void {
52
+ this.healthMonitor.addChunk({
53
+ content,
54
+ tokens,
55
+ timestamp: new Date(),
56
+ type
57
+ });
58
+ }
59
+
60
+ // ========== Message Tracking ==========
61
+
62
+ addMessage(message: Message): void {
63
+ this.driftDetector.addMessage(message);
64
+
65
+ // Also add as context chunk
66
+ const tokens = this.healthMonitor.estimateTokens(message.content);
67
+ this.addContextChunk(message.content, tokens, 'message');
68
+ }
69
+
70
+ clearConversation(): void {
71
+ this.driftDetector.clearHistory();
72
+ this.healthMonitor.clearChunks();
73
+ }
74
+
75
+ // ========== Drift Detection ==========
76
+
77
+ detectDrift(): DriftResult {
78
+ return this.driftDetector.detectDrift();
79
+ }
80
+
81
+ addRequirement(requirement: string): void {
82
+ this.driftDetector.addRequirement(requirement);
83
+ }
84
+
85
+ getRequirements(): string[] {
86
+ return this.driftDetector.getInitialRequirements();
87
+ }
88
+
89
+ // ========== Critical Context ==========
90
+
91
+ markCritical(
92
+ content: string,
93
+ options?: {
94
+ type?: CriticalContext['type'];
95
+ reason?: string;
96
+ source?: string;
97
+ }
98
+ ): CriticalContext {
99
+ return this.criticalManager.markCritical(content, options);
100
+ }
101
+
102
+ getCriticalContext(type?: CriticalContext['type']): CriticalContext[] {
103
+ return this.criticalManager.getCriticalContext(type);
104
+ }
105
+
106
+ removeCritical(id: string): boolean {
107
+ return this.criticalManager.removeCritical(id);
108
+ }
109
+
110
+ getAllCriticalContent(): string {
111
+ return this.criticalManager.getAllCriticalContent();
112
+ }
113
+
114
+ // ========== Compaction ==========
115
+
116
+ suggestCompaction(): CompactionSuggestion {
117
+ return this.compactionEngine.suggestCompaction();
118
+ }
119
+
120
+ triggerCompaction(options: CompactionOptions): CompactionResult {
121
+ const result = this.compactionEngine.compact(options);
122
+
123
+ // Log compaction event
124
+ this.logCompactionEvent(result);
125
+
126
+ return result;
127
+ }
128
+
129
+ autoCompact(): CompactionResult {
130
+ const result = this.compactionEngine.autoCompact();
131
+ this.logCompactionEvent(result);
132
+ return result;
133
+ }
134
+
135
+ private logCompactionEvent(result: CompactionResult): void {
136
+ try {
137
+ const stmt = this.db.prepare(`
138
+ UPDATE context_health_history
139
+ SET compaction_triggered = 1
140
+ WHERE id = (SELECT MAX(id) FROM context_health_history)
141
+ `);
142
+ stmt.run();
143
+ } catch {
144
+ // Ignore logging errors
145
+ }
146
+ }
147
+
148
+ // ========== Health History ==========
149
+
150
+ getHealthHistory(limit: number = 20): Array<{
151
+ timestamp: Date;
152
+ health: ContextHealth['health'];
153
+ utilizationPercent: number;
154
+ driftScore: number;
155
+ }> {
156
+ return this.healthMonitor.getHealthHistory(limit);
157
+ }
158
+
159
+ // ========== Utilities ==========
160
+
161
+ estimateTokens(text: string): number {
162
+ return this.healthMonitor.estimateTokens(text);
163
+ }
164
+
165
+ isCritical(content: string): boolean {
166
+ return this.criticalManager.isCritical(content);
167
+ }
168
+
169
+ extractCriticalFromText(text: string): Array<{ content: string; type: CriticalContext['type'] }> {
170
+ return this.criticalManager.extractCriticalFromText(text);
171
+ }
172
+
173
+ // ========== Summary for AI ==========
174
+
175
+ getContextSummaryForAI(): string {
176
+ const health = this.getContextHealth();
177
+ const critical = this.getAllCriticalContent();
178
+ const drift = this.detectDrift();
179
+
180
+ const parts: string[] = [];
181
+
182
+ // Health status
183
+ parts.push(`Context Health: ${health.health.toUpperCase()} (${health.utilizationPercent}% used)`);
184
+
185
+ if (health.driftDetected) {
186
+ parts.push(`\nWARNING: Drift detected (score: ${health.driftScore})`);
187
+
188
+ if (drift.missingRequirements.length > 0) {
189
+ parts.push('\nMissing requirements:');
190
+ for (const req of drift.missingRequirements.slice(0, 3)) {
191
+ parts.push(`- ${req}`);
192
+ }
193
+ }
194
+
195
+ if (drift.suggestedReminders.length > 0) {
196
+ parts.push('\nReminders:');
197
+ for (const reminder of drift.suggestedReminders.slice(0, 3)) {
198
+ parts.push(`- ${reminder}`);
199
+ }
200
+ }
201
+ }
202
+
203
+ if (critical) {
204
+ parts.push(`\n${critical}`);
205
+ }
206
+
207
+ if (health.compactionNeeded) {
208
+ parts.push(`\nSuggestion: ${health.suggestions[0] || 'Consider compacting context'}`);
209
+ }
210
+
211
+ return parts.join('\n');
212
+ }
213
+ }
@@ -0,0 +1,221 @@
1
+ import { randomUUID } from 'crypto';
2
+ import type Database from 'better-sqlite3';
3
+ import type { CriticalContext } from '../../types/documentation.js';
4
+
5
+ // Patterns for automatically detecting critical content
6
+ const CRITICAL_PATTERNS = [
7
+ // Explicit instructions
8
+ { pattern: /\b(always|never|must|required|mandatory)\b/i, type: 'instruction' as const },
9
+
10
+ // Decisions
11
+ { pattern: /\b(we decided|the decision|chose to|decided to|will use)\b/i, type: 'decision' as const },
12
+
13
+ // Requirements
14
+ { pattern: /\b(requirement|constraint|rule|spec|specification)\b/i, type: 'requirement' as const },
15
+
16
+ // User preferences
17
+ { pattern: /\b(i prefer|i want|don't want|please don't|make sure)\b/i, type: 'instruction' as const },
18
+
19
+ // Technical constraints
20
+ { pattern: /\b(cannot|must not|impossible|not allowed|forbidden)\b/i, type: 'requirement' as const },
21
+
22
+ // Important markers
23
+ { pattern: /\b(important|critical|essential|crucial|key point)\b/i, type: 'instruction' as const }
24
+ ];
25
+
26
+ export class CriticalContextManager {
27
+ private db: Database.Database;
28
+
29
+ constructor(db: Database.Database) {
30
+ this.db = db;
31
+ }
32
+
33
+ markCritical(
34
+ content: string,
35
+ options?: {
36
+ type?: CriticalContext['type'];
37
+ reason?: string;
38
+ source?: string;
39
+ neverCompress?: boolean;
40
+ }
41
+ ): CriticalContext {
42
+ const id = randomUUID();
43
+ const type = options?.type || this.inferType(content);
44
+ const neverCompress = options?.neverCompress ?? true;
45
+
46
+ const critical: CriticalContext = {
47
+ id,
48
+ type,
49
+ content,
50
+ reason: options?.reason,
51
+ source: options?.source,
52
+ createdAt: new Date(),
53
+ neverCompress
54
+ };
55
+
56
+ const stmt = this.db.prepare(`
57
+ INSERT INTO critical_context (id, type, content, reason, source, never_compress, created_at)
58
+ VALUES (?, ?, ?, ?, ?, ?, ?)
59
+ `);
60
+
61
+ stmt.run(
62
+ id,
63
+ type,
64
+ content,
65
+ options?.reason || null,
66
+ options?.source || null,
67
+ neverCompress ? 1 : 0,
68
+ Math.floor(Date.now() / 1000)
69
+ );
70
+
71
+ return critical;
72
+ }
73
+
74
+ getCriticalContext(type?: CriticalContext['type']): CriticalContext[] {
75
+ let query = `
76
+ SELECT id, type, content, reason, source, never_compress, created_at
77
+ FROM critical_context
78
+ `;
79
+ const params: string[] = [];
80
+
81
+ if (type) {
82
+ query += ' WHERE type = ?';
83
+ params.push(type);
84
+ }
85
+
86
+ query += ' ORDER BY created_at DESC';
87
+
88
+ const stmt = this.db.prepare(query);
89
+ const rows = (params.length > 0 ? stmt.all(params[0]) : stmt.all()) as Array<{
90
+ id: string;
91
+ type: string;
92
+ content: string;
93
+ reason: string | null;
94
+ source: string | null;
95
+ never_compress: number;
96
+ created_at: number;
97
+ }>;
98
+
99
+ return rows.map(row => ({
100
+ id: row.id,
101
+ type: row.type as CriticalContext['type'],
102
+ content: row.content,
103
+ reason: row.reason || undefined,
104
+ source: row.source || undefined,
105
+ createdAt: new Date(row.created_at * 1000),
106
+ neverCompress: row.never_compress === 1
107
+ }));
108
+ }
109
+
110
+ getCriticalById(id: string): CriticalContext | null {
111
+ const stmt = this.db.prepare(`
112
+ SELECT id, type, content, reason, source, never_compress, created_at
113
+ FROM critical_context
114
+ WHERE id = ?
115
+ `);
116
+
117
+ const row = stmt.get(id) as {
118
+ id: string;
119
+ type: string;
120
+ content: string;
121
+ reason: string | null;
122
+ source: string | null;
123
+ never_compress: number;
124
+ created_at: number;
125
+ } | undefined;
126
+
127
+ if (!row) return null;
128
+
129
+ return {
130
+ id: row.id,
131
+ type: row.type as CriticalContext['type'],
132
+ content: row.content,
133
+ reason: row.reason || undefined,
134
+ source: row.source || undefined,
135
+ createdAt: new Date(row.created_at * 1000),
136
+ neverCompress: row.never_compress === 1
137
+ };
138
+ }
139
+
140
+ removeCritical(id: string): boolean {
141
+ const stmt = this.db.prepare('DELETE FROM critical_context WHERE id = ?');
142
+ const result = stmt.run(id);
143
+ return result.changes > 0;
144
+ }
145
+
146
+ getCriticalCount(): number {
147
+ const stmt = this.db.prepare('SELECT COUNT(*) as count FROM critical_context');
148
+ const result = stmt.get() as { count: number };
149
+ return result.count;
150
+ }
151
+
152
+ isCritical(content: string): boolean {
153
+ return CRITICAL_PATTERNS.some(p => p.pattern.test(content));
154
+ }
155
+
156
+ inferType(content: string): CriticalContext['type'] {
157
+ for (const { pattern, type } of CRITICAL_PATTERNS) {
158
+ if (pattern.test(content)) {
159
+ return type;
160
+ }
161
+ }
162
+ return 'custom';
163
+ }
164
+
165
+ extractCriticalFromText(text: string): Array<{ content: string; type: CriticalContext['type'] }> {
166
+ const results: Array<{ content: string; type: CriticalContext['type'] }> = [];
167
+
168
+ // Split into sentences
169
+ const sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(Boolean);
170
+
171
+ for (const sentence of sentences) {
172
+ if (this.isCritical(sentence)) {
173
+ results.push({
174
+ content: sentence,
175
+ type: this.inferType(sentence)
176
+ });
177
+ }
178
+ }
179
+
180
+ return results;
181
+ }
182
+
183
+ getAllCriticalContent(): string {
184
+ const critical = this.getCriticalContext();
185
+
186
+ if (critical.length === 0) {
187
+ return '';
188
+ }
189
+
190
+ const grouped = {
191
+ decision: [] as string[],
192
+ requirement: [] as string[],
193
+ instruction: [] as string[],
194
+ custom: [] as string[]
195
+ };
196
+
197
+ for (const item of critical) {
198
+ const arr = grouped[item.type as keyof typeof grouped];
199
+ if (arr) {
200
+ arr.push(item.content);
201
+ }
202
+ }
203
+
204
+ const parts: string[] = [];
205
+
206
+ if (grouped.decision.length > 0) {
207
+ parts.push('DECISIONS:\n' + grouped.decision.map(d => `- ${d}`).join('\n'));
208
+ }
209
+ if (grouped.requirement.length > 0) {
210
+ parts.push('REQUIREMENTS:\n' + grouped.requirement.map(r => `- ${r}`).join('\n'));
211
+ }
212
+ if (grouped.instruction.length > 0) {
213
+ parts.push('INSTRUCTIONS:\n' + grouped.instruction.map(i => `- ${i}`).join('\n'));
214
+ }
215
+ if (grouped.custom.length > 0) {
216
+ parts.push('OTHER CRITICAL:\n' + grouped.custom.map(c => `- ${c}`).join('\n'));
217
+ }
218
+
219
+ return parts.join('\n\n');
220
+ }
221
+ }
@@ -0,0 +1,255 @@
1
+ import type { DriftResult, Contradiction, CriticalContext } from '../../types/documentation.js';
2
+ import { CriticalContextManager } from './critical-context.js';
3
+
4
+ interface Message {
5
+ role: 'user' | 'assistant' | 'system';
6
+ content: string;
7
+ timestamp?: Date;
8
+ }
9
+
10
+ // Keywords that indicate topic areas
11
+ const TOPIC_KEYWORDS: Record<string, string[]> = {
12
+ authentication: ['auth', 'login', 'jwt', 'session', 'token', 'oauth', 'password'],
13
+ database: ['database', 'db', 'sql', 'query', 'table', 'schema', 'migration'],
14
+ api: ['api', 'endpoint', 'rest', 'graphql', 'route', 'request', 'response'],
15
+ frontend: ['react', 'vue', 'component', 'ui', 'css', 'html', 'dom'],
16
+ testing: ['test', 'spec', 'mock', 'assert', 'coverage', 'jest', 'vitest'],
17
+ deployment: ['deploy', 'docker', 'kubernetes', 'ci', 'cd', 'pipeline'],
18
+ security: ['security', 'encrypt', 'hash', 'vulnerability', 'xss', 'csrf'],
19
+ performance: ['performance', 'optimize', 'cache', 'speed', 'memory', 'latency']
20
+ };
21
+
22
+ // Contradiction patterns (earlier statement vs later statement)
23
+ const CONTRADICTION_PATTERNS = [
24
+ { earlier: /will use (\w+)/i, later: /use (\w+) instead/i },
25
+ { earlier: /decided on (\w+)/i, later: /switch(?:ed|ing)? to (\w+)/i },
26
+ { earlier: /must (\w+)/i, later: /don't need to (\w+)/i },
27
+ { earlier: /always (\w+)/i, later: /never (\w+)/i }
28
+ ];
29
+
30
+ export class DriftDetector {
31
+ private criticalManager: CriticalContextManager;
32
+ private conversationHistory: Message[] = [];
33
+ private initialRequirements: string[] = [];
34
+
35
+ constructor(criticalManager: CriticalContextManager) {
36
+ this.criticalManager = criticalManager;
37
+ }
38
+
39
+ addMessage(message: Message): void {
40
+ this.conversationHistory.push({
41
+ ...message,
42
+ timestamp: message.timestamp || new Date()
43
+ });
44
+
45
+ // Extract requirements from early user messages
46
+ if (message.role === 'user' && this.conversationHistory.length <= 5) {
47
+ const extracted = this.extractRequirements(message.content);
48
+ this.initialRequirements.push(...extracted);
49
+ }
50
+ }
51
+
52
+ clearHistory(): void {
53
+ this.conversationHistory = [];
54
+ this.initialRequirements = [];
55
+ }
56
+
57
+ getHistory(): Message[] {
58
+ return [...this.conversationHistory];
59
+ }
60
+
61
+ detectDrift(): DriftResult {
62
+ const criticalContext = this.criticalManager.getCriticalContext();
63
+ const recentMessages = this.conversationHistory.slice(-10);
64
+ const earlyMessages = this.conversationHistory.slice(0, 10);
65
+
66
+ // Calculate drift score based on multiple factors
67
+ const requirementAdherence = this.checkRequirementAdherence(recentMessages);
68
+ const contradictions = this.findContradictions();
69
+ const topicShift = this.calculateTopicShift(earlyMessages, recentMessages);
70
+
71
+ // Weighted drift score
72
+ const driftScore = Math.min(1, (
73
+ (1 - requirementAdherence.score) * 0.4 +
74
+ (contradictions.length > 0 ? Math.min(contradictions.length * 0.15, 0.3) : 0) +
75
+ topicShift * 0.3
76
+ ));
77
+
78
+ // Generate suggested reminders
79
+ const suggestedReminders = this.generateReminders(
80
+ requirementAdherence.missing,
81
+ criticalContext
82
+ );
83
+
84
+ return {
85
+ driftScore: Math.round(driftScore * 100) / 100,
86
+ driftDetected: driftScore >= 0.3,
87
+ missingRequirements: requirementAdherence.missing,
88
+ contradictions,
89
+ suggestedReminders,
90
+ topicShift: Math.round(topicShift * 100) / 100
91
+ };
92
+ }
93
+
94
+ private extractRequirements(text: string): string[] {
95
+ const requirements: string[] = [];
96
+ const patterns = [
97
+ /(?:must|should|need to|have to|required to)\s+(.+?)(?:[.!?]|$)/gi,
98
+ /(?:make sure|ensure|always)\s+(.+?)(?:[.!?]|$)/gi,
99
+ /(?:don't|never|avoid)\s+(.+?)(?:[.!?]|$)/gi
100
+ ];
101
+
102
+ for (const pattern of patterns) {
103
+ let match;
104
+ while ((match = pattern.exec(text)) !== null) {
105
+ if (match[1] && match[1].length > 5 && match[1].length < 200) {
106
+ requirements.push(match[1].trim());
107
+ }
108
+ }
109
+ }
110
+
111
+ return requirements;
112
+ }
113
+
114
+ private checkRequirementAdherence(recentMessages: Message[]): {
115
+ score: number;
116
+ missing: string[];
117
+ } {
118
+ if (this.initialRequirements.length === 0) {
119
+ return { score: 1, missing: [] };
120
+ }
121
+
122
+ const recentText = recentMessages
123
+ .filter(m => m.role === 'assistant')
124
+ .map(m => m.content.toLowerCase())
125
+ .join(' ');
126
+
127
+ const missing: string[] = [];
128
+ let found = 0;
129
+
130
+ for (const req of this.initialRequirements) {
131
+ // Check if requirement is being addressed in recent responses
132
+ const keywords = req.toLowerCase().split(/\s+/).filter(w => w.length > 3);
133
+ const matchCount = keywords.filter(kw => recentText.includes(kw)).length;
134
+
135
+ if (matchCount >= keywords.length * 0.5) {
136
+ found++;
137
+ } else {
138
+ missing.push(req);
139
+ }
140
+ }
141
+
142
+ const score = this.initialRequirements.length > 0
143
+ ? found / this.initialRequirements.length
144
+ : 1;
145
+
146
+ return { score, missing };
147
+ }
148
+
149
+ private findContradictions(): Contradiction[] {
150
+ const contradictions: Contradiction[] = [];
151
+ const messages = this.conversationHistory;
152
+
153
+ // Compare each message with later messages
154
+ for (let i = 0; i < messages.length - 1; i++) {
155
+ const earlier = messages[i]!;
156
+
157
+ for (let j = i + 1; j < messages.length; j++) {
158
+ const later = messages[j]!;
159
+
160
+ // Only check assistant responses for contradictions
161
+ if (earlier.role !== 'assistant' || later.role !== 'assistant') continue;
162
+
163
+ for (const pattern of CONTRADICTION_PATTERNS) {
164
+ const earlierMatch = earlier.content.match(pattern.earlier);
165
+ const laterMatch = later.content.match(pattern.later);
166
+
167
+ if (earlierMatch && laterMatch) {
168
+ // Check if it's talking about the same thing
169
+ const earlierSubject = earlierMatch[1]?.toLowerCase();
170
+ const laterSubject = laterMatch[1]?.toLowerCase();
171
+
172
+ if (earlierSubject && laterSubject && earlierSubject !== laterSubject) {
173
+ contradictions.push({
174
+ earlier: earlier.content.slice(0, 100),
175
+ later: later.content.slice(0, 100),
176
+ severity: j - i > 10 ? 'high' : j - i > 5 ? 'medium' : 'low'
177
+ });
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ // Limit to most recent contradictions
185
+ return contradictions.slice(-5);
186
+ }
187
+
188
+ private calculateTopicShift(
189
+ earlyMessages: Message[],
190
+ recentMessages: Message[]
191
+ ): number {
192
+ const earlyTopics = this.extractTopics(earlyMessages);
193
+ const recentTopics = this.extractTopics(recentMessages);
194
+
195
+ if (earlyTopics.size === 0 || recentTopics.size === 0) {
196
+ return 0;
197
+ }
198
+
199
+ // Calculate Jaccard similarity
200
+ const intersection = new Set([...earlyTopics].filter(t => recentTopics.has(t)));
201
+ const union = new Set([...earlyTopics, ...recentTopics]);
202
+
203
+ const similarity = intersection.size / union.size;
204
+
205
+ // Topic shift is inverse of similarity
206
+ return 1 - similarity;
207
+ }
208
+
209
+ private extractTopics(messages: Message[]): Set<string> {
210
+ const topics = new Set<string>();
211
+ const text = messages.map(m => m.content.toLowerCase()).join(' ');
212
+
213
+ for (const [topic, keywords] of Object.entries(TOPIC_KEYWORDS)) {
214
+ for (const keyword of keywords) {
215
+ if (text.includes(keyword)) {
216
+ topics.add(topic);
217
+ break;
218
+ }
219
+ }
220
+ }
221
+
222
+ return topics;
223
+ }
224
+
225
+ private generateReminders(
226
+ missingRequirements: string[],
227
+ criticalContext: CriticalContext[]
228
+ ): string[] {
229
+ const reminders: string[] = [];
230
+
231
+ // Add reminders for missing requirements
232
+ for (const req of missingRequirements.slice(0, 3)) {
233
+ reminders.push(`Remember: ${req}`);
234
+ }
235
+
236
+ // Add reminders for critical context
237
+ for (const critical of criticalContext.slice(0, 3)) {
238
+ if (critical.type === 'decision') {
239
+ reminders.push(`Decision: ${critical.content}`);
240
+ } else if (critical.type === 'requirement') {
241
+ reminders.push(`Requirement: ${critical.content}`);
242
+ }
243
+ }
244
+
245
+ return reminders;
246
+ }
247
+
248
+ getInitialRequirements(): string[] {
249
+ return [...this.initialRequirements];
250
+ }
251
+
252
+ addRequirement(requirement: string): void {
253
+ this.initialRequirements.push(requirement);
254
+ }
255
+ }
@@ -0,0 +1,7 @@
1
+ // Context Rot Prevention Module - Barrel Export
2
+
3
+ export { ContextRotPrevention } from './context-rot-prevention.js';
4
+ export { ContextHealthMonitor } from './context-health.js';
5
+ export { DriftDetector } from './drift-detector.js';
6
+ export { CompactionEngine } from './compaction.js';
7
+ export { CriticalContextManager } from './critical-context.js';