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
@@ -13,7 +13,13 @@ import * as fs from "node:fs";
13
13
  import * as path from "node:path";
14
14
  import { AstGrepParser } from "./ast-grep-parser.js";
15
15
  import { AstGrepRuleManager } from "./ast-grep-rule-manager.js";
16
- import { type SgMatch, SgRunner } from "./sg-runner.js";
16
+ import type {
17
+ AstGrepDiagnostic,
18
+ AstGrepMatch,
19
+ RuleDescription,
20
+ SgMatch,
21
+ } from "./ast-grep-types.js";
22
+ import { SgRunner } from "./sg-runner.js";
17
23
 
18
24
  const _getExtensionDir = () => {
19
25
  if (typeof __dirname !== "undefined") {
@@ -22,39 +28,6 @@ const _getExtensionDir = () => {
22
28
  return ".";
23
29
  };
24
30
 
25
- // --- Types ---
26
-
27
- export interface RuleDescription {
28
- id: string;
29
- message: string;
30
- note?: string;
31
- severity: "error" | "warning" | "info" | "hint";
32
- grade?: number;
33
- }
34
-
35
- export interface AstGrepMatch {
36
- file: string;
37
- range: {
38
- start: { line: number; column: number };
39
- end: { line: number; column: number };
40
- };
41
- text: string;
42
- replacement?: string;
43
- }
44
-
45
- export interface AstGrepDiagnostic {
46
- line: number;
47
- column: number;
48
- endLine: number;
49
- endColumn: number;
50
- severity: "error" | "warning" | "info" | "hint";
51
- message: string;
52
- rule: string;
53
- ruleDescription?: RuleDescription;
54
- file: string;
55
- fix?: string;
56
- }
57
-
58
31
  // --- Client ---
59
32
 
60
33
  export class AstGrepClient {
@@ -140,7 +113,7 @@ export class AstGrepClient {
140
113
  ruleId: string,
141
114
  ruleYaml: string,
142
115
  timeout = 30000,
143
- ): any[] {
116
+ ): AstGrepMatch[] {
144
117
  if (!this.isAvailable()) return [];
145
118
  return this.runner.tempScan(dir, ruleId, ruleYaml, timeout);
146
119
  }
@@ -171,7 +144,7 @@ message: found
171
144
  return this.groupSimilarFunctions(matches);
172
145
  }
173
146
 
174
- private groupSimilarFunctions(matches: any[]): Array<{
147
+ private groupSimilarFunctions(matches: AstGrepMatch[]): Array<{
175
148
  pattern: string;
176
149
  functions: Array<{ name: string; file: string; line: number }>;
177
150
  }> {
@@ -289,7 +262,7 @@ message: found
289
262
  {
290
263
  encoding: "utf-8",
291
264
  timeout: 15000,
292
- shell: true,
265
+ shell: process.platform === "win32",
293
266
  },
294
267
  );
295
268
 
@@ -302,8 +275,10 @@ message: found
302
275
  (sev) => this.mapSeverity(sev),
303
276
  );
304
277
  return parser.parseOutput(output, absolutePath);
305
- } catch (err: any) {
306
- this.log(`Scan error: ${err.message}`);
278
+ } catch (err) {
279
+ this.log(
280
+ `Scan error: ${err instanceof Error ? err.message : String(err)}`,
281
+ );
307
282
  return [];
308
283
  }
309
284
  }
@@ -1,5 +1,5 @@
1
1
  import * as path from "node:path";
2
- import type { AstGrepDiagnostic, RuleDescription } from "./ast-grep-client.js";
2
+ import type { AstGrepDiagnostic, RuleDescription } from "./ast-grep-types.js";
3
3
 
4
4
  // New ast-grep JSON format
