pi-lens 3.2.0 → 3.3.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 (77) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -10
  3. package/clients/__tests__/file-time.test.js +216 -0
  4. package/clients/__tests__/format-service.test.js +245 -0
  5. package/clients/__tests__/formatters.test.js +271 -0
  6. package/clients/agent-behavior-client.test.js +94 -0
  7. package/clients/biome-client.test.js +144 -0
  8. package/clients/cache-manager.test.js +197 -0
  9. package/clients/complexity-client.test.js +234 -0
  10. package/clients/dependency-checker.test.js +60 -0
  11. package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
  12. package/clients/dispatch/__tests__/runner-registration.test.js +234 -0
  13. package/clients/dispatch/__tests__/runner-registration.test.ts +2 -2
  14. package/clients/dispatch/dispatcher.edge.test.js +82 -0
  15. package/clients/dispatch/dispatcher.format.test.js +46 -0
  16. package/clients/dispatch/dispatcher.inline.test.js +74 -0
  17. package/clients/dispatch/dispatcher.test.js +116 -0
  18. package/clients/dispatch/runners/architect.test.js +138 -0
  19. package/clients/dispatch/runners/ast-grep-napi.test.js +106 -0
  20. package/clients/dispatch/runners/lsp.js +42 -5
  21. package/clients/dispatch/runners/oxlint.test.js +230 -0
  22. package/clients/dispatch/runners/pyright.test.js +98 -0
  23. package/clients/dispatch/runners/python-slop.test.js +203 -0
  24. package/clients/dispatch/runners/scan_codebase.test.js +89 -0
  25. package/clients/dispatch/runners/shellcheck.test.js +98 -0
  26. package/clients/dispatch/runners/spellcheck.test.js +158 -0
  27. package/clients/dispatch/utils/format-utils.js +1 -6
  28. package/clients/dispatch/utils/format-utils.ts +1 -6
  29. package/clients/dogfood.test.js +201 -0
  30. package/clients/file-kinds.test.js +169 -0
  31. package/clients/formatters.js +1 -1
  32. package/clients/go-client.test.js +127 -0
  33. package/clients/jscpd-client.test.js +127 -0
  34. package/clients/knip-client.test.js +112 -0
  35. package/clients/lsp/__tests__/client.test.js +310 -0
  36. package/clients/lsp/__tests__/client.test.ts +1 -46
  37. package/clients/lsp/__tests__/config.test.js +167 -0
  38. package/clients/lsp/__tests__/error-recovery.test.js +213 -0
  39. package/clients/lsp/__tests__/integration.test.js +127 -0
  40. package/clients/lsp/__tests__/launch.test.js +313 -0
  41. package/clients/lsp/__tests__/server.test.js +259 -0
  42. package/clients/lsp/__tests__/service.test.js +435 -0
  43. package/clients/lsp/client.js +32 -44
  44. package/clients/lsp/client.ts +36 -45
  45. package/clients/lsp/launch.js +11 -6
  46. package/clients/lsp/launch.ts +11 -6
  47. package/clients/lsp/server.js +27 -2
  48. package/clients/metrics-client.test.js +141 -0
  49. package/clients/ruff-client.test.js +132 -0
  50. package/clients/rust-client.test.js +108 -0
  51. package/clients/sanitize.test.js +177 -0
  52. package/clients/secrets-scanner.test.js +100 -0
  53. package/clients/test-runner-client.test.js +192 -0
  54. package/clients/todo-scanner.test.js +301 -0
  55. package/clients/type-coverage-client.test.js +105 -0
  56. package/clients/typescript-client.codefix.test.js +157 -0
  57. package/clients/typescript-client.test.js +105 -0
  58. package/commands/rate.test.js +119 -0
  59. package/index.ts +66 -72
  60. package/package.json +1 -1
  61. package/clients/bus/bus.js +0 -191
  62. package/clients/bus/bus.ts +0 -251
  63. package/clients/bus/events.js +0 -214
  64. package/clients/bus/events.ts +0 -279
  65. package/clients/bus/index.js +0 -8
  66. package/clients/bus/index.ts +0 -9
  67. package/clients/bus/integration.js +0 -158
  68. package/clients/bus/integration.ts +0 -227
  69. package/clients/dispatch/bus-dispatcher.js +0 -178
  70. package/clients/dispatch/bus-dispatcher.ts +0 -258
  71. package/clients/services/__tests__/effect-integration.test.ts +0 -111
  72. package/clients/services/effect-integration.js +0 -198
  73. package/clients/services/effect-integration.ts +0 -276
  74. package/clients/services/index.js +0 -7
  75. package/clients/services/index.ts +0 -8
  76. package/clients/services/runner-service.js +0 -134
  77. package/clients/services/runner-service.ts +0 -225
