@sylphx/flow 1.7.0 → 1.8.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/assets/agents/coder.md +72 -119
  3. package/assets/agents/orchestrator.md +26 -90
  4. package/assets/agents/reviewer.md +76 -47
  5. package/assets/agents/writer.md +82 -63
  6. package/assets/output-styles/silent.md +141 -8
  7. package/assets/rules/code-standards.md +9 -33
  8. package/assets/rules/core.md +67 -59
  9. package/package.json +2 -12
  10. package/src/commands/flow/execute.ts +470 -0
  11. package/src/commands/flow/index.ts +11 -0
  12. package/src/commands/flow/prompt.ts +35 -0
  13. package/src/commands/flow/setup.ts +312 -0
  14. package/src/commands/flow/targets.ts +18 -0
  15. package/src/commands/flow/types.ts +47 -0
  16. package/src/commands/flow-command.ts +18 -967
  17. package/src/commands/flow-orchestrator.ts +14 -5
  18. package/src/commands/hook-command.ts +1 -1
  19. package/src/commands/init-core.ts +12 -3
  20. package/src/commands/run-command.ts +1 -1
  21. package/src/config/rules.ts +1 -1
  22. package/src/core/error-handling.ts +1 -1
  23. package/src/core/loop-controller.ts +1 -1
  24. package/src/core/state-detector.ts +1 -1
  25. package/src/core/target-manager.ts +1 -1
  26. package/src/index.ts +1 -1
  27. package/src/shared/files/index.ts +1 -1
  28. package/src/shared/processing/index.ts +1 -1
  29. package/src/targets/claude-code.ts +3 -3
  30. package/src/targets/opencode.ts +3 -3
  31. package/src/utils/agent-enhancer.ts +2 -2
  32. package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
  33. package/src/utils/{paths.ts → config/paths.ts} +1 -1
  34. package/src/utils/{settings.ts → config/settings.ts} +1 -1
  35. package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
  36. package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
  37. package/src/utils/display/banner.ts +25 -0
  38. package/src/utils/display/status.ts +55 -0
  39. package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
  40. package/src/utils/files/jsonc.ts +36 -0
  41. package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
  42. package/src/utils/index.ts +42 -61
  43. package/src/utils/version.ts +47 -0
  44. package/src/components/benchmark-monitor.tsx +0 -331
  45. package/src/components/reindex-progress.tsx +0 -261
  46. package/src/composables/functional/index.ts +0 -14
  47. package/src/composables/functional/useEnvironment.ts +0 -171
  48. package/src/composables/functional/useFileSystem.ts +0 -139
  49. package/src/composables/index.ts +0 -4
  50. package/src/composables/useEnv.ts +0 -13
  51. package/src/composables/useRuntimeConfig.ts +0 -27
  52. package/src/core/ai-sdk.ts +0 -603
  53. package/src/core/app-factory.ts +0 -381
  54. package/src/core/builtin-agents.ts +0 -9
  55. package/src/core/command-system.ts +0 -550
  56. package/src/core/config-system.ts +0 -550
  57. package/src/core/connection-pool.ts +0 -390
  58. package/src/core/di-container.ts +0 -155
  59. package/src/core/headless-display.ts +0 -96
  60. package/src/core/interfaces/index.ts +0 -22
  61. package/src/core/interfaces/repository.interface.ts +0 -91
  62. package/src/core/interfaces/service.interface.ts +0 -133
  63. package/src/core/interfaces.ts +0 -96
  64. package/src/core/result.ts +0 -351
  65. package/src/core/service-config.ts +0 -252
  66. package/src/core/session-service.ts +0 -121
  67. package/src/core/storage-factory.ts +0 -115
  68. package/src/core/stream-handler.ts +0 -288
  69. package/src/core/type-utils.ts +0 -427
  70. package/src/core/unified-storage.ts +0 -456
  71. package/src/core/validation/limit.ts +0 -46
  72. package/src/core/validation/query.ts +0 -20
  73. package/src/db/auto-migrate.ts +0 -322
  74. package/src/db/base-database-client.ts +0 -144
  75. package/src/db/cache-db.ts +0 -218
  76. package/src/db/cache-schema.ts +0 -75
  77. package/src/db/database.ts +0 -70
  78. package/src/db/index.ts +0 -252
  79. package/src/db/memory-db.ts +0 -153
  80. package/src/db/memory-schema.ts +0 -29
  81. package/src/db/schema.ts +0 -289
  82. package/src/db/session-repository.ts +0 -733
  83. package/src/domains/index.ts +0 -6
  84. package/src/domains/utilities/index.ts +0 -6
  85. package/src/domains/utilities/time/index.ts +0 -5
  86. package/src/domains/utilities/time/tools.ts +0 -291
  87. package/src/services/agent-service.ts +0 -273
  88. package/src/services/evaluation-service.ts +0 -271
  89. package/src/services/functional/evaluation-logic.ts +0 -296
  90. package/src/services/functional/file-processor.ts +0 -273
  91. package/src/services/functional/index.ts +0 -12
  92. package/src/services/memory.service.ts +0 -476
  93. package/src/types/api/batch.ts +0 -108
  94. package/src/types/api/errors.ts +0 -118
  95. package/src/types/api/index.ts +0 -55
  96. package/src/types/api/requests.ts +0 -76
  97. package/src/types/api/responses.ts +0 -180
  98. package/src/types/api/websockets.ts +0 -85
  99. package/src/types/benchmark.ts +0 -49
  100. package/src/types/database.types.ts +0 -510
  101. package/src/types/memory-types.ts +0 -63
  102. package/src/utils/advanced-tokenizer.ts +0 -191
  103. package/src/utils/ai-model-fetcher.ts +0 -19
  104. package/src/utils/async-file-operations.ts +0 -516
  105. package/src/utils/audio-player.ts +0 -345
  106. package/src/utils/codebase-helpers.ts +0 -211
  107. package/src/utils/console-ui.ts +0 -79
  108. package/src/utils/database-errors.ts +0 -140
  109. package/src/utils/debug-logger.ts +0 -49
  110. package/src/utils/file-scanner.ts +0 -259
  111. package/src/utils/help.ts +0 -20
  112. package/src/utils/immutable-cache.ts +0 -106
  113. package/src/utils/jsonc.ts +0 -158
  114. package/src/utils/memory-tui.ts +0 -414
  115. package/src/utils/models-dev.ts +0 -91
  116. package/src/utils/parallel-operations.ts +0 -487
  117. package/src/utils/process-manager.ts +0 -155
  118. package/src/utils/prompts.ts +0 -120
  119. package/src/utils/search-tool-builder.ts +0 -214
  120. package/src/utils/session-manager.ts +0 -168
  121. package/src/utils/session-title.ts +0 -87
  122. package/src/utils/simplified-errors.ts +0 -410
  123. package/src/utils/template-engine.ts +0 -94
  124. package/src/utils/test-audio.ts +0 -71
  125. package/src/utils/todo-context.ts +0 -46
  126. package/src/utils/token-counter.ts +0 -288
  127. /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
  128. /package/src/utils/{logger.ts → display/logger.ts} +0 -0
  129. /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
  130. /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
  131. /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);