@sylphx/flow 1.8.0 → 1.8.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 (126) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/assets/output-styles/silent.md +145 -8
  3. package/assets/rules/core.md +19 -2
  4. package/package.json +2 -12
  5. package/src/commands/flow/execute.ts +470 -0
  6. package/src/commands/flow/index.ts +11 -0
  7. package/src/commands/flow/prompt.ts +35 -0
  8. package/src/commands/flow/setup.ts +312 -0
  9. package/src/commands/flow/targets.ts +18 -0
  10. package/src/commands/flow/types.ts +47 -0
  11. package/src/commands/flow-command.ts +18 -967
  12. package/src/commands/flow-orchestrator.ts +14 -5
  13. package/src/commands/hook-command.ts +1 -1
  14. package/src/commands/init-core.ts +12 -3
  15. package/src/commands/run-command.ts +1 -1
  16. package/src/config/rules.ts +1 -1
  17. package/src/core/error-handling.ts +1 -1
  18. package/src/core/loop-controller.ts +1 -1
  19. package/src/core/state-detector.ts +1 -1
  20. package/src/core/target-manager.ts +1 -1
  21. package/src/index.ts +1 -1
  22. package/src/shared/files/index.ts +1 -1
  23. package/src/shared/processing/index.ts +1 -1
  24. package/src/targets/claude-code.ts +3 -3
  25. package/src/targets/opencode.ts +3 -3
  26. package/src/utils/agent-enhancer.ts +2 -2
  27. package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
  28. package/src/utils/{paths.ts → config/paths.ts} +1 -1
  29. package/src/utils/{settings.ts → config/settings.ts} +1 -1
  30. package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
  31. package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
  32. package/src/utils/display/banner.ts +25 -0
  33. package/src/utils/display/status.ts +55 -0
  34. package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
  35. package/src/utils/files/jsonc.ts +36 -0
  36. package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
  37. package/src/utils/index.ts +42 -61
  38. package/src/utils/version.ts +47 -0
  39. package/src/components/benchmark-monitor.tsx +0 -331
  40. package/src/components/reindex-progress.tsx +0 -261
  41. package/src/composables/functional/index.ts +0 -14
  42. package/src/composables/functional/useEnvironment.ts +0 -171
  43. package/src/composables/functional/useFileSystem.ts +0 -139
  44. package/src/composables/index.ts +0 -4
  45. package/src/composables/useEnv.ts +0 -13
  46. package/src/composables/useRuntimeConfig.ts +0 -27
  47. package/src/core/ai-sdk.ts +0 -603
  48. package/src/core/app-factory.ts +0 -381
  49. package/src/core/builtin-agents.ts +0 -9
  50. package/src/core/command-system.ts +0 -550
  51. package/src/core/config-system.ts +0 -550
  52. package/src/core/connection-pool.ts +0 -390
  53. package/src/core/di-container.ts +0 -155
  54. package/src/core/headless-display.ts +0 -96
  55. package/src/core/interfaces/index.ts +0 -22
  56. package/src/core/interfaces/repository.interface.ts +0 -91
  57. package/src/core/interfaces/service.interface.ts +0 -133
  58. package/src/core/interfaces.ts +0 -96
  59. package/src/core/result.ts +0 -351
  60. package/src/core/service-config.ts +0 -252
  61. package/src/core/session-service.ts +0 -121
  62. package/src/core/storage-factory.ts +0 -115
  63. package/src/core/stream-handler.ts +0 -288
  64. package/src/core/type-utils.ts +0 -427
  65. package/src/core/unified-storage.ts +0 -456
  66. package/src/core/validation/limit.ts +0 -46
  67. package/src/core/validation/query.ts +0 -20
  68. package/src/db/auto-migrate.ts +0 -322
  69. package/src/db/base-database-client.ts +0 -144
  70. package/src/db/cache-db.ts +0 -218
  71. package/src/db/cache-schema.ts +0 -75
  72. package/src/db/database.ts +0 -70
  73. package/src/db/index.ts +0 -252
  74. package/src/db/memory-db.ts +0 -153
  75. package/src/db/memory-schema.ts +0 -29
  76. package/src/db/schema.ts +0 -289
  77. package/src/db/session-repository.ts +0 -733
  78. package/src/domains/index.ts +0 -6
  79. package/src/domains/utilities/index.ts +0 -6
  80. package/src/domains/utilities/time/index.ts +0 -5
  81. package/src/domains/utilities/time/tools.ts +0 -291
  82. package/src/services/agent-service.ts +0 -273
  83. package/src/services/evaluation-service.ts +0 -271
  84. package/src/services/functional/evaluation-logic.ts +0 -296
  85. package/src/services/functional/file-processor.ts +0 -273
  86. package/src/services/functional/index.ts +0 -12
  87. package/src/services/memory.service.ts +0 -476
  88. package/src/types/api/batch.ts +0 -108
  89. package/src/types/api/errors.ts +0 -118
  90. package/src/types/api/index.ts +0 -55
  91. package/src/types/api/requests.ts +0 -76
  92. package/src/types/api/responses.ts +0 -180
  93. package/src/types/api/websockets.ts +0 -85
  94. package/src/types/benchmark.ts +0 -49
  95. package/src/types/database.types.ts +0 -510
  96. package/src/types/memory-types.ts +0 -63
  97. package/src/utils/advanced-tokenizer.ts +0 -191
  98. package/src/utils/ai-model-fetcher.ts +0 -19
  99. package/src/utils/async-file-operations.ts +0 -516
  100. package/src/utils/audio-player.ts +0 -345
  101. package/src/utils/codebase-helpers.ts +0 -211
  102. package/src/utils/console-ui.ts +0 -79
  103. package/src/utils/database-errors.ts +0 -140
  104. package/src/utils/debug-logger.ts +0 -49
  105. package/src/utils/file-scanner.ts +0 -259
  106. package/src/utils/help.ts +0 -20
  107. package/src/utils/immutable-cache.ts +0 -106
  108. package/src/utils/jsonc.ts +0 -158
  109. package/src/utils/memory-tui.ts +0 -414
  110. package/src/utils/models-dev.ts +0 -91
  111. package/src/utils/parallel-operations.ts +0 -487
  112. package/src/utils/process-manager.ts +0 -155
  113. package/src/utils/prompts.ts +0 -120
  114. package/src/utils/search-tool-builder.ts +0 -214
  115. package/src/utils/session-manager.ts +0 -168
  116. package/src/utils/session-title.ts +0 -87
  117. package/src/utils/simplified-errors.ts +0 -410
  118. package/src/utils/template-engine.ts +0 -94
  119. package/src/utils/test-audio.ts +0 -71
  120. package/src/utils/todo-context.ts +0 -46
  121. package/src/utils/token-counter.ts +0 -288
  122. /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
  123. /package/src/utils/{logger.ts → display/logger.ts} +0 -0
  124. /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
  125. /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
  126. /package/src/utils/{security.ts → security/security.ts} +0 -0
