pi-lens 3.1.2 → 3.2.0

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 (154) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +16 -12
  3. package/clients/ast-grep-client.js +8 -1
  4. package/clients/ast-grep-client.ts +9 -1
  5. package/clients/biome-client.js +51 -38
  6. package/clients/biome-client.ts +60 -58
  7. package/clients/dependency-checker.js +30 -1
  8. package/clients/dependency-checker.ts +35 -1
  9. package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
  10. package/clients/dispatch/bus-dispatcher.js +15 -14
  11. package/clients/dispatch/bus-dispatcher.ts +32 -25
  12. package/clients/dispatch/dispatcher.js +18 -25
  13. package/clients/dispatch/dispatcher.test.ts +2 -1
  14. package/clients/dispatch/dispatcher.ts +17 -28
  15. package/clients/dispatch/plan.js +77 -32
  16. package/clients/dispatch/plan.ts +78 -32
  17. package/clients/dispatch/runners/ast-grep-napi.js +36 -376
  18. package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
  19. package/clients/dispatch/runners/index.js +8 -4
  20. package/clients/dispatch/runners/index.ts +8 -4
  21. package/clients/dispatch/runners/lsp.js +65 -0
  22. package/clients/dispatch/runners/lsp.ts +125 -0
  23. package/clients/dispatch/runners/oxlint.js +2 -2
  24. package/clients/dispatch/runners/oxlint.ts +2 -2
  25. package/clients/dispatch/runners/pyright.js +24 -8
  26. package/clients/dispatch/runners/pyright.ts +28 -14
  27. package/clients/dispatch/runners/rust-clippy.js +2 -2
  28. package/clients/dispatch/runners/rust-clippy.ts +2 -4
  29. package/clients/dispatch/runners/tree-sitter.js +14 -2
  30. package/clients/dispatch/runners/tree-sitter.ts +15 -2
  31. package/clients/dispatch/runners/ts-lsp.js +3 -3
  32. package/clients/dispatch/runners/ts-lsp.ts +8 -5
  33. package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
  34. package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
  35. package/clients/dispatch/types.js +3 -0
  36. package/clients/dispatch/types.ts +3 -0
  37. package/clients/formatters.js +67 -14
  38. package/clients/formatters.ts +68 -15
  39. package/clients/installer/index.js +78 -10
  40. package/clients/installer/index.ts +519 -426
  41. package/clients/jscpd-client.js +28 -0
  42. package/clients/jscpd-client.ts +41 -3
  43. package/clients/knip-client.js +30 -1
  44. package/clients/knip-client.ts +34 -2
  45. package/clients/lsp/__tests__/client.test.ts +64 -41
  46. package/clients/lsp/__tests__/config.test.ts +25 -17
  47. package/clients/lsp/__tests__/launch.test.ts +108 -43
  48. package/clients/lsp/__tests__/service.test.ts +76 -48
  49. package/clients/lsp/client.js +87 -2
  50. package/clients/lsp/client.ts +150 -6
  51. package/clients/lsp/config.js +8 -11
  52. package/clients/lsp/config.ts +24 -21
  53. package/clients/lsp/index.js +69 -0
  54. package/clients/lsp/index.ts +82 -0
  55. package/clients/lsp/interactive-install.js +19 -8
  56. package/clients/lsp/interactive-install.ts +52 -27
  57. package/clients/lsp/launch.js +182 -32
  58. package/clients/lsp/launch.ts +241 -38
  59. package/clients/lsp/path-utils.js +3 -46
  60. package/clients/lsp/path-utils.ts +11 -51
  61. package/clients/lsp/server.js +93 -71
  62. package/clients/lsp/server.ts +173 -131
  63. package/clients/path-utils.js +142 -0
  64. package/clients/path-utils.ts +153 -0
  65. package/clients/ruff-client.js +33 -4
  66. package/clients/ruff-client.ts +44 -13
  67. package/clients/safe-spawn.js +3 -1
  68. package/clients/safe-spawn.ts +3 -1
  69. package/clients/services/effect-integration.js +11 -7
  70. package/clients/services/effect-integration.ts +34 -26
  71. package/clients/sg-runner.js +51 -9
  72. package/clients/sg-runner.ts +58 -15
  73. package/clients/tree-sitter-client.js +12 -0
  74. package/clients/tree-sitter-client.ts +12 -0
  75. package/clients/typescript-client.js +6 -2
  76. package/clients/typescript-client.ts +9 -2
  77. package/commands/booboo.js +2 -4
  78. package/commands/booboo.ts +2 -4
  79. package/index.ts +377 -93
  80. package/package.json +2 -1
  81. package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
  82. package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
  83. package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
  84. package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
  85. package/tsconfig.json +1 -1
  86. package/clients/__tests__/file-time.test.js +0 -216
  87. package/clients/__tests__/format-service.test.js +0 -245
  88. package/clients/__tests__/formatters.test.js +0 -271
  89. package/clients/agent-behavior-client.test.js +0 -94
  90. package/clients/ast-grep-client.test.js +0 -129
  91. package/clients/ast-grep-client.test.ts +0 -155
  92. package/clients/biome-client.test.js +0 -144
  93. package/clients/cache-manager.test.js +0 -197
  94. package/clients/complexity-client.test.js +0 -234
  95. package/clients/dependency-checker.test.js +0 -60
  96. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  97. package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
  98. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  99. package/clients/dispatch/dispatcher.format.test.js +0 -46
  100. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  101. package/clients/dispatch/dispatcher.test.js +0 -115
  102. package/clients/dispatch/runners/architect.test.js +0 -138
  103. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
  104. package/clients/dispatch/runners/oxlint.test.js +0 -230
  105. package/clients/dispatch/runners/pyright.test.js +0 -98
  106. package/clients/dispatch/runners/python-slop.test.js +0 -203
  107. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  108. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  109. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  110. package/clients/dispatch/runners/ts-slop.test.js +0 -180
  111. package/clients/dispatch/runners/ts-slop.test.ts +0 -230
  112. package/clients/dogfood.test.js +0 -201
  113. package/clients/file-kinds.test.js +0 -169
  114. package/clients/go-client.test.js +0 -127
  115. package/clients/jscpd-client.test.js +0 -127
  116. package/clients/knip-client.test.js +0 -112
  117. package/clients/lsp/__tests__/client.test.js +0 -325
  118. package/clients/lsp/__tests__/config.test.js +0 -166
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/integration.test.js +0 -127
  121. package/clients/lsp/__tests__/launch.test.js +0 -260
  122. package/clients/lsp/__tests__/server.test.js +0 -259
  123. package/clients/lsp/__tests__/service.test.js +0 -417
  124. package/clients/metrics-client.test.js +0 -141
  125. package/clients/ruff-client.test.js +0 -132
  126. package/clients/rust-client.test.js +0 -108
  127. package/clients/sanitize.test.js +0 -177
  128. package/clients/secrets-scanner.test.js +0 -100
  129. package/clients/services/__tests__/effect-integration.test.js +0 -86
  130. package/clients/test-runner-client.test.js +0 -192
  131. package/clients/todo-scanner.test.js +0 -301
  132. package/clients/type-coverage-client.test.js +0 -105
  133. package/clients/typescript-client.codefix.test.js +0 -157
  134. package/clients/typescript-client.test.js +0 -105
  135. package/commands/clients/ast-grep-client.js +0 -250
  136. package/commands/clients/ast-grep-parser.js +0 -86
  137. package/commands/clients/ast-grep-rule-manager.js +0 -91
  138. package/commands/clients/ast-grep-types.js +0 -9
  139. package/commands/clients/biome-client.js +0 -380
  140. package/commands/clients/complexity-client.js +0 -667
  141. package/commands/clients/file-kinds.js +0 -177
  142. package/commands/clients/file-utils.js +0 -40
  143. package/commands/clients/jscpd-client.js +0 -169
  144. package/commands/clients/knip-client.js +0 -211
  145. package/commands/clients/ruff-client.js +0 -297
  146. package/commands/clients/safe-spawn.js +0 -88
  147. package/commands/clients/scan-utils.js +0 -83
  148. package/commands/clients/sg-runner.js +0 -190
  149. package/commands/clients/types.js +0 -11
  150. package/commands/clients/typescript-client.js +0 -505
  151. package/commands/rate.test.js +0 -119
  152. package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
  153. package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
  154. package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
