pi-lens 2.2.9 → 3.0.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 (304) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +709 -519
  3. package/clients/__tests__/file-time.test.js +216 -0
  4. package/clients/__tests__/file-time.test.ts +276 -0
  5. package/clients/__tests__/format-service.test.js +245 -0
  6. package/clients/__tests__/format-service.test.ts +339 -0
  7. package/clients/__tests__/formatters.test.js +271 -0
  8. package/clients/__tests__/formatters.test.ts +401 -0
  9. package/clients/amain-types.js +164 -0
  10. package/clients/amain-types.ts +165 -0
  11. package/clients/architect-client.js +56 -12
  12. package/clients/architect-client.ts +81 -16
  13. package/clients/ast-grep-client.js +2 -2
  14. package/clients/ast-grep-client.ts +14 -39
  15. package/clients/ast-grep-parser.ts +1 -1
  16. package/clients/ast-grep-rule-manager.js +8 -0
  17. package/clients/ast-grep-rule-manager.ts +10 -1
  18. package/clients/ast-grep-types.js +9 -0
  19. package/clients/ast-grep-types.ts +106 -0
  20. package/clients/auto-loop.js +10 -0
  21. package/clients/auto-loop.ts +14 -1
  22. package/clients/biome-client.js +81 -19
  23. package/clients/biome-client.ts +103 -22
  24. package/clients/bus/bus.js +191 -0
  25. package/clients/bus/bus.ts +251 -0
  26. package/clients/bus/events.js +214 -0
  27. package/clients/bus/events.ts +279 -0
  28. package/clients/bus/index.js +8 -0
  29. package/clients/bus/index.ts +9 -0
  30. package/clients/bus/integration.js +158 -0
  31. package/clients/bus/integration.ts +214 -0
  32. package/clients/complexity-client.js +13 -7
  33. package/clients/complexity-client.ts +13 -7
  34. package/clients/config-validator.js +465 -0
  35. package/clients/config-validator.ts +558 -0
  36. package/clients/dependency-checker.js +4 -10
  37. package/clients/dependency-checker.ts +4 -10
  38. package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
  39. package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
  40. package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
  41. package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
  42. package/clients/dispatch/bus-dispatcher.js +177 -0
  43. package/clients/dispatch/bus-dispatcher.ts +251 -0
  44. package/clients/dispatch/dispatcher.edge.test.js +82 -0
  45. package/clients/dispatch/dispatcher.edge.test.ts +100 -0
  46. package/clients/dispatch/dispatcher.format.test.js +46 -0
  47. package/clients/dispatch/dispatcher.format.test.ts +58 -0
  48. package/clients/dispatch/dispatcher.inline.test.js +74 -0
  49. package/clients/dispatch/dispatcher.inline.test.ts +93 -0
  50. package/clients/dispatch/dispatcher.js +19 -53
  51. package/clients/dispatch/dispatcher.ts +20 -67
  52. package/clients/dispatch/plan.js +9 -4
  53. package/clients/dispatch/plan.ts +9 -4
  54. package/clients/dispatch/runners/architect.js +21 -7
  55. package/clients/dispatch/runners/architect.test.js +138 -0
  56. package/clients/dispatch/runners/architect.test.ts +162 -0
  57. package/clients/dispatch/runners/architect.ts +22 -7
  58. package/clients/dispatch/runners/ast-grep-napi.js +462 -0
  59. package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
  60. package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
  61. package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
  62. package/clients/dispatch/runners/ast-grep.js +62 -19
  63. package/clients/dispatch/runners/ast-grep.ts +70 -18
  64. package/clients/dispatch/runners/biome.js +29 -53
  65. package/clients/dispatch/runners/biome.ts +29 -63
  66. package/clients/dispatch/runners/config-validation.js +67 -0
  67. package/clients/dispatch/runners/config-validation.ts +82 -0
  68. package/clients/dispatch/runners/go-vet.js +4 -28
  69. package/clients/dispatch/runners/go-vet.ts +4 -32
  70. package/clients/dispatch/runners/index.js +30 -10
  71. package/clients/dispatch/runners/index.ts +30 -10
  72. package/clients/dispatch/runners/oxlint.js +141 -0
  73. package/clients/dispatch/runners/oxlint.test.js +230 -0
  74. package/clients/dispatch/runners/oxlint.test.ts +303 -0
  75. package/clients/dispatch/runners/oxlint.ts +175 -0
  76. package/clients/dispatch/runners/pyright.js +40 -70
  77. package/clients/dispatch/runners/pyright.test.js +16 -2
  78. package/clients/dispatch/runners/pyright.test.ts +14 -2
  79. package/clients/dispatch/runners/pyright.ts +48 -91
  80. package/clients/dispatch/runners/python-slop.js +97 -0
  81. package/clients/dispatch/runners/python-slop.test.js +203 -0
  82. package/clients/dispatch/runners/python-slop.test.ts +298 -0
  83. package/clients/dispatch/runners/python-slop.ts +124 -0
  84. package/clients/dispatch/runners/ruff.js +18 -71
  85. package/clients/dispatch/runners/ruff.ts +19 -79
  86. package/clients/dispatch/runners/rust-clippy.js +28 -32
  87. package/clients/dispatch/runners/rust-clippy.ts +29 -31
  88. package/clients/dispatch/runners/scan_codebase.test.js +89 -0
  89. package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
  90. package/clients/dispatch/runners/shellcheck.js +147 -0
  91. package/clients/dispatch/runners/shellcheck.test.js +98 -0
  92. package/clients/dispatch/runners/shellcheck.test.ts +129 -0
  93. package/clients/dispatch/runners/shellcheck.ts +188 -0
  94. package/clients/dispatch/runners/similarity.js +230 -0
  95. package/clients/dispatch/runners/similarity.ts +339 -0
  96. package/clients/dispatch/runners/spellcheck.js +106 -0
  97. package/clients/dispatch/runners/spellcheck.test.js +158 -0
  98. package/clients/dispatch/runners/spellcheck.test.ts +214 -0
  99. package/clients/dispatch/runners/spellcheck.ts +136 -0
  100. package/clients/dispatch/runners/tree-sitter.js +107 -0
  101. package/clients/dispatch/runners/tree-sitter.ts +135 -0
  102. package/clients/dispatch/runners/ts-lsp.js +104 -33
  103. package/clients/dispatch/runners/ts-lsp.ts +120 -38
  104. package/clients/dispatch/runners/ts-slop.js +113 -0
  105. package/clients/dispatch/runners/ts-slop.test.js +180 -0
  106. package/clients/dispatch/runners/ts-slop.test.ts +230 -0
  107. package/clients/dispatch/runners/ts-slop.ts +142 -0
  108. package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
  109. package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
  110. package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
  111. package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
  112. package/clients/dispatch/runners/utils.js +2 -4
  113. package/clients/dispatch/runners/utils.ts +2 -4
  114. package/clients/dispatch/types.ts +1 -1
  115. package/clients/dispatch/utils/format-utils.js +49 -0
  116. package/clients/dispatch/utils/format-utils.ts +60 -0
  117. package/clients/dogfood.test.js +201 -0
  118. package/clients/dogfood.test.ts +269 -0
  119. package/clients/file-time.js +152 -0
  120. package/clients/file-time.ts +208 -0
  121. package/clients/file-utils.js +40 -0
  122. package/clients/file-utils.ts +44 -0
  123. package/clients/fix-scanners.js +10 -20
  124. package/clients/fix-scanners.ts +10 -22
  125. package/clients/format-service.js +172 -0
  126. package/clients/format-service.ts +254 -0
  127. package/clients/formatters.js +435 -0
  128. package/clients/formatters.ts +508 -0
  129. package/clients/go-client.js +5 -14
  130. package/clients/go-client.ts +5 -13
  131. package/clients/installer/index.js +356 -0
  132. package/clients/installer/index.ts +426 -0
  133. package/clients/jscpd-client.js +11 -9
  134. package/clients/jscpd-client.ts +12 -8
  135. package/clients/knip-client.js +3 -7
  136. package/clients/knip-client.ts +3 -6
  137. package/clients/lsp/__tests__/client.test.js +325 -0
  138. package/clients/lsp/__tests__/client.test.ts +434 -0
  139. package/clients/lsp/__tests__/config.test.js +166 -0
  140. package/clients/lsp/__tests__/config.test.ts +209 -0
  141. package/clients/lsp/__tests__/error-recovery.test.js +213 -0
  142. package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
  143. package/clients/lsp/__tests__/integration.test.js +127 -0
  144. package/clients/lsp/__tests__/integration.test.ts +160 -0
  145. package/clients/lsp/__tests__/launch.test.js +260 -0
  146. package/clients/lsp/__tests__/launch.test.ts +329 -0
  147. package/clients/lsp/__tests__/server.test.js +259 -0
  148. package/clients/lsp/__tests__/server.test.ts +332 -0
  149. package/clients/lsp/__tests__/service.test.js +417 -0
  150. package/clients/lsp/__tests__/service.test.ts +499 -0
  151. package/clients/lsp/client.js +235 -0
  152. package/clients/lsp/client.ts +328 -0
  153. package/clients/lsp/config.js +115 -0
  154. package/clients/lsp/config.ts +149 -0
  155. package/clients/lsp/index.js +222 -0
  156. package/clients/lsp/index.ts +280 -0
  157. package/clients/lsp/installer/index.js +391 -0
  158. package/clients/lsp/interactive-install.js +210 -0
  159. package/clients/lsp/interactive-install.ts +251 -0
  160. package/clients/lsp/language.js +170 -0
  161. package/clients/lsp/language.ts +216 -0
  162. package/clients/lsp/launch.js +174 -0
  163. package/clients/lsp/launch.ts +240 -0
  164. package/clients/lsp/lsp/launch.js +116 -0
  165. package/clients/lsp/lsp/server.js +532 -0
  166. package/clients/lsp/lsp-index.js +10 -0
  167. package/clients/lsp/lsp-index.ts +11 -0
  168. package/clients/lsp/path-utils.js +48 -0
  169. package/clients/lsp/path-utils.ts +52 -0
  170. package/clients/lsp/server.js +615 -0
  171. package/clients/lsp/server.ts +800 -0
  172. package/clients/lsp/test-py-spawn/requirements.txt +1 -0
  173. package/clients/lsp/test-py-spawn/test.py +3 -0
  174. package/clients/lsp/test-py-svc/requirements.txt +1 -0
  175. package/clients/lsp/test-py-svc/test.py +3 -0
  176. package/clients/lsp/test-python-project/requirements.txt +1 -0
  177. package/clients/lsp/test-python-project/test.py +5 -0
  178. package/clients/metrics-history.js +2 -2
  179. package/clients/metrics-history.ts +2 -2
  180. package/clients/production-readiness.js +522 -0
  181. package/clients/production-readiness.ts +556 -0
  182. package/clients/project-index.js +255 -0
  183. package/clients/project-index.ts +383 -0
  184. package/clients/project-metadata.js +531 -0
  185. package/clients/project-metadata.ts +624 -0
  186. package/clients/ruff-client.js +56 -16
  187. package/clients/ruff-client.ts +72 -15
  188. package/clients/runner-tracker.js +152 -0
  189. package/clients/runner-tracker.ts +213 -0
  190. package/clients/rust-client.js +4 -11
  191. package/clients/rust-client.ts +5 -11
  192. package/clients/safe-spawn.js +96 -0
  193. package/clients/safe-spawn.ts +128 -0
  194. package/clients/scan-architectural-debt.js +3 -6
  195. package/clients/scan-architectural-debt.ts +3 -6
  196. package/clients/scan-utils.js +5 -20
  197. package/clients/scan-utils.ts +5 -29
  198. package/clients/secrets-scanner.js +3 -17
  199. package/clients/secrets-scanner.ts +4 -20
  200. package/clients/services/__tests__/effect-integration.test.js +86 -0
  201. package/clients/services/__tests__/effect-integration.test.ts +111 -0
  202. package/clients/services/effect-integration.js +194 -0
  203. package/clients/services/effect-integration.ts +268 -0
  204. package/clients/services/index.js +7 -0
  205. package/clients/services/index.ts +8 -0
  206. package/clients/services/runner-service.js +105 -0
  207. package/clients/services/runner-service.ts +179 -0
  208. package/clients/sg-runner.js +87 -13
  209. package/clients/sg-runner.ts +97 -13
  210. package/clients/state-matrix.js +160 -0
  211. package/clients/state-matrix.ts +202 -0
  212. package/clients/subprocess-client.js +10 -9
  213. package/clients/subprocess-client.ts +10 -8
  214. package/clients/test-runner-client.js +3 -7
  215. package/clients/test-runner-client.ts +3 -6
  216. package/clients/tool-availability.js +4 -10
  217. package/clients/tool-availability.ts +4 -9
  218. package/clients/tree-sitter-client.js +564 -0
  219. package/clients/tree-sitter-client.ts +797 -0
  220. package/clients/tree-sitter-query-loader.js +355 -0
  221. package/clients/tree-sitter-query-loader.ts +425 -0
  222. package/clients/type-coverage-client.js +3 -7
  223. package/clients/type-coverage-client.ts +3 -6
  224. package/clients/typescript-client.codefix.test.js +157 -0
  225. package/clients/typescript-client.codefix.test.ts +186 -0
  226. package/clients/typescript-client.js +43 -0
  227. package/clients/typescript-client.ts +98 -0
  228. package/commands/booboo.js +799 -219
  229. package/commands/booboo.ts +1004 -225
  230. package/commands/clients/ast-grep-client.js +250 -0
  231. package/commands/clients/ast-grep-parser.js +86 -0
  232. package/commands/clients/ast-grep-rule-manager.js +91 -0
  233. package/commands/clients/ast-grep-types.js +9 -0
  234. package/commands/clients/biome-client.js +380 -0
  235. package/commands/clients/complexity-client.js +667 -0
  236. package/commands/clients/file-kinds.js +177 -0
  237. package/commands/clients/file-utils.js +40 -0
  238. package/commands/clients/jscpd-client.js +169 -0
  239. package/commands/clients/knip-client.js +211 -0
  240. package/commands/clients/ruff-client.js +297 -0
  241. package/commands/clients/safe-spawn.js +88 -0
  242. package/commands/clients/scan-utils.js +83 -0
  243. package/commands/clients/sg-runner.js +190 -0
  244. package/commands/clients/types.js +11 -0
  245. package/commands/clients/typescript-client.js +505 -0
  246. package/commands/fix-from-booboo.js +398 -0
  247. package/commands/fix-from-booboo.ts +485 -0
  248. package/commands/fix-simplified.js +618 -0
  249. package/commands/fix-simplified.ts +768 -0
  250. package/commands/rate.js +10 -14
  251. package/commands/rate.ts +9 -16
  252. package/default-architect.yaml +59 -15
  253. package/index.ts +342 -429
  254. package/package.json +16 -3
  255. package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
  256. package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
  257. package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
  258. package/rules/python-slop-rules/.sgconfig.yml +4 -0
  259. package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
  260. package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
  261. package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
  262. package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
  263. package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
  264. package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
  265. package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
  266. package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
  267. package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
  268. package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
  269. package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
  270. package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
  271. package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
  272. package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
  273. package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
  274. package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
  275. package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
  276. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
  277. package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
  278. package/rules/ts-slop-rules/.sgconfig.yml +4 -0
  279. package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
  280. package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
  281. package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
  282. package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
  283. package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
  284. package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
  285. package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
  286. package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
  287. package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
  288. package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
  289. package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
  290. package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
  291. package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
  292. package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
  293. package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
  294. package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
  295. package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
  296. package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
  297. package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
  298. package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
  299. package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
  300. package/skills/ast-grep/SKILL.md +182 -0
  301. package/clients/dispatch/runners/secrets.js +0 -109
  302. package/commands/fix.js +0 -244
  303. package/commands/fix.ts +0 -373
  304. package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
