@smallironman/mcp-memory-keeper 0.12.2-fork1

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 (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
@@ -0,0 +1,476 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FeatureFlagManager = void 0;
4
+ const uuid_1 = require("uuid");
5
+ class FeatureFlagManager {
6
+ db;
7
+ constructor(db) {
8
+ this.db = db;
9
+ this.initializeTables();
10
+ }
11
+ initializeTables() {
12
+ this.db.getDatabase().exec(`
13
+ CREATE TABLE IF NOT EXISTS feature_flags (
14
+ id TEXT PRIMARY KEY,
15
+ name TEXT NOT NULL,
16
+ key TEXT UNIQUE NOT NULL,
17
+ enabled BOOLEAN DEFAULT false,
18
+ description TEXT,
19
+ environments TEXT, -- JSON array
20
+ users TEXT, -- JSON array
21
+ percentage INTEGER DEFAULT 0,
22
+ enabled_from TIMESTAMP,
23
+ enabled_until TIMESTAMP,
24
+ category TEXT,
25
+ tags TEXT, -- JSON array
26
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
27
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
28
+ created_by TEXT,
29
+ last_modified_by TEXT
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS feature_flag_evaluations (
33
+ id TEXT PRIMARY KEY,
34
+ flag_id TEXT NOT NULL,
35
+ flag_key TEXT NOT NULL,
36
+ enabled BOOLEAN NOT NULL,
37
+ reason TEXT NOT NULL,
38
+ context TEXT, -- JSON
39
+ evaluated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40
+ FOREIGN KEY (flag_id) REFERENCES feature_flags(id)
41
+ );
42
+
43
+ CREATE TABLE IF NOT EXISTS feature_flag_audit (
44
+ id TEXT PRIMARY KEY,
45
+ flag_id TEXT NOT NULL,
46
+ flag_key TEXT NOT NULL,
47
+ action TEXT NOT NULL, -- 'created', 'updated', 'deleted', 'enabled', 'disabled'
48
+ old_value TEXT, -- JSON
49
+ new_value TEXT, -- JSON
50
+ user_id TEXT,
51
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
52
+ );
53
+
54
+ CREATE INDEX IF NOT EXISTS idx_feature_flags_key ON feature_flags(key);
55
+ CREATE INDEX IF NOT EXISTS idx_feature_flags_enabled ON feature_flags(enabled);
56
+ CREATE INDEX IF NOT EXISTS idx_feature_flags_category ON feature_flags(category);
57
+ CREATE INDEX IF NOT EXISTS idx_evaluations_flag ON feature_flag_evaluations(flag_id);
58
+ CREATE INDEX IF NOT EXISTS idx_evaluations_time ON feature_flag_evaluations(evaluated_at);
59
+ CREATE INDEX IF NOT EXISTS idx_audit_flag ON feature_flag_audit(flag_id);
60
+ CREATE INDEX IF NOT EXISTS idx_audit_time ON feature_flag_audit(timestamp);
61
+ `);
62
+ }
63
+ createFlag(flag) {
64
+ const id = (0, uuid_1.v4)();
65
+ const now = new Date().toISOString();
66
+ const flagWithDefaults = {
67
+ id,
68
+ createdAt: now,
69
+ updatedAt: now,
70
+ ...flag,
71
+ enabled: flag.enabled ?? false,
72
+ percentage: flag.percentage ?? undefined,
73
+ };
74
+ this.db
75
+ .getDatabase()
76
+ .prepare(`
77
+ INSERT INTO feature_flags (
78
+ id, name, key, enabled, description, environments, users, percentage,
79
+ enabled_from, enabled_until, category, tags, created_at, updated_at,
80
+ created_by, last_modified_by
81
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
82
+ `)
83
+ .run(id, flag.name, flag.key, flag.enabled ? 1 : 0, flag.description, flag.environments ? JSON.stringify(flag.environments) : null, flag.users ? JSON.stringify(flag.users) : null, flag.percentage !== undefined ? flag.percentage : null, flag.enabledFrom, flag.enabledUntil, flag.category, flag.tags ? JSON.stringify(flag.tags) : null, now, now, flag.createdBy, flag.lastModifiedBy);
84
+ // Log creation
85
+ this.logAudit(id, flag.key, 'created', null, flagWithDefaults, flag.createdBy);
86
+ return id;
87
+ }
88
+ updateFlag(id, updates, userId) {
89
+ const existing = this.getFlag(id);
90
+ if (!existing) {
91
+ throw new Error(`Feature flag not found: ${id}`);
92
+ }
93
+ const updated = { ...existing, ...updates, id, updatedAt: new Date().toISOString() };
94
+ if (userId) {
95
+ updated.lastModifiedBy = userId;
96
+ }
97
+ this.db
98
+ .getDatabase()
99
+ .prepare(`
100
+ UPDATE feature_flags SET
101
+ name = ?, enabled = ?, description = ?, environments = ?, users = ?,
102
+ percentage = ?, enabled_from = ?, enabled_until = ?, category = ?,
103
+ tags = ?, updated_at = ?, last_modified_by = ?
104
+ WHERE id = ?
105
+ `)
106
+ .run(updated.name, updated.enabled ? 1 : 0, updated.description, updated.environments ? JSON.stringify(updated.environments) : null, updated.users ? JSON.stringify(updated.users) : null, updated.percentage !== undefined ? updated.percentage : null, updated.enabledFrom, updated.enabledUntil, updated.category, updated.tags ? JSON.stringify(updated.tags) : null, updated.updatedAt, updated.lastModifiedBy, id);
107
+ // Log update
108
+ this.logAudit(id, existing.key, 'updated', existing, updated, userId);
109
+ }
110
+ getFlag(id) {
111
+ const row = this.db
112
+ .getDatabase()
113
+ .prepare(`
114
+ SELECT * FROM feature_flags WHERE id = ?
115
+ `)
116
+ .get(id);
117
+ if (!row)
118
+ return null;
119
+ return this.rowToFlag(row);
120
+ }
121
+ getFlagByKey(key) {
122
+ const row = this.db
123
+ .getDatabase()
124
+ .prepare(`
125
+ SELECT * FROM feature_flags WHERE key = ?
126
+ `)
127
+ .get(key);
128
+ if (!row)
129
+ return null;
130
+ return this.rowToFlag(row);
131
+ }
132
+ listFlags(options = {}) {
133
+ let query = 'SELECT * FROM feature_flags WHERE 1=1';
134
+ const params = [];
135
+ if (options.category) {
136
+ query += ' AND category = ?';
137
+ params.push(options.category);
138
+ }
139
+ if (options.enabled !== undefined) {
140
+ query += ' AND enabled = ?';
141
+ params.push(options.enabled ? 1 : 0);
142
+ }
143
+ if (options.environment) {
144
+ query += ' AND (environments IS NULL OR environments LIKE ?)';
145
+ params.push(`%"${options.environment}"%`);
146
+ }
147
+ if (options.tag) {
148
+ query += ' AND tags LIKE ?';
149
+ params.push(`%"${options.tag}"%`);
150
+ }
151
+ query += ' ORDER BY updated_at DESC';
152
+ if (options.limit) {
153
+ query += ' LIMIT ?';
154
+ params.push(options.limit);
155
+ }
156
+ const rows = this.db
157
+ .getDatabase()
158
+ .prepare(query)
159
+ .all(...params);
160
+ return rows.map(row => this.rowToFlag(row));
161
+ }
162
+ deleteFlag(id, userId) {
163
+ const existing = this.getFlag(id);
164
+ if (!existing) {
165
+ throw new Error(`Feature flag not found: ${id}`);
166
+ }
167
+ // Delete related evaluation records first to avoid foreign key constraints
168
+ this.db
169
+ .getDatabase()
170
+ .prepare(`
171
+ DELETE FROM feature_flag_evaluations WHERE flag_id = ?
172
+ `)
173
+ .run(id);
174
+ // Now delete the flag (audit log can stay for historical purposes)
175
+ this.db
176
+ .getDatabase()
177
+ .prepare(`
178
+ DELETE FROM feature_flags WHERE id = ?
179
+ `)
180
+ .run(id);
181
+ // Log deletion after removing the flag
182
+ this.logAudit(id, existing.key, 'deleted', existing, null, userId);
183
+ }
184
+ evaluateFlag(key, context = {}) {
185
+ const flag = this.getFlagByKey(key);
186
+ const timestamp = context.timestamp || new Date().toISOString();
187
+ if (!flag) {
188
+ const evaluation = {
189
+ flag: {
190
+ id: '',
191
+ name: key,
192
+ key,
193
+ enabled: false,
194
+ createdAt: timestamp,
195
+ updatedAt: timestamp,
196
+ },
197
+ enabled: false,
198
+ reason: 'Flag not found',
199
+ context: { ...context, timestamp },
200
+ };
201
+ this.logEvaluation('', key, false, 'Flag not found', evaluation.context);
202
+ return evaluation;
203
+ }
204
+ let enabled = false;
205
+ let reason = 'Flag disabled';
206
+ // Check if flag is globally enabled
207
+ if (!flag.enabled) {
208
+ enabled = false;
209
+ reason = 'Flag globally disabled';
210
+ }
211
+ // Check date constraints
212
+ else if (flag.enabledFrom && new Date(timestamp) < new Date(flag.enabledFrom)) {
213
+ enabled = false;
214
+ reason = `Flag not yet active (starts ${flag.enabledFrom})`;
215
+ }
216
+ else if (flag.enabledUntil && new Date(timestamp) > new Date(flag.enabledUntil)) {
217
+ enabled = false;
218
+ reason = `Flag expired (ended ${flag.enabledUntil})`;
219
+ }
220
+ // Check environment constraints
221
+ else if (flag.environments && flag.environments.length > 0 && context.environment) {
222
+ if (flag.environments.includes(context.environment)) {
223
+ enabled = true;
224
+ reason = `Enabled for environment: ${context.environment}`;
225
+ }
226
+ else {
227
+ enabled = false;
228
+ reason = `Not enabled for environment: ${context.environment}`;
229
+ }
230
+ }
231
+ // Check user constraints
232
+ else if (flag.users && flag.users.length > 0 && context.userId) {
233
+ if (flag.users.includes(context.userId)) {
234
+ enabled = true;
235
+ reason = `Enabled for user: ${context.userId}`;
236
+ }
237
+ else {
238
+ enabled = false;
239
+ reason = `Not enabled for user: ${context.userId}`;
240
+ }
241
+ }
242
+ // Check percentage rollout
243
+ else if (flag.percentage !== undefined && flag.percentage !== null) {
244
+ if (flag.percentage === 0) {
245
+ enabled = false;
246
+ reason = `Disabled by percentage rollout (${flag.percentage}%)`;
247
+ }
248
+ else if (flag.percentage === 100) {
249
+ enabled = true;
250
+ reason = `Enabled by percentage rollout (${flag.percentage}%)`;
251
+ }
252
+ else {
253
+ // Use hash of key + userId/environment for consistent percentage evaluation
254
+ const hashInput = key + (context.userId || context.environment || 'anonymous');
255
+ const hash = this.simpleHash(hashInput);
256
+ const userPercentile = hash % 100;
257
+ if (userPercentile < flag.percentage) {
258
+ enabled = true;
259
+ reason = `Enabled by percentage rollout (${flag.percentage}%)`;
260
+ }
261
+ else {
262
+ enabled = false;
263
+ reason = `Disabled by percentage rollout (${flag.percentage}%, user at ${userPercentile}%)`;
264
+ }
265
+ }
266
+ }
267
+ // Default to flag enabled state
268
+ else {
269
+ enabled = flag.enabled;
270
+ reason = enabled ? 'Flag enabled' : 'Flag disabled';
271
+ }
272
+ const evaluation = {
273
+ flag,
274
+ enabled,
275
+ reason,
276
+ context: { ...context, timestamp },
277
+ };
278
+ // Log evaluation
279
+ this.logEvaluation(flag.id, key, enabled, reason, evaluation.context);
280
+ return evaluation;
281
+ }
282
+ isEnabled(key, context = {}) {
283
+ return this.evaluateFlag(key, context).enabled;
284
+ }
285
+ getStats() {
286
+ const flags = this.listFlags();
287
+ const enabledFlags = flags.filter(f => f.enabled);
288
+ // By category
289
+ const byCategory = {};
290
+ flags.forEach(flag => {
291
+ const category = flag.category || 'uncategorized';
292
+ if (!byCategory[category]) {
293
+ byCategory[category] = { count: 0, enabled: 0 };
294
+ }
295
+ byCategory[category].count++;
296
+ if (flag.enabled) {
297
+ byCategory[category].enabled++;
298
+ }
299
+ });
300
+ // By environment
301
+ const byEnvironment = {};
302
+ flags.forEach(flag => {
303
+ const environments = flag.environments || ['default'];
304
+ environments.forEach(env => {
305
+ if (!byEnvironment[env]) {
306
+ byEnvironment[env] = { count: 0, enabled: 0 };
307
+ }
308
+ byEnvironment[env].count++;
309
+ if (flag.enabled) {
310
+ byEnvironment[env].enabled++;
311
+ }
312
+ });
313
+ });
314
+ // Scheduled changes
315
+ const now = new Date();
316
+ const toEnable = flags
317
+ .filter(f => !f.enabled && f.enabledFrom && new Date(f.enabledFrom) > now)
318
+ .map(f => ({ flag: f.name, date: f.enabledFrom }));
319
+ const toDisable = flags
320
+ .filter(f => f.enabled && f.enabledUntil && new Date(f.enabledUntil) > now)
321
+ .map(f => ({ flag: f.name, date: f.enabledUntil }));
322
+ // Recent activity
323
+ const recentActivity = this.db
324
+ .getDatabase()
325
+ .prepare(`
326
+ SELECT flag_key, action, timestamp, user_id
327
+ FROM feature_flag_audit
328
+ ORDER BY timestamp DESC
329
+ LIMIT 10
330
+ `)
331
+ .all();
332
+ return {
333
+ totalFlags: flags.length,
334
+ enabledFlags: enabledFlags.length,
335
+ disabledFlags: flags.length - enabledFlags.length,
336
+ byCategory,
337
+ byEnvironment,
338
+ scheduledChanges: { toEnable, toDisable },
339
+ recentActivity: recentActivity.map(row => ({
340
+ flag: row.flag_key,
341
+ action: row.action,
342
+ timestamp: row.timestamp,
343
+ user: row.user_id,
344
+ })),
345
+ };
346
+ }
347
+ getEvaluationHistory(flagKey, limit = 100) {
348
+ return this.db
349
+ .getDatabase()
350
+ .prepare(`
351
+ SELECT * FROM feature_flag_evaluations
352
+ WHERE flag_key = ?
353
+ ORDER BY evaluated_at DESC
354
+ LIMIT ?
355
+ `)
356
+ .all(flagKey, limit);
357
+ }
358
+ getAuditLog(flagId, limit = 50) {
359
+ const query = flagId
360
+ ? 'SELECT * FROM feature_flag_audit WHERE flag_id = ? ORDER BY timestamp DESC LIMIT ?'
361
+ : 'SELECT * FROM feature_flag_audit ORDER BY timestamp DESC LIMIT ?';
362
+ const params = flagId ? [flagId, limit] : [limit];
363
+ return this.db
364
+ .getDatabase()
365
+ .prepare(query)
366
+ .all(...params);
367
+ }
368
+ // Bulk operations
369
+ enableFlag(key, userId) {
370
+ const flag = this.getFlagByKey(key);
371
+ if (!flag) {
372
+ throw new Error(`Feature flag not found: ${key}`);
373
+ }
374
+ this.updateFlag(flag.id, { enabled: true }, userId);
375
+ this.logAudit(flag.id, key, 'enabled', { enabled: false }, { enabled: true }, userId);
376
+ }
377
+ disableFlag(key, userId) {
378
+ const flag = this.getFlagByKey(key);
379
+ if (!flag) {
380
+ throw new Error(`Feature flag not found: ${key}`);
381
+ }
382
+ this.updateFlag(flag.id, { enabled: false }, userId);
383
+ this.logAudit(flag.id, key, 'disabled', { enabled: true }, { enabled: false }, userId);
384
+ }
385
+ // Utility methods
386
+ rowToFlag(row) {
387
+ return {
388
+ id: row.id,
389
+ name: row.name,
390
+ key: row.key,
391
+ enabled: Boolean(row.enabled),
392
+ description: row.description,
393
+ environments: row.environments ? JSON.parse(row.environments) : undefined,
394
+ users: row.users ? JSON.parse(row.users) : undefined,
395
+ percentage: row.percentage,
396
+ enabledFrom: row.enabled_from,
397
+ enabledUntil: row.enabled_until,
398
+ category: row.category,
399
+ tags: row.tags ? JSON.parse(row.tags) : undefined,
400
+ createdAt: row.created_at,
401
+ updatedAt: row.updated_at,
402
+ createdBy: row.created_by,
403
+ lastModifiedBy: row.last_modified_by,
404
+ };
405
+ }
406
+ logEvaluation(flagId, flagKey, enabled, reason, context) {
407
+ // Only log if flag exists (flagId is not empty)
408
+ if (flagId) {
409
+ this.db
410
+ .getDatabase()
411
+ .prepare(`
412
+ INSERT INTO feature_flag_evaluations (id, flag_id, flag_key, enabled, reason, context)
413
+ VALUES (?, ?, ?, ?, ?, ?)
414
+ `)
415
+ .run((0, uuid_1.v4)(), flagId, flagKey, enabled ? 1 : 0, reason, JSON.stringify(context));
416
+ }
417
+ }
418
+ logAudit(flagId, flagKey, action, oldValue, newValue, userId) {
419
+ this.db
420
+ .getDatabase()
421
+ .prepare(`
422
+ INSERT INTO feature_flag_audit (id, flag_id, flag_key, action, old_value, new_value, user_id)
423
+ VALUES (?, ?, ?, ?, ?, ?, ?)
424
+ `)
425
+ .run((0, uuid_1.v4)(), flagId, flagKey, action, oldValue ? JSON.stringify(oldValue) : null, newValue ? JSON.stringify(newValue) : null, userId);
426
+ }
427
+ simpleHash(str) {
428
+ let hash = 0;
429
+ for (let i = 0; i < str.length; i++) {
430
+ const char = str.charCodeAt(i);
431
+ hash = (hash << 5) - hash + char;
432
+ hash = hash & hash; // Convert to 32-bit integer
433
+ }
434
+ return Math.abs(hash);
435
+ }
436
+ // Predefined feature flags for common features
437
+ static getDefaultFlags() {
438
+ return [
439
+ {
440
+ name: 'Enhanced Search',
441
+ key: 'enhanced_search',
442
+ enabled: true,
443
+ description: 'Enable enhanced search capabilities with filters',
444
+ category: 'search',
445
+ tags: ['core', 'stable'],
446
+ },
447
+ {
448
+ name: 'Beta Features',
449
+ key: 'beta_features',
450
+ enabled: false,
451
+ description: 'Enable experimental beta features',
452
+ category: 'experimental',
453
+ tags: ['beta', 'experimental'],
454
+ environments: ['development', 'staging'],
455
+ },
456
+ {
457
+ name: 'Advanced Analytics',
458
+ key: 'advanced_analytics',
459
+ enabled: false,
460
+ description: 'Enable detailed analytics and metrics',
461
+ category: 'analytics',
462
+ tags: ['analytics', 'metrics'],
463
+ percentage: 25, // 25% rollout
464
+ },
465
+ {
466
+ name: 'Auto Compression',
467
+ key: 'auto_compression',
468
+ enabled: true,
469
+ description: 'Automatically compress old context data',
470
+ category: 'performance',
471
+ tags: ['performance', 'storage'],
472
+ },
473
+ ];
474
+ }
475
+ }
476
+ exports.FeatureFlagManager = FeatureFlagManager;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.GitOperations = void 0;
37
+ const simple_git_1 = require("simple-git");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ class GitOperations {
41
+ git;
42
+ constructor(workingDirectory = process.cwd()) {
43
+ this.git = (0, simple_git_1.simpleGit)(workingDirectory);
44
+ }
45
+ async getGitInfo() {
46
+ try {
47
+ // Check if we're in a git repository
48
+ const isRepo = await this.isGitRepository();
49
+ if (!isRepo) {
50
+ return {
51
+ status: 'Not a git repository',
52
+ branch: 'none',
53
+ isGitRepo: false,
54
+ };
55
+ }
56
+ // Get status and branch info
57
+ const [status, branch] = await Promise.all([this.git.status(), this.git.branch()]);
58
+ return {
59
+ status: JSON.stringify({
60
+ modified: status.modified,
61
+ created: status.created,
62
+ deleted: status.deleted,
63
+ staged: status.staged,
64
+ not_added: status.not_added, // untracked files
65
+ ahead: status.ahead,
66
+ behind: status.behind,
67
+ }),
68
+ branch: branch.current,
69
+ isGitRepo: true,
70
+ };
71
+ }
72
+ catch (_error) {
73
+ // Handle any git errors gracefully
74
+ return {
75
+ status: `Git error: ${_error instanceof Error ? _error.message : String(_error)}`,
76
+ branch: 'error',
77
+ isGitRepo: false,
78
+ };
79
+ }
80
+ }
81
+ async getCurrentBranch() {
82
+ try {
83
+ // First try using git command
84
+ const branch = await this.git.branch();
85
+ if (branch.current && branch.current.trim() !== '') {
86
+ return branch.current;
87
+ }
88
+ // Fallback to reading .git/HEAD
89
+ const gitHeadPath = path.join(process.cwd(), '.git', 'HEAD');
90
+ if (fs.existsSync(gitHeadPath)) {
91
+ const headContent = fs.readFileSync(gitHeadPath, 'utf8').trim();
92
+ if (headContent.startsWith('ref: refs/heads/')) {
93
+ return headContent.replace('ref: refs/heads/', '');
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ catch (_error) {
99
+ return null;
100
+ }
101
+ }
102
+ async safeCommit(message) {
103
+ try {
104
+ // Check if we're in a git repository
105
+ const isRepo = await this.isGitRepository();
106
+ if (!isRepo) {
107
+ return {
108
+ success: false,
109
+ error: 'Not a git repository',
110
+ };
111
+ }
112
+ // Check if there are changes to commit
113
+ const status = await this.git.status();
114
+ if (status.files.length === 0) {
115
+ return {
116
+ success: false,
117
+ error: 'No changes to commit',
118
+ };
119
+ }
120
+ // Add all changes and commit
121
+ await this.git.add('.');
122
+ const commitResult = await this.git.commit(message);
123
+ return {
124
+ success: true,
125
+ commit: commitResult.commit,
126
+ };
127
+ }
128
+ catch (_error) {
129
+ return {
130
+ success: false,
131
+ error: _error instanceof Error ? _error.message : String(_error),
132
+ };
133
+ }
134
+ }
135
+ async isGitRepository() {
136
+ try {
137
+ await this.git.checkIsRepo();
138
+ return true;
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ }
145
+ exports.GitOperations = GitOperations;