@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,487 +0,0 @@
1
- /**
2
- * Parallel Operations Utility
3
- * Provides utilities for executing async operations in parallel with proper error handling and resource management
4
- */
5
-
6
- import { chunk } from './functional/array.js';
7
- import { logger } from './logger.js';
8
-
9
- /**
10
- * Configuration for parallel operations
11
- */
12
- export interface ParallelOptions {
13
- /** Maximum number of concurrent operations */
14
- concurrency?: number;
15
- /** Whether to continue on error or stop immediately */
16
- continueOnError?: boolean;
17
- /** Timeout for individual operations (ms) */
18
- timeout?: number;
19
- /** Delay between batches (ms) */
20
- batchDelay?: number;
21
- /** Progress callback */
22
- onProgress?: (completed: number, total: number, current?: unknown) => void;
23
- }
24
-
25
- /**
26
- * Result of a parallel operation
27
- */
28
- export interface ParallelResult<T> {
29
- /** Successful results */
30
- successful: Array<{ index: number; result: T; item: unknown }>;
31
- /** Failed operations */
32
- failed: Array<{ index: number; error: Error; item: unknown }>;
33
- /** Total number of operations */
34
- total: number;
35
- /** Success count */
36
- successCount: number;
37
- /** Failure count */
38
- failureCount: number;
39
- /** Execution time in milliseconds */
40
- duration: number;
41
- }
42
-
43
- /**
44
- * Batch operation configuration
45
- */
46
- export interface BatchOptions<T> extends ParallelOptions {
47
- /** Function to process each batch */
48
- processor: (batch: T[], batchIndex: number) => Promise<unknown[]>;
49
- /** Batch size */
50
- batchSize?: number;
51
- }
52
-
53
- /**
54
- * Execute operations in parallel with controlled concurrency
55
- */
56
- export async function parallel<T>(
57
- items: unknown[],
58
- operation: (item: unknown, index: number) => Promise<T>,
59
- options: ParallelOptions = {}
60
- ): Promise<ParallelResult<T>> {
61
- const startTime = Date.now();
62
- const { concurrency = 10, continueOnError = true, timeout = 30000, onProgress } = options;
63
-
64
- logger.debug('Starting parallel operations', {
65
- itemCount: items.length,
66
- concurrency,
67
- continueOnError,
68
- timeout,
69
- });
70
-
71
- // FUNCTIONAL: Use chunk and reduce to accumulate results instead of imperative loop
72
- const batches = chunk(concurrency)(items);
73
-
74
- const results = await batches.reduce<Promise<ParallelResult<T>>>(
75
- async (accPromise, batch, batchIdx) => {
76
- const acc = await accPromise;
77
-
78
- const batchPromises = batch.map(async (item, batchIndex) => {
79
- const globalIndex = batchIdx * concurrency + batchIndex;
80
-
81
- try {
82
- // Add timeout to individual operations
83
- const operationPromise = operation(item, globalIndex);
84
- const timeoutPromise = new Promise<never>((_, reject) => {
85
- setTimeout(() => reject(new Error(`Operation timeout after ${timeout}ms`)), timeout);
86
- });
87
-
88
- const result = await Promise.race([operationPromise, timeoutPromise]);
89
-
90
- // Report progress
91
- if (onProgress) {
92
- onProgress(acc.successCount + acc.failureCount + 1, items.length, item);
93
- }
94
-
95
- return { success: true as const, index: globalIndex, result, item };
96
- } catch (error) {
97
- const errorObj = error instanceof Error ? error : new Error(String(error));
98
-
99
- logger.warn('Parallel operation failed', {
100
- index: globalIndex,
101
- error: errorObj.message,
102
- item: typeof item === 'object' ? JSON.stringify(item) : item,
103
- });
104
-
105
- // Re-throw if not continuing on error
106
- if (!continueOnError) {
107
- throw errorObj;
108
- }
109
-
110
- return { success: false as const, index: globalIndex, error: errorObj, item };
111
- }
112
- });
113
-
114
- // Wait for current batch to complete
115
- const batchResults = await Promise.all(batchPromises);
116
-
117
- // FUNCTIONAL: Partition results into successful/failed
118
- const newSuccessful = batchResults
119
- .filter((r) => r.success)
120
- .map((r) => ({ index: r.index, result: r.result, item: r.item }));
121
- const newFailed = batchResults
122
- .filter((r) => !r.success)
123
- .map((r) => ({ index: r.index, error: r.error, item: r.item }));
124
-
125
- // Add delay between batches if specified
126
- if (options.batchDelay && batchIdx < batches.length - 1) {
127
- await new Promise((resolve) => setTimeout(resolve, options.batchDelay!));
128
- }
129
-
130
- return {
131
- successful: [...acc.successful, ...newSuccessful],
132
- failed: [...acc.failed, ...newFailed],
133
- total: items.length,
134
- successCount: acc.successCount + newSuccessful.length,
135
- failureCount: acc.failureCount + newFailed.length,
136
- duration: 0, // Will be set after reduce completes
137
- };
138
- },
139
- Promise.resolve({
140
- successful: [],
141
- failed: [],
142
- total: items.length,
143
- successCount: 0,
144
- failureCount: 0,
145
- duration: 0,
146
- })
147
- );
148
-
149
- // Set final duration
150
- results.duration = Date.now() - startTime;
151
-
152
- logger.info('Parallel operations completed', {
153
- total: results.total,
154
- successCount: results.successCount,
155
- failureCount: results.failureCount,
156
- duration: results.duration,
157
- successRate: `${((results.successCount / results.total) * 100).toFixed(1)}%`,
158
- });
159
-
160
- return results;
161
- }
162
-
163
- /**
164
- * Execute operations in parallel with automatic retry for failed operations
165
- */
166
- export async function parallelWithRetry<T>(
167
- items: unknown[],
168
- operation: (item: unknown, index: number) => Promise<T>,
169
- options: ParallelOptions & { maxRetries?: number; retryDelay?: number } = {}
170
- ): Promise<ParallelResult<T>> {
171
- const { maxRetries = 3, retryDelay = 1000, ...parallelOptions } = options;
172
-
173
- const result = await parallel(items, operation, parallelOptions);
174
-
175
- // FUNCTIONAL: Retry failed operations using reduce instead of for loop
176
- const attempts = Array.from({ length: maxRetries }, (_, i) => i + 1);
177
-
178
- const finalResult = await attempts.reduce<Promise<ParallelResult<T>>>(
179
- async (accPromise, attempt) => {
180
- const acc = await accPromise;
181
-
182
- if (acc.failed.length === 0) {
183
- return acc; // No more failures to retry
184
- }
185
-
186
- logger.info(`Retrying failed operations (attempt ${attempt}/${maxRetries})`, {
187
- failedCount: acc.failed.length,
188
- retryDelay,
189
- });
190
-
191
- // Wait before retry
192
- if (retryDelay > 0) {
193
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
194
- }
195
-
196
- // Retry only failed items
197
- const failedItems = acc.failed.map((f) => f.item);
198
- const retryResult = await parallel(failedItems, operation, parallelOptions);
199
-
200
- // Merge results immutably
201
- return {
202
- successful: [...acc.successful, ...retryResult.successful],
203
- failed: retryResult.failed,
204
- total: acc.total,
205
- successCount: acc.successful.length + retryResult.successful.length,
206
- failureCount: retryResult.failed.length,
207
- duration: acc.duration,
208
- };
209
- },
210
- Promise.resolve(result)
211
- );
212
-
213
- return finalResult;
214
- }
215
-
216
- /**
217
- * Process items in batches with parallel execution within each batch
218
- */
219
- export async function batchParallel<T, R>(
220
- items: T[],
221
- processor: (batch: T[], batchIndex: number) => Promise<R[]>,
222
- options: BatchOptions<T> = {}
223
- ): Promise<R[]> {
224
- const { batchSize = 50, concurrency = 3, continueOnError = true, onProgress } = options;
225
-
226
- // FUNCTIONAL: Use chunk utility instead of for loop
227
- const batches = chunk(batchSize)(items);
228
-
229
- logger.info('Starting batch parallel processing', {
230
- totalItems: items.length,
231
- batchSize,
232
- batchCount: batches.length,
233
- concurrency,
234
- });
235
-
236
- const processBatch = async (batch: T[], batchIndex: number): Promise<R[]> => {
237
- try {
238
- const result = await processor(batch, batchIndex);
239
-
240
- if (onProgress) {
241
- onProgress((batchIndex + 1) * batchSize, items.length, batch);
242
- }
243
-
244
- return result;
245
- } catch (error) {
246
- logger.error('Batch processing failed', {
247
- batchIndex,
248
- batchSize: batch.length,
249
- error: (error as Error).message,
250
- });
251
-
252
- if (!continueOnError) {
253
- throw error;
254
- }
255
-
256
- return [];
257
- }
258
- };
259
-
260
- // Process batches in parallel
261
- const results = await parallel(batches, (batch, index) => processBatch(batch as T[], index), {
262
- concurrency,
263
- continueOnError,
264
- onProgress,
265
- });
266
-
267
- // Flatten successful results
268
- return results.successful.flatMap((r) => r.result as R[]);
269
- }
270
-
271
- /**
272
- * Parallel map operation
273
- */
274
- export async function parallelMap<T, R>(
275
- items: T[],
276
- mapper: (item: T, index: number) => Promise<R>,
277
- options?: ParallelOptions
278
- ): Promise<R[]> {
279
- const result = await parallel(items, mapper, options);
280
-
281
- if (result.failureCount > 0) {
282
- logger.warn('parallelMap had failures', {
283
- total: result.total,
284
- failures: result.failureCount,
285
- });
286
- }
287
-
288
- // Return results in original order
289
- const orderedResults = new Array(result.total);
290
- result.successful.forEach(({ index, result }) => {
291
- orderedResults[index] = result;
292
- });
293
-
294
- return orderedResults.filter(Boolean);
295
- }
296
-
297
- /**
298
- * Parallel filter operation
299
- */
300
- export async function parallelFilter<T>(
301
- items: T[],
302
- predicate: (item: T, index: number) => Promise<boolean>,
303
- options?: ParallelOptions
304
- ): Promise<T[]> {
305
- const results = await parallelMap(
306
- items,
307
- async (item, index) => ({
308
- item,
309
- passes: await predicate(item, index),
310
- }),
311
- options
312
- );
313
-
314
- return results.filter((r) => r.passes).map((r) => r.item);
315
- }
316
-
317
- /**
318
- * Parallel reduce operation (for associative operations)
319
- */
320
- export async function parallelReduce<T>(
321
- items: T[],
322
- reducer: (acc: T, item: T) => Promise<T>,
323
- initialValue: T,
324
- options?: ParallelOptions
325
- ): Promise<T> {
326
- if (items.length === 0) {
327
- return initialValue;
328
- }
329
-
330
- // For small arrays, use regular reduce
331
- if (items.length <= 100) {
332
- // FUNCTIONAL: Use reduce instead of for-of loop
333
- return await items.reduce(async (accPromise, item) => {
334
- const acc = await accPromise;
335
- return await reducer(acc, item);
336
- }, Promise.resolve(initialValue));
337
- }
338
-
339
- // For large arrays, split into chunks and reduce in parallel
340
- const chunkSize = Math.ceil(items.length / (options?.concurrency || 10));
341
-
342
- // FUNCTIONAL: Use chunk utility instead of for loop
343
- const chunks = chunk(chunkSize)(items);
344
-
345
- const chunkResults = await parallelMap(
346
- chunks,
347
- async (chunkItems) => {
348
- // FUNCTIONAL: Use reduce instead of for-of loop
349
- return await chunkItems.reduce(async (accPromise, item) => {
350
- const acc = await accPromise;
351
- return await reducer(acc, item);
352
- }, Promise.resolve(initialValue));
353
- },
354
- options
355
- );
356
-
357
- // FUNCTIONAL: Combine chunk results using reduce instead of for-of loop
358
- return await chunkResults.reduce(async (accPromise, chunkResult) => {
359
- const acc = await accPromise;
360
- return await reducer(acc, chunkResult);
361
- }, Promise.resolve(initialValue));
362
- }
363
-
364
- /**
365
- * Execute multiple async operations in parallel and return all results
366
- */
367
- export async function all<T>(operations: Array<() => Promise<T>>): Promise<T[]> {
368
- const promises = operations.map((op) => op());
369
- return Promise.all(promises);
370
- }
371
-
372
- /**
373
- * Execute multiple async operations in parallel and return first successful result
374
- */
375
- export async function any<T>(operations: Array<() => Promise<T>>): Promise<T> {
376
- const promises = operations.map(async (op, index) => {
377
- try {
378
- return { success: true, result: await op(), index };
379
- } catch (error) {
380
- return { success: false, error, index };
381
- }
382
- });
383
-
384
- const results = await Promise.all(promises);
385
- const successful = results.find((r) => r.success);
386
-
387
- if (successful) {
388
- return successful.result as T;
389
- }
390
-
391
- // If none succeeded, throw the first error
392
- const firstFailure = results.find((r) => !r.success);
393
- if (firstFailure) {
394
- throw firstFailure.error;
395
- }
396
-
397
- throw new Error('No operations provided');
398
- }
399
-
400
- /**
401
- * Parallel queue interface
402
- */
403
- export interface ParallelQueueInstance<T> {
404
- add(item: T): Promise<unknown>;
405
- size(): number;
406
- clear(): void;
407
- }
408
-
409
- /**
410
- * Create a parallel execution queue with controlled concurrency
411
- */
412
- export function createParallelQueue<T>(
413
- processor: (item: T) => Promise<unknown>,
414
- concurrency = 10
415
- ): ParallelQueueInstance<T> {
416
- // Closure-based state
417
- const queue: Array<{
418
- item: T;
419
- resolve: (value: unknown) => void;
420
- reject: (error: Error) => void;
421
- }> = [];
422
- let running = 0;
423
-
424
- const process = async (): Promise<void> => {
425
- if (running >= concurrency || queue.length === 0) {
426
- return;
427
- }
428
-
429
- running++;
430
- const { item, resolve, reject } = queue.shift()!;
431
-
432
- try {
433
- const result = await processor(item);
434
- resolve(result);
435
- } catch (error) {
436
- reject(error as Error);
437
- } finally {
438
- running--;
439
- // Process next item in queue
440
- setImmediate(() => process());
441
- }
442
- };
443
-
444
- const add = async (item: T): Promise<unknown> => {
445
- return new Promise((resolve, reject) => {
446
- queue.push({ item, resolve, reject });
447
- process();
448
- });
449
- };
450
-
451
- const size = (): number => {
452
- return queue.length;
453
- };
454
-
455
- const clear = (): void => {
456
- queue.length = 0;
457
- };
458
-
459
- return {
460
- add,
461
- size,
462
- clear,
463
- };
464
- }
465
-
466
- /**
467
- * @deprecated Use createParallelQueue() for new code
468
- */
469
- export class ParallelQueue<T> {
470
- private instance: ParallelQueueInstance<T>;
471
-
472
- constructor(processor: (item: T) => Promise<unknown>, concurrency = 10) {
473
- this.instance = createParallelQueue(processor, concurrency);
474
- }
475
-
476
- async add(item: T): Promise<unknown> {
477
- return this.instance.add(item);
478
- }
479
-
480
- size(): number {
481
- return this.instance.size();
482
- }
483
-
484
- clear(): void {
485
- return this.instance.clear();
486
- }
487
- }
@@ -1,155 +0,0 @@
1
- /**
2
- * ProcessManager interface for managing child processes
3
- */
4
- export interface ProcessManager {
5
- trackChildProcess(childProcess: any): void;
6
- killAllProcesses(): Promise<void>;
7
- // Internal for testing - exposed for tests to access state
8
- readonly _state?: ProcessManagerState;
9
- }
10
-
11
- /**
12
- * Internal state for ProcessManager
13
- */
14
- interface ProcessManagerState {
15
- readonly childProcesses: Set<any>;
16
- isShuttingDown: boolean;
17
- readonly signalHandlers: Map<string, (...args: any[]) => void>;
18
- }
19
-
20
- /**
21
- * Create a ProcessManager instance
22
- */
23
- export function createProcessManager(): ProcessManager {
24
- const state: ProcessManagerState = {
25
- childProcesses: new Set(),
26
- isShuttingDown: false,
27
- signalHandlers: new Map(),
28
- };
29
-
30
- /**
31
- * Cleanup signal handlers and reset state (for testing)
32
- */
33
- const cleanup = (): void => {
34
- // Remove signal handlers
35
- for (const [signal, handler] of state.signalHandlers.entries()) {
36
- process.removeListener(signal as any, handler);
37
- }
38
- state.signalHandlers.clear();
39
-
40
- // Clear child processes
41
- state.childProcesses.clear();
42
- state.isShuttingDown = false;
43
- };
44
-
45
- /**
46
- * Kill all tracked child processes
47
- */
48
- const killAllProcesses = async (): Promise<void> => {
49
- const killPromises = Array.from(state.childProcesses).map(async (childProcess) => {
50
- try {
51
- if (childProcess && !childProcess.killed) {
52
- childProcess.kill('SIGTERM');
53
-
54
- // Force kill if it doesn't stop after 2 seconds
55
- setTimeout(() => {
56
- if (!childProcess.killed) {
57
- childProcess.kill('SIGKILL');
58
- }
59
- }, 2000);
60
- }
61
- } catch (_error) {
62
- // Silently handle kill errors
63
- }
64
- });
65
-
66
- await Promise.all(killPromises);
67
- state.childProcesses.clear();
68
- };
69
-
70
- /**
71
- * Setup signal handlers for graceful shutdown
72
- */
73
- const setupSignalHandlers = (): void => {
74
- const shutdown = async (_signal: string) => {
75
- if (state.isShuttingDown) {
76
- return;
77
- }
78
- state.isShuttingDown = true;
79
-
80
- await killAllProcesses();
81
- process.exit(0);
82
- };
83
-
84
- // Create and store signal handlers
85
- const sigintHandler = () => shutdown('SIGINT');
86
- const sigtermHandler = () => shutdown('SIGTERM');
87
- const sighupHandler = () => shutdown('SIGHUP');
88
-
89
- state.signalHandlers.set('SIGINT', sigintHandler);
90
- state.signalHandlers.set('SIGTERM', sigtermHandler);
91
- state.signalHandlers.set('SIGHUP', sighupHandler);
92
-
93
- // Handle termination signals
94
- process.on('SIGINT', sigintHandler);
95
- process.on('SIGTERM', sigtermHandler);
96
- process.on('SIGHUP', sighupHandler);
97
- };
98
-
99
- /**
100
- * Track a child process for cleanup on shutdown
101
- */
102
- const trackChildProcess = (childProcess: any): void => {
103
- state.childProcesses.add(childProcess);
104
-
105
- // Remove from tracking when process exits
106
- childProcess.on('exit', () => {
107
- state.childProcesses.delete(childProcess);
108
- });
109
- };
110
-
111
- // Setup signal handlers when instance is created
112
- setupSignalHandlers();
113
-
114
- const manager: ProcessManager & { _cleanup?: () => void; _state?: ProcessManagerState } = {
115
- trackChildProcess,
116
- killAllProcesses,
117
- _state: state, // Expose state for testing
118
- };
119
-
120
- // Expose cleanup for testing
121
- (manager as any)._cleanup = cleanup;
122
-
123
- return manager;
124
- }
125
-
126
- // Singleton instance for backward compatibility
127
- let _processManagerInstance: ProcessManager | null = null;
128
-
129
- /**
130
- * Get the singleton ProcessManager instance
131
- * @deprecated Use createProcessManager() for new code
132
- */
133
- export class ProcessManager {
134
- static getInstance(): ProcessManager {
135
- if (!_processManagerInstance) {
136
- _processManagerInstance = createProcessManager();
137
- }
138
- return _processManagerInstance;
139
- }
140
-
141
- /**
142
- * Reset singleton instance (for testing only)
143
- * @internal
144
- */
145
- static resetInstance(): void {
146
- if (_processManagerInstance) {
147
- // Call cleanup if available
148
- const cleanup = (_processManagerInstance as any)._cleanup;
149
- if (cleanup) {
150
- cleanup();
151
- }
152
- _processManagerInstance = null;
153
- }
154
- }
155
- }