@@ -0,0 +1,111 @@
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
+ });
@@ -0,0 +1,194 @@
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 { runRunnersConcurrent, executeEffect, } from "./runner-service.js";
13
+ import { DiagnosticFound, RunnerStarted, RunnerCompleted, FileModified, } from "../bus/events.js";
14
+ import { formatDiagnostics } from "../dispatch/utils/format-utils.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
27
+ .filter((id) => {
28
+ const runner = getRunner(id);
29
+ return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
30
+ })
31
+ : group.runnerIds;
32
+ const runners = runnerDefs
33
+ .map((id) => getRunner(id))
34
+ .filter((r) => r !== undefined)
35
+ .filter((r) => (r.when ? r.when(ctx) : true));
36
+ if (runners.length === 0) {
37
+ return { results: [], diagnostics: [] };
38
+ }
39
+ // Build the single runner execution function
40
+ const runSingle = async (filePath, runnerId) => {
41
+ const runner = getRunner(runnerId);
42
+ if (!runner) {
43
+ return { diagnostics: [], durationMs: 0 };
44
+ }
45
+ // Publish started event
46
+ RunnerStarted.publish({
47
+ runnerId,
48
+ filePath,
49
+ timestamp: Date.now(),
50
+ });
51
+ const runnerStart = Date.now();
52
+ let status = "completed";
53
+ let diagnostics = [];
54
+ try {
55
+ const result = await runner.run(ctx);
56
+ diagnostics = result.diagnostics.map((d) => ({
57
+ id: d.id,
58
+ message: d.message,
59
+ filePath: ctx.filePath,
60
+ line: d.line,
61
+ column: d.column,
62
+ severity: d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "info",
63
+ semantic: d.semantic ?? result.semantic ?? "warning",
64
+ tool: runnerId,
65
+ rule: d.rule,
66
+ fixable: d.fixable,
67
+ fixSuggestion: d.fixSuggestion,
68
+ }));
69
+ // Publish diagnostic found event
70
+ if (diagnostics.length > 0) {
71
+ DiagnosticFound.publish({
72
+ runnerId,
73
+ filePath: ctx.filePath,
74
+ diagnostics,
75
+ durationMs: Date.now() - runnerStart,
76
+ });
77
+ }
78
+ return {
79
+ diagnostics: diagnostics.map((d) => ({
80
+ id: d.id,
81
+ message: d.message,
82
+ severity: d.severity,
83
+ semantic: d.semantic,
84
+ })),
85
+ durationMs: Date.now() - runnerStart,
86
+ };
87
+ }
88
+ catch (err) {
89
+ status = "failed";
90
+ return {
91
+ diagnostics: [],
92
+ durationMs: Date.now() - runnerStart,
93
+ error: String(err),
94
+ };
95
+ }
96
+ finally {
97
+ // Publish completed event
98
+ RunnerCompleted.publish({
99
+ runnerId,
100
+ filePath: ctx.filePath,
101
+ status,
102
+ durationMs: Date.now() - runnerStart,
103
+ diagnosticCount: diagnostics.length,
104
+ });
105
+ }
106
+ };
107
+ // Run all runners concurrently using Effect
108
+ const runnerIds = runners.map((r) => r.id);
109
+ const concurrentResults = await executeEffect(runRunnersConcurrent(ctx.filePath, runnerIds, runSingle, 30000));
110
+ // Collect all diagnostics
111
+ const allDiagnostics = [];
112
+ for (const result of concurrentResults) {
113
+ if (result.status === "success") {
114
+ allDiagnostics.push(...result.diagnostics.map((d) => ({
115
+ id: d.id,
116
+ message: d.message,
117
+ filePath: ctx.filePath,
118
+ severity: d.severity,
119
+ semantic: d.semantic ?? group.semantic ?? "warning",
120
+ tool: result.runnerId,
121
+ })));
122
+ }
123
+ }
124
+ return {
125
+ results: concurrentResults,
126
+ diagnostics: allDiagnostics,
127
+ };
128
+ }
129
+ // --- Main Dispatch Function ---
130
+ export async function dispatchWithEffect(ctx, groups) {
131
+ const startTime = Date.now();
132
+ const allDiagnostics = [];
133
+ const allRunnerResults = [];
134
+ let stopped = false;
135
+ // Publish file modified event
136
+ FileModified.publish({
137
+ filePath: ctx.filePath,
138
+ changeType: "external",
139
+ });
140
+ for (const group of groups) {
141
+ if (stopped && ctx.pi.getFlag("stop-on-error")) {
142
+ break;
143
+ }
144
+ const { results, diagnostics } = await runGroupConcurrent(ctx, group);
145
+ allDiagnostics.push(...diagnostics);
146
+ allRunnerResults.push(...results);
147
+ // Check for blockers
148
+ const semantic = group.semantic ?? "warning";
149
+ if (semantic === "blocking" && diagnostics.length > 0) {
150
+ stopped = true;
151
+ }
152
+ }
153
+ // Categorize results
154
+ const blockers = allDiagnostics.filter((d) => d.semantic === "blocking");
155
+ const warnings = allDiagnostics.filter((d) => d.semantic === "warning" || d.semantic === "none");
156
+ const fixedItems = allDiagnostics.filter((d) => d.semantic === "fixed");
157
+ const silentItems = allDiagnostics.filter((d) => d.semantic === "silent");
158
+ // Format output
159
+ let output = formatDiagnostics(blockers, "blocking");
160
+ output += formatDiagnostics(fixedItems, "fixed");
161
+ const durationMs = Date.now() - startTime;
162
+ // Log performance info in debug mode
163
+ if (ctx.pi.getFlag("lens-bus-debug")) {
164
+ console.error(`[effect] Total duration: ${durationMs}ms`);
165
+ for (const r of allRunnerResults) {
166
+ console.error(`[effect] ${r.runnerId}: ${r.status} (${r.durationMs}ms)`);
167
+ }
168
+ }
169
+ return {
170
+ diagnostics: allDiagnostics,
171
+ blockers,
172
+ warnings,
173
+ fixed: fixedItems,
174
+ silent: silentItems,
175
+ output,
176
+ hasBlockers: blockers.length > 0,
177
+ durationMs,
178
+ runnerResults: allRunnerResults,
179
+ };
180
+ }
181
+ // --- Simple Integration Helper ---
182
+ export async function dispatchLintWithEffect(filePath, cwd, pi) {
183
+ const { createDispatchContext } = await import("../dispatch/dispatcher.js");
184
+ const { TOOL_PLANS } = await import("../dispatch/plan.js");
185
+ const ctx = createDispatchContext(filePath, cwd, pi);
186
+ const kind = ctx.kind;
187
+ if (!kind)
188
+ return "";
189
+ const plan = TOOL_PLANS[kind];
190
+ if (!plan)
191
+ return "";
192
+ const result = await dispatchWithEffect(ctx, plan.groups);
193
+ return result.output;
194
+ }
@@ -0,0 +1,268 @@
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
+ runRunnersConcurrent,
15
+ executeEffect,
16
+ formatError,
17
+ type RunnerResult,
18
+ type ConcurrentRunnerResult,
19
+ } from "./runner-service.js";
20
+
21
+ import {
22
+ DiagnosticFound,
23
+ RunnerStarted,
24
+ RunnerCompleted,
25
+ FileModified,
26
+ type Diagnostic,
27
+ } from "../bus/events.js";
28
+ import { formatDiagnostic, formatDiagnostics } from "../dispatch/utils/format-utils.js";
29
+ // Import runners to register them in the dispatcher
30
+ import "../dispatch/runners/index.js";
31
+
32
+ import type { DispatchContext, RunnerGroup } from "../dispatch/types.js";
33
+
34
+ // --- Enhanced Result Type ---
35
+
36
+ export interface EffectDispatchResult {
37
+ diagnostics: Diagnostic[];
38
+ blockers: Diagnostic[];
39
+ warnings: Diagnostic[];
40
+ fixed: Diagnostic[];
41
+ silent: Diagnostic[];
42
+ output: string;
43
+ hasBlockers: boolean;
44
+ durationMs: number;
45
+ runnerResults: ConcurrentRunnerResult[];
46
+ }
47
+
48
+ // --- Core Functions ---
49
+
50
+ /**
51
+ * Run all runners in a group concurrently using Effect
52
+ */
53
+ async function runGroupConcurrent(
54
+ ctx: DispatchContext,
55
+ group: RunnerGroup,
56
+ ): Promise<{ results: ConcurrentRunnerResult[]; diagnostics: Diagnostic[] }> {
57
+ const { getRunner, getRunnersForKind } = await import("../dispatch/dispatcher.js");
58
+ const startTime = Date.now();
59
+
60
+ // Get runner definitions
61
+ const runnerDefs = group.filterKinds
62
+ ? group.runnerIds
63
+ .filter((id) => {
64
+ const runner = getRunner(id);
65
+ return runner && ctx.kind && group.filterKinds?.includes(ctx.kind);
66
+ })
67
+ : group.runnerIds;
68
+
69
+ const runners = runnerDefs
70
+ .map((id) => getRunner(id))
71
+ .filter((r): r is NonNullable<typeof r> => r !== undefined)
72
+ .filter((r) => (r.when ? r.when(ctx) : true));
73
+
74
+ if (runners.length === 0) {
75
+ return { results: [], diagnostics: [] };
76
+ }
77
+
78
+ // Build the single runner execution function
79
+ const runSingle = async (filePath: string, runnerId: string): Promise<RunnerResult> => {
80
+ const runner = getRunner(runnerId);
81
+ if (!runner) {
82
+ return { diagnostics: [], durationMs: 0 };
83
+ }
84
+
85
+ // Publish started event
86
+ RunnerStarted.publish({
87
+ runnerId,
88
+ filePath,
89
+ timestamp: Date.now(),
90
+ });
91
+
92
+ const runnerStart = Date.now();
93
+ let status: "completed" | "failed" = "completed";
94
+ let diagnostics: Diagnostic[] = [];
95
+
96
+ try {
97
+ const result = await runner.run(ctx);
98
+ diagnostics = result.diagnostics.map((d) => ({
99
+ id: d.id,
100
+ message: d.message,
101
+ filePath: ctx.filePath,
102
+ line: d.line,
103
+ column: d.column,
104
+ severity: d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "info",
105
+ semantic: d.semantic ?? result.semantic ?? "warning",
106
+ tool: runnerId,
107
+ rule: d.rule,
108
+ fixable: d.fixable,
109
+ fixSuggestion: d.fixSuggestion,
110
+ }));
111
+
112
+ // Publish diagnostic found event
113
+ if (diagnostics.length > 0) {
114
+ DiagnosticFound.publish({
115
+ runnerId,
116
+ filePath: ctx.filePath,
117
+ diagnostics,
118
+ durationMs: Date.now() - runnerStart,
119
+ });
120
+ }
121
+
122
+ return {
123
+ diagnostics: diagnostics.map((d) => ({
124
+ id: d.id,
125
+ message: d.message,
126
+ severity: d.severity,
127
+ semantic: d.semantic,
128
+ })),
129
+ durationMs: Date.now() - runnerStart,
130
+ };
131
+ } catch (err) {
132
+ status = "failed";
133
+ return {
134
+ diagnostics: [],
135
+ durationMs: Date.now() - runnerStart,
136
+ error: String(err),
137
+ };
138
+ } finally {
139
+ // Publish completed event
140
+ RunnerCompleted.publish({
141
+ runnerId,
142
+ filePath: ctx.filePath,
143
+ status,
144
+ durationMs: Date.now() - runnerStart,
145
+ diagnosticCount: diagnostics.length,
146
+ });
147
+ }
148
+ };
149
+
150
+ // Run all runners concurrently using Effect
151
+ const runnerIds = runners.map((r) => r.id);
152
+ const concurrentResults = await executeEffect(
153
+ runRunnersConcurrent(ctx.filePath, runnerIds, runSingle, 30_000)
154
+ );
155
+
156
+ // Collect all diagnostics
157
+ const allDiagnostics: Diagnostic[] = [];
158
+ for (const result of concurrentResults) {
159
+ if (result.status === "success") {
160
+ allDiagnostics.push(
161
+ ...result.diagnostics.map((d) => ({
162
+ id: d.id,
163
+ message: d.message,
164
+ filePath: ctx.filePath,
165
+ severity: d.severity,
166
+ semantic: d.semantic ?? group.semantic ?? "warning",
167
+ tool: result.runnerId,
168
+ }))
169
+ );
170
+ }
171
+ }
172
+
173
+ return {
174
+ results: concurrentResults,
175
+ diagnostics: allDiagnostics,
176
+ };
177
+ }
178
+
179
+ // --- Main Dispatch Function ---
180
+
181
+ export async function dispatchWithEffect(
182
+ ctx: DispatchContext,
183
+ groups: RunnerGroup[],
184
+ ): Promise<EffectDispatchResult> {
185
+ const startTime = Date.now();
186
+ const allDiagnostics: Diagnostic[] = [];
187
+ const allRunnerResults: ConcurrentRunnerResult[] = [];
188
+ let stopped = false;
189
+
190
+ // Publish file modified event
191
+ FileModified.publish({
192
+ filePath: ctx.filePath,
193
+ changeType: "external",
194
+ });
195
+
196
+ for (const group of groups) {
197
+ if (stopped && ctx.pi.getFlag("stop-on-error")) {
198
+ break;
199
+ }
200
+
201
+ const { results, diagnostics } = await runGroupConcurrent(ctx, group);
202
+
203
+ allDiagnostics.push(...diagnostics);
204
+ allRunnerResults.push(...results);
205
+
206
+ // Check for blockers
207
+ const semantic = group.semantic ?? "warning";
208
+ if (semantic === "blocking" && diagnostics.length > 0) {
209
+ stopped = true;
210
+ }
211
+ }
212
+
213
+ // Categorize results
214
+ const blockers = allDiagnostics.filter((d) => d.semantic === "blocking");
215
+ const warnings = allDiagnostics.filter(
216
+ (d) => d.semantic === "warning" || d.semantic === "none",
217
+ );
218
+ const fixedItems = allDiagnostics.filter((d) => d.semantic === "fixed");
219
+ const silentItems = allDiagnostics.filter((d) => d.semantic === "silent");
220
+
221
+ // Format output
222
+ let output = formatDiagnostics(blockers, "blocking");
223
+ output += formatDiagnostics(fixedItems, "fixed");
224
+
225
+ const durationMs = Date.now() - startTime;
226
+
227
+ // Log performance info in debug mode
228
+ if (ctx.pi.getFlag("lens-bus-debug")) {
229
+ console.error(`[effect] Total duration: ${durationMs}ms`);
230
+ for (const r of allRunnerResults) {
231
+ console.error(`[effect] ${r.runnerId}: ${r.status} (${r.durationMs}ms)`);
232
+ }
233
+ }
234
+
235
+ return {
236
+ diagnostics: allDiagnostics,
237
+ blockers,
238
+ warnings,
239
+ fixed: fixedItems,
240
+ silent: silentItems,
241
+ output,
242
+ hasBlockers: blockers.length > 0,
243
+ durationMs,
244
+ runnerResults: allRunnerResults,
245
+ };
246
+ }
247
+
248
+ // --- Simple Integration Helper ---
249
+
250
+ export async function dispatchLintWithEffect(
251
+ filePath: string,
252
+ cwd: string,
253
+ pi: { getFlag(flag: string): string | boolean | undefined },
254
+ ): Promise<string> {
255
+ const { createDispatchContext } = await import("../dispatch/dispatcher.js");
256
+ const { TOOL_PLANS } = await import("../dispatch/plan.js");
257
+
258
+ const ctx = createDispatchContext(filePath, cwd, pi);
259
+
260
+ const kind = ctx.kind;
261
+ if (!kind) return "";
262
+
263
+ const plan = TOOL_PLANS[kind];
264
+ if (!plan) return "";
265
+
266
+ const result = await dispatchWithEffect(ctx, plan.groups);
267
+ return result.output;
268
+ }
@@ -0,0 +1,7 @@
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";
@@ -0,0 +1,8 @@
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";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Effect-TS Service Infrastructure for pi-lens
3
+ *
4
+ * Simplified implementation focusing on:
5
+ * - Concurrent runner execution
6
+ * - Timeout handling
7
+ * - Error recovery
8
+ */
9
+ import { Effect } from "effect";
10
+ // --- Error Types ---
11
+ export class RunnerError {
12
+ constructor(runnerId, cause) {
13
+ this.runnerId = runnerId;
14
+ this.cause = cause;
15
+ this._tag = "RunnerError";
16
+ }
17
+ }
18
+ export class TimeoutError {
19
+ constructor(operation, timeoutMs) {
20
+ this.operation = operation;
21
+ this.timeoutMs = timeoutMs;
22
+ this._tag = "TimeoutError";
23
+ }
24
+ }
25
+ // --- Concurrent Execution Helper ---
26
+ /**
27
+ * Run multiple runners concurrently with Effect
28
+ *
29
+ * Features:
30
+ * - Parallel execution with Effect.all
31
+ * - Per-runner timeout handling
32
+ * - Graceful error recovery (individual failures don't stop others)
33
+ * - Automatic resource cleanup
34
+ */
35
+ export function runRunnersConcurrent(filePath, runnerIds, runSingle, timeoutMs = 30000) {
36
+ return Effect.gen(function* () {
37
+ const startTime = Date.now();
38
+ // Run all runners in parallel
39
+ const results = yield* Effect.all(runnerIds.map((runnerId) => Effect.gen(function* () {
40
+ const runnerStart = Date.now();
41
+ // Execute with timeout and error handling
42
+ const result = yield* Effect.tryPromise({
43
+ try: () => runSingle(filePath, runnerId),
44
+ catch: (err) => err,
45
+ }).pipe(Effect.timeout(timeoutMs), Effect.catchAll((err) => Effect.succeed({
46
+ diagnostics: [],
47
+ durationMs: Date.now() - runnerStart,
48
+ error: String(err),
49
+ })));
50
+ const isError = "error" in result;
51
+ return {
52
+ runnerId,
53
+ status: isError ? "failure" : "success",
54
+ diagnostics: isError ? [] : result.diagnostics,
55
+ durationMs: isError ? result.durationMs : result.durationMs,
56
+ error: isError ? result.error : undefined,
57
+ };
58
+ })), { concurrency: "unbounded" });
59
+ return results;
60
+ });
61
+ }
62
+ /**
63
+ * Run a single runner with timeout and error handling
64
+ */
65
+ export function runRunnerWithTimeout(filePath, runnerId, runSingle, timeoutMs = 30000) {
66
+ return Effect.gen(function* () {
67
+ const startTime = Date.now();
68
+ const result = yield* Effect.tryPromise({
69
+ try: () => runSingle(filePath, runnerId),
70
+ catch: (err) => new RunnerError(runnerId, err),
71
+ }).pipe(Effect.timeout(timeoutMs), Effect.mapError((err) => {
72
+ if (err instanceof RunnerError)
73
+ return err;
74
+ return new TimeoutError(`runner:${runnerId}`, timeoutMs);
75
+ }));
76
+ return {
77
+ diagnostics: result.diagnostics,
78
+ durationMs: Date.now() - startTime,
79
+ };
80
+ });
81
+ }
82
+ // --- Execution Helpers ---
83
+ /**
84
+ * Execute Effect and get result
85
+ */
86
+ export function executeEffect(effect) {
87
+ return Effect.runPromise(effect);
88
+ }
89
+ /**
90
+ * Execute Effect with error handling
91
+ */
92
+ export function executeEffectWithError(effect, onError) {
93
+ return Effect.runPromise(Effect.catchAll(effect, (err) => Effect.succeed(onError(err))));
94
+ }
95
+ // --- Error Formatting ---
96
+ export function formatError(err) {
97
+ switch (err._tag) {
98
+ case "RunnerError":
99
+ return `Runner ${err.runnerId} failed: ${err.cause}`;
100
+ case "TimeoutError":
101
+ return `Operation ${err.operation} timed out after ${err.timeoutMs}ms`;
102
+ default:
103
+ return "Unknown error";
104
+ }
105
+ }