@sylphx/flow 1.0.1 → 1.0.3
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.
- package/CHANGELOG.md +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- package/dist/shared/chunk-x66eh37x.js +0 -137
|
@@ -0,0 +1,487 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized path resolution for all static assets
|
|
3
|
+
*
|
|
4
|
+
* Structure:
|
|
5
|
+
* assets/ (at project root) - single source of truth
|
|
6
|
+
*
|
|
7
|
+
* Path resolution:
|
|
8
|
+
* - Development: src/utils/paths.ts reads ../assets
|
|
9
|
+
* - Production: dist/xxx.js reads ../assets
|
|
10
|
+
* - No copying needed, both read same location
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { pathSecurity } from './security.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Find package root by walking up directory tree
|
|
20
|
+
* Pure function - finds package.json location
|
|
21
|
+
*
|
|
22
|
+
* @param context - Optional context for error message (e.g., 'assets', 'migrations')
|
|
23
|
+
* @returns Absolute path to package root directory
|
|
24
|
+
* @throws Error if package.json cannot be found within 10 levels
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const root = findPackageRoot(); // 'Cannot find package.json'
|
|
28
|
+
* const root = findPackageRoot('drizzle migrations'); // 'Cannot find package.json - drizzle migrations location unknown'
|
|
29
|
+
*/
|
|
30
|
+
export function findPackageRoot(context?: string): string {
|
|
31
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
32
|
+
let currentDir = path.dirname(__filename);
|
|
33
|
+
|
|
34
|
+
// Walk up max 10 levels to find package.json
|
|
35
|
+
for (let i = 0; i < 10; i++) {
|
|
36
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
37
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
38
|
+
return currentDir;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parentDir = path.dirname(currentDir);
|
|
42
|
+
if (parentDir === currentDir) break; // reached filesystem root
|
|
43
|
+
currentDir = parentDir;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const errorMsg = context
|
|
47
|
+
? `Cannot find package.json - ${context} location unknown`
|
|
48
|
+
: 'Cannot find package.json';
|
|
49
|
+
throw new Error(errorMsg);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Find monorepo root (parent of packages/flow) for assets
|
|
53
|
+
const PACKAGE_ROOT = findPackageRoot();
|
|
54
|
+
const MONOREPO_ROOT = path.join(PACKAGE_ROOT, '..', '..');
|
|
55
|
+
const ASSETS_ROOT = fs.existsSync(path.join(MONOREPO_ROOT, 'assets'))
|
|
56
|
+
? path.join(MONOREPO_ROOT, 'assets')
|
|
57
|
+
: path.join(PACKAGE_ROOT, 'assets');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get path to agents directory
|
|
61
|
+
*/
|
|
62
|
+
export function getAgentsDir(): string {
|
|
63
|
+
return path.join(ASSETS_ROOT, 'agents');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get path to templates directory
|
|
68
|
+
*/
|
|
69
|
+
export function getTemplatesDir(): string {
|
|
70
|
+
return path.join(ASSETS_ROOT, 'templates');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get path to rules directory
|
|
75
|
+
*/
|
|
76
|
+
export function getRulesDir(): string {
|
|
77
|
+
return path.join(ASSETS_ROOT, 'rules');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get path to knowledge directory
|
|
82
|
+
*/
|
|
83
|
+
export function getKnowledgeDir(): string {
|
|
84
|
+
return path.join(ASSETS_ROOT, 'knowledge');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get path to output styles directory
|
|
89
|
+
*/
|
|
90
|
+
export function getOutputStylesDir(): string {
|
|
91
|
+
return path.join(ASSETS_ROOT, 'output-styles');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get path to slash commands directory
|
|
96
|
+
*/
|
|
97
|
+
export function getSlashCommandsDir(): string {
|
|
98
|
+
return path.join(ASSETS_ROOT, 'slash-commands');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get path to a specific rule file with path traversal protection
|
|
103
|
+
*/
|
|
104
|
+
export function getRuleFile(filename: string): string {
|
|
105
|
+
// Validate filename to prevent path traversal
|
|
106
|
+
if (!filename || typeof filename !== 'string') {
|
|
107
|
+
throw new Error('Filename must be a non-empty string');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check for path traversal attempts
|
|
111
|
+
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
112
|
+
throw new Error(`Invalid filename: ${filename}. Path traversal not allowed.`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Validate filename contains only safe characters
|
|
116
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {
|
|
117
|
+
throw new Error(`Filename contains invalid characters: ${filename}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Safely join paths
|
|
121
|
+
const rulesDir = getRulesDir();
|
|
122
|
+
const filePath = pathSecurity.safeJoin(rulesDir, filename);
|
|
123
|
+
|
|
124
|
+
if (!fs.existsSync(filePath)) {
|
|
125
|
+
throw new Error(`Rule file not found: ${filename} (looked in ${rulesDir})`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return filePath;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Debug info - shows where assets are resolved from
|
|
133
|
+
*/
|
|
134
|
+
export function getPathsInfo() {
|
|
135
|
+
return {
|
|
136
|
+
assetsRoot: ASSETS_ROOT,
|
|
137
|
+
agents: getAgentsDir(),
|
|
138
|
+
templates: getTemplatesDir(),
|
|
139
|
+
rules: getRulesDir(),
|
|
140
|
+
outputStyles: getOutputStylesDir(),
|
|
141
|
+
slashCommands: getSlashCommandsDir(),
|
|
142
|
+
};
|
|
143
|
+
}
|