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
@@ -8,10 +8,10 @@
8
8
  * Docs: https://biomejs.dev/
9
9
  */
10
10
 
11
- import { spawnSync } from "node:child_process";
12
11
  import * as fs from "node:fs";
13
12
  import * as path from "node:path";
14
13
  import { isFileKind } from "./file-kinds.js";
14
+ import { safeSpawn } from "./safe-spawn.js";
15
15
 
16
16
  // --- Types ---
17
17
 
@@ -57,10 +57,8 @@ export class BiomeClient {
57
57
  if (this.biomeAvailable !== null) return this.biomeAvailable;
58
58
 
59
59
  // Try npx biome first (works without global install)
60
- const result = spawnSync("npx", ["@biomejs/biome", "--version"], {
61
- encoding: "utf-8",
60
+ const result = safeSpawn("npx", ["@biomejs/biome", "--version"], {
62
61
  timeout: 10000,
63
- shell: true,
64
62
  });
65
63
 
66
64
  this.biomeAvailable = !result.error && result.status === 0;
@@ -105,7 +103,7 @@ export class BiomeClient {
105
103
  if (!absolutePath) return [];
106
104
 
107
105
  try {
108
- const result = spawnSync(
106
+ const result = safeSpawn(
109
107
  "npx",
110
108
  [
111
109
  "@biomejs/biome",
@@ -115,9 +113,7 @@ export class BiomeClient {
115
113
  absolutePath,
116
114
  ],
117
115
  {
118
- encoding: "utf-8",
119
116
  timeout: 15000,
120
- shell: true,
121
117
  },
122
118
  );
123
119
 
@@ -126,8 +122,10 @@ export class BiomeClient {
126
122
  if (!output.trim()) return [];
127
123
 
128
124
  return this.parseDiagnostics(output, absolutePath);
129
- } catch (err: any) {
130
- this.log(`Check error: ${err.message}`);
125
+ } catch (err) {
126
+ this.log(
127
+ `Check error: ${err instanceof Error ? err.message : String(err)}`,
128
+ );
131
129
  return [];
132
130
  }
133
131
  }
@@ -151,13 +149,11 @@ export class BiomeClient {
151
149
  const content = fs.readFileSync(absolutePath, "utf-8");
152
150
 
153
151
  try {
154
- const result = spawnSync(
152
+ const result = safeSpawn(
155
153
  "npx",
156
154
  ["@biomejs/biome", "format", "--write", absolutePath],
157
155
  {
158
- encoding: "utf-8",
159
156
  timeout: 15000,
160
- shell: true,
161
157
  },
162
158
  );
163
159
 
@@ -174,8 +170,12 @@ export class BiomeClient {
174
170
  }
175
171
 
176
172
  return { success: true, changed };
177
- } catch (err: any) {
178
- return { success: false, changed: false, error: err.message };
173
+ } catch (err) {
174
+ return {
175
+ success: false,
176
+ changed: false,
177
+ error: err instanceof Error ? err.message : String(err),
178
+ };
179
179
  }
180
180
  }
181
181
 
@@ -205,7 +205,7 @@ export class BiomeClient {
205
205
  const fixableCount = beforeDiags.filter((d) => d.fixable).length;
206
206
 
207
207
  // Apply fixes
208
- const result = spawnSync(
208
+ const result = safeSpawn(
209
209
  "npx",
210
210
  [
211
211
  "@biomejs/biome",
@@ -215,9 +215,7 @@ export class BiomeClient {
215
215
  absolutePath,
216
216
  ],
217
217
  {
218
- encoding: "utf-8",
219
218
  timeout: 15000,
220
- shell: true,
221
219
  },
222
220
  );
223
221
 
@@ -240,8 +238,93 @@ export class BiomeClient {
240
238
  }
241
239
 
242
240
  return { success: true, changed, fixed: fixableCount };
243
- } catch (err: any) {
244
- return { success: false, changed: false, fixed: 0, error: err.message };
241
+ } catch (err) {
242
+ return {
243
+ success: false,
244
+ changed: false,
245
+ fixed: 0,
246
+ error: err instanceof Error ? err.message : String(err),
247
+ };
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Fix multiple files at once (much faster than file-by-file)
253
+ */
254
+ fixFiles(filePaths: string[]): {
255
+ success: boolean;
256
+ fixed: number;
257
+ changed: number;
258
+ error?: string;
259
+ } {
260
+ if (!this.isAvailable()) {
261
+ return {
262
+ success: false,
263
+ fixed: 0,
264
+ changed: 0,
265
+ error: "Biome not available",
266
+ };
267
+ }
268
+
269
+ // Filter to existing files
270
+ const validFiles = filePaths
271
+ .map(f => path.resolve(f))
272
+ .filter(f => fs.existsSync(f));
273
+
274
+ if (validFiles.length === 0) {
275
+ return { success: true, fixed: 0, changed: 0 };
276
+ }
277
+
278
+ try {
279
+ // Count fixable issues before fixing
280
+ let totalFixable = 0;
281
+ for (const file of validFiles) {
282
+ const diags = this.checkFile(file);
283
+ totalFixable += diags.filter(d => d.fixable).length;
284
+ }
285
+
286
+ // Run biome once on all files - much faster than npx per file
287
+ const result = safeSpawn(
288
+ "npx",
289
+ [
290
+ "@biomejs/biome",
291
+ "check",
292
+ "--write",
293
+ "--unsafe",
294
+ ...validFiles,
295
+ ],
296
+ {
297
+ timeout: 60000, // Longer timeout for batch
298
+ },
299
+ );
300
+
301
+ if (result.error) {
302
+ return {
303
+ success: false,
304
+ fixed: 0,
305
+ changed: 0,
306
+ error: result.error.message,
307
+ };
308
+ }
309
+
310
+ // Count how many files actually changed
311
+ let changedCount = 0;
312
+ for (const file of validFiles) {
313
+ // We don't know exactly which files changed without re-reading,
314
+ // so we report total files processed
315
+ changedCount++;
316
+ }
317
+
318
+ this.log(`Fixed ${totalFixable} issue(s) in ${validFiles.length} file(s)`);
319
+
320
+ return { success: true, fixed: totalFixable, changed: changedCount };
321
+ } catch (err) {
322
+ return {
323
+ success: false,
324
+ fixed: 0,
325
+ changed: 0,
326
+ error: err instanceof Error ? err.message : String(err),
327
+ };
245
328
  }
246
329
  }
247
330
 
@@ -291,13 +374,11 @@ export class BiomeClient {
291
374
 
292
375
  try {
293
376
  // Get formatted output without writing
294
- const result = spawnSync(
377
+ const result = safeSpawn(
295
378
  "npx",
296
379
  ["@biomejs/biome", "format", absolutePath],
297
380
  {
298
- encoding: "utf-8",
299
381
  timeout: 15000,
300
- shell: true,
301
382
  },
302
383
  );
303
384
 
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Event Bus for pi-lens
3
+ *
4
+ * Decoupled pub/sub system for diagnostic events.
5
+ * Enables loose coupling between diagnostic producers (runners, LSP clients)
6
+ * and consumers (UI, aggregators, history trackers).
7
+ */
8
+ // --- Internal State ---
9
+ const subscribers = new Map();
10
+ const globalMiddleware = [];
11
+ let eventCount = 0;
12
+ let isDebugEnabled = false;
13
+ // --- Core Functions ---
14
+ function debug(type, event) {
15
+ if (!isDebugEnabled)
16
+ return;
17
+ const timestamp = new Date(event.timestamp).toISOString();
18
+ console.error(`[bus] [${timestamp}] ${type}: ${event.type}`);
19
+ }
20
+ /**
21
+ * Publish an event to all subscribers
22
+ */
23
+ export function publish(event) {
24
+ eventCount++;
25
+ debug("publish", event);
26
+ // Run through middleware
27
+ let currentEvent = event;
28
+ for (const mw of globalMiddleware) {
29
+ currentEvent = mw(currentEvent);
30
+ if (!currentEvent)
31
+ return; // Middleware cancelled the event
32
+ }
33
+ const handlers = subscribers.get(event.type);
34
+ if (!handlers || handlers.size === 0)
35
+ return;
36
+ // Notify all subscribers (fire-and-forget for async handlers)
37
+ for (const handler of handlers) {
38
+ try {
39
+ const result = handler(currentEvent);
40
+ if (result instanceof Promise) {
41
+ result.catch((err) => {
42
+ console.error(`[bus] async handler error for ${event.type}:`, err);
43
+ });
44
+ }
45
+ }
46
+ catch (err) {
47
+ console.error(`[bus] handler error for ${event.type}:`, err);
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Subscribe to a specific event type
53
+ * @returns Unsubscribe function
54
+ */
55
+ export function subscribe(eventType, handler) {
56
+ if (!subscribers.has(eventType)) {
57
+ subscribers.set(eventType, new Set());
58
+ }
59
+ const handlers = subscribers.get(eventType);
60
+ handlers.add(handler);
61
+ debug("subscribe", { type: eventType, properties: {}, timestamp: Date.now() });
62
+ // Return unsubscribe function
63
+ return () => {
64
+ handlers.delete(handler);
65
+ if (handlers.size === 0) {
66
+ subscribers.delete(eventType);
67
+ }
68
+ };
69
+ }
70
+ /**
71
+ * Subscribe to an event type and automatically unsubscribe after first match
72
+ */
73
+ export function once(eventType, predicate) {
74
+ return new Promise((resolve) => {
75
+ const unsubscribe = subscribe(eventType, (event) => {
76
+ if (!predicate || predicate(event)) {
77
+ unsubscribe();
78
+ resolve(event);
79
+ }
80
+ });
81
+ });
82
+ }
83
+ /**
84
+ * Wait for an event with timeout
85
+ */
86
+ export function waitFor(eventType, timeoutMs, predicate) {
87
+ return new Promise((resolve) => {
88
+ const timer = setTimeout(() => {
89
+ unsubscribe();
90
+ resolve(undefined);
91
+ }, timeoutMs);
92
+ const unsubscribe = subscribe(eventType, (event) => {
93
+ if (!predicate || predicate(event)) {
94
+ clearTimeout(timer);
95
+ unsubscribe();
96
+ resolve(event);
97
+ }
98
+ });
99
+ });
100
+ }
101
+ // --- Middleware ---
102
+ export function addMiddleware(mw) {
103
+ globalMiddleware.push(mw);
104
+ return () => {
105
+ const idx = globalMiddleware.indexOf(mw);
106
+ if (idx !== -1)
107
+ globalMiddleware.splice(idx, 1);
108
+ };
109
+ }
110
+ // --- Utilities ---
111
+ export function enableDebug(enabled = true) {
112
+ isDebugEnabled = enabled;
113
+ }
114
+ export function getStats() {
115
+ return {
116
+ subscriberCount: Array.from(subscribers.values()).reduce((sum, set) => sum + set.size, 0),
117
+ eventTypes: Array.from(subscribers.keys()),
118
+ totalEvents: eventCount,
119
+ };
120
+ }
121
+ export function clearAllSubscribers() {
122
+ subscribers.clear();
123
+ }
124
+ export var BusEvent;
125
+ (function (BusEvent) {
126
+ /**
127
+ * Define a typed event type with Zod schema validation
128
+ */
129
+ function define(type, _schema) {
130
+ return {
131
+ type,
132
+ create(properties) {
133
+ return {
134
+ type,
135
+ properties,
136
+ timestamp: Date.now(),
137
+ };
138
+ },
139
+ subscribe(handler) {
140
+ return subscribe(type, handler);
141
+ },
142
+ publish(properties) {
143
+ publish({
144
+ type,
145
+ properties,
146
+ timestamp: Date.now(),
147
+ });
148
+ },
149
+ };
150
+ }
151
+ BusEvent.define = define;
152
+ /**
153
+ * Create a simple event type without schema (for internal use)
154
+ */
155
+ function defineSimple(type) {
156
+ return {
157
+ type,
158
+ create(properties) {
159
+ return {
160
+ type,
161
+ properties,
162
+ timestamp: Date.now(),
163
+ };
164
+ },
165
+ subscribe(handler) {
166
+ return subscribe(type, handler);
167
+ },
168
+ publish(properties) {
169
+ publish({
170
+ type,
171
+ properties,
172
+ timestamp: Date.now(),
173
+ });
174
+ },
175
+ };
176
+ }
177
+ BusEvent.defineSimple = defineSimple;
178
+ /**
179
+ * Create a raw event (helper)
180
+ */
181
+ function create(type, properties) {
182
+ return {
183
+ type,
184
+ properties,
185
+ timestamp: Date.now(),
186
+ };
187
+ }
188
+ BusEvent.create = create;
189
+ })(BusEvent || (BusEvent = {}));
190
+ // --- Re-export for convenience ---
191
+ export { subscribe as on, publish as emit };
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Event Bus for pi-lens
3
+ *
4
+ * Decoupled pub/sub system for diagnostic events.
5
+ * Enables loose coupling between diagnostic producers (runners, LSP clients)
6
+ * and consumers (UI, aggregators, history trackers).
7
+ */
8
+
9
+ import type { z } from "zod";
10
+
11
+ // --- Types ---
12
+
13
+ export interface BusEvent<T = unknown> {
14
+ type: string;
15
+ properties: T;
16
+ timestamp: number;
17
+ }
18
+
19
+ export type EventHandler<T> = (event: BusEvent<T>) => void | Promise<void>;
20
+
21
+ // --- Internal State ---
22
+
23
+ const subscribers = new Map<string, Set<EventHandler<unknown>>>();
24
+ const globalMiddleware: Array<(event: BusEvent) => BusEvent | undefined> = [];
25
+
26
+ let eventCount = 0;
27
+ let isDebugEnabled = false;
28
+
29
+ // --- Core Functions ---
30
+
31
+ function debug(type: string, event: BusEvent) {
32
+ if (!isDebugEnabled) return;
33
+ const timestamp = new Date(event.timestamp).toISOString();
34
+ console.error(`[bus] [${timestamp}] ${type}: ${event.type}`);
35
+ }
36
+
37
+ /**
38
+ * Publish an event to all subscribers
39
+ */
40
+ export function publish<T>(event: BusEvent<T>): void {
41
+ eventCount++;
42
+ debug("publish", event as BusEvent<unknown>);
43
+
44
+ // Run through middleware
45
+ let currentEvent: BusEvent | undefined = event as BusEvent;
46
+ for (const mw of globalMiddleware) {
47
+ currentEvent = mw(currentEvent);
48
+ if (!currentEvent) return; // Middleware cancelled the event
49
+ }
50
+
51
+ const handlers = subscribers.get(event.type);
52
+ if (!handlers || handlers.size === 0) return;
53
+
54
+ // Notify all subscribers (fire-and-forget for async handlers)
55
+ for (const handler of handlers) {
56
+ try {
57
+ const result = handler(currentEvent as BusEvent<unknown>);
58
+ if (result instanceof Promise) {
59
+ result.catch((err) => {
60
+ console.error(`[bus] async handler error for ${event.type}:`, err);
61
+ });
62
+ }
63
+ } catch (err) {
64
+ console.error(`[bus] handler error for ${event.type}:`, err);
65
+ }
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Subscribe to a specific event type
71
+ * @returns Unsubscribe function
72
+ */
73
+ export function subscribe<T>(
74
+ eventType: string,
75
+ handler: EventHandler<T>,
76
+ ): () => void {
77
+ if (!subscribers.has(eventType)) {
78
+ subscribers.set(eventType, new Set());
79
+ }
80
+
81
+ const handlers = subscribers.get(eventType)!;
82
+ handlers.add(handler as EventHandler<unknown>);
83
+
84
+ debug("subscribe", { type: eventType, properties: {}, timestamp: Date.now() });
85
+
86
+ // Return unsubscribe function
87
+ return () => {
88
+ handlers.delete(handler as EventHandler<unknown>);
89
+ if (handlers.size === 0) {
90
+ subscribers.delete(eventType);
91
+ }
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Subscribe to an event type and automatically unsubscribe after first match
97
+ */
98
+ export function once<T>(
99
+ eventType: string,
100
+ predicate?: (event: BusEvent<T>) => boolean,
101
+ ): Promise<BusEvent<T>> {
102
+ return new Promise((resolve) => {
103
+ const unsubscribe = subscribe<T>(eventType, (event) => {
104
+ if (!predicate || predicate(event)) {
105
+ unsubscribe();
106
+ resolve(event);
107
+ }
108
+ });
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Wait for an event with timeout
114
+ */
115
+ export function waitFor<T>(
116
+ eventType: string,
117
+ timeoutMs: number,
118
+ predicate?: (event: BusEvent<T>) => boolean,
119
+ ): Promise<BusEvent<T> | undefined> {
120
+ return new Promise((resolve) => {
121
+ const timer = setTimeout(() => {
122
+ unsubscribe();
123
+ resolve(undefined);
124
+ }, timeoutMs);
125
+
126
+ const unsubscribe = subscribe<T>(eventType, (event) => {
127
+ if (!predicate || predicate(event)) {
128
+ clearTimeout(timer);
129
+ unsubscribe();
130
+ resolve(event);
131
+ }
132
+ });
133
+ });
134
+ }
135
+
136
+ // --- Middleware ---
137
+
138
+ export function addMiddleware(
139
+ mw: (event: BusEvent) => BusEvent | undefined,
140
+ ): () => void {
141
+ globalMiddleware.push(mw);
142
+ return () => {
143
+ const idx = globalMiddleware.indexOf(mw);
144
+ if (idx !== -1) globalMiddleware.splice(idx, 1);
145
+ };
146
+ }
147
+
148
+ // --- Utilities ---
149
+
150
+ export function enableDebug(enabled = true): void {
151
+ isDebugEnabled = enabled;
152
+ }
153
+
154
+ export function getStats(): {
155
+ subscriberCount: number;
156
+ eventTypes: string[];
157
+ totalEvents: number;
158
+ } {
159
+ return {
160
+ subscriberCount: Array.from(subscribers.values()).reduce(
161
+ (sum, set) => sum + set.size,
162
+ 0,
163
+ ),
164
+ eventTypes: Array.from(subscribers.keys()),
165
+ totalEvents: eventCount,
166
+ };
167
+ }
168
+
169
+ export function clearAllSubscribers(): void {
170
+ subscribers.clear();
171
+ }
172
+
173
+ // --- Event Factory ---
174
+
175
+ export interface EventDefinition<T> {
176
+ type: string;
177
+ create(properties: T): BusEvent<T>;
178
+ subscribe(handler: EventHandler<T>): () => void;
179
+ publish(properties: T): void;
180
+ }
181
+
182
+ export namespace BusEvent {
183
+ /**
184
+ * Define a typed event type with Zod schema validation
185
+ */
186
+ export function define<T>(
187
+ type: string,
188
+ _schema: z.ZodType<T>,
189
+ ): EventDefinition<T> {
190
+ return {
191
+ type,
192
+ create(properties): BusEvent<T> {
193
+ return {
194
+ type,
195
+ properties,
196
+ timestamp: Date.now(),
197
+ };
198
+ },
199
+ subscribe(handler: EventHandler<T>): () => void {
200
+ return subscribe(type, handler);
201
+ },
202
+ publish(properties: T): void {
203
+ publish({
204
+ type,
205
+ properties,
206
+ timestamp: Date.now(),
207
+ });
208
+ },
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Create a simple event type without schema (for internal use)
214
+ */
215
+ export function defineSimple<T>(type: string): EventDefinition<T> {
216
+ return {
217
+ type,
218
+ create(properties: T): BusEvent<T> {
219
+ return {
220
+ type,
221
+ properties,
222
+ timestamp: Date.now(),
223
+ };
224
+ },
225
+ subscribe(handler: EventHandler<T>): () => void {
226
+ return subscribe(type, handler);
227
+ },
228
+ publish(properties: T): void {
229
+ publish({
230
+ type,
231
+ properties,
232
+ timestamp: Date.now(),
233
+ });
234
+ },
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Create a raw event (helper)
240
+ */
241
+ export function create<T>(type: string, properties: T): BusEvent<T> {
242
+ return {
243
+ type,
244
+ properties,
245
+ timestamp: Date.now(),
246
+ };
247
+ }
248
+ }
249
+
250
+ // --- Re-export for convenience ---
251
+ export { subscribe as on, publish as emit };