@@ -1,191 +0,0 @@
1
- /**
2
- * Advanced Code Tokenizer - 純粹 StarCoder2,無任何多餘處理
3
- * 完全信任 StarCoder2 嘅世界級 tokenization 能力
4
- */
5
-
6
- import { AutoTokenizer } from '@huggingface/transformers';
7
-
8
- export interface AdvancedToken {
9
- text: string;
10
- id: number;
11
- score: number;
12
- confidence: number;
13
- relevance: 'high' | 'medium' | 'low';
14
- }
15
-
16
- export interface AdvancedTokenizerResult {
17
- tokens: AdvancedToken[];
18
- metadata: {
19
- totalTokens: number;
20
- vocabSize: number;
21
- processingTime: number;
22
- averageConfidence: number;
23
- };
24
- raw: {
25
- inputIds: number[];
26
- decodedText: string;
27
- };
28
- }
29
-
30
- /**
31
- * Advanced Code Tokenizer - 純粹 StarCoder2
32
- */
33
- export class AdvancedCodeTokenizer {
34
- private tokenizer: any;
35
- private initialized = false;
36
- private modelPath: string;
37
-
38
- constructor(
39
- options: {
40
- modelPath?: string;
41
- } = {}
42
- ) {
43
- this.modelPath = options.modelPath || './models/starcoder2';
44
- }
45
-
46
- /**
47
- * 初始化 tokenizer
48
- */
49
- async initialize(): Promise<void> {
50
- if (this.initialized) {
51
- return;
52
- }
53
-
54
- try {
55
- this.tokenizer = await AutoTokenizer.from_pretrained(this.modelPath);
56
- this.initialized = true;
57
- } catch (error) {
58
- throw new Error(`Tokenizer initialization failed: ${error.message}`);
59
- }
60
- }
61
-
62
- /**
63
- * 純粹 StarCoder2 tokenization - 無任何安全限制或多餘分析
64
- */
65
- async tokenize(content: string): Promise<AdvancedTokenizerResult> {
66
- if (!this.initialized) {
67
- await this.initialize();
68
- }
69
-
70
- const startTime = Date.now();
71
-
72
- // Handle empty content
73
- if (!content || content.trim().length === 0) {
74
- return {
75
- tokens: [],
76
- metadata: {
77
- totalTokens: 0,
78
- vocabSize: 49152,
79
- processingTime: Date.now() - startTime,
80
- averageConfidence: 0,
81
- },
82
- raw: {
83
- inputIds: [],
84
- decodedText: '',
85
- },
86
- };
87
- }
88
-
89
- try {
90
- // 完全信任 StarCoder2,直接處理所有內容
91
- const encoded = await this.tokenizer(content);
92
- const inputIds = encoded.input_ids.tolist()[0];
93
-
94
- // 解碼獲得原文
95
- const decodedText = await this.tokenizer.decode(inputIds);
96
-
97
- // 直接用 StarCoder2 嘅輸出,唔做多餘分析
98
- const tokens = await this.createDirectTokens(decodedText, inputIds);
99
-
100
- const processingTime = Date.now() - startTime;
101
-
102
- return {
103
- tokens,
104
- metadata: {
105
- totalTokens: tokens.length,
106
- vocabSize: 49152,
107
- processingTime,
108
- averageConfidence: 0.95, // StarCoder2 本身就係高質量
109
- },
110
- raw: {
111
- inputIds,
112
- decodedText,
113
- },
114
- };
115
- } catch (error) {
116
- throw new Error(`Tokenization failed: ${error.message}`);
117
- }
118
- }
119
-
120
- /**
121
- * 純粹 StarCoder2 tokenization - 完全信任,無任何額外處理
122
- * 直接使用 StarCoder2 嘅 token IDs,逐個解碼成文字
123
- */
124
- private async createDirectTokens(
125
- _decodedText: string,
126
- inputIds: number[]
127
- ): Promise<AdvancedToken[]> {
128
- const tokens: AdvancedToken[] = [];
129
-
130
- // 完全信任 StarCoder2,直接使用佢嘅 tokenization
131
- // 逐個 token ID 解碼,得到 StarCoder2 認為嘅最佳分割
132
- for (let i = 0; i < inputIds.length; i++) {
133
- const tokenId = inputIds[i];
134
- try {
135
- // 直接使用 StarCoder2 嘅解碼結果
136
- const tokenText = await this.tokenizer.decode([tokenId], { skip_special_tokens: true });
137
- const cleaned = tokenText.trim().toLowerCase();
138
-
139
- // 只過濾空白 token,其他全部保留(完全信任 StarCoder2)
140
- if (cleaned.length > 0) {
141
- tokens.push({
142
- text: cleaned,
143
- id: tokenId,
144
- score: 1.0, // StarCoder2 嘅所有 token 都係高質量
145
- confidence: 1.0, // 完全信任 StarCoder2
146
- relevance: 'high' as const,
147
- });
148
- }
149
- } catch (_error) {}
150
- }
151
-
152
- return tokens;
153
- }
154
-
155
- /**
156
- * 便利方法:只返回高質量 tokens
157
- */
158
- async getTopTokens(content: string, limit = 20): Promise<AdvancedToken[]> {
159
- const result = await this.tokenize(content);
160
- return result.tokens.slice(0, limit);
161
- }
162
-
163
- /**
164
- * 便利方法:返回所有 tokens (StarCoder2 嘅輸出全部都係高質量)
165
- */
166
- async getTechnicalTokens(content: string): Promise<AdvancedToken[]> {
167
- const result = await this.tokenize(content);
168
- return result.tokens; // StarCoder2 嘅所有輸出都係技術相關
169
- }
170
-
171
- /**
172
- * 解碼 token IDs 回文本
173
- */
174
- async decode(tokenIds: number[]): Promise<string> {
175
- if (!this.initialized) {
176
- throw new Error('Tokenizer not initialized. Call initialize() first.');
177
- }
178
- return await this.tokenizer.decode(tokenIds);
179
- }
180
-
181
- /**
182
- * 編碼文本為 token IDs
183
- */
184
- async encode(text: string): Promise<number[]> {
185
- if (!this.initialized) {
186
- throw new Error('Tokenizer not initialized. Call initialize() first.');
187
- }
188
- const result = await this.tokenizer(text);
189
- return result.input_ids.tolist()[0];
190
- }
191
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * AI Model Fetcher
3
- * Dynamically fetch available models from providers using provider registry
4
- */
5
-
6
- import type { ProviderId } from '../types/provider.types.js';
7
- import type { ProviderConfig, ModelInfo } from '../providers/base-provider.js';
8
- import { getProvider } from '../providers/index.js';
9
-
10
- // Re-export ModelInfo for backward compatibility
11
- export type { ModelInfo } from '../providers/base-provider.js';
12
-
13
- /**
14
- * Fetch models for a provider using provider registry
15
- */
16
- export async function fetchModels(provider: ProviderId, config: ProviderConfig = {}): Promise<ModelInfo[]> {
17
- const providerInstance = getProvider(provider);
18
- return providerInstance.fetchModels(config);
19
- }
@@ -1,516 +0,0 @@
1
- /**
2
- * Async File Operations Utility
3
- *
4
- * Provides async wrappers for common file operations with better error handling,
5
- * concurrency control, and performance optimizations
6
- */
7
-
8
- import { createHash } from 'node:crypto';
9
- import * as fs from 'node:fs/promises';
10
- import * as path from 'node:path';
11
-
12
- export interface FileOperationOptions {
13
- encoding?: BufferEncoding;
14
- signal?: AbortSignal;
15
- retryAttempts?: number;
16
- retryDelay?: number;
17
- timeout?: number;
18
- }
19
-
20
- export interface FileStats {
21
- size: number;
22
- created: Date;
23
- modified: Date;
24
- accessed: Date;
25
- isFile: boolean;
26
- isDirectory: boolean;
27
- permissions: string;
28
- }
29
-
30
- export interface DirectoryEntry {
31
- name: string;
32
- path: string;
33
- isFile: boolean;
34
- isDirectory: boolean;
35
- stats?: FileStats;
36
- }
37
-
38
- export interface CopyOptions {
39
- overwrite?: boolean;
40
- preserveTimestamps?: boolean;
41
- filter?: (source: string, dest: string) => boolean;
42
- concurrency?: number;
43
- }
44
-
45
- export interface ReadDirOptions {
46
- withFileTypes?: boolean;
47
- recursive?: boolean;
48
- includeStats?: boolean;
49
- filter?: (entry: DirectoryEntry) => boolean;
50
- maxDepth?: number;
51
- }
52
-
53
- /**
54
- * Async file operations with error handling and retries
55
- */
56
- export class AsyncFileOperations {
57
- private readonly defaultOptions: Required<FileOperationOptions> = {
58
- encoding: 'utf8',
59
- retryAttempts: 3,
60
- retryDelay: 1000,
61
- timeout: 30000,
62
- };
63
-
64
- /**
65
- * Read file content with retries
66
- */
67
- async readFile(filePath: string, options: FileOperationOptions = {}): Promise<string | Buffer> {
68
- const opts = { ...this.defaultOptions, ...options };
69
-
70
- return this.withRetry(
71
- async () => {
72
- if (opts.timeout) {
73
- return await this.withTimeout(() => fs.readFile(filePath, opts.encoding), opts.timeout);
74
- }
75
- return await fs.readFile(filePath, opts.encoding);
76
- },
77
- opts.retryAttempts,
78
- opts.retryDelay
79
- );
80
- }
81
-
82
- /**
83
- * Write file content with retries and backup
84
- */
85
- async writeFile(
86
- filePath: string,
87
- content: string | Buffer | NodeJS.ArrayBufferView,
88
- options: FileOperationOptions & { createBackup?: boolean } = {}
89
- ): Promise<void> {
90
- const opts = { ...this.defaultOptions, createBackup: true, ...options };
91
-
92
- // Create backup if requested and file exists
93
- if (opts.createBackup && (await this.exists(filePath))) {
94
- await this.createBackup(filePath);
95
- }
96
-
97
- return this.withRetry(
98
- async () => {
99
- // Ensure directory exists
100
- await this.ensureDir(path.dirname(filePath));
101
-
102
- if (opts.timeout) {
103
- return await this.withTimeout(
104
- () => fs.writeFile(filePath, content, opts.encoding),
105
- opts.timeout
106
- );
107
- }
108
- return await fs.writeFile(filePath, content, opts.encoding);
109
- },
110
- opts.retryAttempts,
111
- opts.retryDelay
112
- );
113
- }
114
-
115
- /**
116
- * Append to file with retries
117
- */
118
- async appendFile(
119
- filePath: string,
120
- content: string | Buffer,
121
- options: FileOperationOptions = {}
122
- ): Promise<void> {
123
- const opts = { ...this.defaultOptions, ...options };
124
-
125
- return this.withRetry(
126
- async () => {
127
- // Ensure directory exists
128
- await this.ensureDir(path.dirname(filePath));
129
-
130
- if (opts.timeout) {
131
- return await this.withTimeout(
132
- () => fs.appendFile(filePath, content, opts.encoding),
133
- opts.timeout
134
- );
135
- }
136
- return await fs.appendFile(filePath, content, opts.encoding);
137
- },
138
- opts.retryAttempts,
139
- opts.retryDelay
140
- );
141
- }
142
-
143
- /**
144
- * Check if file or directory exists
145
- */
146
- async exists(filePath: string): Promise<boolean> {
147
- try {
148
- await fs.access(filePath);
149
- return true;
150
- } catch {
151
- return false;
152
- }
153
- }
154
-
155
- /**
156
- * Get file statistics
157
- */
158
- async getStats(filePath: string): Promise<FileStats> {
159
- const stats = await fs.stat(filePath);
160
-
161
- return {
162
- size: stats.size,
163
- created: stats.birthtime,
164
- modified: stats.mtime,
165
- accessed: stats.atime,
166
- isFile: stats.isFile(),
167
- isDirectory: stats.isDirectory(),
168
- permissions: stats.mode.toString(8),
169
- };
170
- }
171
-
172
- /**
173
- * Create directory recursively
174
- */
175
- async ensureDir(dirPath: string): Promise<void> {
176
- try {
177
- await fs.mkdir(dirPath, { recursive: true });
178
- } catch (error) {
179
- // Check if directory already exists
180
- if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST')) {
181
- throw error;
182
- }
183
- }
184
- }
185
-
186
- /**
187
- * Remove file or directory recursively
188
- */
189
- async remove(
190
- targetPath: string,
191
- options: { recursive?: boolean; force?: boolean } = {}
192
- ): Promise<void> {
193
- const { recursive = true, force = false } = options;
194
-
195
- try {
196
- const stats = await fs.lstat(targetPath);
197
-
198
- if (stats.isDirectory() && recursive) {
199
- await fs.rm(targetPath, { recursive: true, force });
200
- } else if (stats.isDirectory() && !recursive) {
201
- throw new Error('Cannot remove directory without recursive option');
202
- } else {
203
- await fs.unlink(targetPath);
204
- }
205
- } catch (error) {
206
- if (!force || !(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
207
- throw error;
208
- }
209
- }
210
- }
211
-
212
- /**
213
- * Copy file or directory
214
- */
215
- async copy(source: string, destination: string, options: CopyOptions = {}): Promise<void> {
216
- const { overwrite = false, preserveTimestamps = true, filter, concurrency = 10 } = options;
217
-
218
- const sourceStats = await fs.lstat(source);
219
-
220
- if (sourceStats.isDirectory()) {
221
- await this.copyDirectory(source, destination, {
222
- overwrite,
223
- preserveTimestamps,
224
- filter,
225
- concurrency,
226
- });
227
- } else {
228
- await this.copyFile(source, destination, {
229
- overwrite,
230
- preserveTimestamps,
231
- filter,
232
- });
233
- }
234
- }
235
-
236
- /**
237
- * Move file or directory
238
- */
239
- async move(
240
- source: string,
241
- destination: string,
242
- options: { overwrite?: boolean } = {}
243
- ): Promise<void> {
244
- const { overwrite = false } = options;
245
-
246
- // Check if destination exists
247
- if ((await this.exists(destination)) && !overwrite) {
248
- throw new Error(`Destination already exists: ${destination}`);
249
- }
250
-
251
- try {
252
- await fs.rename(source, destination);
253
- } catch (error) {
254
- // Fallback to copy + delete if rename fails (cross-device)
255
- if (error instanceof Error && 'code' in error && error.code === 'EXDEV') {
256
- await this.copy(source, destination, { overwrite });
257
- await this.remove(source, { recursive: true });
258
- } else {
259
- throw error;
260
- }
261
- }
262
- }
263
-
264
- /**
265
- * Read directory contents
266
- */
267
- async readDir(dirPath: string, options: ReadDirOptions = {}): Promise<DirectoryEntry[]> {
268
- const {
269
- withFileTypes = false,
270
- recursive = false,
271
- includeStats = false,
272
- filter,
273
- maxDepth = 10,
274
- } = options;
275
-
276
- const results: DirectoryEntry[] = [];
277
-
278
- const processDirectory = async (currentPath: string, currentDepth: number): Promise<void> => {
279
- if (currentDepth > maxDepth) {
280
- return;
281
- }
282
-
283
- try {
284
- const entries = await fs.readdir(currentPath, { withFileTypes });
285
-
286
- const promises = entries.map(async (entry) => {
287
- const fullPath = path.join(currentPath, entry.name);
288
- const directoryEntry: DirectoryEntry = {
289
- name: entry.name,
290
- path: fullPath,
291
- isFile: entry.isFile(),
292
- isDirectory: entry.isDirectory(),
293
- };
294
-
295
- if (includeStats) {
296
- try {
297
- directoryEntry.stats = await this.getStats(fullPath);
298
- } catch (_error) {
299
- // Skip if stats can't be retrieved
300
- }
301
- }
302
-
303
- // Apply filter if provided
304
- if (filter && !filter(directoryEntry)) {
305
- return;
306
- }
307
-
308
- results.push(directoryEntry);
309
-
310
- // Recursively process subdirectories
311
- if (recursive && entry.isDirectory()) {
312
- await processDirectory(fullPath, currentDepth + 1);
313
- }
314
- });
315
-
316
- await Promise.all(promises);
317
- } catch (error) {
318
- throw new Error(`Failed to read directory ${currentPath}: ${error}`);
319
- }
320
- };
321
-
322
- await processDirectory(dirPath, 0);
323
- return results;
324
- }
325
-
326
- /**
327
- * Calculate file hash
328
- */
329
- async calculateHash(filePath: string, algorithm = 'sha256'): Promise<string> {
330
- const content = await this.readFile(filePath);
331
- return createHash(algorithm)
332
- .update(content as Buffer)
333
- .digest('hex');
334
- }
335
-
336
- /**
337
- * Watch file or directory for changes
338
- */
339
- async watch(
340
- targetPath: string,
341
- callback: (eventType: string, filename: string | null) => void,
342
- options: { recursive?: boolean } = {}
343
- ): Promise<fs.FSWatcher> {
344
- const { recursive = false } = options;
345
-
346
- return new Promise((resolve, reject) => {
347
- try {
348
- const watcher = fs.watch(targetPath, { recursive }, callback);
349
- resolve(watcher);
350
- } catch (error) {
351
- reject(error);
352
- }
353
- });
354
- }
355
-
356
- /**
357
- * Create backup of file
358
- */
359
- private async createBackup(filePath: string): Promise<string> {
360
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
361
- const backupPath = `${filePath}.backup.${timestamp}`;
362
- await this.copy(filePath, backupPath);
363
- return backupPath;
364
- }
365
-
366
- /**
367
- * Copy file with options
368
- */
369
- private async copyFile(
370
- source: string,
371
- destination: string,
372
- options: {
373
- overwrite?: boolean;
374
- preserveTimestamps?: boolean;
375
- filter?: (source: string, dest: string) => boolean;
376
- }
377
- ): Promise<void> {
378
- const { overwrite = false, preserveTimestamps = true, filter } = options;
379
-
380
- // Apply filter
381
- if (filter && !filter(source, destination)) {
382
- return;
383
- }
384
-
385
- // Check if destination exists
386
- if ((await this.exists(destination)) && !overwrite) {
387
- throw new Error(`Destination already exists: ${destination}`);
388
- }
389
-
390
- // Ensure destination directory exists
391
- await this.ensureDir(path.dirname(destination));
392
-
393
- // Copy file
394
- await fs.copyFile(source, destination);
395
-
396
- // Preserve timestamps if requested
397
- if (preserveTimestamps) {
398
- const sourceStats = await fs.stat(source);
399
- await fs.utimes(destination, sourceStats.atime, sourceStats.mtime);
400
- }
401
- }
402
-
403
- /**
404
- * Copy directory with options
405
- */
406
- private async copyDirectory(
407
- source: string,
408
- destination: string,
409
- options: {
410
- overwrite?: boolean;
411
- preserveTimestamps?: boolean;
412
- filter?: (source: string, dest: string) => boolean;
413
- concurrency?: number;
414
- }
415
- ): Promise<void> {
416
- const { concurrency = 10 } = options;
417
-
418
- // Ensure destination directory exists
419
- await this.ensureDir(destination);
420
-
421
- // Get all entries
422
- const entries = await this.readDir(source, { recursive: true });
423
-
424
- // Process entries in batches
425
- for (let i = 0; i < entries.length; i += concurrency) {
426
- const batch = entries.slice(i, i + concurrency);
427
-
428
- await Promise.all(
429
- batch.map(async (entry) => {
430
- const relativePath = path.relative(source, entry.path);
431
- const destPath = path.join(destination, relativePath);
432
-
433
- if (entry.isFile) {
434
- await this.copyFile(entry.path, destPath, options);
435
- } else if (entry.isDirectory) {
436
- await this.ensureDir(destPath);
437
- }
438
- })
439
- );
440
- }
441
- }
442
-
443
- /**
444
- * Execute operation with retry logic
445
- */
446
- private async withRetry<T>(
447
- operation: () => Promise<T>,
448
- attempts: number,
449
- delay: number
450
- ): Promise<T> {
451
- let lastError: Error;
452
-
453
- for (let i = 0; i < attempts; i++) {
454
- try {
455
- return await operation();
456
- } catch (error) {
457
- lastError = error instanceof Error ? error : new Error(String(error));
458
-
459
- if (i < attempts - 1) {
460
- await this.sleep(delay * 2 ** i); // Exponential backoff
461
- }
462
- }
463
- }
464
-
465
- throw lastError!;
466
- }
467
-
468
- /**
469
- * Execute operation with timeout
470
- */
471
- private async withTimeout<T>(operation: () => Promise<T>, timeoutMs: number): Promise<T> {
472
- const timeoutPromise = new Promise<never>((_, reject) => {
473
- setTimeout(() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)), timeoutMs);
474
- });
475
-
476
- return Promise.race([operation(), timeoutPromise]);
477
- }
478
-
479
- /**
480
- * Sleep utility
481
- */
482
- private sleep(ms: number): Promise<void> {
483
- return new Promise((resolve) => setTimeout(resolve, ms));
484
- }
485
- }
486
-
487
- // Export singleton instance
488
- export const asyncFileOps = new AsyncFileOperations();
489
-
490
- /**
491
- * Convenience functions for common operations
492
- */
493
- export const readFile = (filePath: string, options?: FileOperationOptions) =>
494
- asyncFileOps.readFile(filePath, options);
495
-
496
- export const writeFile = (
497
- filePath: string,
498
- content: string | Buffer,
499
- options?: FileOperationOptions
500
- ) => asyncFileOps.writeFile(filePath, content, options);
501
-
502
- export const exists = (filePath: string) => asyncFileOps.exists(filePath);
503
-
504
- export const ensureDir = (dirPath: string) => asyncFileOps.ensureDir(dirPath);
505
-
506
- export const remove = (targetPath: string, options?: { recursive?: boolean; force?: boolean }) =>
507
- asyncFileOps.remove(targetPath, options);
508
-
509
- export const copy = (source: string, destination: string, options?: CopyOptions) =>
510
- asyncFileOps.copy(source, destination, options);
511
-
512
- export const move = (source: string, destination: string, options?: { overwrite?: boolean }) =>
513
- asyncFileOps.move(source, destination, options);
514
-
515
- export const readDir = (dirPath: string, options?: ReadDirOptions) =>
516
- asyncFileOps.readDir(dirPath, options);