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,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';
|