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,178 +0,0 @@
1
- import { DiagnosticFound, FileModified, ReportReady, RunnerCompleted, RunnerStarted, } from "../bus/events.js";
2
- import { formatDiagnostics } from "./utils/format-utils.js";
3
- // Import runners to register them
4
- import "./runners/index.js";
5
- // --- Core Functions ---
6
- async function runRunner(ctx, runner, defaultSemantic) {
7
- const startTime = Date.now();
8
- // Publish runner started event
9
- RunnerStarted.publish({
10
- runnerId: runner.id,
11
- filePath: ctx.filePath,
12
- timestamp: startTime,
13
- });
14
- try {
15
- // Check when() condition async-safely (sync filter in caller can't await Promises)
16
- if (runner.when && !(await runner.when(ctx))) {
17
- return {
18
- status: "skipped",
19
- diagnostics: [],
20
- semantic: defaultSemantic,
21
- durationMs: Date.now() - startTime,
22
- };
23
- }
24
- const result = await runner.run(ctx);
25
- const durationMs = Date.now() - startTime;
26
- // Publish diagnostic found event
27
- if (result.diagnostics.length > 0) {
28
- DiagnosticFound.publish({
29
- runnerId: runner.id,
30
- filePath: ctx.filePath,
31
- diagnostics: result.diagnostics,
32
- durationMs,
33
- });
34
- }
35
- // Publish runner completed event
36
- // Map "succeeded" to "completed" for the event status
37
- const eventStatus = result.status === "succeeded" ? "completed" : result.status;
38
- RunnerCompleted.publish({
39
- runnerId: runner.id,
40
- filePath: ctx.filePath,
41
- status: eventStatus,
42
- durationMs,
43
- diagnosticCount: result.diagnostics.length,
44
- });
45
- return {
46
- ...result,
47
- semantic: result.semantic ?? defaultSemantic,
48
- durationMs,
49
- };
50
- }
51
- catch (error) {
52
- const durationMs = Date.now() - startTime;
53
- ctx.log?.(`Runner ${runner.id} failed: ${error}`);
54
- RunnerCompleted.publish({
55
- runnerId: runner.id,
56
- filePath: ctx.filePath,
57
- status: "failed",
58
- durationMs,
59
- diagnosticCount: 0,
60
- });
61
- return {
62
- status: "failed",
63
- diagnostics: [],
64
- semantic: defaultSemantic,
65
- durationMs,
66
- };
67
- }
68
- }
69
- // --- Concurrent Dispatch (new feature) ---
70
- export async function dispatchConcurrent(ctx, groups) {
71
- const startTime = Date.now();
72
- const allDiagnostics = [];
73
- const runnerResults = [];
74
- let stopped = false;
75
- for (const group of groups) {
76
- if (stopped && ctx.pi.getFlag("stop-on-error")) {
77
- break;
78
- }
79
- // Get applicable runners
80
- const { getRunner } = await import("./dispatcher.js");
81
- const runnerIds = group.filterKinds
82
- ? group.runnerIds.filter((id) => {
83
- const runner = getRunner(id);
84
- return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
85
- })
86
- : group.runnerIds;
87
- // Note: when() is async — must be awaited. Filter it out here synchronously
88
- // (runners without when always run; runners with when are checked inside runRunner).
89
- const runners = runnerIds
90
- .map((id) => getRunner(id))
91
- .filter((r) => r !== undefined);
92
- const semantic = group.semantic ?? "warning";
93
- if (group.mode === "all") {
94
- // Run all runners concurrently
95
- const results = await Promise.all(runners.map((runner) => runRunner(ctx, runner, semantic)));
96
- for (const result of results) {
97
- runnerResults.push({
98
- runnerId: runners[results.indexOf(result)].id,
99
- status: result.status === "succeeded" ? "completed" : result.status,
100
- durationMs: result.durationMs,
101
- diagnosticCount: result.diagnostics.length,
102
- });
103
- allDiagnostics.push(...result.diagnostics);
104
- if (semantic === "blocking" && result.diagnostics.length > 0) {
105
- stopped = true;
106
- }
107
- }
108
- }
109
- else if (group.mode === "fallback") {
110
- // Run sequentially until first success
111
- for (const runner of runners) {
112
- const result = await runRunner(ctx, runner, semantic);
113
- runnerResults.push({
114
- runnerId: runner.id,
115
- status: result.status === "succeeded" ? "completed" : result.status,
116
- durationMs: result.durationMs,
117
- diagnosticCount: result.diagnostics.length,
118
- });
119
- allDiagnostics.push(...result.diagnostics);
120
- if (result.diagnostics.length === 0 || result.semantic === "fixed") {
121
- break;
122
- }
123
- }
124
- }
125
- }
126
- // Categorize results
127
- const blockers = allDiagnostics.filter((d) => d.semantic === "blocking");
128
- const warnings = allDiagnostics.filter((d) => d.semantic === "warning" || d.semantic === "none");
129
- const fixedItems = allDiagnostics.filter((d) => d.semantic === "fixed");
130
- const silentItems = allDiagnostics.filter((d) => d.semantic === "silent");
131
- // Format output
132
- let output = formatDiagnostics(blockers, "blocking");
133
- output += formatDiagnostics(fixedItems, "fixed");
134
- const durationMs = Date.now() - startTime;
135
- // Publish report ready event
136
- ReportReady.publish({
137
- filePath: ctx.filePath,
138
- report: {
139
- blockers,
140
- warnings,
141
- fixed: fixedItems,
142
- silent: silentItems,
143
- },
144
- durationMs,
145
- });
146
- return {
147
- diagnostics: allDiagnostics,
148
- blockers,
149
- warnings,
150
- fixed: fixedItems,
151
- silent: silentItems,
152
- output,
153
- hasBlockers: blockers.length > 0,
154
- durationMs,
155
- runnerResults,
156
- };
157
- }
158
- // --- Simple Integration Helper ---
159
- export async function dispatchLintWithBus(filePath, cwd, pi) {
160
- const { createDispatchContext } = await import("./dispatcher.js");
161
- const { getRunnersForKind } = await import("./dispatcher.js");
162
- const { TOOL_PLANS } = await import("./plan.js");
163
- // Publish file modified event to trigger any background processing
164
- FileModified.publish({
165
- filePath,
166
- changeType: "external",
167
- });
168
- // blockingOnly=true: post-write dispatch only reports blocking errors (same as standard dispatchLint)
169
- const ctx = createDispatchContext(filePath, cwd, pi, undefined, true);
170
- const kind = ctx.kind;
171
- if (!kind)
172
- return "";
173
- const plan = TOOL_PLANS[kind];
174
- if (!plan)
175
- return "";
176
- const result = await dispatchConcurrent(ctx, plan.groups);
177
- return result.output;
178
- }
@@ -1,258 +0,0 @@
1
- import {
2
- type Diagnostic,
3
- DiagnosticFound,
4
- FileModified,
5
- type OutputSemantic,
6
- ReportReady,
7
- RunnerCompleted,
8
- RunnerStarted,
9
- } from "../bus/events.js";
10
- import { formatDiagnostics } from "./utils/format-utils.js";
11
- // Import runners to register them
12
- import "./runners/index.js";
13
-
14
- import type {
15
- DispatchContext,
16
- RunnerDefinition,
17
- RunnerGroup,
18
- RunnerResult,
19
- } from "./types.js";
20
-
21
- // --- Enhanced Dispatch Result ---
22
-
23
- export interface BusDispatchResult {
24
- diagnostics: Diagnostic[];
25
- blockers: Diagnostic[];
26
- warnings: Diagnostic[];
27
- fixed: Diagnostic[];
28
- silent: Diagnostic[];
29
- output: string;
30
- hasBlockers: boolean;
31
- durationMs: number;
32
- runnerResults: Array<{
33
- runnerId: string;
34
- status: "succeeded" | "failed" | "skipped" | "completed";
35
- durationMs: number;
36
- diagnosticCount: number;
37
- }>;
38
- }
39
-
40
- // --- Core Functions ---
41
-
42
- async function runRunner(
43
- ctx: DispatchContext,
44
- runner: RunnerDefinition,
45
- defaultSemantic: OutputSemantic,
46
- ): Promise<RunnerResult & { durationMs: number }> {
47
- const startTime = Date.now();
48
-
49
- // Publish runner started event
50
- RunnerStarted.publish({
51
- runnerId: runner.id,
52
- filePath: ctx.filePath,
53
- timestamp: startTime,
54
- });
55
-
56
- try {
57
- // Check when() condition async-safely (sync filter in caller can't await Promises)
58
- if (runner.when && !(await runner.when(ctx))) {
59
- return {
60
- status: "skipped",
61
- diagnostics: [],
62
- semantic: defaultSemantic,
63
- durationMs: Date.now() - startTime,
64
- };
65
- }
66
- const result = await runner.run(ctx);
67
- const durationMs = Date.now() - startTime;
68
-
69
- // Publish diagnostic found event
70
- if (result.diagnostics.length > 0) {
71
- DiagnosticFound.publish({
72
- runnerId: runner.id,
73
- filePath: ctx.filePath,
74
- diagnostics: result.diagnostics,
75
- durationMs,
76
- });
77
- }
78
-
79
- // Publish runner completed event
80
- // Map "succeeded" to "completed" for the event status
81
- const eventStatus =
82
- result.status === "succeeded" ? "completed" : result.status;
83
- RunnerCompleted.publish({
84
- runnerId: runner.id,
85
- filePath: ctx.filePath,
86
- status: eventStatus,
87
- durationMs,
88
- diagnosticCount: result.diagnostics.length,
89
- });
90
-
91
- return {
92
- ...result,
93
- semantic: result.semantic ?? defaultSemantic,
94
- durationMs,
95
- };
96
- } catch (error) {
97
- const durationMs = Date.now() - startTime;
98
- ctx.log?.(`Runner ${runner.id} failed: ${error}`);
99
-
100
- RunnerCompleted.publish({
101
- runnerId: runner.id,
102
- filePath: ctx.filePath,
103
- status: "failed",
104
- durationMs,
105
- diagnosticCount: 0,
106
- });
107
-
108
- return {
109
- status: "failed",
110
- diagnostics: [],
111
- semantic: defaultSemantic,
112
- durationMs,
113
- };
114
- }
115
- }
116
-
117
- // --- Concurrent Dispatch (new feature) ---
118
-
119
- export async function dispatchConcurrent(
120
- ctx: DispatchContext,
121
- groups: RunnerGroup[],
122
- ): Promise<BusDispatchResult> {
123
- const startTime = Date.now();
124
- const allDiagnostics: Diagnostic[] = [];
125
- const runnerResults: BusDispatchResult["runnerResults"] = [];
126
- let stopped = false;
127
-
128
- for (const group of groups) {
129
- if (stopped && ctx.pi.getFlag("stop-on-error")) {
130
- break;
131
- }
132
-
133
- // Get applicable runners
134
- const { getRunner } = await import("./dispatcher.js");
135
- const runnerIds = group.filterKinds
136
- ? group.runnerIds.filter((id) => {
137
- const runner = getRunner(id);
138
- return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
139
- })
140
- : group.runnerIds;
141
-
142
- // Note: when() is async — must be awaited. Filter it out here synchronously
143
- // (runners without when always run; runners with when are checked inside runRunner).
144
- const runners = runnerIds
145
- .map((id) => getRunner(id))
146
- .filter((r): r is RunnerDefinition => r !== undefined);
147
-
148
- const semantic = group.semantic ?? "warning";
149
-
150
- if (group.mode === "all") {
151
- // Run all runners concurrently
152
- const results = await Promise.all(
153
- runners.map((runner) => runRunner(ctx, runner, semantic)),
154
- );
155
-
156
- for (const result of results) {
157
- runnerResults.push({
158
- runnerId: runners[results.indexOf(result)].id,
159
- status: result.status === "succeeded" ? "completed" : result.status,
160
- durationMs: result.durationMs,
161
- diagnosticCount: result.diagnostics.length,
162
- });
163
- allDiagnostics.push(...result.diagnostics);
164
-
165
- if (semantic === "blocking" && result.diagnostics.length > 0) {
166
- stopped = true;
167
- }
168
- }
169
- } else if (group.mode === "fallback") {
170
- // Run sequentially until first success
171
- for (const runner of runners) {
172
- const result = await runRunner(ctx, runner, semantic);
173
- runnerResults.push({
174
- runnerId: runner.id,
175
- status: result.status === "succeeded" ? "completed" : result.status,
176
- durationMs: result.durationMs,
177
- diagnosticCount: result.diagnostics.length,
178
- });
179
- allDiagnostics.push(...result.diagnostics);
180
-
181
- if (result.diagnostics.length === 0 || result.semantic === "fixed") {
182
- break;
183
- }
184
- }
185
- }
186
- }
187
-
188
- // Categorize results
189
- const blockers = allDiagnostics.filter((d) => d.semantic === "blocking");
190
- const warnings = allDiagnostics.filter(
191
- (d) => d.semantic === "warning" || d.semantic === "none",
192
- );
193
- const fixedItems = allDiagnostics.filter((d) => d.semantic === "fixed");
194
- const silentItems = allDiagnostics.filter((d) => d.semantic === "silent");
195
-
196
- // Format output
197
- let output = formatDiagnostics(blockers, "blocking");
198
- output += formatDiagnostics(fixedItems, "fixed");
199
-
200
- const durationMs = Date.now() - startTime;
201
-
202
- // Publish report ready event
203
- ReportReady.publish({
204
- filePath: ctx.filePath,
205
- report: {
206
- blockers,
207
- warnings,
208
- fixed: fixedItems,
209
- silent: silentItems,
210
- },
211
- durationMs,
212
- });
213
-
214
- return {
215
- diagnostics: allDiagnostics,
216
- blockers,
217
- warnings,
218
- fixed: fixedItems,
219
- silent: silentItems,
220
- output,
221
- hasBlockers: blockers.length > 0,
222
- durationMs,
223
- runnerResults,
224
- };
225
- }
226
-
227
- // --- Simple Integration Helper ---
228
-
229
- export async function dispatchLintWithBus(
230
- filePath: string,
231
- cwd: string,
232
- pi: {
233
- getFlag(flag: string): string | boolean | undefined;
234
- log?: (msg: string) => void;
235
- },
236
- ): Promise<string> {
237
- const { createDispatchContext } = await import("./dispatcher.js");
238
- const { getRunnersForKind } = await import("./dispatcher.js");
239
- const { TOOL_PLANS } = await import("./plan.js");
240
-
241
- // Publish file modified event to trigger any background processing
242
- FileModified.publish({
243
- filePath,
244
- changeType: "external",
245
- });
246
-
247
- // blockingOnly=true: post-write dispatch only reports blocking errors (same as standard dispatchLint)
248
- const ctx = createDispatchContext(filePath, cwd, pi, undefined, true);
249
-
250
- const kind = ctx.kind;
251
- if (!kind) return "";
252
-
253
- const plan = TOOL_PLANS[kind];
254
- if (!plan) return "";
255
-
256
- const result = await dispatchConcurrent(ctx, plan.groups);
257
- return result.output;
258
- }
@@ -1,111 +0,0 @@
1
- /**
2
- * Effect Integration Tests
3
- *
4
- * Tests for Effect-TS concurrent runner execution.
5
- */
6
-
7
- import { beforeEach, describe, expect, it, vi } from "vitest";
8
- import type {
9
- DispatchContext,
10
- RunnerDefinition,
11
- RunnerGroup,
12
- RunnerResult,
13
- } from "../../dispatch/types.js";
14
- import {
15
- clearRunnerRegistry,
16
- getRunner,
17
- listRunners,
18
- registerRunner,
19
- } from "../../dispatch/dispatcher.js";
20
- import {
21
- dispatchLintWithEffect,
22
- dispatchWithEffect,
23
- type EffectDispatchResult,
24
- } from "../effect-integration.js";
25
-
26
- describe("Effect Integration", () => {
27
- beforeEach(async () => {
28
- clearRunnerRegistry();
29
- // Register a simple test runner
30
- const testRunner: RunnerDefinition = {
31
- id: "test-runner",
32
- appliesTo: ["jsts"],
33
- priority: 10,
34
- enabledByDefault: true,
35
- async run(ctx) {
36
- return {
37
- status: "succeeded",
38
- diagnostics: [{
39
- id: "test:1",
40
- message: "Test diagnostic",
41
- filePath: ctx.filePath,
42
- severity: "info",
43
- semantic: "silent",
44
- tool: "test-runner",
45
- }],
46
- semantic: "none",
47
- };
48
- },
49
- };
50
- registerRunner(testRunner);
51
- });
52
-
53
- it("should have runners registered", () => {
54
- const runners = listRunners();
55
- expect(runners.length).toBeGreaterThan(0);
56
- expect(runners.some(r => r.id === "test-runner")).toBe(true);
57
- });
58
-
59
- it("should run effect integration with real runners", async () => {
60
- const ctx: DispatchContext = {
61
- filePath: "test.ts",
62
- cwd: "/test",
63
- kind: "jsts",
64
- pi: {
65
- getFlag: vi.fn(() => false),
66
- },
67
- autofix: true,
68
- deltaMode: false,
69
- baselines: new Map(),
70
- hasTool: vi.fn(() => Promise.resolve(false)),
71
- log: vi.fn(),
72
- };
73
-
74
- // Get actual runners for jsts
75
- const { getRunnersForKind } = await import("../../dispatch/dispatcher.js");
76
- const runners = getRunnersForKind("jsts");
77
-
78
- const group: RunnerGroup = {
79
- runnerIds: runners.slice(0, 2).map(r => r.id), // Use first 2 runners
80
- mode: "all",
81
- };
82
-
83
- const result = await dispatchWithEffect(ctx, [group]);
84
-
85
- // Just verify it doesn't crash and returns valid result
86
- expect(result).toBeDefined();
87
- expect(typeof result.durationMs).toBe("number");
88
- });
89
-
90
- it("should handle --lens-effect flag path", async () => {
91
- const mockPi = {
92
- getFlag: vi.fn((flag: string) => flag === "lens-effect"),
93
- readFile: vi.fn(),
94
- writeFile: vi.fn(),
95
- editFile: vi.fn(),
96
- bash: vi.fn(),
97
- ui: {
98
- notify: vi.fn(),
99
- progress: vi.fn(),
100
- prompt: vi.fn(),
101
- },
102
- llm: {
103
- stream: vi.fn(),
104
- createMessage: vi.fn(),
105
- },
106
- };
107
-
108
- const output = await dispatchLintWithEffect("test.ts", "/test", mockPi);
109
- expect(typeof output).toBe("string");
110
- });
111
- });