@@ -1,198 +0,0 @@
1
- /**
2
- * Effect-TS Integration for pi-lens Dispatch
3
- *
4
- * Bridges the Effect service layer with the existing dispatch system.
5
- *
6
- * This provides:
7
- * - Concurrent runner execution with Effect.all
8
- * - Timeout handling for slow runners
9
- * - Graceful error recovery
10
- * - Bus event integration
11
- */
12
- import { DiagnosticFound, FileModified, RunnerCompleted, RunnerStarted, } from "../bus/events.js";
13
- import { formatDiagnostics } from "../dispatch/utils/format-utils.js";
14
- import { executeEffect, runRunnersConcurrent, } from "./runner-service.js";
15
- // Import runners to register them in the dispatcher
16
- import "../dispatch/runners/index.js";
17
- // --- Core Functions ---
18
- /**
19
- * Run all runners in a group concurrently using Effect
20
- */
21
- async function runGroupConcurrent(ctx, group) {
22
- const { getRunner, getRunnersForKind } = await import("../dispatch/dispatcher.js");
23
- const _startTime = Date.now();
24
- // Get runner definitions
25
- const runnerDefs = group.filterKinds
26
- ? group.runnerIds.filter((id) => {
27
- const runner = getRunner(id);
28
- return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
29
- })
30
- : group.runnerIds;
31
- const runners = runnerDefs
32
- .map((id) => getRunner(id))
33
- .filter((r) => r !== undefined)
34
- .filter((r) => (r.when ? r.when(ctx) : true));
35
- if (runners.length === 0) {
36
- return { results: [], diagnostics: [] };
37
- }
38
- // Build the single runner execution function
39
- const runSingle = async (filePath, runnerId) => {
40
- const runner = getRunner(runnerId);
41
- if (!runner) {
42
- return { diagnostics: [], durationMs: 0 };
43
- }
44
- // Publish started event
45
- RunnerStarted.publish({
46
- runnerId,
47
- filePath,
48
- timestamp: Date.now(),
49
- });
50
- const runnerStart = Date.now();
51
- let status = "completed";
52
- let diagnostics = [];
53
- try {
54
- const result = await runner.run(ctx);
55
- diagnostics = result.diagnostics.map((d) => ({
56
- id: d.id,
57
- message: d.message,
58
- filePath: ctx.filePath,
59
- line: d.line,
60
- column: d.column,
61
- severity: d.severity === "error"
62
- ? "error"
63
- : d.severity === "warning"
64
- ? "warning"
65
- : "info",
66
- semantic: d.semantic ?? result.semantic ?? "warning",
67
- tool: runnerId,
68
- rule: d.rule,
69
- fixable: d.fixable,
70
- fixSuggestion: d.fixSuggestion,
71
- }));
72
- // Publish diagnostic found event
73
- if (diagnostics.length > 0) {
74
- DiagnosticFound.publish({
75
- runnerId,
76
- filePath: ctx.filePath,
77
- diagnostics,
78
- durationMs: Date.now() - runnerStart,
79
- });
80
- }
81
- return {
82
- diagnostics: diagnostics.map((d) => ({
83
- id: d.id,
84
- message: d.message,
85
- severity: d.severity,
86
- semantic: d.semantic,
87
- })),
88
- durationMs: Date.now() - runnerStart,
89
- };
90
- }
91
- catch (err) {
92
- status = "failed";
93
- return {
94
- diagnostics: [],
95
- durationMs: Date.now() - runnerStart,
96
- error: String(err),
97
- };
98
- }
99
- finally {
100
- // Publish completed event
101
- RunnerCompleted.publish({
102
- runnerId,
103
- filePath: ctx.filePath,
104
- status,
105
- durationMs: Date.now() - runnerStart,
106
- diagnosticCount: diagnostics.length,
107
- });
108
- }
109
- };
110
- // Run all runners concurrently using Effect
111
- const runnerIds = runners.map((r) => r.id);
112
- const concurrentResults = await executeEffect(runRunnersConcurrent(ctx.filePath, runnerIds, runSingle, 30000));
113
- // Collect all diagnostics
114
- const allDiagnostics = [];
115
- for (const result of concurrentResults) {
116
- if (result.status === "success") {
117
- allDiagnostics.push(...result.diagnostics.map((d) => ({
118
- id: d.id,
119
- message: d.message,
120
- filePath: ctx.filePath,
121
- severity: d.severity,
122
- semantic: d.semantic ?? group.semantic ?? "warning",
123
- tool: result.runnerId,
124
- })));
125
- }
126
- }
127
- return {
128
- results: concurrentResults,
129
- diagnostics: allDiagnostics,
130
- };
131
- }
132
- // --- Main Dispatch Function ---
133
- export async function dispatchWithEffect(ctx, groups) {
134
- const startTime = Date.now();
135
- const allDiagnostics = [];
136
- const allRunnerResults = [];
137
- let stopped = false;
138
- // Publish file modified event
139
- FileModified.publish({
140
- filePath: ctx.filePath,
141
- changeType: "external",
142
- });
143
- for (const group of groups) {
144
- if (stopped && ctx.pi.getFlag("stop-on-error")) {
145
- break;
146
- }
147
- const { results, diagnostics } = await runGroupConcurrent(ctx, group);
148
- allDiagnostics.push(...diagnostics);
149
- allRunnerResults.push(...results);
150
- // Check for blockers
151
- const semantic = group.semantic ?? "warning";
152
- if (semantic === "blocking" && diagnostics.length > 0) {
153
- stopped = true;
154
- }
155
- }
156
- // Categorize results
157
- const blockers = allDiagnostics.filter((d) => d.semantic === "blocking");
158
- const warnings = allDiagnostics.filter((d) => d.semantic === "warning" || d.semantic === "none");
159
- const fixedItems = allDiagnostics.filter((d) => d.semantic === "fixed");
160
- const silentItems = allDiagnostics.filter((d) => d.semantic === "silent");
161
- // Format output
162
- let output = formatDiagnostics(blockers, "blocking");
163
- output += formatDiagnostics(fixedItems, "fixed");
164
- const durationMs = Date.now() - startTime;
165
- // Log performance info in debug mode
166
- if (ctx.pi.getFlag("lens-bus-debug")) {
167
- console.error(`[effect] Total duration: ${durationMs}ms`);
168
- for (const r of allRunnerResults) {
169
- console.error(`[effect] ${r.runnerId}: ${r.status} (${r.durationMs}ms)`);
170
- }
171
- }
172
- return {
173
- diagnostics: allDiagnostics,
174
- blockers,
175
- warnings,
176
- fixed: fixedItems,
177
- silent: silentItems,
178
- output,
179
- hasBlockers: blockers.length > 0,
180
- durationMs,
181
- runnerResults: allRunnerResults,
182
- };
183
- }
184
- // --- Simple Integration Helper ---
185
- export async function dispatchLintWithEffect(filePath, cwd, pi) {
186
- const { createDispatchContext } = await import("../dispatch/dispatcher.js");
187
- const { TOOL_PLANS } = await import("../dispatch/plan.js");
188
- // blockingOnly=true: post-write dispatch only reports blocking errors (same as standard dispatchLint)
189
- const ctx = createDispatchContext(filePath, cwd, pi, undefined, true);
190
- const kind = ctx.kind;
191
- if (!kind)
192
- return "";
193
- const plan = TOOL_PLANS[kind];
194
- if (!plan)
195
- return "";
196
- const result = await dispatchWithEffect(ctx, plan.groups);
197
- return result.output;
198
- }
@@ -1,276 +0,0 @@
1
- /**
2
- * Effect-TS Integration for pi-lens Dispatch
3
- *
4
- * Bridges the Effect service layer with the existing dispatch system.
5
- *
6
- * This provides:
7
- * - Concurrent runner execution with Effect.all
8
- * - Timeout handling for slow runners
9
- * - Graceful error recovery
10
- * - Bus event integration
11
- */
12
-
13
- import {
14
- type Diagnostic,
15
- DiagnosticFound,
16
- FileModified,
17
- RunnerCompleted,
18
- RunnerStarted,
19
- } from "../bus/events.js";
20
- import { formatDiagnostics } from "../dispatch/utils/format-utils.js";
21
- import {
22
- type ConcurrentRunnerResult,
23
- executeEffect,
24
- type RunnerResult,
25
- runRunnersConcurrent,
26
- } from "./runner-service.js";
27
- // Import runners to register them in the dispatcher
28
- import "../dispatch/runners/index.js";
29
-
30
- import type { DispatchContext, RunnerGroup } from "../dispatch/types.js";
31
-
32
- // --- Enhanced Result Type ---
33
-
34
- export interface EffectDispatchResult {
35
- diagnostics: Diagnostic[];
36
- blockers: Diagnostic[];
37
- warnings: Diagnostic[];
38
- fixed: Diagnostic[];
39
- silent: Diagnostic[];
40
- output: string;
41
- hasBlockers: boolean;
42
- durationMs: number;
43
- runnerResults: ConcurrentRunnerResult[];
44
- }
45
-
46
- // --- Core Functions ---
47
-
48
- /**
49
- * Run all runners in a group concurrently using Effect
50
- */
51
- async function runGroupConcurrent(
52
- ctx: DispatchContext,
53
- group: RunnerGroup,
54
- ): Promise<{ results: ConcurrentRunnerResult[]; diagnostics: Diagnostic[] }> {
55
- const { getRunner, getRunnersForKind } = await import(
56
- "../dispatch/dispatcher.js"
57
- );
58
- const _startTime = Date.now();
59
-
60
- // Get runner definitions
61
- const runnerDefs = group.filterKinds
62
- ? group.runnerIds.filter((id) => {
63
- const runner = getRunner(id);
64
- return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
65
- })
66
- : group.runnerIds;
67
-
68
- const runners = runnerDefs
69
- .map((id) => getRunner(id))
70
- .filter((r): r is NonNullable<typeof r> => r !== undefined)
71
- .filter((r) => (r.when ? r.when(ctx) : true));
72
-
73
- if (runners.length === 0) {
74
- return { results: [], diagnostics: [] };
75
- }
76
-
77
- // Build the single runner execution function
78
- const runSingle = async (
79
- filePath: string,
80
- runnerId: string,
81
- ): Promise<RunnerResult> => {
82
- const runner = getRunner(runnerId);
83
- if (!runner) {
84
- return { diagnostics: [], durationMs: 0 };
85
- }
86
-
87
- // Publish started event
88
- RunnerStarted.publish({
89
- runnerId,
90
- filePath,
91
- timestamp: Date.now(),
92
- });
93
-
94
- const runnerStart = Date.now();
95
- let status: "completed" | "failed" = "completed";
96
- let diagnostics: Diagnostic[] = [];
97
-
98
- try {
99
- const result = await runner.run(ctx);
100
- diagnostics = result.diagnostics.map((d) => ({
101
- id: d.id,
102
- message: d.message,
103
- filePath: ctx.filePath,
104
- line: d.line,
105
- column: d.column,
106
- severity:
107
- d.severity === "error"
108
- ? "error"
109
- : d.severity === "warning"
110
- ? "warning"
111
- : "info",
112
- semantic: d.semantic ?? result.semantic ?? "warning",
113
- tool: runnerId,
114
- rule: d.rule,
115
- fixable: d.fixable,
116
- fixSuggestion: d.fixSuggestion,
117
- }));
118
-
119
- // Publish diagnostic found event
120
- if (diagnostics.length > 0) {
121
- DiagnosticFound.publish({
122
- runnerId,
123
- filePath: ctx.filePath,
124
- diagnostics,
125
- durationMs: Date.now() - runnerStart,
126
- });
127
- }
128
-
129
- return {
130
- diagnostics: diagnostics.map((d) => ({
131
- id: d.id,
132
- message: d.message,
133
- severity: d.severity,
134
- semantic: d.semantic,
135
- })),
136
- durationMs: Date.now() - runnerStart,
137
- };
138
- } catch (err) {
139
- status = "failed";
140
- return {
141
- diagnostics: [],
142
- durationMs: Date.now() - runnerStart,
143
- error: String(err),
144
- };
145
- } finally {
146
- // Publish completed event
147
- RunnerCompleted.publish({
148
- runnerId,
149
- filePath: ctx.filePath,
150
- status,
151
- durationMs: Date.now() - runnerStart,
152
- diagnosticCount: diagnostics.length,
153
- });
154
- }
155
- };
156
-
157
- // Run all runners concurrently using Effect
158
- const runnerIds = runners.map((r) => r.id);
159
- const concurrentResults = await executeEffect(
160
- runRunnersConcurrent(ctx.filePath, runnerIds, runSingle, 30_000),
161
- );
162
-
163
- // Collect all diagnostics
164
- const allDiagnostics: Diagnostic[] = [];
165
- for (const result of concurrentResults) {
166
- if (result.status === "success") {
167
- allDiagnostics.push(
168
- ...result.diagnostics.map((d) => ({
169
- id: d.id,
170
- message: d.message,
171
- filePath: ctx.filePath,
172
- severity: d.severity,
173
- semantic: d.semantic ?? group.semantic ?? "warning",
174
- tool: result.runnerId,
175
- })),
176
- );
177
- }
178
- }
179
-
180
- return {
181
- results: concurrentResults,
182
- diagnostics: allDiagnostics,
183
- };
184
- }
185
-
186
- // --- Main Dispatch Function ---
187
-
188
- export async function dispatchWithEffect(
189
- ctx: DispatchContext,
190
- groups: RunnerGroup[],
191
- ): Promise<EffectDispatchResult> {
192
- const startTime = Date.now();
193
- const allDiagnostics: Diagnostic[] = [];
194
- const allRunnerResults: ConcurrentRunnerResult[] = [];
195
- let stopped = false;
196
-
197
- // Publish file modified event
198
- FileModified.publish({
199
- filePath: ctx.filePath,
200
- changeType: "external",
201
- });
202
-
203
- for (const group of groups) {
204
- if (stopped && ctx.pi.getFlag("stop-on-error")) {
205
- break;
206
- }
207
-
208
- const { results, diagnostics } = await runGroupConcurrent(ctx, group);
209
-
210
- allDiagnostics.push(...diagnostics);
211
- allRunnerResults.push(...results);
212
-
213
- // Check for blockers
214
- const semantic = group.semantic ?? "warning";
215
- if (semantic === "blocking" && diagnostics.length > 0) {
216
- stopped = true;
217
- }
218
- }
219
-
220
- // Categorize results
221
- const blockers = allDiagnostics.filter((d) => d.semantic === "blocking");
222
- const warnings = allDiagnostics.filter(
223
- (d) => d.semantic === "warning" || d.semantic === "none",
224
- );
225
- const fixedItems = allDiagnostics.filter((d) => d.semantic === "fixed");
226
- const silentItems = allDiagnostics.filter((d) => d.semantic === "silent");
227
-
228
- // Format output
229
- let output = formatDiagnostics(blockers, "blocking");
230
- output += formatDiagnostics(fixedItems, "fixed");
231
-
232
- const durationMs = Date.now() - startTime;
233
-
234
- // Log performance info in debug mode
235
- if (ctx.pi.getFlag("lens-bus-debug")) {
236
- console.error(`[effect] Total duration: ${durationMs}ms`);
237
- for (const r of allRunnerResults) {
238
- console.error(`[effect] ${r.runnerId}: ${r.status} (${r.durationMs}ms)`);
239
- }
240
- }
241
-
242
- return {
243
- diagnostics: allDiagnostics,
244
- blockers,
245
- warnings,
246
- fixed: fixedItems,
247
- silent: silentItems,
248
- output,
249
- hasBlockers: blockers.length > 0,
250
- durationMs,
251
- runnerResults: allRunnerResults,
252
- };
253
- }
254
-
255
- // --- Simple Integration Helper ---
256
-
257
- export async function dispatchLintWithEffect(
258
- filePath: string,
259
- cwd: string,
260
- pi: { getFlag(flag: string): string | boolean | undefined },
261
- ): Promise<string> {
262
- const { createDispatchContext } = await import("../dispatch/dispatcher.js");
263
- const { TOOL_PLANS } = await import("../dispatch/plan.js");
264
-
265
- // blockingOnly=true: post-write dispatch only reports blocking errors (same as standard dispatchLint)
266
- const ctx = createDispatchContext(filePath, cwd, pi, undefined, true);
267
-
268
- const kind = ctx.kind;
269
- if (!kind) return "";
270
-
271
- const plan = TOOL_PLANS[kind];
272
- if (!plan) return "";
273
-
274
- const result = await dispatchWithEffect(ctx, plan.groups);
275
- return result.output;
276
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Effect-TS Services for pi-lens
3
- *
4
- * Provides composable, testable async operations.
5
- */
6
- export * from "./runner-service.js";
7
- export * from "./effect-integration.js";
@@ -1,8 +0,0 @@
1
- /**
2
- * Effect-TS Services for pi-lens
3
- *
4
- * Provides composable, testable async operations.
5
- */
6
-
7
- export * from "./runner-service.js";
8
- export * from "./effect-integration.js";
@@ -1,134 +0,0 @@
1
- /**
2
- * Effect-TS Service Infrastructure for pi-lens
3
- *
4
- * Simplified implementation focusing on:
5
- * - Concurrent runner execution with resource limits
6
- * - Timeout handling with proper process cleanup
7
- * - Error recovery
8
- */
9
- import { Effect } from "effect";
10
- // --- Configuration ---
11
- /** Maximum concurrent runner processes to prevent resource contention */
12
- const DEFAULT_CONCURRENCY = 3;
13
- /** Maximum concurrent formatters */
14
- const DEFAULT_FORMATTER_CONCURRENCY = 2;
15
- // --- Error Types ---
16
- export class RunnerError {
17
- constructor(runnerId, cause) {
18
- this.runnerId = runnerId;
19
- this.cause = cause;
20
- this._tag = "RunnerError";
21
- }
22
- }
23
- export class TimeoutError {
24
- constructor(operation, timeoutMs) {
25
- this.operation = operation;
26
- this.timeoutMs = timeoutMs;
27
- this._tag = "TimeoutError";
28
- }
29
- }
30
- // --- Concurrent Execution Helper ---
31
- /**
32
- * Run multiple runners concurrently with Effect
33
- *
34
- * Features:
35
- * - Parallel execution with Effect.all (limited concurrency to prevent resource exhaustion)
36
- * - Per-runner timeout handling
37
- * - Graceful error recovery (individual failures don't stop others)
38
- * - Automatic resource cleanup
39
- *
40
- * Concurrency is limited to DEFAULT_CONCURRENCY (3) to prevent:
41
- * - CPU thrashing from too many parallel processes
42
- * - Memory exhaustion from large LSP initializations
43
- * - File descriptor exhaustion
44
- */
45
- export function runRunnersConcurrent(filePath, runnerIds, runSingle, timeoutMs = 30000, concurrency = DEFAULT_CONCURRENCY) {
46
- return Effect.gen(function* () {
47
- const _startTime = Date.now();
48
- // Run runners with limited concurrency
49
- // This prevents resource contention when many linters are configured
50
- const results = yield* Effect.all(runnerIds.map((runnerId) => Effect.gen(function* () {
51
- const runnerStart = Date.now();
52
- // Execute with timeout and error handling
53
- const result = yield* Effect.tryPromise({
54
- try: () => runSingle(filePath, runnerId),
55
- catch: (err) => err,
56
- }).pipe(Effect.timeout(timeoutMs), Effect.catchAll((err) => Effect.succeed({
57
- diagnostics: [],
58
- durationMs: Date.now() - runnerStart,
59
- error: String(err),
60
- })));
61
- const isError = "error" in result;
62
- return {
63
- runnerId,
64
- status: isError ? "failure" : "success",
65
- diagnostics: isError ? [] : result.diagnostics,
66
- durationMs: isError ? result.durationMs : result.durationMs,
67
- error: isError ? result.error : undefined,
68
- };
69
- })), { concurrency });
70
- return results;
71
- });
72
- }
73
- /**
74
- * Run multiple formatters concurrently with limited concurrency
75
- *
76
- * Formatters are typically I/O bound, so we can be slightly more aggressive
77
- * than runners, but still limit to prevent overwhelming the system.
78
- */
79
- export function runFormattersConcurrent(formatters, timeoutMs = 30000, concurrency = DEFAULT_FORMATTER_CONCURRENCY) {
80
- return Effect.gen(function* () {
81
- const results = yield* Effect.all(formatters.map((formatter) => Effect.gen(function* () {
82
- const result = yield* Effect.tryPromise({
83
- try: () => formatter(),
84
- catch: (err) => err,
85
- }).pipe(Effect.timeout(timeoutMs), Effect.catchAll((err) => Effect.succeed(err)));
86
- return result;
87
- })), { concurrency });
88
- return results;
89
- });
90
- }
91
- /**
92
- * Run a single runner with timeout and error handling
93
- */
94
- export function runRunnerWithTimeout(filePath, runnerId, runSingle, timeoutMs = 30000) {
95
- return Effect.gen(function* () {
96
- const startTime = Date.now();
97
- const result = yield* Effect.tryPromise({
98
- try: () => runSingle(filePath, runnerId),
99
- catch: (err) => new RunnerError(runnerId, err),
100
- }).pipe(Effect.timeout(timeoutMs), Effect.mapError((err) => {
101
- if (err instanceof RunnerError)
102
- return err;
103
- return new TimeoutError(`runner:${runnerId}`, timeoutMs);
104
- }));
105
- return {
106
- diagnostics: result.diagnostics,
107
- durationMs: Date.now() - startTime,
108
- };
109
- });
110
- }
111
- // --- Execution Helpers ---
112
- /**
113
- * Execute Effect and get result
114
- */
115
- export function executeEffect(effect) {
116
- return Effect.runPromise(effect);
117
- }
118
- /**
119
- * Execute Effect with error handling
120
- */
121
- export function executeEffectWithError(effect, onError) {
122
- return Effect.runPromise(Effect.catchAll(effect, (err) => Effect.succeed(onError(err))));
123
- }
124
- // --- Error Formatting ---
125
- export function formatError(err) {
126
- switch (err._tag) {
127
- case "RunnerError":
128
- return `Runner ${err.runnerId} failed: ${err.cause}`;
129
- case "TimeoutError":
130
- return `Operation ${err.operation} timed out after ${err.timeoutMs}ms`;
131
- default:
132
- return "Unknown error";
133
- }
134
- }