5
5
  export interface AstGrepJsonDiagnostic {
@@ -63,6 +63,14 @@ export class AstGrepRuleManager {
63
63
  const gradeMatch = content.match(/Grade\s+(\d+\.\d+)/i);
64
64
  if (gradeMatch)
65
65
  result.grade = parseFloat(gradeMatch[1]);
66
+ const fixMatch = content.match(/^fix:\s*\|?([\s\S]*?)(?=^\w|^rule:|Z)/m);
67
+ if (fixMatch) {
68
+ result.fix = fixMatch[1]
69
+ .split("\n")
70
+ .map((line) => line.replace(/^\s*\|?\s*/, ""))
71
+ .filter((line) => line.length > 0)
72
+ .join("\n");
73
+ }
66
74
  if (result.id && result.message) {
67
75
  return result;
68
76
  }
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import type { RuleDescription } from "./ast-grep-client.js";
3
+ import type { RuleDescription } from "./ast-grep-types.js";
4
4
 
5
5
  export class AstGrepRuleManager {
6
6
  private ruleDescriptions: Map<string, RuleDescription> | null = null;
@@ -79,6 +79,15 @@ export class AstGrepRuleManager {
79
79
  const gradeMatch = content.match(/Grade\s+(\d+\.\d+)/i);
80
80
  if (gradeMatch) result.grade = parseFloat(gradeMatch[1]);
81
81
 
82
+ const fixMatch = content.match(/^fix:\s*\|?([\s\S]*?)(?=^\w|^rule:|Z)/m);
83
+ if (fixMatch) {
84
+ result.fix = fixMatch[1]
85
+ .split("\n")
86
+ .map((line) => line.replace(/^\s*\|?\s*/, ""))
87
+ .filter((line) => line.length > 0)
88
+ .join("\n");
89
+ }
90
+
82
91
  if (result.id && result.message) {
83
92
  return result as RuleDescription;
84
93
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared types for ast-grep client, parser, and rule manager.
3
+ *
4
+ * Extracted to prevent circular dependencies between:
5
+ * - ast-grep-client.ts
6
+ * - ast-grep-parser.ts
7
+ * - ast-grep-rule-manager.ts
8
+ */
9
+ export {};
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Shared types for ast-grep client, parser, and rule manager.
3
+ *
4
+ * Extracted to prevent circular dependencies between:
5
+ * - ast-grep-client.ts
6
+ * - ast-grep-parser.ts
7
+ * - ast-grep-rule-manager.ts
8
+ */
9
+
10
+ // =============================================================================
11
+ // RULE DESCRIPTIONS
12
+ // =============================================================================
13
+
14
+ export interface RuleDescription {
15
+ id: string;
16
+ message: string;
17
+ note?: string;
18
+ fix?: string; // Suggested fix from rule
19
+ severity: "error" | "warning" | "info" | "hint";
20
+ grade?: number;
21
+ }
22
+
23
+ // =============================================================================
24
+ // MATCHES (from sg-runner)
25
+ // =============================================================================
26
+
27
+ export interface SgMatch {
28
+ file: string;
29
+ range: {
30
+ start: { line: number; column: number };
31
+ end: { line: number; column: number };
32
+ };
33
+ text: string;
34
+ replacement?: string;
35
+ }
36
+
37
+ export interface SgResult {
38
+ matches: SgMatch[];
39
+ error?: string;
40
+ }
41
+
42
+ // =============================================================================
43
+ // DIAGNOSTICS
44
+ // =============================================================================
45
+
46
+ export interface AstGrepMatch {
47
+ file: string;
48
+ range: {
49
+ start: { line: number; column: number };
50
+ end: { line: number; column: number };
51
+ };
52
+ text: string;
53
+ replacement?: string;
54
+ labels?: Array<{
55
+ range: {
56
+ start: { line: number; column: number };
57
+ end: { line: number; column: number };
58
+ };
59
+ }>;
60
+ }
61
+
62
+ export interface AstGrepDiagnostic {
63
+ line: number;
64
+ column: number;
65
+ endLine: number;
66
+ endColumn: number;
67
+ severity: "error" | "warning" | "info" | "hint";
68
+ message: string;
69
+ rule: string;
70
+ ruleDescription?: RuleDescription;
71
+ file: string;
72
+ fix?: string;
73
+ note?: string;
74
+ }
75
+
76
+ // =============================================================================
77
+ // JSON FORMAT (ast-grep CLI output)
78
+ // =============================================================================
79
+
80
+ export interface AstGrepJsonDiagnostic {
81
+ ruleId: string;
82
+ severity: string;
83
+ message: string;
84
+ note?: string;
85
+ labels: Array<{
86
+ text: string;
87
+ range: {
88
+ start: { line: number; column: number };
89
+ end: { line: number; column: number };
90
+ };
91
+ file?: string;
92
+ style: string;
93
+ }>;
94
+ // Legacy format support
95
+ Message?: { text: string };
96
+ Severity?: string;
97
+ spans?: Array<{
98
+ context: string;
99
+ range: {
100
+ start: { line: number; column: number };
101
+ end: { line: number; column: number };
102
+ };
103
+ file: string;
104
+ }>;
105
+ name?: string;
106
+ }
@@ -99,6 +99,15 @@ export function createAutoLoop(pi, config) {
99
99
  complete(ctx, "completed - no more work");
100
100
  return;
101
101
  }
102
+ // Check if agent is waiting for manual fixes (indicated in the prompt)
103
+ // If the last message says "When done, run..." we should NOT auto-continue
104
+ const awaitingManualFix = textContent.includes("When done, run");
105
+ if (awaitingManualFix) {
106
+ console.error("[auto-loop] Paused - awaiting agent manual fixes");
107
+ updateStatus(ctx);
108
+ // Don't send followUp - wait for agent to manually continue
109
+ return;
110
+ }
102
111
  // Check max iterations
103
112
  state.iteration++;
104
113
  if (state.iteration >= state.maxIterations) {
@@ -108,6 +117,7 @@ export function createAutoLoop(pi, config) {
108
117
  // Continue to next iteration - send command as follow-up
109
118
  updateStatus(ctx);
110
119
  const continueMsg = config.continuePrompt || `Run ${config.command} to continue.`;
120
+ console.error(`[auto-loop] Triggering iteration ${state.iteration + 1}/${state.maxIterations}: ${config.command}`);
111
121
  pi.sendUserMessage(`🔄 Auto-loop (${state.iteration + 1}/${state.maxIterations}): ${continueMsg}`, { deliverAs: "followUp" });
112
122
  });
113
123
  return {
@@ -20,7 +20,7 @@ export interface LoopConfig {
20
20
  name: string;
21
21
  /** Maximum iterations before stopping */
22
22
  maxIterations: number;
23
- /** Command to run for each iteration (e.g., "/lens-booboo-fix --loop") */
23
+ /** Command to run for each iteration */
24
24
  command: string;
25
25
  /** Patterns that indicate the loop should exit (e.g., "no more fixable issues") */
26
26
  exitPatterns: RegExp[];
@@ -159,6 +159,16 @@ export function createAutoLoop(
159
159
  return;
160
160
  }
161
161
 
162
+ // Check if agent is waiting for manual fixes (indicated in the prompt)
163
+ // If the last message says "When done, run..." we should NOT auto-continue
164
+ const awaitingManualFix = textContent.includes("When done, run");
165
+ if (awaitingManualFix) {
166
+ console.error("[auto-loop] Paused - awaiting agent manual fixes");
167
+ updateStatus(ctx);
168
+ // Don't send followUp - wait for agent to manually continue
169
+ return;
170
+ }
171
+
162
172
  // Check max iterations
163
173
  state.iteration++;
164
174
  if (state.iteration >= state.maxIterations) {
@@ -170,6 +180,9 @@ export function createAutoLoop(
170
180
  updateStatus(ctx);
171
181
  const continueMsg =
172
182
  config.continuePrompt || `Run ${config.command} to continue.`;
183
+ console.error(
184
+ `[auto-loop] Triggering iteration ${state.iteration + 1}/${state.maxIterations}: ${config.command}`,
185
+ );
173
186
  pi.sendUserMessage(
174
187
  `🔄 Auto-loop (${state.iteration + 1}/${state.maxIterations}): ${continueMsg}`,
175
188
  { deliverAs: "followUp" },
@@ -7,10 +7,10 @@
7
7
  * Requires: npm install @biomejs/biome (or npx @biomejs/biome)
8
8
  * Docs: https://biomejs.dev/
9
9
  */
10
- import { spawnSync } from "node:child_process";
11
10
  import * as fs from "node:fs";
12
11
  import * as path from "node:path";
13
12
  import { isFileKind } from "./file-kinds.js";
13
+ import { safeSpawn } from "./safe-spawn.js";
14
14
  // --- Client ---
15
15
  export class BiomeClient {
16
16
  constructor(verbose = false) {
@@ -26,10 +26,8 @@ export class BiomeClient {
26
26
  if (this.biomeAvailable !== null)
27
27
  return this.biomeAvailable;
28
28
  // Try npx biome first (works without global install)
29
- const result = spawnSync("npx", ["@biomejs/biome", "--version"], {
30
- encoding: "utf-8",
29
+ const result = safeSpawn("npx", ["@biomejs/biome", "--version"], {
31
30
  timeout: 10000,
32
- shell: true,
33
31
  });
34
32
  this.biomeAvailable = !result.error && result.status === 0;
35
33
  if (this.biomeAvailable) {
@@ -67,16 +65,14 @@ export class BiomeClient {
67
65
  if (!absolutePath)
68
66
  return [];
69
67
  try {
70
- const result = spawnSync("npx", [
68
+ const result = safeSpawn("npx", [
71
69
  "@biomejs/biome",
72
70
  "check",
73
71
  "--reporter=json",
74
72
  "--max-diagnostics=50",
75
73
  absolutePath,
76
74
  ], {
77
- encoding: "utf-8",
78
75
  timeout: 15000,
79
- shell: true,
80
76
  });
81
77
  // Biome exits 0 on success, 1 on issues found
82
78
  const output = result.stdout || "";
@@ -85,7 +81,7 @@ export class BiomeClient {
85
81
  return this.parseDiagnostics(output, absolutePath);
86
82
  }
87
83
  catch (err) {
88
- this.log(`Check error: ${err.message}`);
84
+ this.log(`Check error: ${err instanceof Error ? err.message : String(err)}`);
89
85
  return [];
90
86
  }
91
87
  }
@@ -102,10 +98,8 @@ export class BiomeClient {
102
98
  };
103
99
  const content = fs.readFileSync(absolutePath, "utf-8");
104
100
  try {
105
- const result = spawnSync("npx", ["@biomejs/biome", "format", "--write", absolutePath], {
106
- encoding: "utf-8",
101
+ const result = safeSpawn("npx", ["@biomejs/biome", "format", "--write", absolutePath], {
107
102
  timeout: 15000,
108
- shell: true,
109
103
  });
110
104
  if (result.error) {
111
105
  return { success: false, changed: false, error: result.error.message };
@@ -119,7 +113,11 @@ export class BiomeClient {
119
113
  return { success: true, changed };
120
114
  }
121
115
  catch (err) {
122
- return { success: false, changed: false, error: err.message };
116
+ return {
117
+ success: false,
118
+ changed: false,
119
+ error: err instanceof Error ? err.message : String(err),
120
+ };
123
121
  }
124
122
  }
125
123
  /**
@@ -140,16 +138,14 @@ export class BiomeClient {
140
138
  const beforeDiags = this.checkFile(filePath);
141
139
  const fixableCount = beforeDiags.filter((d) => d.fixable).length;
142
140
  // Apply fixes
143
- const result = spawnSync("npx", [
141
+ const result = safeSpawn("npx", [
144
142
  "@biomejs/biome",
145
143
  "check",
146
144
  "--write",
147
145
  "--unsafe", // Apply unsafe fixes too
148
146
  absolutePath,
149
147
  ], {
150
- encoding: "utf-8",
151
148
  timeout: 15000,
152
- shell: true,
153
149
  });
154
150
  if (result.error) {
155
151
  return {
@@ -167,7 +163,75 @@ export class BiomeClient {
167
163
  return { success: true, changed, fixed: fixableCount };
168
164
  }
169
165
  catch (err) {
170
- return { success: false, changed: false, fixed: 0, error: err.message };
166
+ return {
167
+ success: false,
168
+ changed: false,
169
+ fixed: 0,
170
+ error: err instanceof Error ? err.message : String(err),
171
+ };
172
+ }
173
+ }
174
+ /**
175
+ * Fix multiple files at once (much faster than file-by-file)
176
+ */
177
+ fixFiles(filePaths) {
178
+ if (!this.isAvailable()) {
179
+ return {
180
+ success: false,
181
+ fixed: 0,
182
+ changed: 0,
183
+ error: "Biome not available",
184
+ };
185
+ }
186
+ // Filter to existing files
187
+ const validFiles = filePaths
188
+ .map(f => path.resolve(f))
189
+ .filter(f => fs.existsSync(f));
190
+ if (validFiles.length === 0) {
191
+ return { success: true, fixed: 0, changed: 0 };
192
+ }
193
+ try {
194
+ // Count fixable issues before fixing
195
+ let totalFixable = 0;
196
+ for (const file of validFiles) {
197
+ const diags = this.checkFile(file);
198
+ totalFixable += diags.filter(d => d.fixable).length;
199
+ }
200
+ // Run biome once on all files - much faster than npx per file
201
+ const result = safeSpawn("npx", [
202
+ "@biomejs/biome",
203
+ "check",
204
+ "--write",
205
+ "--unsafe",
206
+ ...validFiles,
207
+ ], {
208
+ timeout: 60000, // Longer timeout for batch
209
+ });
210
+ if (result.error) {
211
+ return {
212
+ success: false,
213
+ fixed: 0,
214
+ changed: 0,
215
+ error: result.error.message,
216
+ };
217
+ }
218
+ // Count how many files actually changed
219
+ let changedCount = 0;
220
+ for (const file of validFiles) {
221
+ // We don't know exactly which files changed without re-reading,
222
+ // so we report total files processed
223
+ changedCount++;
224
+ }
225
+ this.log(`Fixed ${totalFixable} issue(s) in ${validFiles.length} file(s)`);
226
+ return { success: true, fixed: totalFixable, changed: changedCount };
227
+ }
228
+ catch (err) {
229
+ return {
230
+ success: false,
231
+ fixed: 0,
232
+ changed: 0,
233
+ error: err instanceof Error ? err.message : String(err),
234
+ };
171
235
  }
172
236
  }
173
237
  /**
@@ -213,10 +277,8 @@ export class BiomeClient {
213
277
  const content = fs.readFileSync(absolutePath, "utf-8");
214
278
  try {
215
279
  // Get formatted output without writing
216
- const result = spawnSync("npx", ["@biomejs/biome", "format", absolutePath], {
217
- encoding: "utf-8",
280
+ const result = safeSpawn("npx", ["@biomejs/biome", "format", absolutePath], {
218
281
  timeout: 15000,
219
- shell: true,
220
282
  });
221
283
  if (result.error || !result.stdout)
222
284
  return "";