@@ -1,282 +1,286 @@
1
- /**
2
- * Runner Registration Verification Tests
3
- *
4
- * Ensures all runners are properly registered and unique.
5
- * Catches issues like missing runner imports.
6
- */
7
-
8
- import { beforeAll, describe, expect, it } from "vitest";
9
- import type { FileKind } from "../../file-kinds.js";
10
- import {
11
- clearRunnerRegistry,
12
- getRunner,
13
- getRunnersForKind,
14
- listRunners,
15
- } from "../dispatcher.js";
16
- import type { RunnerDefinition } from "../types.js";
17
-
18
- describe("Runner Registration", () => {
19
- let allRunners: RunnerDefinition[];
20
-
21
- beforeAll(async () => {
22
- // Clear any existing registrations for clean slate
23
- clearRunnerRegistry();
24
-
25
- // Import runners to trigger registration
26
- // This is the critical import that was missing in the effect-integration bug
27
- await import("../runners/index.js");
28
-
29
- // Get all registered runners
30
- allRunners = listRunners();
31
- });
32
-
33
- describe("Basic Registration", () => {
34
- it("should have runners registered", () => {
35
- expect(allRunners.length).toBeGreaterThan(0);
36
- });
37
-
38
- it("should have unique runner IDs", () => {
39
- const ids = allRunners.map((r) => r.id);
40
- const uniqueIds = new Set(ids);
41
-
42
- // All IDs should be unique
43
- expect(uniqueIds.size).toBe(ids.length);
44
- });
45
-
46
- it("should be able to retrieve any registered runner by ID", () => {
47
- for (const runner of allRunners) {
48
- // Skip disabled runners (those with no appliesTo)
49
- if (!(runner.appliesTo?.length ?? 0)) continue;
50
- const retrieved = getRunner(runner.id);
51
- expect(retrieved).toBeDefined();
52
- expect(retrieved?.id).toBe(runner.id);
53
- }
54
- });
55
-
56
- it("should return undefined for unknown runner IDs", () => {
57
- const unknown = getRunner("definitely-not-a-real-runner-id");
58
- expect(unknown).toBeUndefined();
59
- });
60
- });
61
-
62
- describe("Runner Properties", () => {
63
- it("should have valid appliesTo for all runners", () => {
64
- const validKinds: FileKind[] = [
65
- "jsts",
66
- "python",
67
- "rust",
68
- "go",
69
- "shell",
70
- "json",
71
- "markdown",
72
- "cmake",
73
- "cxx",
74
- ];
75
-
76
- for (const runner of allRunners) {
77
- // Skip disabled runners (those with no appliesTo)
78
- if (!(runner.appliesTo?.length ?? 0)) continue;
79
- // Each runner should have at least one appliesTo
80
- if (!(runner.appliesTo?.length ?? 0)) { console.error(`Runner ${runner.id} has no appliesTo`); } expect(runner.appliesTo?.length ?? 0, `Runner ${runner.id} should have appliesTo`).toBeGreaterThan(0);
81
-
82
- // All appliesTo should be valid kinds
83
- for (const kind of runner.appliesTo) {
84
- expect(validKinds).toContain(kind);
85
- }
86
- }
87
- });
88
-
89
- it("should have priority defined", () => {
90
- for (const runner of allRunners) {
91
- // Skip disabled runners (those with no appliesTo)
92
- if (!(runner.appliesTo?.length ?? 0)) continue;
93
- // Priority should be a number (or undefined, which defaults to 100)
94
- if (runner.priority !== undefined) {
95
- expect(typeof runner.priority).toBe("number");
96
- expect(runner.priority).toBeGreaterThanOrEqual(0);
97
- }
98
- }
99
- });
100
-
101
- it("should have enabledByDefault boolean", () => {
102
- for (const runner of allRunners) {
103
- // Skip disabled runners (those with no appliesTo)
104
- if (!(runner.appliesTo?.length ?? 0)) continue;
105
- expect(typeof runner.enabledByDefault).toBe("boolean");
106
- }
107
- });
108
-
109
- it("should have a run function", () => {
110
- for (const runner of allRunners) {
111
- // Skip disabled runners (those with no appliesTo)
112
- if (!(runner.appliesTo?.length ?? 0)) continue;
113
- expect(typeof runner.run).toBe("function");
114
- }
115
- });
116
- });
117
-
118
- describe("Expected Runners", () => {
119
- const expectedRunners = [
120
- "ts-lsp",
121
- "ts-slop",
122
- "pyright",
123
- "python-slop",
124
- "biome-lint",
125
-
126
- "oxlint",
127
- "ruff-lint",
128
- "shellcheck",
129
- "spellcheck",
130
- "ast-grep",
131
- "ast-grep-napi",
132
- "architect",
133
- "ast-grep-napi",
134
- "config-validation",
135
- ];
136
-
137
- it("should have all expected critical runners", () => {
138
- const registeredIds = allRunners.map((r) => r.id);
139
-
140
- for (const expectedId of expectedRunners) {
141
- expect(registeredIds).toContain(expectedId);
142
- }
143
- });
144
-
145
- it("should have TypeScript-related runners", () => {
146
- const tsRunners = getRunnersForKind("jsts");
147
- const tsIds = tsRunners.map((r) => r.id);
148
-
149
- // Should have at least ts-lsp
150
- expect(tsIds).toContain("ts-lsp");
151
-
152
- // Should have ts-slop
153
- expect(tsIds).toContain("ts-slop");
154
- });
155
-
156
- it("should have Python-related runners", () => {
157
- const pyRunners = getRunnersForKind("python");
158
- const pyIds = pyRunners.map((r) => r.id);
159
-
160
- // Should have pyright
161
- expect(pyIds).toContain("pyright");
162
-
163
- // Should have python-slop
164
- expect(pyIds).toContain("python-slop");
165
- });
166
-
167
- it("should have lint runners", () => {
168
- const jstsRunners = getRunnersForKind("jsts");
169
- const lintIds = ["biome-lint", "oxlint", "ts-slop"];
170
-
171
- for (const lintId of lintIds) {
172
- // At least one should be present
173
- const hasLintRunner = jstsRunners.some((r) => r.id === lintId);
174
- if (hasLintRunner) {
175
- // Found at least one
176
- expect(hasLintRunner).toBe(true);
177
- break;
178
- }
179
- }
180
- });
181
-
182
- it("should have format runners", () => {
183
- const jstsRunners = getRunnersForKind("jsts");
184
- const formatIds = ["biome-lint"];
185
-
186
- for (const formatId of formatIds) {
187
- const hasFormatRunner = jstsRunners.some((r) => r.id === formatId);
188
- if (hasFormatRunner) {
189
- expect(hasFormatRunner).toBe(true);
190
- break;
191
- }
192
- }
193
- });
194
- });
195
-
196
- describe("Runner Import Verification", () => {
197
- it("should load runner index without errors", async () => {
198
- // This catches the bug where runners weren't imported
199
- // in effect-integration.ts and bus-dispatcher.ts
200
- expect(async () => {
201
- await import("../runners/index.js");
202
- }).not.toThrow();
203
- });
204
-
205
- it("should have runners available after import", async () => {
206
- // Clear and re-import to verify fresh load
207
- const initialCount = listRunners().length;
208
-
209
- // Import again - should not duplicate due to id check
210
- await import("../runners/index.js");
211
-
212
- const finalCount = listRunners().length;
213
-
214
- // Should be same count (no duplicates)
215
- expect(finalCount).toBe(initialCount);
216
- });
217
- });
218
-
219
- describe("Runner Condition Functions", () => {
220
- it("should handle runners with when conditions", () => {
221
- const runnersWithWhen = allRunners.filter((r) => r.when !== undefined);
222
-
223
- for (const runner of runnersWithWhen) {
224
- // when should be a function
225
- expect(typeof runner.when).toBe("function");
226
- }
227
- });
228
-
229
- it("should evaluate when conditions correctly", async () => {
230
- // Find a runner with a when condition (e.g., autofix runners)
231
- const conditionalRunner = allRunners.find((r) => r.when !== undefined);
232
-
233
- if (conditionalRunner) {
234
- // Create mock contexts
235
- const ctxWithAutofix = {
236
- autofix: true,
237
- filePath: "test.ts",
238
- cwd: "/test",
239
- kind: "jsts" as FileKind,
240
- pi: { getFlag: () => false },
241
- deltaMode: false,
242
- baselines: new Map(),
243
- hasTool: async () => false,
244
- log: () => {},
245
- };
246
-
247
- const ctxWithoutAutofix = {
248
- ...ctxWithAutofix,
249
- autofix: false,
250
- };
251
-
252
- // Evaluate condition
253
- const shouldRunWith = await conditionalRunner.when?.(ctxWithAutofix);
254
- const shouldRunWithout =
255
- await conditionalRunner.when?.(ctxWithoutAutofix);
256
-
257
- // Results should be boolean
258
- expect(typeof shouldRunWith).toBe("boolean");
259
- expect(typeof shouldRunWithout).toBe("boolean");
260
- }
261
- });
262
- });
263
-
264
- describe("Priority Ordering", () => {
265
- it("should return runners sorted by priority", () => {
266
- const kinds: FileKind[] = ["jsts", "python", "rust", "go"];
267
-
268
- for (const kind of kinds) {
269
- const runners = getRunnersForKind(kind);
270
-
271
- if (runners.length > 1) {
272
- const priorities = runners.map((r) => r.priority ?? 100);
273
-
274
- // Should be sorted ascending
275
- for (let i = 1; i < priorities.length; i++) {
276
- expect(priorities[i - 1]).toBeLessThanOrEqual(priorities[i]);
277
- }
278
- }
279
- }
280
- });
281
- });
282
- });
1
+ /**
2
+ * Runner Registration Verification Tests
3
+ *
4
+ * Ensures all runners are properly registered and unique.
5
+ * Catches issues like missing runner imports.
6
+ */
7
+
8
+ import { beforeAll, describe, expect, it } from "vitest";
9
+ import type { FileKind } from "../../file-kinds.js";
10
+ import {
11
+ clearRunnerRegistry,
12
+ getRunner,
13
+ getRunnersForKind,
14
+ listRunners,
15
+ } from "../dispatcher.js";
16
+ import type { RunnerDefinition } from "../types.js";
17
+
18
+ describe("Runner Registration", () => {
19
+ let allRunners: RunnerDefinition[];
20
+
21
+ beforeAll(async () => {
22
+ // Clear any existing registrations for clean slate
23
+ clearRunnerRegistry();
24
+
25
+ // Import runners to trigger registration
26
+ // This is the critical import that was missing in the effect-integration bug
27
+ await import("../runners/index.js");
28
+
29
+ // Get all registered runners
30
+ allRunners = listRunners();
31
+ });
32
+
33
+ describe("Basic Registration", () => {
34
+ it("should have runners registered", () => {
35
+ expect(allRunners.length).toBeGreaterThan(0);
36
+ });
37
+
38
+ it("should have unique runner IDs", () => {
39
+ const ids = allRunners.map((r) => r.id);
40
+ const uniqueIds = new Set(ids);
41
+
42
+ // All IDs should be unique
43
+ expect(uniqueIds.size).toBe(ids.length);
44
+ });
45
+
46
+ it("should be able to retrieve any registered runner by ID", () => {
47
+ for (const runner of allRunners) {
48
+ // Skip disabled runners (those with no appliesTo)
49
+ if (!(runner.appliesTo?.length ?? 0)) continue;
50
+ const retrieved = getRunner(runner.id);
51
+ expect(retrieved).toBeDefined();
52
+ expect(retrieved?.id).toBe(runner.id);
53
+ }
54
+ });
55
+
56
+ it("should return undefined for unknown runner IDs", () => {
57
+ const unknown = getRunner("definitely-not-a-real-runner-id");
58
+ expect(unknown).toBeUndefined();
59
+ });
60
+ });
61
+
62
+ describe("Runner Properties", () => {
63
+ it("should have valid appliesTo for all runners", () => {
64
+ const validKinds: FileKind[] = [
65
+ "jsts",
66
+ "python",
67
+ "rust",
68
+ "go",
69
+ "shell",
70
+ "json",
71
+ "markdown",
72
+ "cmake",
73
+ "cxx",
74
+ ];
75
+
76
+ for (const runner of allRunners) {
77
+ // Skip disabled runners (those with no appliesTo)
78
+ if (!(runner.appliesTo?.length ?? 0)) continue;
79
+ // Each runner should have at least one appliesTo
80
+ if (!(runner.appliesTo?.length ?? 0)) {
81
+ console.error(`Runner ${runner.id} has no appliesTo`);
82
+ }
83
+ expect(
84
+ runner.appliesTo?.length ?? 0,
85
+ `Runner ${runner.id} should have appliesTo`,
86
+ ).toBeGreaterThan(0);
87
+
88
+ // All appliesTo should be valid kinds
89
+ for (const kind of runner.appliesTo) {
90
+ expect(validKinds).toContain(kind);
91
+ }
92
+ }
93
+ });
94
+
95
+ it("should have priority defined", () => {
96
+ for (const runner of allRunners) {
97
+ // Skip disabled runners (those with no appliesTo)
98
+ if (!(runner.appliesTo?.length ?? 0)) continue;
99
+ // Priority should be a number (or undefined, which defaults to 100)
100
+ if (runner.priority !== undefined) {
101
+ expect(typeof runner.priority).toBe("number");
102
+ expect(runner.priority).toBeGreaterThanOrEqual(0);
103
+ }
104
+ }
105
+ });
106
+
107
+ it("should have enabledByDefault boolean", () => {
108
+ for (const runner of allRunners) {
109
+ // Skip disabled runners (those with no appliesTo)
110
+ if (!(runner.appliesTo?.length ?? 0)) continue;
111
+ expect(typeof runner.enabledByDefault).toBe("boolean");
112
+ }
113
+ });
114
+
115
+ it("should have a run function", () => {
116
+ for (const runner of allRunners) {
117
+ // Skip disabled runners (those with no appliesTo)
118
+ if (!(runner.appliesTo?.length ?? 0)) continue;
119
+ expect(typeof runner.run).toBe("function");
120
+ }
121
+ });
122
+ });
123
+
124
+ describe("Expected Runners", () => {
125
+ const expectedRunners = [
126
+ "ts-lsp",
127
+ "ts-slop",
128
+ "pyright",
129
+ "python-slop",
130
+ "biome-lint",
131
+
132
+ "oxlint",
133
+ "ruff-lint",
134
+ "shellcheck",
135
+ "spellcheck",
136
+ "ast-grep-napi",
137
+ "architect",
138
+ "config-validation",
139
+ ];
140
+
141
+ it("should have all expected critical runners", () => {
142
+ const registeredIds = allRunners.map((r) => r.id);
143
+
144
+ for (const expectedId of expectedRunners) {
145
+ expect(registeredIds).toContain(expectedId);
146
+ }
147
+ });
148
+
149
+ it("should have TypeScript-related runners", () => {
150
+ const tsRunners = getRunnersForKind("jsts");
151
+ const tsIds = tsRunners.map((r) => r.id);
152
+
153
+ // Should have at least ts-lsp
154
+ expect(tsIds).toContain("ts-lsp");
155
+
156
+ // Should have ts-slop
157
+ expect(tsIds).toContain("ts-slop");
158
+ });
159
+
160
+ it("should have Python-related runners", () => {
161
+ const pyRunners = getRunnersForKind("python");
162
+ const pyIds = pyRunners.map((r) => r.id);
163
+
164
+ // Should have pyright
165
+ expect(pyIds).toContain("pyright");
166
+
167
+ // Should have python-slop
168
+ expect(pyIds).toContain("python-slop");
169
+ });
170
+
171
+ it("should have lint runners", () => {
172
+ const jstsRunners = getRunnersForKind("jsts");
173
+ const lintIds = ["biome-lint", "oxlint", "ts-slop"];
174
+
175
+ for (const lintId of lintIds) {
176
+ // At least one should be present
177
+ const hasLintRunner = jstsRunners.some((r) => r.id === lintId);
178
+ if (hasLintRunner) {
179
+ // Found at least one
180
+ expect(hasLintRunner).toBe(true);
181
+ break;
182
+ }
183
+ }
184
+ });
185
+
186
+ it("should have format runners", () => {
187
+ const jstsRunners = getRunnersForKind("jsts");
188
+ const formatIds = ["biome-lint"];
189
+
190
+ for (const formatId of formatIds) {
191
+ const hasFormatRunner = jstsRunners.some((r) => r.id === formatId);
192
+ if (hasFormatRunner) {
193
+ expect(hasFormatRunner).toBe(true);
194
+ break;
195
+ }
196
+ }
197
+ });
198
+ });
199
+
200
+ describe("Runner Import Verification", () => {
201
+ it("should load runner index without errors", async () => {
202
+ // This catches the bug where runners weren't imported
203
+ // in effect-integration.ts and bus-dispatcher.ts
204
+ expect(async () => {
205
+ await import("../runners/index.js");
206
+ }).not.toThrow();
207
+ });
208
+
209
+ it("should have runners available after import", async () => {
210
+ // Clear and re-import to verify fresh load
211
+ const initialCount = listRunners().length;
212
+
213
+ // Import again - should not duplicate due to id check
214
+ await import("../runners/index.js");
215
+
216
+ const finalCount = listRunners().length;
217
+
218
+ // Should be same count (no duplicates)
219
+ expect(finalCount).toBe(initialCount);
220
+ });
221
+ });
222
+
223
+ describe("Runner Condition Functions", () => {
224
+ it("should handle runners with when conditions", () => {
225
+ const runnersWithWhen = allRunners.filter((r) => r.when !== undefined);
226
+
227
+ for (const runner of runnersWithWhen) {
228
+ // when should be a function
229
+ expect(typeof runner.when).toBe("function");
230
+ }
231
+ });
232
+
233
+ it("should evaluate when conditions correctly", async () => {
234
+ // Find a runner with a when condition (e.g., autofix runners)
235
+ const conditionalRunner = allRunners.find((r) => r.when !== undefined);
236
+
237
+ if (conditionalRunner) {
238
+ // Create mock contexts
239
+ const ctxWithAutofix = {
240
+ autofix: true,
241
+ filePath: "test.ts",
242
+ cwd: "/test",
243
+ kind: "jsts" as FileKind,
244
+ pi: { getFlag: () => false },
245
+ deltaMode: false,
246
+ baselines: new Map(),
247
+ hasTool: async () => false,
248
+ log: () => {},
249
+ };
250
+
251
+ const ctxWithoutAutofix = {
252
+ ...ctxWithAutofix,
253
+ autofix: false,
254
+ };
255
+
256
+ // Evaluate condition
257
+ const shouldRunWith = await conditionalRunner.when?.(ctxWithAutofix);
258
+ const shouldRunWithout =
259
+ await conditionalRunner.when?.(ctxWithoutAutofix);
260
+
261
+ // Results should be boolean
262
+ expect(typeof shouldRunWith).toBe("boolean");
263
+ expect(typeof shouldRunWithout).toBe("boolean");
264
+ }
265
+ });
266
+ });
267
+
268
+ describe("Priority Ordering", () => {
269
+ it("should return runners sorted by priority", () => {
270
+ const kinds: FileKind[] = ["jsts", "python", "rust", "go"];
271
+
272
+ for (const kind of kinds) {
273
+ const runners = getRunnersForKind(kind);
274
+
275
+ if (runners.length > 1) {
276
+ const priorities = runners.map((r) => r.priority ?? 100);
277
+
278
+ // Should be sorted ascending
279
+ for (let i = 1; i < priorities.length; i++) {
280
+ expect(priorities[i - 1]).toBeLessThanOrEqual(priorities[i]);
281
+ }
282
+ }
283
+ }
284
+ });
285
+ });
286
+ });
@@ -1,14 +1,4 @@
1
- /**
2
- * Bus-Integrated Dispatcher for pi-lens
3
- *
4
- * Bridges the declarative dispatch system with the event bus.
5
- *
6
- * Changes from original dispatcher:
7
- * - Publishes events for each runner lifecycle phase
8
- * - Supports concurrent execution with progress tracking
9
- * - Integrates with DiagnosticAggregator for results
10
- */
11
- import { DiagnosticFound, RunnerStarted, RunnerCompleted, FileModified, ReportReady, } from "../bus/events.js";
1
+ import { DiagnosticFound, FileModified, ReportReady, RunnerCompleted, RunnerStarted, } from "../bus/events.js";
12
2
  import { formatDiagnostics } from "./utils/format-utils.js";
