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.
- package/CHANGELOG.md +20 -0
- package/README.md +4 -10
- package/clients/__tests__/file-time.test.js +216 -0
- package/clients/__tests__/format-service.test.js +245 -0
- package/clients/__tests__/formatters.test.js +271 -0
- package/clients/agent-behavior-client.test.js +94 -0
- package/clients/biome-client.test.js +144 -0
- package/clients/cache-manager.test.js +197 -0
- package/clients/complexity-client.test.js +234 -0
- package/clients/dependency-checker.test.js +60 -0
- package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
- package/clients/dispatch/__tests__/runner-registration.test.js +234 -0
- package/clients/dispatch/__tests__/runner-registration.test.ts +2 -2
- package/clients/dispatch/dispatcher.edge.test.js +82 -0
- package/clients/dispatch/dispatcher.format.test.js +46 -0
- package/clients/dispatch/dispatcher.inline.test.js +74 -0
- package/clients/dispatch/dispatcher.test.js +116 -0
- package/clients/dispatch/runners/architect.test.js +138 -0
- package/clients/dispatch/runners/ast-grep-napi.test.js +106 -0
- package/clients/dispatch/runners/lsp.js +42 -5
- package/clients/dispatch/runners/oxlint.test.js +230 -0
- package/clients/dispatch/runners/pyright.test.js +98 -0
- package/clients/dispatch/runners/python-slop.test.js +203 -0
- package/clients/dispatch/runners/scan_codebase.test.js +89 -0
- package/clients/dispatch/runners/shellcheck.test.js +98 -0
- package/clients/dispatch/runners/spellcheck.test.js +158 -0
- package/clients/dispatch/utils/format-utils.js +1 -6
- package/clients/dispatch/utils/format-utils.ts +1 -6
- package/clients/dogfood.test.js +201 -0
- package/clients/file-kinds.test.js +169 -0
- package/clients/formatters.js +1 -1
- package/clients/go-client.test.js +127 -0
- package/clients/jscpd-client.test.js +127 -0
- package/clients/knip-client.test.js +112 -0
- package/clients/lsp/__tests__/client.test.js +310 -0
- package/clients/lsp/__tests__/client.test.ts +1 -46
- package/clients/lsp/__tests__/config.test.js +167 -0
- package/clients/lsp/__tests__/error-recovery.test.js +213 -0
- package/clients/lsp/__tests__/integration.test.js +127 -0
- package/clients/lsp/__tests__/launch.test.js +313 -0
- package/clients/lsp/__tests__/server.test.js +259 -0
- package/clients/lsp/__tests__/service.test.js +435 -0
- package/clients/lsp/client.js +32 -44
- package/clients/lsp/client.ts +36 -45
- package/clients/lsp/launch.js +11 -6
- package/clients/lsp/launch.ts +11 -6
- package/clients/lsp/server.js +27 -2
- package/clients/metrics-client.test.js +141 -0
- package/clients/ruff-client.test.js +132 -0
- package/clients/rust-client.test.js +108 -0
- package/clients/sanitize.test.js +177 -0
- package/clients/secrets-scanner.test.js +100 -0
- package/clients/test-runner-client.test.js +192 -0
- package/clients/todo-scanner.test.js +301 -0
- package/clients/type-coverage-client.test.js +105 -0
- package/clients/typescript-client.codefix.test.js +157 -0
- package/clients/typescript-client.test.js +105 -0
- package/commands/rate.test.js +119 -0
- package/index.ts +66 -72
- package/package.json +1 -1
- package/clients/bus/bus.js +0 -191
- package/clients/bus/bus.ts +0 -251
- package/clients/bus/events.js +0 -214
- package/clients/bus/events.ts +0 -279
- package/clients/bus/index.js +0 -8
- package/clients/bus/index.ts +0 -9
- package/clients/bus/integration.js +0 -158
- package/clients/bus/integration.ts +0 -227
- package/clients/dispatch/bus-dispatcher.js +0 -178
- package/clients/dispatch/bus-dispatcher.ts +0 -258
- package/clients/services/__tests__/effect-integration.test.ts +0 -111
- package/clients/services/effect-integration.js +0 -198
- package/clients/services/effect-integration.ts +0 -276
- package/clients/services/index.js +0 -7
- package/clients/services/index.ts +0 -8
- package/clients/services/runner-service.js +0 -134
- 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,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
|
-
}
|