@exaudeus/workrail 0.1.0 → 0.1.2

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 (30) hide show
  1. package/README.md +153 -189
  2. package/dist/application/services/classification-engine.d.ts +33 -0
  3. package/dist/application/services/classification-engine.js +258 -0
  4. package/dist/application/services/compression-service.d.ts +20 -0
  5. package/dist/application/services/compression-service.js +312 -0
  6. package/dist/application/services/context-management-service.d.ts +38 -0
  7. package/dist/application/services/context-management-service.js +301 -0
  8. package/dist/application/services/context-persistence-service.d.ts +45 -0
  9. package/dist/application/services/context-persistence-service.js +273 -0
  10. package/dist/cli/migrate-workflow.js +3 -2
  11. package/dist/infrastructure/storage/context-storage.d.ts +150 -0
  12. package/dist/infrastructure/storage/context-storage.js +40 -0
  13. package/dist/infrastructure/storage/filesystem-blob-storage.d.ts +27 -0
  14. package/dist/infrastructure/storage/filesystem-blob-storage.js +363 -0
  15. package/dist/infrastructure/storage/hybrid-context-storage.d.ts +29 -0
  16. package/dist/infrastructure/storage/hybrid-context-storage.js +400 -0
  17. package/dist/infrastructure/storage/migrations/001_initial_schema.sql +38 -0
  18. package/dist/infrastructure/storage/migrations/002_context_concurrency_enhancements.sql +234 -0
  19. package/dist/infrastructure/storage/migrations/003_classification_overrides.sql +20 -0
  20. package/dist/infrastructure/storage/sqlite-metadata-storage.d.ts +35 -0
  21. package/dist/infrastructure/storage/sqlite-metadata-storage.js +410 -0
  22. package/dist/infrastructure/storage/sqlite-migrator.d.ts +46 -0
  23. package/dist/infrastructure/storage/sqlite-migrator.js +293 -0
  24. package/dist/types/context-types.d.ts +236 -0
  25. package/dist/types/context-types.js +10 -0
  26. package/dist/utils/storage-security.js +1 -1
  27. package/package.json +4 -1
  28. package/workflows/coding-task-workflow-with-loops.json +434 -0
  29. package/workflows/mr-review-workflow.json +75 -26
  30. package/workflows/systemic-bug-investigation-with-loops.json +423 -0