13
3
  // Import runners to register them
14
4
  import "./runners/index.js";
@@ -22,6 +12,15 @@ async function runRunner(ctx, runner, defaultSemantic) {
22
12
  timestamp: startTime,
23
13
  });
24
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
+ }
25
24
  const result = await runner.run(ctx);
26
25
  const durationMs = Date.now() - startTime;
27
26
  // Publish diagnostic found event
@@ -85,10 +84,11 @@ export async function dispatchConcurrent(ctx, groups) {
85
84
  return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
86
85
  })
87
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).
88
89
  const runners = runnerIds
89
90
  .map((id) => getRunner(id))
90
- .filter((r) => r !== undefined)
91
- .filter((r) => (r.when ? r.when(ctx) : true));
91
+ .filter((r) => r !== undefined);
92
92
  const semantic = group.semantic ?? "warning";
93
93
  if (group.mode === "all") {
94
94
  // Run all runners concurrently
@@ -165,7 +165,8 @@ export async function dispatchLintWithBus(filePath, cwd, pi) {
165
165
  filePath,
166
166
  changeType: "external",
167
167
  });
168
- const ctx = createDispatchContext(filePath, cwd, pi);
168
+ // blockingOnly=true: post-write dispatch only reports blocking errors (same as standard dispatchLint)
169
+ const ctx = createDispatchContext(filePath, cwd, pi, undefined, true);
169
170
  const kind = ctx.kind;
170
171
  if (!kind)
171
172
  return "";