k0ntext 3.3.0 → 3.3.1
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/README.md +15 -0
- package/dist/cli/repl/index.d.ts +3 -0
- package/dist/cli/repl/index.d.ts.map +1 -1
- package/dist/cli/repl/index.js +95 -89
- package/dist/cli/repl/index.js.map +1 -1
- package/dist/cli/repl/tui/panels/config.d.ts +71 -0
- package/dist/cli/repl/tui/panels/config.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/config.js +392 -0
- package/dist/cli/repl/tui/panels/config.js.map +1 -0
- package/dist/cli/repl/tui/panels/drift.d.ts +95 -0
- package/dist/cli/repl/tui/panels/drift.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/drift.js +353 -0
- package/dist/cli/repl/tui/panels/drift.js.map +1 -0
- package/dist/cli/repl/tui/panels/indexing.d.ts +86 -0
- package/dist/cli/repl/tui/panels/indexing.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/indexing.js +254 -0
- package/dist/cli/repl/tui/panels/indexing.js.map +1 -0
- package/dist/cli/repl/tui/panels/search.d.ts +66 -0
- package/dist/cli/repl/tui/panels/search.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/search.js +215 -0
- package/dist/cli/repl/tui/panels/search.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/repl/index.ts +102 -99
- package/src/cli/repl/tui/panels/config.ts +457 -0
- package/src/cli/repl/tui/panels/drift.ts +458 -0
- package/src/cli/repl/tui/panels/indexing.ts +324 -0
- package/src/cli/repl/tui/panels/search.ts +272 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift Detection Panel
|
|
3
|
+
*
|
|
4
|
+
* Enhanced drift detection with detailed analysis and visualization
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { K0NTEXT_THEME } from '../theme.js';
|
|
11
|
+
import { DatabaseClient } from '../../../../db/client.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Drift type
|
|
15
|
+
*/
|
|
16
|
+
export type DriftType = 'file_dates' | 'structure' | 'git_diff';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Drift severity
|
|
20
|
+
*/
|
|
21
|
+
export type DriftSeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* File drift information
|
|
25
|
+
*/
|
|
26
|
+
interface FileDrift {
|
|
27
|
+
filePath: string;
|
|
28
|
+
fileType: 'doc' | 'code' | 'config';
|
|
29
|
+
lastModified: Date;
|
|
30
|
+
lastIndexed: Date;
|
|
31
|
+
daysSince: number;
|
|
32
|
+
severity: DriftSeverity;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Structure drift information
|
|
37
|
+
*/
|
|
38
|
+
interface StructureDrift {
|
|
39
|
+
addedFiles: string[];
|
|
40
|
+
removedFiles: string[];
|
|
41
|
+
severity: DriftSeverity;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Git drift information
|
|
46
|
+
*/
|
|
47
|
+
interface GitDrift {
|
|
48
|
+
committedChanges: number;
|
|
49
|
+
uncommittedChanges: string[];
|
|
50
|
+
severity: DriftSeverity;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Combined drift report
|
|
55
|
+
*/
|
|
56
|
+
export interface DriftReport {
|
|
57
|
+
fileDrifts: FileDrift[];
|
|
58
|
+
structureDrift: StructureDrift | null;
|
|
59
|
+
gitDrift: GitDrift | null;
|
|
60
|
+
overallSeverity: DriftSeverity;
|
|
61
|
+
summary: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Drift Detection Panel
|
|
66
|
+
*/
|
|
67
|
+
export class DriftDetectionPanel {
|
|
68
|
+
private projectRoot: string;
|
|
69
|
+
|
|
70
|
+
constructor(projectRoot: string) {
|
|
71
|
+
this.projectRoot = projectRoot;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run complete drift analysis
|
|
76
|
+
*/
|
|
77
|
+
async analyze(): Promise<DriftReport> {
|
|
78
|
+
const [fileDrifts, structureDrift, gitDrift] = await Promise.all([
|
|
79
|
+
this.analyzeFileDates(),
|
|
80
|
+
this.analyzeStructure(),
|
|
81
|
+
this.analyzeGitDiff()
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
// Determine overall severity
|
|
85
|
+
const severities: DriftSeverity[] = [
|
|
86
|
+
this.getDriftSeverity(fileDrifts),
|
|
87
|
+
structureDrift?.severity || 'low',
|
|
88
|
+
gitDrift?.severity || 'low'
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const overallSeverity: DriftSeverity = severities.includes('critical')
|
|
92
|
+
? 'critical'
|
|
93
|
+
: severities.includes('high')
|
|
94
|
+
? 'high'
|
|
95
|
+
: severities.includes('medium')
|
|
96
|
+
? 'medium'
|
|
97
|
+
: 'low';
|
|
98
|
+
|
|
99
|
+
const summary = this.generateSummary(fileDrifts, structureDrift, gitDrift);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
fileDrifts,
|
|
103
|
+
structureDrift,
|
|
104
|
+
gitDrift,
|
|
105
|
+
overallSeverity,
|
|
106
|
+
summary
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Analyze file date drifts
|
|
112
|
+
*/
|
|
113
|
+
private async analyzeFileDates(): Promise<FileDrift[]> {
|
|
114
|
+
const drifts: FileDrift[] = [];
|
|
115
|
+
const thresholdDays = 7; // Files not updated in 7 days are considered drifted
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const db = new DatabaseClient(this.projectRoot);
|
|
119
|
+
const items = db.getAllItems();
|
|
120
|
+
|
|
121
|
+
const now = new Date();
|
|
122
|
+
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
if (!item.updatedAt) continue;
|
|
125
|
+
|
|
126
|
+
const lastIndexed = new Date(item.updatedAt);
|
|
127
|
+
const daysSince = Math.floor((now.getTime() - lastIndexed.getTime()) / (1000 * 60 * 60 * 24));
|
|
128
|
+
|
|
129
|
+
// Only include items that are drifted (> 7 days old)
|
|
130
|
+
if (daysSince <= thresholdDays) continue;
|
|
131
|
+
|
|
132
|
+
// Determine file type from item type
|
|
133
|
+
let fileType: 'doc' | 'code' | 'config' = 'doc';
|
|
134
|
+
if (item.type === 'code' || item.type === 'command' || item.type === 'commit') {
|
|
135
|
+
fileType = 'code';
|
|
136
|
+
} else if (item.type === 'config' || item.type === 'tool_config') {
|
|
137
|
+
fileType = 'config';
|
|
138
|
+
} else {
|
|
139
|
+
fileType = 'doc';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Determine severity based on days since update
|
|
143
|
+
let severity: DriftSeverity = 'medium';
|
|
144
|
+
if (daysSince > 30) {
|
|
145
|
+
severity = 'critical';
|
|
146
|
+
} else if (daysSince > 14) {
|
|
147
|
+
severity = 'high';
|
|
148
|
+
} else {
|
|
149
|
+
severity = 'medium';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
drifts.push({
|
|
153
|
+
filePath: item.filePath || item.id,
|
|
154
|
+
fileType,
|
|
155
|
+
lastModified: lastIndexed,
|
|
156
|
+
lastIndexed,
|
|
157
|
+
daysSince,
|
|
158
|
+
severity
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
db.close();
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// If database is not available, return empty array
|
|
165
|
+
console.error('Failed to analyze file dates:', error);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return drifts;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Analyze structure changes
|
|
173
|
+
*/
|
|
174
|
+
private async analyzeStructure(): Promise<StructureDrift | null> {
|
|
175
|
+
const addedFiles: string[] = [];
|
|
176
|
+
const removedFiles: string[] = [];
|
|
177
|
+
|
|
178
|
+
// Check for .k0ntext directory
|
|
179
|
+
const k0ntextDir = path.join(this.projectRoot, '.k0ntext');
|
|
180
|
+
if (!fs.existsSync(k0ntextDir)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Could scan for untracked files
|
|
185
|
+
// This is a simplified implementation
|
|
186
|
+
|
|
187
|
+
let severity: DriftSeverity = 'low';
|
|
188
|
+
const totalChanges = addedFiles.length + removedFiles.length;
|
|
189
|
+
|
|
190
|
+
if (totalChanges > 50) severity = 'critical';
|
|
191
|
+
else if (totalChanges > 20) severity = 'high';
|
|
192
|
+
else if (totalChanges > 5) severity = 'medium';
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
addedFiles,
|
|
196
|
+
removedFiles,
|
|
197
|
+
severity
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Analyze git diff for changes
|
|
203
|
+
*/
|
|
204
|
+
private async analyzeGitDiff(): Promise<GitDrift | null> {
|
|
205
|
+
const gitDir = path.join(this.projectRoot, '.git');
|
|
206
|
+
if (!fs.existsSync(gitDir)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check for uncommitted changes
|
|
211
|
+
const uncommittedChanges: string[] = [];
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
// Use git diff to check for changes
|
|
215
|
+
const { execSync } = require('child_process');
|
|
216
|
+
|
|
217
|
+
// Check for modified files
|
|
218
|
+
const modified = execSync('git diff --name-only', {
|
|
219
|
+
cwd: this.projectRoot,
|
|
220
|
+
encoding: 'utf-8',
|
|
221
|
+
stdio: 'pipe'
|
|
222
|
+
}) as string;
|
|
223
|
+
|
|
224
|
+
if (modified.trim()) {
|
|
225
|
+
uncommittedChanges.push(...modified.trim().split('\n').filter(Boolean));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for untracked files
|
|
229
|
+
const untracked = execSync('git ls-files --others --exclude-standard', {
|
|
230
|
+
cwd: this.projectRoot,
|
|
231
|
+
encoding: 'utf-8',
|
|
232
|
+
stdio: 'pipe'
|
|
233
|
+
}) as string;
|
|
234
|
+
|
|
235
|
+
if (untracked.trim()) {
|
|
236
|
+
uncommittedChanges.push(...untracked.trim().split('\n').filter(Boolean));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Get commit count
|
|
240
|
+
const commitCountStr = execSync('git rev-list --count HEAD', {
|
|
241
|
+
cwd: this.projectRoot,
|
|
242
|
+
encoding: 'utf-8',
|
|
243
|
+
stdio: 'pipe'
|
|
244
|
+
}) as string;
|
|
245
|
+
|
|
246
|
+
const commitCount = parseInt(commitCountStr.trim() || '0');
|
|
247
|
+
|
|
248
|
+
let severity: DriftSeverity = 'low';
|
|
249
|
+
const totalChanges = uncommittedChanges.length;
|
|
250
|
+
|
|
251
|
+
if (totalChanges > 20) severity = 'critical';
|
|
252
|
+
else if (totalChanges > 10) severity = 'high';
|
|
253
|
+
else if (totalChanges > 5) severity = 'medium';
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
committedChanges: commitCount,
|
|
257
|
+
uncommittedChanges,
|
|
258
|
+
severity
|
|
259
|
+
};
|
|
260
|
+
} catch {
|
|
261
|
+
// Git not available or error
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get drift severity from file drifts
|
|
268
|
+
*/
|
|
269
|
+
private getDriftSeverity(drifts: FileDrift[]): DriftSeverity {
|
|
270
|
+
if (drifts.length === 0) return 'low';
|
|
271
|
+
|
|
272
|
+
const criticalDrifts = drifts.filter(d => d.severity === 'critical').length;
|
|
273
|
+
const highDrifts = drifts.filter(d => d.severity === 'high').length;
|
|
274
|
+
const mediumDrifts = drifts.filter(d => d.severity === 'medium').length;
|
|
275
|
+
|
|
276
|
+
if (criticalDrifts > 5) return 'critical';
|
|
277
|
+
if (criticalDrifts > 2 || highDrifts > 10) return 'high';
|
|
278
|
+
if (mediumDrifts > 10 || highDrifts > 3) return 'medium';
|
|
279
|
+
return 'low';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Generate summary text
|
|
284
|
+
*/
|
|
285
|
+
private generateSummary(
|
|
286
|
+
fileDrifts: FileDrift[],
|
|
287
|
+
structureDrift: StructureDrift | null,
|
|
288
|
+
gitDrift: GitDrift | null
|
|
289
|
+
): string {
|
|
290
|
+
const parts: string[] = [];
|
|
291
|
+
|
|
292
|
+
const fileCount = fileDrifts.length;
|
|
293
|
+
const structChanges = structureDrift ? structureDrift.addedFiles.length + structureDrift.removedFiles.length : 0;
|
|
294
|
+
const gitChanges = gitDrift ? gitDrift.uncommittedChanges.length : 0;
|
|
295
|
+
|
|
296
|
+
if (fileCount === 0 && structChanges === 0 && gitChanges === 0) {
|
|
297
|
+
return 'All context files are up to date with your codebase.';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (fileCount > 0) {
|
|
301
|
+
parts.push(`${fileCount} files may be outdated`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (structChanges > 0) {
|
|
305
|
+
parts.push(`${structChanges} structural changes detected`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (gitChanges > 0) {
|
|
309
|
+
parts.push(`${gitChanges} uncommitted changes`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return parts.join(', ') || 'No drift detected';
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Display drift report
|
|
317
|
+
*/
|
|
318
|
+
displayReport(report: DriftReport): string {
|
|
319
|
+
const lines: string[] = [];
|
|
320
|
+
|
|
321
|
+
lines.push('');
|
|
322
|
+
lines.push(K0NTEXT_THEME.header('━━━ Documentation Drift Analysis ━━━'));
|
|
323
|
+
lines.push('');
|
|
324
|
+
|
|
325
|
+
// Overall severity
|
|
326
|
+
const severityEmoji = {
|
|
327
|
+
critical: K0NTEXT_THEME.error('🔴 Critical'),
|
|
328
|
+
high: K0NTEXT_THEME.warning('🟠 High'),
|
|
329
|
+
medium: K0NTEXT_THEME.warning('🟡 Medium'),
|
|
330
|
+
low: K0NTEXT_THEME.success('🟢 Good')
|
|
331
|
+
}[report.overallSeverity];
|
|
332
|
+
|
|
333
|
+
lines.push(` Overall Status: ${severityEmoji}`);
|
|
334
|
+
lines.push(` Summary: ${report.summary}`);
|
|
335
|
+
lines.push('');
|
|
336
|
+
|
|
337
|
+
// File drifts
|
|
338
|
+
if (report.fileDrifts.length > 0) {
|
|
339
|
+
lines.push(K0NTEXT_THEME.header('━━━ File Date Drifts ━──'));
|
|
340
|
+
|
|
341
|
+
const bySeverity = this.groupBySeverity(report.fileDrifts);
|
|
342
|
+
|
|
343
|
+
for (const [severity, drifts] of Object.entries(bySeverity)) {
|
|
344
|
+
if (drifts.length === 0) continue;
|
|
345
|
+
|
|
346
|
+
const severityLabel = {
|
|
347
|
+
critical: '🔴 Critical',
|
|
348
|
+
high: '🟠 High',
|
|
349
|
+
medium: '🟡 Medium',
|
|
350
|
+
low: '🟢 Low'
|
|
351
|
+
}[severity as DriftSeverity];
|
|
352
|
+
|
|
353
|
+
lines.push(` ${severityLabel} (${drifts.length} files):`);
|
|
354
|
+
|
|
355
|
+
for (const drift of drifts.slice(0, 5)) {
|
|
356
|
+
const icon = this.getFileTypeIcon(drift.fileType);
|
|
357
|
+
const days = drift.daysSince;
|
|
358
|
+
lines.push(` ${icon} ${K0NTEXT_THEME.dim(drift.filePath)}`);
|
|
359
|
+
lines.push(` ${K0NTEXT_THEME.dim(`not updated in ${days} days`)}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (drifts.length > 5) {
|
|
363
|
+
lines.push(` ${K0NTEXT_THEME.dim(`... and ${drifts.length - 5} more`)}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
lines.push('');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Structure drifts
|
|
371
|
+
if (report.structureDrift) {
|
|
372
|
+
lines.push(K0NTEXT_THEME.header('━━━ Structure Changes ━──'));
|
|
373
|
+
|
|
374
|
+
if (report.structureDrift.addedFiles.length > 0) {
|
|
375
|
+
lines.push(` ${K0NTEXT_THEME.success('+')} New files: ${report.structureDrift.addedFiles.length}`);
|
|
376
|
+
for (const file of report.structureDrift.addedFiles.slice(0, 5)) {
|
|
377
|
+
lines.push(` ${K0NTEXT_THEME.dim(file)}`);
|
|
378
|
+
}
|
|
379
|
+
if (report.structureDrift.addedFiles.length > 5) {
|
|
380
|
+
lines.push(` ${K0NTEXT_THEME.dim('... and more')}`);
|
|
381
|
+
}
|
|
382
|
+
lines.push('');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (report.structureDrift.removedFiles.length > 0) {
|
|
386
|
+
lines.push(` ${K0NTEXT_THEME.error('-')} Removed files: ${report.structureDrift.removedFiles.length}`);
|
|
387
|
+
for (const file of report.structureDrift.removedFiles.slice(0, 5)) {
|
|
388
|
+
lines.push(` ${K0NTEXT_THEME.dim(file)}`);
|
|
389
|
+
}
|
|
390
|
+
if (report.structureDrift.removedFiles.length > 5) {
|
|
391
|
+
lines.push(` ${K0NTEXT_THEME.dim('... and more')}`);
|
|
392
|
+
}
|
|
393
|
+
lines.push('');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Git drifts
|
|
398
|
+
if (report.gitDrift) {
|
|
399
|
+
lines.push(K0NTEXT_THEME.header('━━━ Git Changes ━──'));
|
|
400
|
+
|
|
401
|
+
if (report.gitDrift.uncommittedChanges.length > 0) {
|
|
402
|
+
lines.push(` Uncommitted changes: ${report.gitDrift.uncommittedChanges.length}`);
|
|
403
|
+
|
|
404
|
+
for (const file of report.gitDrift.uncommittedChanges.slice(0, 5)) {
|
|
405
|
+
const status = file.includes('(new file)') ? 'new' : 'modified';
|
|
406
|
+
const statusIcon = status === 'new' ? K0NTEXT_THEME.success('+') : K0NTEXT_THEME.warning('~');
|
|
407
|
+
lines.push(` ${statusIcon} ${K0NTEXT_THEME.dim(file)}`);
|
|
408
|
+
}
|
|
409
|
+
if (report.gitDrift.uncommittedChanges.length > 5) {
|
|
410
|
+
lines.push(` ${K0NTEXT_THEME.dim('... and more')}`);
|
|
411
|
+
}
|
|
412
|
+
lines.push('');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Recommendations
|
|
417
|
+
lines.push(K0NTEXT_THEME.header('━━━ Recommendations ━━━'));
|
|
418
|
+
|
|
419
|
+
if (report.overallSeverity === 'critical' || report.overallSeverity === 'high') {
|
|
420
|
+
lines.push(` ${K0NTEXT_THEME.warning('⚠ Urgent action recommended:')}`);
|
|
421
|
+
lines.push(` ${K0NTEXT_THEME.cyan('•')} Run ${K0NTEXT_THEME.highlight('index')} to update your context`);
|
|
422
|
+
lines.push(` ${K0NTEXT_THEME.cyan('•')} Commit your changes to keep tracking in sync`);
|
|
423
|
+
} else if (report.overallSeverity === 'medium') {
|
|
424
|
+
lines.push(` ${K0NTEXT_THEME.info('ℹ Consider updating soon:')}`);
|
|
425
|
+
lines.push(` ${K0NTEXT_THEME.cyan('•')} Run ${K0NTEXT_THEME.highlight('index')} to refresh context`);
|
|
426
|
+
} else {
|
|
427
|
+
lines.push(` ${K0NTEXT_THEME.success('✓ Your context is up to date!')}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
lines.push('');
|
|
431
|
+
|
|
432
|
+
return lines.join('\n');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Group drifts by severity
|
|
437
|
+
*/
|
|
438
|
+
private groupBySeverity(drifts: FileDrift[]): Record<string, FileDrift[]> {
|
|
439
|
+
return {
|
|
440
|
+
critical: drifts.filter(d => d.severity === 'critical'),
|
|
441
|
+
high: drifts.filter(d => d.severity === 'high'),
|
|
442
|
+
medium: drifts.filter(d => d.severity === 'medium'),
|
|
443
|
+
low: drifts.filter(d => d.severity === 'low')
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get file type icon
|
|
449
|
+
*/
|
|
450
|
+
private getFileTypeIcon(type: 'doc' | 'code' | 'config'): string {
|
|
451
|
+
const icons = {
|
|
452
|
+
doc: '📄',
|
|
453
|
+
code: '💻',
|
|
454
|
+
config: '⚙️'
|
|
455
|
+
};
|
|
456
|
+
return icons[type] || '📄';
|
|
457
|
+
}
|
|
458
|
+
}
|