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
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * TypeScript LSP runner for dispatch system
3
3
  *
4
- * Wraps the existing TypeScriptClient for LSP diagnostics.
4
+ * Uses the new LSP client architecture (Phase 3) when --lens-lsp is enabled.
5
+ * Falls back to built-in TypeScriptClient for backward compatibility.
6
+ *
7
+ * @deprecated The built-in TypeScriptClient is deprecated. Use --lens-lsp for full LSP support.
5
8
  */
6
9
  import { TypeScriptClient } from "../../typescript-client.js";
10
+ import { getLSPService } from "../../lsp/index.js";
7
11
  import { readFileContent } from "./utils.js";
8
12
  const tsLspRunner = {
9
13
  id: "ts-lsp",
@@ -15,39 +19,106 @@ const tsLspRunner = {
15
19
  if (!ctx.filePath.match(/\.tsx?$/)) {
16
20
  return { status: "skipped", diagnostics: [], semantic: "none" };
17
21
  }
18
- // Use the existing TypeScriptClient
19
- const tsClient = new TypeScriptClient();
20
- const content = readFileContent(ctx.filePath);
21
- if (!content) {
22
- return { status: "skipped", diagnostics: [], semantic: "none" };
23
- }
24
- tsClient.updateFile(ctx.filePath, content);
25
- const diags = tsClient.getDiagnostics(ctx.filePath);
26
- if (diags.length === 0) {
27
- return { status: "succeeded", diagnostics: [], semantic: "none" };
28
- }
29
- // Convert to diagnostics
30
- const diagnostics = [];
31
- for (const d of diags) {
32
- const severity = d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info";
33
- diagnostics.push({
34
- id: `ts-${d.range.start.line}-${d.code}`,
35
- message: d.message,
36
- filePath: ctx.filePath,
37
- line: d.range.start.line + 1,
38
- severity,
39
- semantic: d.severity === 1 ? "blocking" : "warning",
40
- tool: "ts-lsp",
41
- rule: `TS${d.code}`,
42
- });
22
+ // Phase 3: Use LSP client if --lens-lsp flag is enabled
23
+ if (ctx.pi.getFlag("lens-lsp")) {
24
+ return runWithLSPClient(ctx);
43
25
  }
44
- return {
45
- status: diagnostics.some((d) => d.severity === "error")
46
- ? "failed"
47
- : "succeeded",
48
- diagnostics,
49
- semantic: "warning",
50
- };
26
+ // DEPRECATED: Fall back to built-in TypeScriptClient
27
+ // This path is deprecated and will be removed in a future release
28
+ return runWithBuiltinClient(ctx);
51
29
  },
52
30
  };
31
+ /**
32
+ * Run with new LSP client (Phase 3)
33
+ */
34
+ async function runWithLSPClient(ctx) {
35
+ const lspService = getLSPService();
36
+ // Check if we have LSP available for this file
37
+ const hasLSP = await lspService.hasLSP(ctx.filePath);
38
+ if (!hasLSP) {
39
+ return { status: "skipped", diagnostics: [], semantic: "none" };
40
+ }
41
+ // Read file content
42
+ const content = readFileContent(ctx.filePath);
43
+ if (!content) {
44
+ return { status: "skipped", diagnostics: [], semantic: "none" };
45
+ }
46
+ // Open file in LSP and get diagnostics
47
+ await lspService.openFile(ctx.filePath, content);
48
+ // Small delay to let diagnostics propagate
49
+ await new Promise(r => setTimeout(r, 500));
50
+ const lspDiags = await lspService.getDiagnostics(ctx.filePath);
51
+ // Convert LSP diagnostics to our format
52
+ // Defensive: filter out malformed diagnostics that may lack range
53
+ const diagnostics = lspDiags
54
+ .filter((d) => d.range?.start?.line !== undefined)
55
+ .map((d) => ({
56
+ id: `ts-lsp:${d.code ?? "unknown"}:${d.range.start.line}`,
57
+ message: d.message,
58
+ filePath: ctx.filePath,
59
+ line: d.range.start.line + 1,
60
+ column: d.range.start.character + 1,
61
+ severity: d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info",
62
+ semantic: d.severity === 1 ? "blocking" : "warning",
63
+ tool: "ts-lsp",
64
+ code: String(d.code ?? ""),
65
+ }));
66
+ return {
67
+ status: "failed",
68
+ diagnostics,
69
+ semantic: "blocking",
70
+ };
71
+ }
72
+ /**
73
+ * Run with deprecated built-in TypeScriptClient
74
+ * @deprecated Use runWithLSPClient instead
75
+ */
76
+ async function runWithBuiltinClient(ctx) {
77
+ const tsClient = new TypeScriptClient();
78
+ const content = readFileContent(ctx.filePath);
79
+ if (!content) {
80
+ return { status: "skipped", diagnostics: [], semantic: "none" };
81
+ }
82
+ tsClient.updateFile(ctx.filePath, content);
83
+ const diags = tsClient.getDiagnostics(ctx.filePath);
84
+ if (diags.length === 0) {
85
+ return { status: "succeeded", diagnostics: [], semantic: "none" };
86
+ }
87
+ // Get code fixes for all errors
88
+ const allFixes = tsClient.getAllCodeFixes(ctx.filePath);
89
+ // Convert to diagnostics
90
+ const diagnostics = [];
91
+ // The built-in client returns Diagnostic with { range: { start: { line, character } } }
92
+ for (const d of diags) {
93
+ // Safely access nested properties
94
+ if (!d.range?.start)
95
+ continue;
96
+ const line = d.range.start.line;
97
+ const character = d.range.start.character ?? 0;
98
+ const severity = d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info";
99
+ const semantic = d.severity === 1 ? "blocking" : "warning";
100
+ // Find fixes for this line
101
+ const lineFixes = allFixes.get(line);
102
+ const fixDescription = lineFixes?.[0]?.description;
103
+ diagnostics.push({
104
+ id: `ts:${d.code}:${line}`,
105
+ message: fixDescription
106
+ ? `${d.message} [💡 ${fixDescription}]`
107
+ : d.message,
108
+ filePath: ctx.filePath,
109
+ line: line + 1,
110
+ column: character + 1,
111
+ severity,
112
+ semantic,
113
+ tool: "ts-lsp",
114
+ fixable: !!lineFixes && lineFixes.length > 0,
115
+ fixSuggestion: fixDescription,
116
+ });
117
+ }
118
+ return {
119
+ status: "failed",
120
+ diagnostics,
121
+ semantic: "blocking",
122
+ };
123
+ }
53
124
  export default tsLspRunner;
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * TypeScript LSP runner for dispatch system
3
3
  *
4
- * Wraps the existing TypeScriptClient for LSP diagnostics.
4
+ * Uses the new LSP client architecture (Phase 3) when --lens-lsp is enabled.
5
+ * Falls back to built-in TypeScriptClient for backward compatibility.
6
+ *
7
+ * @deprecated The built-in TypeScriptClient is deprecated. Use --lens-lsp for full LSP support.
5
8
  */
6
9
 
7
10
  import { TypeScriptClient } from "../../typescript-client.js";
11
+ import { getLSPService } from "../../lsp/index.js";
8
12
  import type {
9
13
  Diagnostic,
10
14
  DispatchContext,
@@ -25,47 +29,125 @@ const tsLspRunner: RunnerDefinition = {
25
29
  return { status: "skipped", diagnostics: [], semantic: "none" };
26
30
  }
27
31
 
28
- // Use the existing TypeScriptClient
29
- const tsClient = new TypeScriptClient();
30
-
31
- const content = readFileContent(ctx.filePath);
32
- if (!content) {
33
- return { status: "skipped", diagnostics: [], semantic: "none" };
32
+ // Phase 3: Use LSP client if --lens-lsp flag is enabled
33
+ if (ctx.pi.getFlag("lens-lsp")) {
34
+ return runWithLSPClient(ctx);
34
35
  }
35
- tsClient.updateFile(ctx.filePath, content);
36
36
 
37
- const diags = tsClient.getDiagnostics(ctx.filePath);
37
+ // DEPRECATED: Fall back to built-in TypeScriptClient
38
+ // This path is deprecated and will be removed in a future release
39
+ return runWithBuiltinClient(ctx);
40
+ },
41
+ };
38
42
 
39
- if (diags.length === 0) {
40
- return { status: "succeeded", diagnostics: [], semantic: "none" };
41
- }
43
+ /**
44
+ * Run with new LSP client (Phase 3)
45
+ */
46
+ async function runWithLSPClient(ctx: DispatchContext): Promise<RunnerResult> {
47
+ const lspService = getLSPService();
42
48
 
43
- // Convert to diagnostics
44
- const diagnostics: Diagnostic[] = [];
45
-
46
- for (const d of diags) {
47
- const severity =
48
- d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info";
49
- diagnostics.push({
50
- id: `ts-${d.range.start.line}-${d.code}`,
51
- message: d.message,
52
- filePath: ctx.filePath,
53
- line: d.range.start.line + 1,
54
- severity,
55
- semantic: d.severity === 1 ? "blocking" : "warning",
56
- tool: "ts-lsp",
57
- rule: `TS${d.code}`,
58
- });
59
- }
49
+ // Check if we have LSP available for this file
50
+ const hasLSP = await lspService.hasLSP(ctx.filePath);
51
+ if (!hasLSP) {
52
+ return { status: "skipped", diagnostics: [], semantic: "none" };
53
+ }
60
54
 
61
- return {
62
- status: diagnostics.some((d) => d.severity === "error")
63
- ? "failed"
64
- : "succeeded",
65
- diagnostics,
66
- semantic: "warning",
67
- };
68
- },
69
- };
55
+ // Read file content
56
+ const content = readFileContent(ctx.filePath);
57
+ if (!content) {
58
+ return { status: "skipped", diagnostics: [], semantic: "none" };
59
+ }
60
+
61
+ // Open file in LSP and get diagnostics
62
+ await lspService.openFile(ctx.filePath, content);
63
+ // Small delay to let diagnostics propagate
64
+ await new Promise(r => setTimeout(r, 500));
65
+ const lspDiags = await lspService.getDiagnostics(ctx.filePath);
66
+
67
+ // Convert LSP diagnostics to our format
68
+ // Defensive: filter out malformed diagnostics that may lack range
69
+ const diagnostics: Diagnostic[] = lspDiags
70
+ .filter((d) => d.range?.start?.line !== undefined)
71
+ .map((d) => ({
72
+ id: `ts-lsp:${d.code ?? "unknown"}:${d.range.start.line}`,
73
+ message: d.message,
74
+ filePath: ctx.filePath,
75
+ line: d.range.start.line + 1,
76
+ column: d.range.start.character + 1,
77
+ severity: d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info",
78
+ semantic: d.severity === 1 ? "blocking" : "warning",
79
+ tool: "ts-lsp",
80
+ code: String(d.code ?? ""),
81
+ }));
82
+
83
+ return {
84
+ status: "failed",
85
+ diagnostics,
86
+ semantic: "blocking",
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Run with deprecated built-in TypeScriptClient
92
+ * @deprecated Use runWithLSPClient instead
93
+ */
94
+ async function runWithBuiltinClient(ctx: DispatchContext): Promise<RunnerResult> {
95
+ const tsClient = new TypeScriptClient();
96
+
97
+ const content = readFileContent(ctx.filePath);
98
+ if (!content) {
99
+ return { status: "skipped", diagnostics: [], semantic: "none" };
100
+ }
101
+ tsClient.updateFile(ctx.filePath, content);
102
+
103
+ const diags = tsClient.getDiagnostics(ctx.filePath);
104
+
105
+ if (diags.length === 0) {
106
+ return { status: "succeeded", diagnostics: [], semantic: "none" };
107
+ }
108
+
109
+ // Get code fixes for all errors
110
+ const allFixes = tsClient.getAllCodeFixes(ctx.filePath);
111
+
112
+ // Convert to diagnostics
113
+ const diagnostics: Diagnostic[] = [];
114
+
115
+ // The built-in client returns Diagnostic with { range: { start: { line, character } } }
116
+ for (const d of diags) {
117
+ // Safely access nested properties
118
+ if (!d.range?.start) continue;
119
+
120
+ const line = d.range.start.line;
121
+ const character = d.range.start.character ?? 0;
122
+ const severity =
123
+ d.severity === 1 ? "error" : d.severity === 2 ? "warning" : "info";
124
+ const semantic = d.severity === 1 ? "blocking" : "warning";
125
+
126
+ // Find fixes for this line
127
+ const lineFixes = allFixes.get(line);
128
+ const fixDescription = lineFixes?.[0]?.description;
129
+
130
+ diagnostics.push({
131
+ id: `ts:${d.code}:${line}`,
132
+ message: fixDescription
133
+ ? `${d.message} [💡 ${fixDescription}]`
134
+ : d.message,
135
+ filePath: ctx.filePath,
136
+ line: line + 1,
137
+ column: character + 1,
138
+ severity,
139
+ semantic,
140
+ tool: "ts-lsp",
141
+ fixable: !!lineFixes && lineFixes.length > 0,
142
+ fixSuggestion: fixDescription,
143
+ });
144
+ }
145
+
146
+ return {
147
+ status: "failed",
148
+ diagnostics,
149
+ semantic: "blocking",
150
+ };
151
+ }
70
152
 
71
153
  export default tsLspRunner;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * TypeScript Slop runner for dispatch system
3
+ *
4
+ * Detects "slop" patterns in TypeScript/JavaScript code:
5
+ * - Verbose patterns (ceremony that adds no value)
6
+ * - Defensive over-checking (excessive guards)
7
+ * - Manual reimplementation of builtins
8
+ * - Unnecessary object allocations
9
+ *
10
+ * Based on slop-code-bench patterns adapted for TypeScript
11
+ */
12
+ import { safeSpawn } from "../../safe-spawn.js";
13
+ import { createConfigFinder, isSgAvailable, } from "./utils/runner-helpers.js";
14
+ const findSlopConfig = createConfigFinder("ts-slop-rules");
15
+ const tsSlopRunner = {
16
+ id: "ts-slop",
17
+ // NOTE: TypeScript/JavaScript slop detection is now handled by ast-grep-napi
18
+ // This CLI runner is kept as fallback for edge cases but disabled by default
19
+ appliesTo: [], // Disabled - use ast-grep-napi instead
20
+ priority: 20,
21
+ enabledByDefault: false,
22
+ skipTestFiles: true,
23
+ async run(ctx) {
24
+ // Check if ast-grep is available
25
+ if (!isSgAvailable()) {
26
+ return { status: "skipped", diagnostics: [], semantic: "none" };
27
+ }
28
+ // Find slop config
29
+ const configPath = findSlopConfig(ctx.cwd);
30
+ if (!configPath) {
31
+ return { status: "skipped", diagnostics: [], semantic: "none" };
32
+ }
33
+ // Run ast-grep scan
34
+ const args = ["sg", "scan", "--config", configPath, "--json", ctx.filePath];
35
+ const result = safeSpawn("npx", args, {
36
+ timeout: 30000,
37
+ });
38
+ const raw = result.stdout + result.stderr;
39
+ if (result.status === 0 && !raw.trim()) {
40
+ return { status: "succeeded", diagnostics: [], semantic: "none" };
41
+ }
42
+ // Parse results
43
+ const diagnostics = parseSlopOutput(raw, ctx.filePath);
44
+ if (diagnostics.length === 0) {
45
+ return { status: "succeeded", diagnostics: [], semantic: "none" };
46
+ }
47
+ return {
48
+ status: "failed",
49
+ diagnostics,
50
+ semantic: "warning",
51
+ };
52
+ },
53
+ };
54
+ function parseSlopOutput(raw, filePath) {
55
+ const diagnostics = [];
56
+ try {
57
+ const parsed = JSON.parse(raw);
58
+ if (Array.isArray(parsed)) {
59
+ for (const item of parsed) {
60
+ const line = item.range?.start?.line || 1;
61
+ const ruleId = item.rule || "unknown";
62
+ const message = item.message || "";
63
+ // Categorize by severity based on weight from metadata
64
+ const weight = item.metadata?.weight || 3;
65
+ const severity = weight >= 4 ? "error" : "warning";
66
+ const category = item.metadata?.category || "slop";
67
+ // Add slop category indicator to message
68
+ let enhancedMessage = `[${category}] ${message}`;
69
+ if (item.replacement) {
70
+ const preview = item.replacement.length > 40
71
+ ? `${item.replacement.substring(0, 40)}...`
72
+ : item.replacement;
73
+ enhancedMessage += `\n💡 Suggested fix: → "${preview}"`;
74
+ }
75
+ diagnostics.push({
76
+ id: `ts-slop-${line}-${ruleId}`,
77
+ message: enhancedMessage,
78
+ filePath,
79
+ line,
80
+ column: item.range?.start?.column || 0,
81
+ severity,
82
+ semantic: severity === "error" ? "blocking" : "warning",
83
+ tool: "ts-slop",
84
+ rule: ruleId,
85
+ fixable: !!item.replacement,
86
+ fixSuggestion: item.replacement,
87
+ });
88
+ }
89
+ }
90
+ }
91
+ catch {
92
+ // JSON parse failed, try line-by-line
93
+ const lines = raw.split("\n");
94
+ for (const line of lines) {
95
+ if (line.includes(":") && line.includes("L")) {
96
+ const match = line.match(/L(\d+):?\s*(.+)/);
97
+ if (match) {
98
+ diagnostics.push({
99
+ id: `ts-slop-${match[1]}-line`,
100
+ message: `[slop] ${match[2].trim()}`,
101
+ filePath,
102
+ line: parseInt(match[1], 10),
103
+ severity: "warning",
104
+ semantic: "warning",
105
+ tool: "ts-slop",
106
+ });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ return diagnostics;
112
+ }
113
+ export default tsSlopRunner;
@@ -0,0 +1,180 @@
1
+ import * as fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import * as path from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ function createMockContext(filePath) {
6
+ return {
7
+ filePath,
8
+ cwd: process.cwd(),
9
+ kind: "jsts",
10
+ autofix: false,
11
+ deltaMode: false,
12
+ baselines: { get: () => [], add: () => { }, save: () => { } },
13
+ pi: {},
14
+ hasTool: async () => false,
15
+ log: () => { },
16
+ };
17
+ }
18
+ // Helper for safe file cleanup
19
+ function safeUnlink(filePath) {
20
+ try {
21
+ if (fs.existsSync(filePath)) {
22
+ fs.unlinkSync(filePath);
23
+ }
24
+ }
25
+ catch {
26
+ // Ignore cleanup errors on Windows
27
+ }
28
+ }
29
+ describe("ts-slop runner", () => {
30
+ const require = createRequire(import.meta.url);
31
+ it("should have correct runner definition", async () => {
32
+ const slopModule = await import("./ts-slop.js");
33
+ const runner = slopModule.default;
34
+ expect(runner.id).toBe("ts-slop");
35
+ // NOTE: TS/JS slop is now handled by ast-grep-napi
36
+ // This CLI runner is disabled by default as fallback
37
+ expect(runner.appliesTo).toEqual([]); // Disabled - use ast-grep-napi
38
+ expect(runner.priority).toBe(20);
39
+ expect(runner.enabledByDefault).toBe(false);
40
+ expect(runner.skipTestFiles).toBe(true);
41
+ });
42
+ it("should detect ast-grep availability", () => {
43
+ const { spawnSync } = require("node:child_process");
44
+ const result = spawnSync("npx", ["sg", "--version"], {
45
+ encoding: "utf-8",
46
+ timeout: 10000,
47
+ shell: true,
48
+ });
49
+ expect(result.error || result.status !== 0 ? "not available" : "available").toBe("available");
50
+ });
51
+ it("should detect for-index-length pattern (or other slop)", async () => {
52
+ const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_for_${Date.now()}.ts`);
53
+ fs.writeFileSync(tmpFile, `// Slop: using index loop instead of for-of
54
+ function processItems(items: string[]) {
55
+ for (let i = 0; i < items.length; i++) {
56
+ console.log(items[i]);
57
+ }
58
+ }
59
+ `);
60
+ try {
61
+ const slopModule = await import("./ts-slop.js");
62
+ const runner = slopModule.default;
63
+ const result = await runner.run(createMockContext(tmpFile));
64
+ // Should detect at least some slop patterns
65
+ // (specific patterns may vary based on ast-grep rule accuracy)
66
+ expect(result.status).not.toBe("skipped");
67
+ }
68
+ finally {
69
+ safeUnlink(tmpFile);
70
+ }
71
+ });
72
+ it("should detect manual Math min/max pattern (or other slop)", async () => {
73
+ const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_minmax_${Date.now()}.ts`);
74
+ fs.writeFileSync(tmpFile, `// Slop: manual min/max instead of Math
75
+ function getMax(a: number, b: number): number {
76
+ if (a > b) {
77
+ const m = a;
78
+ } else {
79
+ const m = b;
80
+ }
81
+ return m;
82
+ }
83
+ `);
84
+ try {
85
+ const slopModule = await import("./ts-slop.js");
86
+ const runner = slopModule.default;
87
+ const result = await runner.run(createMockContext(tmpFile));
88
+ // Should detect at least some slop patterns
89
+ // (specific patterns may vary based on ast-grep rule accuracy)
90
+ expect(result.status).not.toBe("skipped");
91
+ }
92
+ finally {
93
+ safeUnlink(tmpFile);
94
+ }
95
+ });
96
+ it("should detect indexOf !== -1 pattern (or other slop)", async () => {
97
+ const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_indexof_${Date.now()}.ts`);
98
+ fs.writeFileSync(tmpFile, `// Slop: indexOf check instead of includes
99
+ function hasItem(arr: string[], item: string): boolean {
100
+ if (arr.indexOf(item) !== -1) {
101
+ return true;
102
+ }
103
+ return false;
104
+ }
105
+ `);
106
+ try {
107
+ const slopModule = await import("./ts-slop.js");
108
+ const runner = slopModule.default;
109
+ const result = await runner.run(createMockContext(tmpFile));
110
+ // Should detect at least some slop patterns
111
+ // (specific patterns may vary based on ast-grep rule accuracy)
112
+ expect(result.status).not.toBe("skipped");
113
+ }
114
+ finally {
115
+ safeUnlink(tmpFile);
116
+ }
117
+ });
118
+ it("should detect array length > 0 pattern", async () => {
119
+ const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_length_${Date.now()}.ts`);
120
+ fs.writeFileSync(tmpFile, `// Slop: length check instead of truthiness
121
+ function processItems(arr: string[]): void {
122
+ if (arr.length > 0) {
123
+ console.log("has items");
124
+ }
125
+ }
126
+ `);
127
+ try {
128
+ const slopModule = await import("./ts-slop.js");
129
+ const runner = slopModule.default;
130
+ const result = await runner.run(createMockContext(tmpFile));
131
+ // This pattern may or may not be detected depending on rule specificity
132
+ // Just verify the scan ran without errors
133
+ expect(result.status).toBe("succeeded");
134
+ }
135
+ finally {
136
+ safeUnlink(tmpFile);
137
+ }
138
+ });
139
+ it("should pass clean TypeScript files", async () => {
140
+ const tmpFile = path.join(process.env.TEMP || "/tmp", `ts_slop_test_ok_${Date.now()}.ts`);
141
+ fs.writeFileSync(tmpFile, `// Clean TypeScript code
142
+ function processItems(items: string[]): void {
143
+ for (const item of items) {
144
+ console.log(item);
145
+ }
146
+ }
147
+
148
+ function findMax(a: number, b: number): number {
149
+ return Math.max(a, b);
150
+ }
151
+
152
+ function contains(arr: string[], item: string): boolean {
153
+ return arr.includes(item);
154
+ }
155
+
156
+ function hasItems(arr: string[]): boolean {
157
+ return arr.length > 0; // This is actually OK, but let's see
158
+ }
159
+ `);
160
+ try {
161
+ const slopModule = await import("./ts-slop.js");
162
+ const runner = slopModule.default;
163
+ const result = await runner.run(createMockContext(tmpFile));
164
+ // Should have minimal or no slop issues for clean code
165
+ const slopIssues = result.diagnostics.filter((d) => d.tool === "ts-slop");
166
+ // Allow for minor issues - the length check might still trigger
167
+ expect(slopIssues.length).toBeLessThanOrEqual(1);
168
+ }
169
+ finally {
170
+ try {
171
+ if (fs.existsSync(tmpFile)) {
172
+ safeUnlink(tmpFile);
173
+ }
174
+ }
175
+ catch {
176
+ // Ignore cleanup errors
177
+ }
178
+ }
179
+ });
180
+ });