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,284 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContextChunk,
|
|
3
|
+
CompactionResult,
|
|
4
|
+
CompactionOptions,
|
|
5
|
+
CompactionSuggestion
|
|
6
|
+
} from '../../types/documentation.js';
|
|
7
|
+
import { ContextHealthMonitor } from './context-health.js';
|
|
8
|
+
import { CriticalContextManager } from './critical-context.js';
|
|
9
|
+
|
|
10
|
+
// Thresholds for different strategies
|
|
11
|
+
const RELEVANCE_THRESHOLD_SUMMARIZE = 0.5;
|
|
12
|
+
const RELEVANCE_THRESHOLD_SELECTIVE = 0.3;
|
|
13
|
+
const RELEVANCE_THRESHOLD_AGGRESSIVE = 0.2;
|
|
14
|
+
|
|
15
|
+
export class CompactionEngine {
|
|
16
|
+
private healthMonitor: ContextHealthMonitor;
|
|
17
|
+
private criticalManager: CriticalContextManager;
|
|
18
|
+
|
|
19
|
+
constructor(healthMonitor: ContextHealthMonitor, criticalManager: CriticalContextManager) {
|
|
20
|
+
this.healthMonitor = healthMonitor;
|
|
21
|
+
this.criticalManager = criticalManager;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
suggestCompaction(): CompactionSuggestion {
|
|
25
|
+
const chunks = this.healthMonitor.getChunks();
|
|
26
|
+
const tokenLimit = this.healthMonitor.getTokenLimit();
|
|
27
|
+
const currentTokens = this.healthMonitor.getCurrentTokens();
|
|
28
|
+
|
|
29
|
+
const critical: ContextChunk[] = [];
|
|
30
|
+
const summarizable: ContextChunk[] = [];
|
|
31
|
+
const removable: ContextChunk[] = [];
|
|
32
|
+
|
|
33
|
+
for (const chunk of chunks) {
|
|
34
|
+
if (chunk.isCritical || chunk.relevanceScore >= RELEVANCE_THRESHOLD_SUMMARIZE) {
|
|
35
|
+
critical.push(chunk);
|
|
36
|
+
} else if (chunk.relevanceScore >= RELEVANCE_THRESHOLD_SELECTIVE) {
|
|
37
|
+
summarizable.push(chunk);
|
|
38
|
+
} else {
|
|
39
|
+
removable.push(chunk);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const removableTokens = removable.reduce((sum, c) => sum + c.tokens, 0);
|
|
44
|
+
const summarizableTokens = summarizable.reduce((sum, c) => sum + c.tokens, 0);
|
|
45
|
+
|
|
46
|
+
// Estimate tokens after summarization (assume 70% compression)
|
|
47
|
+
const summarizedTokens = Math.ceil(summarizableTokens * 0.3);
|
|
48
|
+
const tokensSaved = removableTokens + (summarizableTokens - summarizedTokens);
|
|
49
|
+
|
|
50
|
+
const newTokens = currentTokens - tokensSaved;
|
|
51
|
+
const newUtilization = (newTokens / tokenLimit) * 100;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
critical,
|
|
55
|
+
summarizable,
|
|
56
|
+
removable,
|
|
57
|
+
tokensSaved,
|
|
58
|
+
newUtilization: Math.round(newUtilization * 10) / 10
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
compact(options: CompactionOptions): CompactionResult {
|
|
63
|
+
const { strategy, preserveRecent = 5, targetUtilization, preserveCritical = true } = options;
|
|
64
|
+
|
|
65
|
+
const chunks = this.healthMonitor.getChunks();
|
|
66
|
+
const tokensBefore = this.healthMonitor.getCurrentTokens();
|
|
67
|
+
|
|
68
|
+
// Separate chunks by type
|
|
69
|
+
const recentChunks = chunks.slice(-preserveRecent);
|
|
70
|
+
const olderChunks = chunks.slice(0, -preserveRecent);
|
|
71
|
+
|
|
72
|
+
let relevanceThreshold: number;
|
|
73
|
+
switch (strategy) {
|
|
74
|
+
case 'aggressive':
|
|
75
|
+
relevanceThreshold = RELEVANCE_THRESHOLD_AGGRESSIVE;
|
|
76
|
+
break;
|
|
77
|
+
case 'selective':
|
|
78
|
+
relevanceThreshold = RELEVANCE_THRESHOLD_SELECTIVE;
|
|
79
|
+
break;
|
|
80
|
+
case 'summarize':
|
|
81
|
+
default:
|
|
82
|
+
relevanceThreshold = RELEVANCE_THRESHOLD_SUMMARIZE;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const toKeep: ContextChunk[] = [];
|
|
86
|
+
const toSummarize: ContextChunk[] = [];
|
|
87
|
+
const toRemove: ContextChunk[] = [];
|
|
88
|
+
|
|
89
|
+
for (const chunk of olderChunks) {
|
|
90
|
+
// Always preserve critical if flag is set
|
|
91
|
+
if (preserveCritical && chunk.isCritical) {
|
|
92
|
+
toKeep.push(chunk);
|
|
93
|
+
} else if (chunk.relevanceScore >= relevanceThreshold) {
|
|
94
|
+
if (strategy === 'aggressive') {
|
|
95
|
+
toSummarize.push(chunk);
|
|
96
|
+
} else {
|
|
97
|
+
toKeep.push(chunk);
|
|
98
|
+
}
|
|
99
|
+
} else if (chunk.relevanceScore >= relevanceThreshold * 0.5 && strategy !== 'aggressive') {
|
|
100
|
+
toSummarize.push(chunk);
|
|
101
|
+
} else {
|
|
102
|
+
toRemove.push(chunk);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Generate summaries for chunks to summarize
|
|
107
|
+
const summaries = this.generateSummaries(toSummarize);
|
|
108
|
+
|
|
109
|
+
// Calculate new token count
|
|
110
|
+
const keptTokens = toKeep.reduce((sum, c) => sum + c.tokens, 0);
|
|
111
|
+
const recentTokens = recentChunks.reduce((sum, c) => sum + c.tokens, 0);
|
|
112
|
+
const summaryTokens = summaries.reduce((sum, s) => sum + this.estimateTokens(s), 0);
|
|
113
|
+
|
|
114
|
+
const tokensAfter = keptTokens + recentTokens + summaryTokens;
|
|
115
|
+
const tokensSaved = tokensBefore - tokensAfter;
|
|
116
|
+
|
|
117
|
+
// Update the health monitor with new chunks
|
|
118
|
+
this.healthMonitor.clearChunks();
|
|
119
|
+
|
|
120
|
+
// Re-add kept chunks
|
|
121
|
+
for (const chunk of toKeep) {
|
|
122
|
+
this.healthMonitor.addChunk(chunk);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Add summary chunks
|
|
126
|
+
for (const summary of summaries) {
|
|
127
|
+
this.healthMonitor.addChunk({
|
|
128
|
+
content: summary,
|
|
129
|
+
tokens: this.estimateTokens(summary),
|
|
130
|
+
timestamp: new Date(),
|
|
131
|
+
type: 'message'
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Re-add recent chunks
|
|
136
|
+
for (const chunk of recentChunks) {
|
|
137
|
+
this.healthMonitor.addChunk(chunk);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check if we hit target utilization
|
|
141
|
+
if (targetUtilization) {
|
|
142
|
+
const currentUtilization = (tokensAfter / this.healthMonitor.getTokenLimit()) * 100;
|
|
143
|
+
if (currentUtilization > targetUtilization && strategy !== 'aggressive') {
|
|
144
|
+
// Recursively compact with more aggressive strategy
|
|
145
|
+
return this.compact({
|
|
146
|
+
...options,
|
|
147
|
+
strategy: strategy === 'summarize' ? 'selective' : 'aggressive'
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
strategy,
|
|
155
|
+
tokensBefore,
|
|
156
|
+
tokensAfter,
|
|
157
|
+
tokensSaved,
|
|
158
|
+
preservedCritical: toKeep.filter(c => c.isCritical).length,
|
|
159
|
+
summarizedChunks: toSummarize.length,
|
|
160
|
+
removedChunks: toRemove.length,
|
|
161
|
+
summaries
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private generateSummaries(chunks: ContextChunk[]): string[] {
|
|
166
|
+
if (chunks.length === 0) return [];
|
|
167
|
+
|
|
168
|
+
// Group chunks by type
|
|
169
|
+
const grouped: Record<string, ContextChunk[]> = {};
|
|
170
|
+
for (const chunk of chunks) {
|
|
171
|
+
if (!grouped[chunk.type]) {
|
|
172
|
+
grouped[chunk.type] = [];
|
|
173
|
+
}
|
|
174
|
+
grouped[chunk.type]!.push(chunk);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const summaries: string[] = [];
|
|
178
|
+
|
|
179
|
+
for (const [type, typeChunks] of Object.entries(grouped)) {
|
|
180
|
+
if (typeChunks.length === 0) continue;
|
|
181
|
+
|
|
182
|
+
// Simple extractive summary: take key sentences
|
|
183
|
+
const allContent = typeChunks.map(c => c.content).join(' ');
|
|
184
|
+
const summary = this.extractiveSummarize(allContent, type);
|
|
185
|
+
summaries.push(summary);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return summaries;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private extractiveSummarize(content: string, type: string): string {
|
|
192
|
+
// Split into sentences
|
|
193
|
+
const sentences = content.split(/[.!?]+/).map(s => s.trim()).filter(s => s.length > 10);
|
|
194
|
+
|
|
195
|
+
if (sentences.length === 0) {
|
|
196
|
+
return `[${type}]: ${content.slice(0, 100)}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Score sentences by importance
|
|
200
|
+
const scored = sentences.map(sentence => ({
|
|
201
|
+
sentence,
|
|
202
|
+
score: this.scoreSentence(sentence)
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
// Sort by score and take top sentences
|
|
206
|
+
scored.sort((a, b) => b.score - a.score);
|
|
207
|
+
const topSentences = scored.slice(0, Math.min(3, sentences.length));
|
|
208
|
+
|
|
209
|
+
// Sort back by original order for coherence
|
|
210
|
+
const originalOrder = topSentences.sort((a, b) => {
|
|
211
|
+
return sentences.indexOf(a.sentence) - sentences.indexOf(b.sentence);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const summary = originalOrder.map(s => s.sentence).join('. ') + '.';
|
|
215
|
+
|
|
216
|
+
return `[Summary - ${type}]: ${summary}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private scoreSentence(sentence: string): number {
|
|
220
|
+
let score = 0;
|
|
221
|
+
|
|
222
|
+
// Longer sentences might have more info (but not too long)
|
|
223
|
+
const wordCount = sentence.split(/\s+/).length;
|
|
224
|
+
if (wordCount >= 5 && wordCount <= 30) {
|
|
225
|
+
score += 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Contains important keywords
|
|
229
|
+
const importantWords = [
|
|
230
|
+
'decided', 'choose', 'use', 'implement', 'because', 'important',
|
|
231
|
+
'must', 'should', 'require', 'need', 'critical', 'key'
|
|
232
|
+
];
|
|
233
|
+
for (const word of importantWords) {
|
|
234
|
+
if (sentence.toLowerCase().includes(word)) {
|
|
235
|
+
score += 0.5;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Contains technical terms (likely important)
|
|
240
|
+
const technicalPatterns = [
|
|
241
|
+
/\b[A-Z][a-z]+(?:[A-Z][a-z]+)+\b/, // CamelCase
|
|
242
|
+
/\b\w+\(\)/, // Function calls
|
|
243
|
+
/`[^`]+`/ // Code markers
|
|
244
|
+
];
|
|
245
|
+
for (const pattern of technicalPatterns) {
|
|
246
|
+
if (pattern.test(sentence)) {
|
|
247
|
+
score += 0.3;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return score;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private estimateTokens(text: string): number {
|
|
255
|
+
// Rough estimation: ~4 characters per token
|
|
256
|
+
return Math.ceil(text.length / 4);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
autoCompact(): CompactionResult {
|
|
260
|
+
const health = this.healthMonitor.getHealth();
|
|
261
|
+
|
|
262
|
+
// Determine strategy based on health
|
|
263
|
+
let strategy: CompactionOptions['strategy'];
|
|
264
|
+
let targetUtilization: number;
|
|
265
|
+
|
|
266
|
+
if (health.health === 'critical') {
|
|
267
|
+
strategy = 'aggressive';
|
|
268
|
+
targetUtilization = 40;
|
|
269
|
+
} else if (health.health === 'warning') {
|
|
270
|
+
strategy = 'selective';
|
|
271
|
+
targetUtilization = 50;
|
|
272
|
+
} else {
|
|
273
|
+
strategy = 'summarize';
|
|
274
|
+
targetUtilization = 60;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return this.compact({
|
|
278
|
+
strategy,
|
|
279
|
+
preserveRecent: 10,
|
|
280
|
+
targetUtilization,
|
|
281
|
+
preserveCritical: true
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { ContextHealth, ContextChunk } from '../../types/documentation.js';
|
|
3
|
+
import { CriticalContextManager } from './critical-context.js';
|
|
4
|
+
|
|
5
|
+
// Default token limits (can be overridden)
|
|
6
|
+
const DEFAULT_TOKEN_LIMIT = 100000;
|
|
7
|
+
|
|
8
|
+
// Health thresholds
|
|
9
|
+
const UTILIZATION_WARNING = 0.7; // 70%
|
|
10
|
+
const UTILIZATION_CRITICAL = 0.85; // 85%
|
|
11
|
+
const DRIFT_WARNING = 0.3;
|
|
12
|
+
const DRIFT_CRITICAL = 0.5;
|
|
13
|
+
|
|
14
|
+
export class ContextHealthMonitor {
|
|
15
|
+
private db: Database.Database;
|
|
16
|
+
private criticalManager: CriticalContextManager;
|
|
17
|
+
private tokenLimit: number;
|
|
18
|
+
|
|
19
|
+
// In-memory tracking for current session
|
|
20
|
+
private contextChunks: ContextChunk[] = [];
|
|
21
|
+
private currentTokens: number = 0;
|
|
22
|
+
|
|
23
|
+
constructor(db: Database.Database, criticalManager: CriticalContextManager, tokenLimit?: number) {
|
|
24
|
+
this.db = db;
|
|
25
|
+
this.criticalManager = criticalManager;
|
|
26
|
+
this.tokenLimit = tokenLimit || DEFAULT_TOKEN_LIMIT;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setTokenLimit(limit: number): void {
|
|
30
|
+
this.tokenLimit = limit;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getTokenLimit(): number {
|
|
34
|
+
return this.tokenLimit;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
addChunk(chunk: Omit<ContextChunk, 'id' | 'relevanceScore' | 'isCritical'>): ContextChunk {
|
|
38
|
+
const id = `chunk_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
39
|
+
const isCritical = this.criticalManager.isCritical(chunk.content);
|
|
40
|
+
|
|
41
|
+
const fullChunk: ContextChunk = {
|
|
42
|
+
...chunk,
|
|
43
|
+
id,
|
|
44
|
+
relevanceScore: 1.0, // New chunks start with full relevance
|
|
45
|
+
isCritical
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.contextChunks.push(fullChunk);
|
|
49
|
+
this.currentTokens += chunk.tokens;
|
|
50
|
+
|
|
51
|
+
// Decay relevance of older chunks
|
|
52
|
+
this.decayRelevance();
|
|
53
|
+
|
|
54
|
+
return fullChunk;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
removeChunk(id: string): boolean {
|
|
58
|
+
const index = this.contextChunks.findIndex(c => c.id === id);
|
|
59
|
+
if (index === -1) return false;
|
|
60
|
+
|
|
61
|
+
const chunk = this.contextChunks[index]!;
|
|
62
|
+
this.currentTokens -= chunk.tokens;
|
|
63
|
+
this.contextChunks.splice(index, 1);
|
|
64
|
+
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getChunks(): ContextChunk[] {
|
|
69
|
+
return [...this.contextChunks];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
clearChunks(): void {
|
|
73
|
+
this.contextChunks = [];
|
|
74
|
+
this.currentTokens = 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setCurrentTokens(tokens: number): void {
|
|
78
|
+
this.currentTokens = tokens;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getCurrentTokens(): number {
|
|
82
|
+
return this.currentTokens;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getHealth(driftScore: number = 0): ContextHealth {
|
|
86
|
+
const tokensUsed = this.currentTokens;
|
|
87
|
+
const utilizationPercent = (tokensUsed / this.tokenLimit) * 100;
|
|
88
|
+
|
|
89
|
+
// Calculate relevance score (average of all chunks)
|
|
90
|
+
const relevanceScore = this.contextChunks.length > 0
|
|
91
|
+
? this.contextChunks.reduce((sum, c) => sum + c.relevanceScore, 0) / this.contextChunks.length
|
|
92
|
+
: 1.0;
|
|
93
|
+
|
|
94
|
+
// Determine health status
|
|
95
|
+
let health: ContextHealth['health'] = 'good';
|
|
96
|
+
if (utilizationPercent >= UTILIZATION_CRITICAL * 100 || driftScore >= DRIFT_CRITICAL) {
|
|
97
|
+
health = 'critical';
|
|
98
|
+
} else if (utilizationPercent >= UTILIZATION_WARNING * 100 || driftScore >= DRIFT_WARNING) {
|
|
99
|
+
health = 'warning';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if compaction is needed
|
|
103
|
+
const compactionNeeded = health !== 'good';
|
|
104
|
+
const driftDetected = driftScore >= DRIFT_WARNING;
|
|
105
|
+
|
|
106
|
+
// Generate suggestions
|
|
107
|
+
const suggestions = this.generateSuggestions(health, utilizationPercent, driftScore);
|
|
108
|
+
|
|
109
|
+
// Get critical context count
|
|
110
|
+
const criticalContextCount = this.criticalManager.getCriticalCount();
|
|
111
|
+
|
|
112
|
+
const healthResult: ContextHealth = {
|
|
113
|
+
tokensUsed,
|
|
114
|
+
tokensLimit: this.tokenLimit,
|
|
115
|
+
utilizationPercent: Math.round(utilizationPercent * 10) / 10,
|
|
116
|
+
health,
|
|
117
|
+
relevanceScore: Math.round(relevanceScore * 100) / 100,
|
|
118
|
+
driftScore: Math.round(driftScore * 100) / 100,
|
|
119
|
+
criticalContextCount,
|
|
120
|
+
driftDetected,
|
|
121
|
+
compactionNeeded,
|
|
122
|
+
suggestions
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Log to history
|
|
126
|
+
this.logHealthCheck(healthResult);
|
|
127
|
+
|
|
128
|
+
return healthResult;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private generateSuggestions(
|
|
132
|
+
health: ContextHealth['health'],
|
|
133
|
+
utilization: number,
|
|
134
|
+
driftScore: number
|
|
135
|
+
): string[] {
|
|
136
|
+
const suggestions: string[] = [];
|
|
137
|
+
|
|
138
|
+
if (health === 'good') {
|
|
139
|
+
suggestions.push('Context is healthy, no action needed');
|
|
140
|
+
return suggestions;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (utilization >= UTILIZATION_CRITICAL * 100) {
|
|
144
|
+
suggestions.push('Context nearly full - compaction strongly recommended');
|
|
145
|
+
suggestions.push('Consider using "aggressive" compaction strategy');
|
|
146
|
+
} else if (utilization >= UTILIZATION_WARNING * 100) {
|
|
147
|
+
suggestions.push('Context getting large - consider compaction');
|
|
148
|
+
suggestions.push('Use "summarize" strategy to compress old context');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (driftScore >= DRIFT_CRITICAL) {
|
|
152
|
+
suggestions.push('Significant drift detected - AI may be ignoring earlier instructions');
|
|
153
|
+
suggestions.push('Review critical context and add reminders if needed');
|
|
154
|
+
} else if (driftScore >= DRIFT_WARNING) {
|
|
155
|
+
suggestions.push('Some drift detected - consider marking critical items');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const criticalCount = this.criticalManager.getCriticalCount();
|
|
159
|
+
if (criticalCount === 0) {
|
|
160
|
+
suggestions.push('No critical context marked - consider marking important decisions/requirements');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return suggestions;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private decayRelevance(): void {
|
|
167
|
+
// Decay relevance of chunks based on position (older = lower relevance)
|
|
168
|
+
const totalChunks = this.contextChunks.length;
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
171
|
+
const chunk = this.contextChunks[i]!;
|
|
172
|
+
|
|
173
|
+
// Critical chunks decay slower
|
|
174
|
+
const decayRate = chunk.isCritical ? 0.98 : 0.95;
|
|
175
|
+
|
|
176
|
+
// Position-based decay (older chunks have lower position)
|
|
177
|
+
const positionFactor = (i + 1) / totalChunks;
|
|
178
|
+
|
|
179
|
+
// New relevance is combination of decay and position
|
|
180
|
+
chunk.relevanceScore = Math.max(0.1, chunk.relevanceScore * decayRate * (0.5 + 0.5 * positionFactor));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private logHealthCheck(health: ContextHealth): void {
|
|
185
|
+
try {
|
|
186
|
+
const stmt = this.db.prepare(`
|
|
187
|
+
INSERT INTO context_health_history
|
|
188
|
+
(tokens_used, tokens_limit, utilization_percent, drift_score, relevance_score, health, compaction_triggered)
|
|
189
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
190
|
+
`);
|
|
191
|
+
|
|
192
|
+
stmt.run(
|
|
193
|
+
health.tokensUsed,
|
|
194
|
+
health.tokensLimit,
|
|
195
|
+
health.utilizationPercent,
|
|
196
|
+
health.driftScore,
|
|
197
|
+
health.relevanceScore,
|
|
198
|
+
health.health,
|
|
199
|
+
0
|
|
200
|
+
);
|
|
201
|
+
} catch {
|
|
202
|
+
// Ignore logging errors
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getHealthHistory(limit: number = 20): Array<{
|
|
207
|
+
timestamp: Date;
|
|
208
|
+
health: ContextHealth['health'];
|
|
209
|
+
utilizationPercent: number;
|
|
210
|
+
driftScore: number;
|
|
211
|
+
}> {
|
|
212
|
+
try {
|
|
213
|
+
const stmt = this.db.prepare(`
|
|
214
|
+
SELECT timestamp, health, utilization_percent, drift_score
|
|
215
|
+
FROM context_health_history
|
|
216
|
+
ORDER BY timestamp DESC
|
|
217
|
+
LIMIT ?
|
|
218
|
+
`);
|
|
219
|
+
|
|
220
|
+
const rows = stmt.all(limit) as Array<{
|
|
221
|
+
timestamp: number;
|
|
222
|
+
health: string;
|
|
223
|
+
utilization_percent: number;
|
|
224
|
+
drift_score: number;
|
|
225
|
+
}>;
|
|
226
|
+
|
|
227
|
+
return rows.map(row => ({
|
|
228
|
+
timestamp: new Date(row.timestamp * 1000),
|
|
229
|
+
health: row.health as ContextHealth['health'],
|
|
230
|
+
utilizationPercent: row.utilization_percent,
|
|
231
|
+
driftScore: row.drift_score
|
|
232
|
+
}));
|
|
233
|
+
} catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
estimateTokens(text: string): number {
|
|
239
|
+
// Rough estimation: ~4 characters per token for English
|
|
240
|
+
// This is a simple heuristic; real implementation would use a proper tokenizer
|
|
241
|
+
return Math.ceil(text.length / 4);
|
|
242
|
+
}
|
|
243
|
+
}
|