@@ -0,0 +1,150 @@
1
+ import { CheckpointMetadata, SessionInfo, CompressedBlob, BlobMetadata } from '../../types/context-types';
2
+ export interface IContextStorage {
3
+ saveCheckpoint(metadata: CheckpointMetadata, blob: CompressedBlob): Promise<void>;
4
+ loadCheckpoint(checkpointId: string): Promise<{
5
+ metadata: CheckpointMetadata;
6
+ blob: CompressedBlob;
7
+ }>;
8
+ listCheckpoints(sessionId: string, limit?: number, offset?: number): Promise<CheckpointMetadata[]>;
9
+ deleteCheckpoint(checkpointId: string): Promise<void>;
10
+ getSession(sessionId: string): Promise<SessionInfo | null>;
11
+ upsertSession(session: SessionInfo): Promise<void>;
12
+ deleteSession(sessionId: string): Promise<void>;
13
+ getStorageStats(): Promise<StorageStats>;
14
+ validateIntegrity(): Promise<ValidationResult>;
15
+ close(): Promise<void>;
16
+ }
17
+ export interface IMetadataStorage {
18
+ initialize(): Promise<void>;
19
+ saveCheckpointMetadata(metadata: CheckpointMetadata): Promise<void>;
20
+ getCheckpointMetadata(checkpointId: string): Promise<CheckpointMetadata | null>;
21
+ listCheckpointMetadata(sessionId: string, limit?: number, offset?: number): Promise<CheckpointMetadata[]>;
22
+ deleteCheckpointMetadata(checkpointId: string): Promise<void>;
23
+ upsertSessionInfo(session: SessionInfo): Promise<void>;
24
+ getSessionInfo(sessionId: string): Promise<SessionInfo | null>;
25
+ deleteSessionInfo(sessionId: string): Promise<void>;
26
+ getMetadataStats(): Promise<MetadataStats>;
27
+ acquireSessionLock(sessionId: string, operationType: string, timeoutMs?: number): Promise<string>;
28
+ releaseSessionLock(operationId: string): Promise<void>;
29
+ updateOperationHeartbeat(operationId: string): Promise<void>;
30
+ cleanupStaleOperations(): Promise<number>;
31
+ close(): Promise<void>;
32
+ }
33
+ export interface IBlobStorage {
34
+ initialize(): Promise<void>;
35
+ saveBlob(sessionId: string, checkpointId: string, blob: CompressedBlob): Promise<BlobMetadata>;
36
+ loadBlob(blobMetadata: BlobMetadata): Promise<CompressedBlob>;
37
+ deleteBlob(blobMetadata: BlobMetadata): Promise<void>;
38
+ getBlobStats(): Promise<BlobStats>;
39
+ validateBlobIntegrity(): Promise<BlobValidationResult>;
40
+ cleanupOrphanedBlobs(referencedPaths: string[]): Promise<number>;
41
+ getAvailableSpace(): Promise<number>;
42
+ close(): Promise<void>;
43
+ }
44
+ export interface StorageStats {
45
+ totalSessions: number;
46
+ totalCheckpoints: number;
47
+ totalSizeBytes: number;
48
+ averageCheckpointSize: number;
49
+ oldestCheckpoint?: string;
50
+ newestCheckpoint?: string;
51
+ storageUtilization: number;
52
+ }
53
+ export interface MetadataStats {
54
+ databaseSizeBytes: number;
55
+ totalSessions: number;
56
+ totalCheckpoints: number;
57
+ activeOperations: number;
58
+ averageCheckpointSize: number;
59
+ indexEfficiency: number;
60
+ }
61
+ export interface BlobStats {
62
+ totalFiles: number;
63
+ totalSizeBytes: number;
64
+ averageFileSize: number;
65
+ compressionRatio: number;
66
+ availableSpaceBytes: number;
67
+ directoryStructure: DirectoryInfo[];
68
+ }
69
+ export interface DirectoryInfo {
70
+ path: string;
71
+ fileCount: number;
72
+ totalSize: number;
73
+ lastModified?: string;
74
+ }
75
+ export interface ValidationResult {
76
+ isValid: boolean;
77
+ errors: ValidationError[];
78
+ warnings: ValidationWarning[];
79
+ repairSuggestions: string[];
80
+ }
81
+ export interface BlobValidationResult {
82
+ isValid: boolean;
83
+ corruptedFiles: string[];
84
+ missingFiles: string[];
85
+ orphanedFiles: string[];
86
+ checksumMismatches: ChecksumMismatch[];
87
+ }
88
+ export interface ValidationError {
89
+ type: 'CORRUPTION' | 'MISSING_DATA' | 'CONSTRAINT_VIOLATION' | 'ORPHANED_DATA';
90
+ description: string;
91
+ affectedItems: string[];
92
+ severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
93
+ }
94
+ export interface ValidationWarning {
95
+ type: 'PERFORMANCE' | 'CLEANUP_NEEDED' | 'QUOTA_WARNING';
96
+ description: string;
97
+ suggestion: string;
98
+ }
99
+ export interface ChecksumMismatch {
100
+ filePath: string;
101
+ expectedChecksum: string;
102
+ actualChecksum: string;
103
+ checkpointId: string;
104
+ }
105
+ export interface ContextStorageConfig {
106
+ dataDirectory: string;
107
+ database: {
108
+ path: string;
109
+ timeout: number;
110
+ maxConnections: number;
111
+ walMode: boolean;
112
+ };
113
+ blobs: {
114
+ directory: string;
115
+ filePermissions: number;
116
+ directoryPermissions: number;
117
+ atomicWrites: boolean;
118
+ };
119
+ concurrency: {
120
+ operationTimeoutMs: number;
121
+ maxRetries: number;
122
+ heartbeatIntervalMs: number;
123
+ cleanupIntervalMs: number;
124
+ };
125
+ quotas: {
126
+ maxTotalSize: number;
127
+ maxCheckpointsPerSession: number;
128
+ warningThreshold: number;
129
+ cleanupThreshold: number;
130
+ };
131
+ validation: {
132
+ enableChecksumValidation: boolean;
133
+ integrityCheckIntervalMs: number;
134
+ enableOrphanedFileCleanup: boolean;
135
+ };
136
+ }
137
+ export declare function createDefaultContextStorageConfig(baseDir?: string): ContextStorageConfig;
138
+ export interface IContextStorageFactory {
139
+ createMetadataStorage(config: ContextStorageConfig): Promise<IMetadataStorage>;
140
+ createBlobStorage(config: ContextStorageConfig): Promise<IBlobStorage>;
141
+ createContextStorage(config: ContextStorageConfig): Promise<IContextStorage>;
142
+ }
143
+ export interface StorageOperationContext {
144
+ operationId: string;
145
+ operationType: string;
146
+ sessionId?: string;
147
+ checkpointId?: string;
148
+ startTime: number;
149
+ metadata?: Record<string, any>;
150
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDefaultContextStorageConfig = createDefaultContextStorageConfig;
4
+ function createDefaultContextStorageConfig(baseDir) {
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const defaultBaseDir = baseDir || path.join(os.homedir(), '.workrail');
8
+ return {
9
+ dataDirectory: defaultBaseDir,
10
+ database: {
11
+ path: 'workrail.db',
12
+ timeout: 5000,
13
+ maxConnections: 10,
14
+ walMode: true
15
+ },
16
+ blobs: {
17
+ directory: 'contexts',
18
+ filePermissions: 0o600,
19
+ directoryPermissions: 0o700,
20
+ atomicWrites: true
21
+ },
22
+ concurrency: {
23
+ operationTimeoutMs: 5000,
24
+ maxRetries: 3,
25
+ heartbeatIntervalMs: 1000,
26
+ cleanupIntervalMs: 60000
27
+ },
28
+ quotas: {
29
+ maxTotalSize: 10 * 1024 * 1024 * 1024,
30
+ maxCheckpointsPerSession: 1000,
31
+ warningThreshold: 0.8,
32
+ cleanupThreshold: 0.9
33
+ },
34
+ validation: {
35
+ enableChecksumValidation: true,
36
+ integrityCheckIntervalMs: 24 * 60 * 60 * 1000,
37
+ enableOrphanedFileCleanup: true
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,27 @@
1
+ import { IBlobStorage, ContextStorageConfig, BlobStats, BlobValidationResult } from './context-storage';
2
+ import { CompressedBlob, BlobMetadata } from '../../types/context-types';
3
+ export declare class FileSystemBlobStorage implements IBlobStorage {
4
+ private config;
5
+ private blobDirectory;
6
+ private isInitialized;
7
+ constructor(config: ContextStorageConfig);
8
+ initialize(): Promise<void>;
9
+ saveBlob(sessionId: string, checkpointId: string, blob: CompressedBlob): Promise<BlobMetadata>;
10
+ loadBlob(blobMetadata: BlobMetadata): Promise<CompressedBlob>;
11
+ deleteBlob(blobMetadata: BlobMetadata): Promise<void>;
12
+ getBlobStats(): Promise<BlobStats>;
13
+ validateBlobIntegrity(): Promise<BlobValidationResult>;
14
+ cleanupOrphanedBlobs(referencedPaths: string[]): Promise<number>;
15
+ getAvailableSpace(): Promise<number>;
16
+ close(): Promise<void>;
17
+ private ensureInitialized;
18
+ private ensureDirectory;
19
+ private generateBlobPath;
20
+ private atomicWrite;
21
+ private calculateChecksum;
22
+ private verifyFileIntegrity;
23
+ private fileExists;
24
+ private getAllBlobFiles;
25
+ private analyzeDirectory;
26
+ private cleanupEmptyDirectories;
27
+ }
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FileSystemBlobStorage = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ class FileSystemBlobStorage {
11
+ constructor(config) {
12
+ this.isInitialized = false;
13
+ this.config = config;
14
+ this.blobDirectory = path_1.default.join(config.dataDirectory, config.blobs.directory);
15
+ }
16
+ async initialize() {
17
+ if (this.isInitialized) {
18
+ return;
19
+ }
20
+ try {
21
+ await this.ensureDirectory(this.blobDirectory);
22
+ const currentDate = new Date();
23
+ const yearMonth = `${currentDate.getFullYear()}/${String(currentDate.getMonth() + 1).padStart(2, '0')}`;
24
+ await this.ensureDirectory(path_1.default.join(this.blobDirectory, yearMonth));
25
+ this.isInitialized = true;
26
+ console.log('✅ Filesystem blob storage initialized');
27
+ }
28
+ catch (error) {
29
+ throw new Error(`Failed to initialize filesystem blob storage: ${error}`);
30
+ }
31
+ }
32
+ async saveBlob(sessionId, checkpointId, blob) {
33
+ this.ensureInitialized();
34
+ try {
35
+ const blobPath = this.generateBlobPath(sessionId, checkpointId);
36
+ const fullPath = path_1.default.join(this.blobDirectory, blobPath);
37
+ await this.ensureDirectory(path_1.default.dirname(fullPath));
38
+ const checksum = this.calculateChecksum(blob.data);
39
+ await this.atomicWrite(fullPath, blob.data);
40
+ await this.verifyFileIntegrity(fullPath, checksum, blob.data.length);
41
+ const metadata = {
42
+ checkpointId,
43
+ sessionId,
44
+ path: blobPath,
45
+ sizeBytes: blob.data.length,
46
+ hash: checksum,
47
+ encrypted: false
48
+ };
49
+ console.log(`💾 Saved blob: ${blobPath} (${blob.data.length} bytes, ratio: ${blob.compressionRatio.toFixed(2)})`);
50
+ return metadata;
51
+ }
52
+ catch (error) {
53
+ throw new Error(`Failed to save blob: ${error}`);
54
+ }
55
+ }
56
+ async loadBlob(blobMetadata) {
57
+ this.ensureInitialized();
58
+ try {
59
+ const fullPath = path_1.default.join(this.blobDirectory, blobMetadata.path);
60
+ if (!await this.fileExists(fullPath)) {
61
+ throw new Error(`Blob file not found: ${blobMetadata.path}`);
62
+ }
63
+ const data = await fs_1.default.promises.readFile(fullPath);
64
+ if (this.config.validation.enableChecksumValidation) {
65
+ const actualChecksum = this.calculateChecksum(data);
66
+ if (actualChecksum !== blobMetadata.hash) {
67
+ throw new Error(`Blob integrity check failed for ${blobMetadata.path}. ` +
68
+ `Expected: ${blobMetadata.hash}, Got: ${actualChecksum}`);
69
+ }
70
+ }
71
+ if (data.length !== blobMetadata.sizeBytes) {
72
+ throw new Error(`Blob size mismatch for ${blobMetadata.path}. ` +
73
+ `Expected: ${blobMetadata.sizeBytes} bytes, Got: ${data.length} bytes`);
74
+ }
75
+ console.log(`📖 Loaded blob: ${blobMetadata.path} (${data.length} bytes)`);
76
+ return {
77
+ data,
78
+ originalSize: blobMetadata.sizeBytes,
79
+ compressedSize: data.length,
80
+ compressionRatio: 1.0,
81
+ algorithm: 'gzip'
82
+ };
83
+ }
84
+ catch (error) {
85
+ throw new Error(`Failed to load blob: ${error}`);
86
+ }
87
+ }
88
+ async deleteBlob(blobMetadata) {
89
+ this.ensureInitialized();
90
+ try {
91
+ const fullPath = path_1.default.join(this.blobDirectory, blobMetadata.path);
92
+ if (await this.fileExists(fullPath)) {
93
+ await fs_1.default.promises.unlink(fullPath);
94
+ console.log(`🗑️ Deleted blob: ${blobMetadata.path}`);
95
+ await this.cleanupEmptyDirectories(path_1.default.dirname(fullPath));
96
+ }
97
+ else {
98
+ console.warn(`Warning: Blob file not found for deletion: ${blobMetadata.path}`);
99
+ }
100
+ }
101
+ catch (error) {
102
+ throw new Error(`Failed to delete blob: ${error}`);
103
+ }
104
+ }
105
+ async getBlobStats() {
106
+ this.ensureInitialized();
107
+ try {
108
+ const directoryInfo = await this.analyzeDirectory(this.blobDirectory);
109
+ const availableSpace = await this.getAvailableSpace();
110
+ return {
111
+ totalFiles: directoryInfo.totalFiles,
112
+ totalSizeBytes: directoryInfo.totalSize,
113
+ averageFileSize: directoryInfo.totalFiles > 0 ? directoryInfo.totalSize / directoryInfo.totalFiles : 0,
114
+ compressionRatio: 1.0,
115
+ availableSpaceBytes: availableSpace,
116
+ directoryStructure: directoryInfo.subdirectories
117
+ };
118
+ }
119
+ catch (error) {
120
+ throw new Error(`Failed to get blob stats: ${error}`);
121
+ }
122
+ }
123
+ async validateBlobIntegrity() {
124
+ this.ensureInitialized();
125
+ const result = {
126
+ isValid: true,
127
+ corruptedFiles: [],
128
+ missingFiles: [],
129
+ orphanedFiles: [],
130
+ checksumMismatches: []
131
+ };
132
+ try {
133
+ const allFiles = await this.getAllBlobFiles();
134
+ for (const filePath of allFiles) {
135
+ try {
136
+ const fullPath = path_1.default.join(this.blobDirectory, filePath);
137
+ const stats = await fs_1.default.promises.stat(fullPath);
138
+ if (stats.size === 0) {
139
+ result.corruptedFiles.push(filePath);
140
+ result.isValid = false;
141
+ }
142
+ }
143
+ catch (error) {
144
+ result.missingFiles.push(filePath);
145
+ result.isValid = false;
146
+ }
147
+ }
148
+ if (result.corruptedFiles.length > 0 || result.missingFiles.length > 0) {
149
+ console.warn(`⚠️ Blob integrity issues found: ${result.corruptedFiles.length} corrupted, ${result.missingFiles.length} missing`);
150
+ }
151
+ else {
152
+ console.log('✅ Blob integrity validation passed');
153
+ }
154
+ return result;
155
+ }
156
+ catch (error) {
157
+ throw new Error(`Failed to validate blob integrity: ${error}`);
158
+ }
159
+ }
160
+ async cleanupOrphanedBlobs(referencedPaths) {
161
+ this.ensureInitialized();
162
+ try {
163
+ const allFiles = await this.getAllBlobFiles();
164
+ const referencedSet = new Set(referencedPaths);
165
+ let cleanedCount = 0;
166
+ for (const filePath of allFiles) {
167
+ if (!referencedSet.has(filePath)) {
168
+ try {
169
+ const fullPath = path_1.default.join(this.blobDirectory, filePath);
170
+ await fs_1.default.promises.unlink(fullPath);
171
+ cleanedCount++;
172
+ console.log(`🧹 Cleaned orphaned blob: ${filePath}`);
173
+ }
174
+ catch (error) {
175
+ console.warn(`Warning: Failed to delete orphaned blob ${filePath}:`, error);
176
+ }
177
+ }
178
+ }
179
+ if (cleanedCount > 0) {
180
+ await this.cleanupEmptyDirectories(this.blobDirectory);
181
+ console.log(`🧹 Cleaned up ${cleanedCount} orphaned blobs`);
182
+ }
183
+ return cleanedCount;
184
+ }
185
+ catch (error) {
186
+ throw new Error(`Failed to cleanup orphaned blobs: ${error}`);
187
+ }
188
+ }
189
+ async getAvailableSpace() {
190
+ try {
191
+ const testFile = path_1.default.join(this.blobDirectory, '.space-test');
192
+ await fs_1.default.promises.writeFile(testFile, 'test');
193
+ await fs_1.default.promises.unlink(testFile);
194
+ return 10 * 1024 * 1024 * 1024;
195
+ }
196
+ catch (error) {
197
+ console.warn('Unable to check available disk space:', error);
198
+ return 1024 * 1024 * 1024;
199
+ }
200
+ }
201
+ async close() {
202
+ this.isInitialized = false;
203
+ console.log('📁 Filesystem blob storage closed');
204
+ }
205
+ ensureInitialized() {
206
+ if (!this.isInitialized) {
207
+ throw new Error('FileSystemBlobStorage not initialized');
208
+ }
209
+ }
210
+ async ensureDirectory(dirPath) {
211
+ try {
212
+ await fs_1.default.promises.mkdir(dirPath, {
213
+ recursive: true,
214
+ mode: this.config.blobs.directoryPermissions
215
+ });
216
+ }
217
+ catch (error) {
218
+ throw new Error(`Failed to create directory ${dirPath}: ${error}`);
219
+ }
220
+ }
221
+ generateBlobPath(sessionId, checkpointId) {
222
+ const now = new Date();
223
+ const year = now.getFullYear();
224
+ const month = String(now.getMonth() + 1).padStart(2, '0');
225
+ const sessionPrefix = sessionId.substring(0, 8);
226
+ return path_1.default.join(String(year), month, sessionPrefix, `${checkpointId}.json.gz`);
227
+ }
228
+ async atomicWrite(filePath, data) {
229
+ if (!this.config.blobs.atomicWrites) {
230
+ await fs_1.default.promises.writeFile(filePath, data, { mode: this.config.blobs.filePermissions });
231
+ return;
232
+ }
233
+ const tempFile = `${filePath}.tmp.${crypto_1.default.randomUUID()}`;
234
+ try {
235
+ await fs_1.default.promises.writeFile(tempFile, data, { mode: this.config.blobs.filePermissions });
236
+ await fs_1.default.promises.rename(tempFile, filePath);
237
+ }
238
+ catch (error) {
239
+ try {
240
+ await fs_1.default.promises.unlink(tempFile);
241
+ }
242
+ catch (cleanupError) {
243
+ }
244
+ throw error;
245
+ }
246
+ }
247
+ calculateChecksum(data) {
248
+ return crypto_1.default.createHash('sha256').update(data).digest('hex');
249
+ }
250
+ async verifyFileIntegrity(filePath, expectedChecksum, expectedSize) {
251
+ try {
252
+ const stats = await fs_1.default.promises.stat(filePath);
253
+ if (stats.size !== expectedSize) {
254
+ throw new Error(`File size mismatch: expected ${expectedSize}, got ${stats.size}`);
255
+ }
256
+ if (this.config.validation.enableChecksumValidation) {
257
+ const data = await fs_1.default.promises.readFile(filePath);
258
+ const actualChecksum = this.calculateChecksum(data);
259
+ if (actualChecksum !== expectedChecksum) {
260
+ throw new Error(`Checksum mismatch: expected ${expectedChecksum}, got ${actualChecksum}`);
261
+ }
262
+ }
263
+ }
264
+ catch (error) {
265
+ throw new Error(`File integrity verification failed: ${error}`);
266
+ }
267
+ }
268
+ async fileExists(filePath) {
269
+ try {
270
+ await fs_1.default.promises.access(filePath, fs_1.default.constants.F_OK);
271
+ return true;
272
+ }
273
+ catch {
274
+ return false;
275
+ }
276
+ }
277
+ async getAllBlobFiles() {
278
+ const files = [];
279
+ const walkDirectory = async (dir, basePath = '') => {
280
+ try {
281
+ const entries = await fs_1.default.promises.readdir(dir, { withFileTypes: true });
282
+ for (const entry of entries) {
283
+ const fullPath = path_1.default.join(dir, entry.name);
284
+ const relativePath = path_1.default.join(basePath, entry.name);
285
+ if (entry.isDirectory()) {
286
+ await walkDirectory(fullPath, relativePath);
287
+ }
288
+ else if (entry.isFile() && entry.name.endsWith('.json.gz')) {
289
+ files.push(relativePath);
290
+ }
291
+ }
292
+ }
293
+ catch (error) {
294
+ console.warn(`Warning: Failed to read directory ${dir}:`, error);
295
+ }
296
+ };
297
+ await walkDirectory(this.blobDirectory);
298
+ return files;
299
+ }
300
+ async analyzeDirectory(dirPath) {
301
+ const subdirectories = [];
302
+ let totalFiles = 0;
303
+ let totalSize = 0;
304
+ const analyzeSubDir = async (subDir, relativePath) => {
305
+ try {
306
+ const entries = await fs_1.default.promises.readdir(subDir, { withFileTypes: true });
307
+ let dirFiles = 0;
308
+ let dirSize = 0;
309
+ let lastModified;
310
+ for (const entry of entries) {
311
+ const fullPath = path_1.default.join(subDir, entry.name);
312
+ if (entry.isFile()) {
313
+ const stats = await fs_1.default.promises.stat(fullPath);
314
+ dirFiles++;
315
+ dirSize += stats.size;
316
+ totalFiles++;
317
+ totalSize += stats.size;
318
+ if (!lastModified || stats.mtime > lastModified) {
319
+ lastModified = stats.mtime;
320
+ }
321
+ }
322
+ else if (entry.isDirectory()) {
323
+ await analyzeSubDir(fullPath, path_1.default.join(relativePath, entry.name));
324
+ }
325
+ }
326
+ if (dirFiles > 0) {
327
+ subdirectories.push({
328
+ path: relativePath,
329
+ fileCount: dirFiles,
330
+ totalSize: dirSize,
331
+ lastModified: lastModified?.toISOString()
332
+ });
333
+ }
334
+ }
335
+ catch (error) {
336
+ console.warn(`Warning: Failed to analyze directory ${subDir}:`, error);
337
+ }
338
+ };
339
+ try {
340
+ await analyzeSubDir(dirPath, '.');
341
+ }
342
+ catch (error) {
343
+ console.warn(`Warning: Failed to analyze root directory ${dirPath}:`, error);
344
+ }
345
+ return { totalFiles, totalSize, subdirectories };
346
+ }
347
+ async cleanupEmptyDirectories(startDir) {
348
+ if (startDir === this.blobDirectory) {
349
+ return;
350
+ }
351
+ try {
352
+ const entries = await fs_1.default.promises.readdir(startDir);
353
+ if (entries.length === 0) {
354
+ await fs_1.default.promises.rmdir(startDir);
355
+ console.log(`🧹 Removed empty directory: ${path_1.default.relative(this.blobDirectory, startDir)}`);
356
+ await this.cleanupEmptyDirectories(path_1.default.dirname(startDir));
357
+ }
358
+ }
359
+ catch (error) {
360
+ }
361
+ }
362
+ }
363
+ exports.FileSystemBlobStorage = FileSystemBlobStorage;
@@ -0,0 +1,29 @@
1
+ import { IContextStorage, IMetadataStorage, IBlobStorage, ContextStorageConfig, StorageStats, ValidationResult } from './context-storage';
2
+ import { CheckpointMetadata, SessionInfo, CompressedBlob } from '../../types/context-types';
3
+ export declare class HybridContextStorage implements IContextStorage {
4
+ private metadataStorage;
5
+ private blobStorage;
6
+ private config;
7
+ private isInitialized;
8
+ constructor(metadataStorage: IMetadataStorage, blobStorage: IBlobStorage, config: ContextStorageConfig);
9
+ initialize(): Promise<void>;
10
+ saveCheckpoint(metadata: CheckpointMetadata, blob: CompressedBlob): Promise<void>;
11
+ loadCheckpoint(checkpointId: string): Promise<{
12
+ metadata: CheckpointMetadata;
13
+ blob: CompressedBlob;
14
+ }>;
15
+ listCheckpoints(sessionId: string, limit?: number, offset?: number): Promise<CheckpointMetadata[]>;
16
+ deleteCheckpoint(checkpointId: string): Promise<void>;
17
+ getSession(sessionId: string): Promise<SessionInfo | null>;
18
+ upsertSession(session: SessionInfo): Promise<void>;
19
+ deleteSession(sessionId: string): Promise<void>;
20
+ getStorageStats(): Promise<StorageStats>;
21
+ getCheckpoint(checkpointId: string): Promise<CheckpointMetadata | null>;
22
+ validateIntegrity(): Promise<ValidationResult>;
23
+ close(): Promise<void>;
24
+ cleanupOrphanedBlobs(): Promise<number>;
25
+ private updateSessionAccess;
26
+ private ensureInitialized;
27
+ }
28
+ export declare function createDefaultHybridContextStorage(config?: Partial<ContextStorageConfig>): Promise<HybridContextStorage>;
29
+ export declare function createCustomHybridContextStorage(metadataStorage: IMetadataStorage, blobStorage: IBlobStorage, config: ContextStorageConfig): Promise<HybridContextStorage>;