pi-lens 2.2.9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +709 -519
  3. package/clients/__tests__/file-time.test.js +216 -0
  4. package/clients/__tests__/file-time.test.ts +276 -0
  5. package/clients/__tests__/format-service.test.js +245 -0
  6. package/clients/__tests__/format-service.test.ts +339 -0
  7. package/clients/__tests__/formatters.test.js +271 -0
  8. package/clients/__tests__/formatters.test.ts +401 -0
  9. package/clients/amain-types.js +164 -0
  10. package/clients/amain-types.ts +165 -0
  11. package/clients/architect-client.js +56 -12
  12. package/clients/architect-client.ts +81 -16
  13. package/clients/ast-grep-client.js +2 -2
  14. package/clients/ast-grep-client.ts +14 -39
  15. package/clients/ast-grep-parser.ts +1 -1
  16. package/clients/ast-grep-rule-manager.js +8 -0
  17. package/clients/ast-grep-rule-manager.ts +10 -1
  18. package/clients/ast-grep-types.js +9 -0
  19. package/clients/ast-grep-types.ts +106 -0
  20. package/clients/auto-loop.js +10 -0
  21. package/clients/auto-loop.ts +14 -1
  22. package/clients/biome-client.js +81 -19
  23. package/clients/biome-client.ts +103 -22
  24. package/clients/bus/bus.js +191 -0
  25. package/clients/bus/bus.ts +251 -0
  26. package/clients/bus/events.js +214 -0
  27. package/clients/bus/events.ts +279 -0
  28. package/clients/bus/index.js +8 -0
  29. package/clients/bus/index.ts +9 -0
  30. package/clients/bus/integration.js +158 -0
  31. package/clients/bus/integration.ts +214 -0
  32. package/clients/complexity-client.js +13 -7
  33. package/clients/complexity-client.ts +13 -7
  34. package/clients/config-validator.js +465 -0
  35. package/clients/config-validator.ts +558 -0
  36. package/clients/dependency-checker.js +4 -10
  37. package/clients/dependency-checker.ts +4 -10
  38. package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
  39. package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
  40. package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
  41. package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
  42. package/clients/dispatch/bus-dispatcher.js +177 -0
  43. package/clients/dispatch/bus-dispatcher.ts +251 -0
  44. package/clients/dispatch/dispatcher.edge.test.js +82 -0
  45. package/clients/dispatch/dispatcher.edge.test.ts +100 -0
  46. package/clients/dispatch/dispatcher.format.test.js +46 -0
  47. package/clients/dispatch/dispatcher.format.test.ts +58 -0
  48. package/clients/dispatch/dispatcher.inline.test.js +74 -0
  49. package/clients/dispatch/dispatcher.inline.test.ts +93 -0
  50. package/clients/dispatch/dispatcher.js +19 -53
  51. package/clients/dispatch/dispatcher.ts +20 -67
  52. package/clients/dispatch/plan.js +9 -4
  53. package/clients/dispatch/plan.ts +9 -4
  54. package/clients/dispatch/runners/architect.js +21 -7
  55. package/clients/dispatch/runners/architect.test.js +138 -0
  56. package/clients/dispatch/runners/architect.test.ts +162 -0
  57. package/clients/dispatch/runners/architect.ts +22 -7
  58. package/clients/dispatch/runners/ast-grep-napi.js +462 -0
  59. package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
  60. package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
  61. package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
  62. package/clients/dispatch/runners/ast-grep.js +62 -19
  63. package/clients/dispatch/runners/ast-grep.ts +70 -18
  64. package/clients/dispatch/runners/biome.js +29 -53
  65. package/clients/dispatch/runners/biome.ts +29 -63
  66. package/clients/dispatch/runners/config-validation.js +67 -0
  67. package/clients/dispatch/runners/config-validation.ts +82 -0
  68. package/clients/dispatch/runners/go-vet.js +4 -28
  69. package/clients/dispatch/runners/go-vet.ts +4 -32
  70. package/clients/dispatch/runners/index.js +30 -10
  71. package/clients/dispatch/runners/index.ts +30 -10
  72. package/clients/dispatch/runners/oxlint.js +141 -0
  73. package/clients/dispatch/runners/oxlint.test.js +230 -0
  74. package/clients/dispatch/runners/oxlint.test.ts +303 -0
  75. package/clients/dispatch/runners/oxlint.ts +175 -0
  76. package/clients/dispatch/runners/pyright.js +40 -70
  77. package/clients/dispatch/runners/pyright.test.js +16 -2
  78. package/clients/dispatch/runners/pyright.test.ts +14 -2
  79. package/clients/dispatch/runners/pyright.ts +48 -91
  80. package/clients/dispatch/runners/python-slop.js +97 -0
  81. package/clients/dispatch/runners/python-slop.test.js +203 -0
  82. package/clients/dispatch/runners/python-slop.test.ts +298 -0
  83. package/clients/dispatch/runners/python-slop.ts +124 -0
  84. package/clients/dispatch/runners/ruff.js +18 -71
  85. package/clients/dispatch/runners/ruff.ts +19 -79
  86. package/clients/dispatch/runners/rust-clippy.js +28 -32
  87. package/clients/dispatch/runners/rust-clippy.ts +29 -31
  88. package/clients/dispatch/runners/scan_codebase.test.js +89 -0
  89. package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
  90. package/clients/dispatch/runners/shellcheck.js +147 -0
  91. package/clients/dispatch/runners/shellcheck.test.js +98 -0
  92. package/clients/dispatch/runners/shellcheck.test.ts +129 -0
  93. package/clients/dispatch/runners/shellcheck.ts +188 -0
  94. package/clients/dispatch/runners/similarity.js +230 -0
  95. package/clients/dispatch/runners/similarity.ts +339 -0
  96. package/clients/dispatch/runners/spellcheck.js +106 -0
  97. package/clients/dispatch/runners/spellcheck.test.js +158 -0
  98. package/clients/dispatch/runners/spellcheck.test.ts +214 -0
  99. package/clients/dispatch/runners/spellcheck.ts +136 -0
  100. package/clients/dispatch/runners/tree-sitter.js +107 -0
  101. package/clients/dispatch/runners/tree-sitter.ts +135 -0
  102. package/clients/dispatch/runners/ts-lsp.js +104 -33
  103. package/clients/dispatch/runners/ts-lsp.ts +120 -38
  104. package/clients/dispatch/runners/ts-slop.js +113 -0
  105. package/clients/dispatch/runners/ts-slop.test.js +180 -0
  106. package/clients/dispatch/runners/ts-slop.test.ts +230 -0
  107. package/clients/dispatch/runners/ts-slop.ts +142 -0
  108. package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
  109. package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
  110. package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
  111. package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
  112. package/clients/dispatch/runners/utils.js +2 -4
  113. package/clients/dispatch/runners/utils.ts +2 -4
  114. package/clients/dispatch/types.ts +1 -1
  115. package/clients/dispatch/utils/format-utils.js +49 -0
  116. package/clients/dispatch/utils/format-utils.ts +60 -0
  117. package/clients/dogfood.test.js +201 -0
  118. package/clients/dogfood.test.ts +269 -0
  119. package/clients/file-time.js +152 -0
  120. package/clients/file-time.ts +208 -0
  121. package/clients/file-utils.js +40 -0
  122. package/clients/file-utils.ts +44 -0
  123. package/clients/fix-scanners.js +10 -20
  124. package/clients/fix-scanners.ts +10 -22
  125. package/clients/format-service.js +172 -0
  126. package/clients/format-service.ts +254 -0
  127. package/clients/formatters.js +435 -0
  128. package/clients/formatters.ts +508 -0
  129. package/clients/go-client.js +5 -14
  130. package/clients/go-client.ts +5 -13
  131. package/clients/installer/index.js +356 -0
  132. package/clients/installer/index.ts +426 -0
  133. package/clients/jscpd-client.js +11 -9
  134. package/clients/jscpd-client.ts +12 -8
  135. package/clients/knip-client.js +3 -7
  136. package/clients/knip-client.ts +3 -6
  137. package/clients/lsp/__tests__/client.test.js +325 -0
  138. package/clients/lsp/__tests__/client.test.ts +434 -0
  139. package/clients/lsp/__tests__/config.test.js +166 -0
  140. package/clients/lsp/__tests__/config.test.ts +209 -0
  141. package/clients/lsp/__tests__/error-recovery.test.js +213 -0
  142. package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
  143. package/clients/lsp/__tests__/integration.test.js +127 -0
  144. package/clients/lsp/__tests__/integration.test.ts +160 -0
  145. package/clients/lsp/__tests__/launch.test.js +260 -0
  146. package/clients/lsp/__tests__/launch.test.ts +329 -0
  147. package/clients/lsp/__tests__/server.test.js +259 -0
  148. package/clients/lsp/__tests__/server.test.ts +332 -0
  149. package/clients/lsp/__tests__/service.test.js +417 -0
  150. package/clients/lsp/__tests__/service.test.ts +499 -0
  151. package/clients/lsp/client.js +235 -0
  152. package/clients/lsp/client.ts +328 -0
  153. package/clients/lsp/config.js +115 -0
  154. package/clients/lsp/config.ts +149 -0
  155. package/clients/lsp/index.js +222 -0
  156. package/clients/lsp/index.ts +280 -0
  157. package/clients/lsp/installer/index.js +391 -0
  158. package/clients/lsp/interactive-install.js +210 -0
  159. package/clients/lsp/interactive-install.ts +251 -0
  160. package/clients/lsp/language.js +170 -0
  161. package/clients/lsp/language.ts +216 -0
  162. package/clients/lsp/launch.js +174 -0
  163. package/clients/lsp/launch.ts +240 -0
  164. package/clients/lsp/lsp/launch.js +116 -0
  165. package/clients/lsp/lsp/server.js +532 -0
  166. package/clients/lsp/lsp-index.js +10 -0
  167. package/clients/lsp/lsp-index.ts +11 -0
  168. package/clients/lsp/path-utils.js +48 -0
  169. package/clients/lsp/path-utils.ts +52 -0
  170. package/clients/lsp/server.js +615 -0
  171. package/clients/lsp/server.ts +800 -0
  172. package/clients/lsp/test-py-spawn/requirements.txt +1 -0
  173. package/clients/lsp/test-py-spawn/test.py +3 -0
  174. package/clients/lsp/test-py-svc/requirements.txt +1 -0
  175. package/clients/lsp/test-py-svc/test.py +3 -0
  176. package/clients/lsp/test-python-project/requirements.txt +1 -0
  177. package/clients/lsp/test-python-project/test.py +5 -0
  178. package/clients/metrics-history.js +2 -2
  179. package/clients/metrics-history.ts +2 -2
  180. package/clients/production-readiness.js +522 -0
  181. package/clients/production-readiness.ts +556 -0
  182. package/clients/project-index.js +255 -0
  183. package/clients/project-index.ts +383 -0
  184. package/clients/project-metadata.js +531 -0
  185. package/clients/project-metadata.ts +624 -0
  186. package/clients/ruff-client.js +56 -16
  187. package/clients/ruff-client.ts +72 -15
  188. package/clients/runner-tracker.js +152 -0
  189. package/clients/runner-tracker.ts +213 -0
  190. package/clients/rust-client.js +4 -11
  191. package/clients/rust-client.ts +5 -11
  192. package/clients/safe-spawn.js +96 -0
  193. package/clients/safe-spawn.ts +128 -0
  194. package/clients/scan-architectural-debt.js +3 -6
  195. package/clients/scan-architectural-debt.ts +3 -6
  196. package/clients/scan-utils.js +5 -20
  197. package/clients/scan-utils.ts +5 -29
  198. package/clients/secrets-scanner.js +3 -17
  199. package/clients/secrets-scanner.ts +4 -20
  200. package/clients/services/__tests__/effect-integration.test.js +86 -0
  201. package/clients/services/__tests__/effect-integration.test.ts +111 -0
  202. package/clients/services/effect-integration.js +194 -0
  203. package/clients/services/effect-integration.ts +268 -0
  204. package/clients/services/index.js +7 -0
  205. package/clients/services/index.ts +8 -0
  206. package/clients/services/runner-service.js +105 -0
  207. package/clients/services/runner-service.ts +179 -0
  208. package/clients/sg-runner.js +87 -13
  209. package/clients/sg-runner.ts +97 -13
  210. package/clients/state-matrix.js +160 -0
  211. package/clients/state-matrix.ts +202 -0
  212. package/clients/subprocess-client.js +10 -9
  213. package/clients/subprocess-client.ts +10 -8
  214. package/clients/test-runner-client.js +3 -7
  215. package/clients/test-runner-client.ts +3 -6
  216. package/clients/tool-availability.js +4 -10
  217. package/clients/tool-availability.ts +4 -9
  218. package/clients/tree-sitter-client.js +564 -0
  219. package/clients/tree-sitter-client.ts +797 -0
  220. package/clients/tree-sitter-query-loader.js +355 -0
  221. package/clients/tree-sitter-query-loader.ts +425 -0
  222. package/clients/type-coverage-client.js +3 -7
  223. package/clients/type-coverage-client.ts +3 -6
  224. package/clients/typescript-client.codefix.test.js +157 -0
  225. package/clients/typescript-client.codefix.test.ts +186 -0
  226. package/clients/typescript-client.js +43 -0
  227. package/clients/typescript-client.ts +98 -0
  228. package/commands/booboo.js +799 -219
  229. package/commands/booboo.ts +1004 -225
  230. package/commands/clients/ast-grep-client.js +250 -0
  231. package/commands/clients/ast-grep-parser.js +86 -0
  232. package/commands/clients/ast-grep-rule-manager.js +91 -0
  233. package/commands/clients/ast-grep-types.js +9 -0
  234. package/commands/clients/biome-client.js +380 -0
  235. package/commands/clients/complexity-client.js +667 -0
  236. package/commands/clients/file-kinds.js +177 -0
  237. package/commands/clients/file-utils.js +40 -0
  238. package/commands/clients/jscpd-client.js +169 -0
  239. package/commands/clients/knip-client.js +211 -0
  240. package/commands/clients/ruff-client.js +297 -0
  241. package/commands/clients/safe-spawn.js +88 -0
  242. package/commands/clients/scan-utils.js +83 -0
  243. package/commands/clients/sg-runner.js +190 -0
  244. package/commands/clients/types.js +11 -0
  245. package/commands/clients/typescript-client.js +505 -0
  246. package/commands/fix-from-booboo.js +398 -0
  247. package/commands/fix-from-booboo.ts +485 -0
  248. package/commands/fix-simplified.js +618 -0
  249. package/commands/fix-simplified.ts +768 -0
  250. package/commands/rate.js +10 -14
  251. package/commands/rate.ts +9 -16
  252. package/default-architect.yaml +59 -15
  253. package/index.ts +342 -429
  254. package/package.json +16 -3
  255. package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
  256. package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
  257. package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
  258. package/rules/python-slop-rules/.sgconfig.yml +4 -0
  259. package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
  260. package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
  261. package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
  262. package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
  263. package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
  264. package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
  265. package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
  266. package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
  267. package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
  268. package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
  269. package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
  270. package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
  271. package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
  272. package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
  273. package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
  274. package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
  275. package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
  276. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
  277. package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
  278. package/rules/ts-slop-rules/.sgconfig.yml +4 -0
  279. package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
  280. package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
  281. package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
  282. package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
  283. package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
  284. package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
  285. package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
  286. package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
  287. package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
  288. package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
  289. package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
  290. package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
  291. package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
  292. package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
  293. package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
  294. package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
  295. package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
  296. package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
  297. package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
  298. package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
  299. package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
  300. package/skills/ast-grep/SKILL.md +182 -0
  301. package/clients/dispatch/runners/secrets.js +0 -109
  302. package/commands/fix.js +0 -244
  303. package/commands/fix.ts +0 -373
  304. package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
@@ -0,0 +1,618 @@
1
+ /**
2
+ * Simplified Fix command for pi-lens
3
+ *
4
+ * One-shot code review & cleanup - no loop, no session.
5
+ * Replaces the complex auto-loop with a simple one-shot approach.
6
+ */
7
+ import * as childProcess from "node:child_process";
8
+ import * as nodeFs from "node:fs";
9
+ import * as path from "node:path";
10
+ import { safeSpawn } from "../clients/safe-spawn.js";
11
+ import { CacheManager } from "../clients/cache-manager.js";
12
+ import { detectFileKind } from "../clients/file-kinds.js";
13
+ import { EXCLUDED_DIRS, isTestFile } from "../clients/file-utils.js";
14
+ // --- Ignore file management ---
15
+ const IGNORE_FILE = ".pi-lens/.booboo-ignore";
16
+ function loadIgnoreFile(cwd) {
17
+ try {
18
+ const content = nodeFs.readFileSync(path.join(cwd, IGNORE_FILE), "utf-8");
19
+ return content
20
+ .split("\n")
21
+ .filter((l) => l.trim() && !l.startsWith("#"))
22
+ .map((l) => {
23
+ const [pattern, reason] = l.split(" #").map((s) => s.trim());
24
+ return { pattern, addedAt: new Date().toISOString(), reason };
25
+ });
26
+ }
27
+ catch {
28
+ return [];
29
+ }
30
+ }
31
+ function saveIgnoreFile(cwd, entries) {
32
+ const content = "# /lens-booboo-fix ignore patterns\n" +
33
+ "# Format: type:file:line # optional reason\n" +
34
+ entries.map((e) => `${e.pattern}${e.reason ? " # " + e.reason : ""}`).join("\n");
35
+ nodeFs.mkdirSync(path.dirname(path.join(cwd, IGNORE_FILE)), { recursive: true });
36
+ nodeFs.writeFileSync(path.join(cwd, IGNORE_FILE), content, "utf-8");
37
+ }
38
+ function isIgnored(pattern, entries) {
39
+ return entries.some((e) => {
40
+ // Simple glob matching
41
+ const regex = new RegExp(e.pattern.replace(/\*/g, ".*").replace(/:/g, "\\:"));
42
+ return regex.test(pattern);
43
+ });
44
+ }
45
+ // --- Get changed files ---
46
+ function getChangedFiles(cwd) {
47
+ const files = new Set();
48
+ // 1. From git diff
49
+ try {
50
+ const gitResult = childProcess.spawnSync("git", ["diff", "HEAD", "--name-only", "--diff-filter=ACM"], { encoding: "utf-8", cwd });
51
+ if (gitResult.status === 0) {
52
+ gitResult.stdout.split("\n").forEach((f) => {
53
+ if (f.trim())
54
+ files.add(path.join(cwd, f.trim()));
55
+ });
56
+ }
57
+ }
58
+ catch {
59
+ // Git not available or not a repo
60
+ }
61
+ // 2. From cache-manager turn state (for files edited in this session)
62
+ const cacheManager = new CacheManager();
63
+ const turnState = cacheManager.readTurnState(cwd);
64
+ for (const file of Object.keys(turnState.files)) {
65
+ files.add(path.join(cwd, file));
66
+ }
67
+ return Array.from(files).filter((f) => nodeFs.existsSync(f));
68
+ }
69
+ // --- File filtering ---
70
+ /**
71
+ * Check if file should be scanned based on exclusion rules:
72
+ * - Test files (.test.ts, .spec.ts, etc.)
73
+ * - Excluded directories (node_modules, dist, etc.)
74
+ * - Hidden directories (.git, .pi-lens, etc.)
75
+ */
76
+ function shouldScanFile(filePath) {
77
+ const normalized = filePath.replace(/\\/g, "/");
78
+ // Skip test files
79
+ if (isTestFile(normalized)) {
80
+ return false;
81
+ }
82
+ // Skip excluded directories
83
+ for (const dir of EXCLUDED_DIRS) {
84
+ if (normalized.includes(`/${dir}/`) || normalized.includes(`\\${dir}\\`)) {
85
+ return false;
86
+ }
87
+ }
88
+ return true;
89
+ }
90
+ // --- Issue detection (AST-grep only - structural issues) ---
91
+ async function detectStructuralIssues(files, cwd, ignoreEntries, clients) {
92
+ const issues = [];
93
+ // Only scan files that pass exclusion filters
94
+ const filesToScan = files.filter(shouldScanFile);
95
+ // Run ast-grep for structural issues (these need human decisions)
96
+ await Promise.all(filesToScan.map(async (file) => {
97
+ const relPath = path.relative(cwd, file);
98
+ const kind = detectFileKind(file);
99
+ // Python files: scan for slop patterns
100
+ if (kind === "python" && clients.astGrep.isAvailable()) {
101
+ const slopMatches = scanPythonSlop(file, cwd);
102
+ for (const m of slopMatches) {
103
+ const id = `python-slop:${relPath}:${m.line}`;
104
+ if (isIgnored(id, ignoreEntries))
105
+ continue;
106
+ issues.push({
107
+ file: relPath,
108
+ line: m.line,
109
+ category: "quality",
110
+ rule: m.rule,
111
+ message: `[slop] ${m.message}`,
112
+ fixable: m.fixable,
113
+ autoFix: m.fix,
114
+ severity: m.severity,
115
+ });
116
+ }
117
+ }
118
+ // JavaScript/TypeScript files: scan for slop patterns first
119
+ if (kind === "jsts" && clients.astGrep.isAvailable()) {
120
+ const slopMatches = scanTsSlop(file, cwd);
121
+ for (const m of slopMatches) {
122
+ const id = `ts-slop:${relPath}:${m.line}`;
123
+ if (isIgnored(id, ignoreEntries))
124
+ continue;
125
+ issues.push({
126
+ file: relPath,
127
+ line: m.line,
128
+ category: "quality",
129
+ rule: m.rule,
130
+ message: `[slop] ${m.message}`,
131
+ fixable: m.fixable,
132
+ autoFix: m.fix,
133
+ severity: m.severity,
134
+ });
135
+ }
136
+ // Also run existing AST-grep structural rules
137
+ const matches = clients.astGrep.scanFile(file);
138
+ for (const m of matches) {
139
+ const id = `ast:${relPath}:${m.line}`;
140
+ if (isIgnored(id, ignoreEntries))
141
+ continue;
142
+ const isSafeFix = isSafeAstGrepFix(m.rule);
143
+ issues.push({
144
+ file: relPath,
145
+ line: m.line,
146
+ category: "quality",
147
+ rule: m.rule,
148
+ message: m.message,
149
+ fixable: !!(m.ruleDescription?.fix || isSafeFix),
150
+ autoFix: m.ruleDescription?.fix,
151
+ severity: m.severity === "error" ? "error" : "warning",
152
+ });
153
+ }
154
+ }
155
+ }));
156
+ return prioritizeIssues(issues);
157
+ }
158
+ function scanPythonSlop(filePath, cwd) {
159
+ const matches = [];
160
+ // Find Python slop config
161
+ const configPaths = [
162
+ path.join(cwd, "rules/python-slop-rules/.sgconfig.yml"),
163
+ path.join(cwd, "../rules/python-slop-rules/.sgconfig.yml"),
164
+ path.join(process.cwd(), "rules/python-slop-rules/.sgconfig.yml"),
165
+ ];
166
+ let configPath;
167
+ for (const p of configPaths) {
168
+ if (nodeFs.existsSync(p)) {
169
+ configPath = p;
170
+ break;
171
+ }
172
+ }
173
+ if (!configPath)
174
+ return matches;
175
+ try {
176
+ const result = safeSpawn("npx", ["sg", "scan", "--config", configPath, "--json", filePath], {
177
+ timeout: 30000,
178
+ });
179
+ const output = result.stdout || result.stderr || "";
180
+ if (!output.trim())
181
+ return matches;
182
+ const parsed = JSON.parse(output);
183
+ if (Array.isArray(parsed)) {
184
+ for (const item of parsed) {
185
+ const weight = item.metadata?.weight || 3;
186
+ matches.push({
187
+ line: (item.range?.start?.line || 0) + 1,
188
+ rule: item.rule || "slop",
189
+ message: item.message || "",
190
+ severity: weight >= 4 ? "error" : "warning",
191
+ fixable: !!item.replacement,
192
+ fix: item.replacement,
193
+ });
194
+ }
195
+ }
196
+ }
197
+ catch {
198
+ // Failed to scan, return empty
199
+ }
200
+ return matches;
201
+ }
202
+ function scanTsSlop(filePath, cwd) {
203
+ const matches = [];
204
+ // Find TypeScript slop config
205
+ const configPaths = [
206
+ path.join(cwd, "rules/ts-slop-rules/.sgconfig.yml"),
207
+ path.join(cwd, "../rules/ts-slop-rules/.sgconfig.yml"),
208
+ path.join(process.cwd(), "rules/ts-slop-rules/.sgconfig.yml"),
209
+ ];
210
+ let configPath;
211
+ for (const p of configPaths) {
212
+ if (nodeFs.existsSync(p)) {
213
+ configPath = p;
214
+ break;
215
+ }
216
+ }
217
+ if (!configPath)
218
+ return matches;
219
+ try {
220
+ const result = safeSpawn("npx", ["sg", "scan", "--config", configPath, "--json", filePath], {
221
+ timeout: 30000,
222
+ });
223
+ const output = result.stdout || result.stderr || "";
224
+ if (!output.trim())
225
+ return matches;
226
+ const parsed = JSON.parse(output);
227
+ if (Array.isArray(parsed)) {
228
+ for (const item of parsed) {
229
+ const weight = item.metadata?.weight || 3;
230
+ matches.push({
231
+ line: (item.range?.start?.line || 0) + 1,
232
+ rule: item.rule || "slop",
233
+ message: item.message || "",
234
+ severity: weight >= 4 ? "error" : "warning",
235
+ fixable: !!item.replacement,
236
+ fix: item.replacement,
237
+ });
238
+ }
239
+ }
240
+ }
241
+ catch {
242
+ // Failed to scan, return empty
243
+ }
244
+ return matches;
245
+ }
246
+ // --- Biome auto-fix (silent - no reporting) ---
247
+ async function autoFixWithBiome(files, clients) {
248
+ if (!clients.biome.isAvailable())
249
+ return 0;
250
+ let fixedCount = 0;
251
+ // Only fix files that pass exclusion filters and are supported by biome
252
+ const filesToFix = files.filter((f => shouldScanFile(f) && clients.biome.isSupportedFile(f)));
253
+ if (filesToFix.length === 0)
254
+ return 0;
255
+ // Run biome with --write --unsafe to auto-fix all issues
256
+ // We run biome once on all files for efficiency
257
+ const biomeArgs = ["check", "--write", "--unsafe", ...filesToFix];
258
+ try {
259
+ const result = safeSpawn("biome", biomeArgs, {
260
+ timeout: 60000,
261
+ });
262
+ // Parse output to count fixed issues
263
+ // Biome outputs formatted files count
264
+ const output = result.stdout || result.stderr || "";
265
+ const fixedMatch = output.match(/Fixed (\d+) file/);
266
+ if (fixedMatch) {
267
+ fixedCount = parseInt(fixedMatch[1], 10);
268
+ }
269
+ // Also count individual fixes from the output
270
+ const fixMatches = output.match(/✓|✅|Fixed/g);
271
+ if (fixMatches && fixedCount === 0) {
272
+ fixedCount = fixMatches.length;
273
+ }
274
+ }
275
+ catch {
276
+ // Biome not available or failed
277
+ }
278
+ return fixedCount;
279
+ }
280
+ function getLanguageFromKind(kind) {
281
+ if (kind === "typescript")
282
+ return "typescript";
283
+ if (kind === "typescript-tsx")
284
+ return "typescript";
285
+ if (kind === "javascript")
286
+ return "javascript";
287
+ if (kind === "javascript-jsx")
288
+ return "javascript";
289
+ if (kind === "python")
290
+ return "python";
291
+ if (kind === "go")
292
+ return "go";
293
+ if (kind === "rust")
294
+ return "rust";
295
+ return undefined;
296
+ }
297
+ function isTypeScriptKind(kind) {
298
+ return kind === "typescript" || kind === "typescript-tsx" || kind === "javascript" || kind === "javascript-jsx";
299
+ }
300
+ function isSafeAstGrepFix(rule) {
301
+ const safeRules = [
302
+ "strict-equality",
303
+ "strict-inequality",
304
+ "no-debugger",
305
+ "no-array-constructor",
306
+ "no-extra-boolean-cast",
307
+ ];
308
+ return safeRules.includes(rule);
309
+ }
310
+ function prioritizeIssues(issues) {
311
+ const severityOrder = { error: 0, warning: 1, hint: 2 };
312
+ const categoryOrder = { types: 0, architecture: 1, quality: 2, reuse: 3 };
313
+ return issues.sort((a, b) => {
314
+ const sevDiff = severityOrder[a.severity] - severityOrder[b.severity];
315
+ if (sevDiff !== 0)
316
+ return sevDiff;
317
+ return categoryOrder[a.category] - categoryOrder[b.category];
318
+ });
319
+ }
320
+ // --- Safe ast-grep fixes (for issues that can't be handled by biome) ---
321
+ async function applySafeAstGrepFixes(issues, cwd) {
322
+ let fixed = 0;
323
+ for (const issue of issues) {
324
+ if (!issue.fixable || !issue.autoFix)
325
+ continue;
326
+ // Only apply very safe fixes that biome doesn't handle
327
+ if (!isSafeAstGrepFix(issue.rule))
328
+ continue;
329
+ const filePath = path.join(cwd, issue.file);
330
+ if (!nodeFs.existsSync(filePath))
331
+ continue;
332
+ const content = nodeFs.readFileSync(filePath, "utf-8");
333
+ const lines = content.split("\n");
334
+ // Simple line-based replacements (very conservative)
335
+ if (issue.line && issue.line <= lines.length) {
336
+ const lineIndex = issue.line - 1;
337
+ const originalLine = lines[lineIndex];
338
+ // Apply specific safe transformations
339
+ let newLine = originalLine;
340
+ if (issue.rule === "strict-equality") {
341
+ newLine = originalLine.replace(/==([^=])/g, "===$1");
342
+ newLine = newLine.replace(/!([^=])/g, "!==$1");
343
+ }
344
+ else if (issue.rule === "no-debugger") {
345
+ if (originalLine.trim() === "debugger;") {
346
+ newLine = "";
347
+ }
348
+ }
349
+ if (newLine !== originalLine) {
350
+ lines[lineIndex] = newLine;
351
+ nodeFs.writeFileSync(filePath, lines.join("\n"), "utf-8");
352
+ fixed++;
353
+ }
354
+ }
355
+ }
356
+ return fixed;
357
+ }
358
+ // --- Action Prompt Generation (imperative - tells AI to fix) ---
359
+ function generateActionPrompt(issues, fixed, files, cwd) {
360
+ // Sort issues: errors first, then by severity
361
+ const sortedIssues = [...issues].sort((a, b) => {
362
+ if (a.severity === "error" && b.severity !== "error")
363
+ return -1;
364
+ if (b.severity === "error" && a.severity !== "error")
365
+ return 1;
366
+ return 0;
367
+ });
368
+ let prompt = `🔧 /LENS-BOOBOO-FIX: ${issues.length} structural issue(s) found in changed files.\n\n`;
369
+ if (fixed > 0) {
370
+ prompt += `✅ Already auto-fixed ${fixed} mechanical issue(s) with Biome.\n\n`;
371
+ }
372
+ prompt += `**ACTION REQUIRED:** Fix the following structural issues using the edit tool.\n`;
373
+ prompt += `Focus on error severity first, then warnings.\n\n`;
374
+ // List issues with specific actions
375
+ const errors = sortedIssues.filter(i => i.severity === "error");
376
+ const warnings = sortedIssues.filter(i => i.severity !== "error");
377
+ if (errors.length > 0) {
378
+ prompt += `## 🔴 ERRORS (fix first):\n\n`;
379
+ for (let i = 0; i < Math.min(errors.length, 10); i++) {
380
+ const issue = errors[i];
381
+ prompt += `${i + 1}. **${issue.file}:${issue.line}** — ${issue.rule}\n`;
382
+ prompt += ` ${issue.message}\n`;
383
+ prompt += ` **Action:** ${getFixInstruction(issue)}\n\n`;
384
+ }
385
+ if (errors.length > 10) {
386
+ prompt += ` ... and ${errors.length - 10} more errors\n\n`;
387
+ }
388
+ }
389
+ if (warnings.length > 0) {
390
+ prompt += `## 🟡 WARNINGS (fix if time):\n\n`;
391
+ for (let i = 0; i < Math.min(warnings.length, 10); i++) {
392
+ const issue = warnings[i];
393
+ prompt += `${i + 1}. **${issue.file}:${issue.line}** — ${issue.rule}\n`;
394
+ prompt += ` ${issue.message.substring(0, 80)}${issue.message.length > 80 ? "..." : ""}\n`;
395
+ prompt += ` **Action:** ${getFixInstruction(issue)}\n\n`;
396
+ }
397
+ if (warnings.length > 10) {
398
+ prompt += ` ... and ${warnings.length - 10} more warnings\n\n`;
399
+ }
400
+ }
401
+ prompt += `**IMPORTANT GUIDANCE:**\n\n`;
402
+ prompt += `These structural issues fall into three categories:\n`;
403
+ prompt += `1. **Quick fixes** (do these now): no-debugger, strict-equality, empty-catch blocks\n`;
404
+ prompt += `2. **False positives** (mark and skip): The rule is technically wrong for this context\n`;
405
+ prompt += `3. **Deep architectural issues** (defer to /lens-booboo-refactor): large-class, long-method requiring major restructuring\n\n`;
406
+ prompt += `**Your approach:**\n`;
407
+ prompt += `- 🔧 Quick fixes: Fix immediately (1-2 edits max per issue)\n`;
408
+ prompt += `- 🤔 Evaluate: Fix if trivial, mark as false positive if the rule is wrong\n`;
409
+ prompt += `- 🏗️ Defer: Skip and use "/lens-booboo-refactor" for deep architectural work\n`;
410
+ prompt += `- Mark false positives with: /lens-booboo-fix --false-positive "rule:file:line"\n`;
411
+ prompt += `- Focus on errors first, then quick fix warnings\n\n`;
412
+ prompt += `**ANTI-SLOP REMINDER:**\n\n`;
413
+ prompt += `When fixing **Python** code, avoid these common slop patterns:\n`;
414
+ prompt += `- Use \`enumerate()\` instead of \`range(len(x))\`\n`;
415
+ prompt += `- Use built-in \`min()\`/\`max()\` instead of manual if/else comparisons\n`;
416
+ prompt += `- Use chained comparisons (\`a < b < c\`) instead of boolean chains\n`;
417
+ prompt += `- Avoid defensive \`if x is None: return None\` guards without good reason\n`;
418
+ prompt += `- Use \`list(iterable)\` instead of \`[x for x in iterable]\` ceremony\n`;
419
+ prompt += `- Prefer truthiness (\`if arr:\`) over \`len(arr) > 0\`\n\n`;
420
+ prompt += `When fixing **TypeScript/JavaScript** code, avoid these common slop patterns:\n`;
421
+ prompt += `- Use \`for...of\` or \`.forEach()\` instead of \`for (let i = 0; i < arr.length; i++)\`\n`;
422
+ prompt += `- Use \`Math.min()\`/\`Math.max()\` instead of manual comparisons\n`;
423
+ prompt += `- Use optional chaining (\`obj?.prop?.nested\`) instead of guard chains\n`;
424
+ prompt += `- Use nullish coalescing (\`x ?? default\`) instead of ternary checks\n`;
425
+ prompt += `- Use \`arr.includes(x)\` instead of \`arr.indexOf(x) !== -1\`\n`;
426
+ prompt += `- Prefer truthiness (\`if (arr)\`) over \`if (arr.length > 0)\`\n`;
427
+ prompt += `- Use spread \`[...arr]\` instead of \`arr.slice()\` for copying\n\n`;
428
+ prompt += `**Only mark as false positive if the rule is truly incorrect. Defer architectural issues to /lens-booboo-refactor, don't mark them as false positives.**`;
429
+ return prompt;
430
+ }
431
+ function getFixInstruction(issue) {
432
+ // Map rules to specific fix instructions
433
+ // 🔧 = quick fix, do now
434
+ // 🤔 = evaluate: fix if trivial, mark FP if rule is wrong
435
+ // 🏗️ = defer to /lens-booboo-refactor for architectural refactoring
436
+ const instructions = {
437
+ // TypeScript/JavaScript rules
438
+ "long-method": "🏗️ DEFER to /lens-booboo-refactor (don't mark as FP)",
439
+ "large-class": "🏗️ DEFER to /lens-booboo-refactor (architectural decision)",
440
+ "empty-catch": "🔧 Add proper error handling or remove the catch",
441
+ "long-parameter-list": "🤔 Use an options object or mark FP if intentional",
442
+ "nested-ternary": "🔧 Convert to if/else or extract to named variables",
443
+ "no-debugger": "🔧 Remove the debugger statement",
444
+ "no-console-log": "🔧 Use a proper logger or remove the log",
445
+ "strict-equality": "🔧 Change == to === and != to !==",
446
+ "no-throw-string": "🔧 Throw new Error() instead of a string",
447
+ "no-return-await": "🔧 Remove unnecessary await",
448
+ "switch-without-default": "🔧 Add a default case",
449
+ "no-shadow": "🤔 Rename variable to avoid shadowing",
450
+ "no-non-null-assertion": "🤔 Add proper null check or mark FP if intentional",
451
+ "no-as-any": "🤔 Replace with proper type or mark FP if intentional",
452
+ "complex-conditional": "🤔 Extract to named boolean variables",
453
+ "deep-nesting": "🤔 Extract nested blocks into functions",
454
+ // Python slop rules
455
+ "for-range-len": "🔧 Use enumerate() instead of range(len(x))",
456
+ "range-len-pattern": "🔧 Use enumerate() instead of range(len(x))",
457
+ "range-len-antipattern": "🔧 Use enumerate() or direct iteration",
458
+ "manual-min-max": "🔧 Use built-in min() or max()",
459
+ "boolean-return-if-else": "🔧 Simplify to return bool(condition)",
460
+ "chained-comparison-opportunity": "🔧 Use chained comparison (a < b < c)",
461
+ "json-dumps-then-loads": "🔧 Remove redundant round-trip",
462
+ "pointless-bool-cast": "🔧 Remove bool() wrapper",
463
+ "pointless-lambda-call": "🔧 Execute code directly, remove lambda",
464
+ "ternary-same-value": "🔧 Remove condition, value is same both ways",
465
+ "empty-init": "🔧 Remove empty __init__ or add pass",
466
+ "explicit-bool-cast": "🔧 Remove unnecessary bool() cast",
467
+ "guard-return-none": "🤔 Consider removing - may be overly defensive",
468
+ "if-none-raise": "🤔 Consider EAFP pattern or keep if needed",
469
+ "int-float-coerce": "🔧 Parse directly with validation",
470
+ "len-comparison": "🔧 Use truthiness: if arr: instead of len(arr) > 0",
471
+ "manual-dict-setdefault": "🔧 Use dict.setdefault()",
472
+ "multiple-isinstance-or": "🔧 Use isinstance(x, (A, B)) tuple",
473
+ "redundant-bool-ternary": "🔧 Use bool(x) directly",
474
+ "redundant-list-comprehension": "🔧 Use list(iterable) directly",
475
+ "redundant-return-none": "🔧 Remove explicit return None",
476
+ "set-literal-list": "🔧 Use set literal {x, y}",
477
+ "unnecessary-cast-str": "🔧 Remove str() wrapper",
478
+ "unnecessary-elif": "🔧 Use if instead of elif after return",
479
+ "unnecessary-else-raise": "🔧 Remove redundant else after raise",
480
+ "unnecessary-lambda": "🔧 Pass function directly, remove lambda",
481
+ "verbose-none-default": "🔧 Use 'x = x or default'",
482
+ "type-equality": "🔧 Use isinstance() instead of type()",
483
+ "dict-str-any": "🤔 Consider tightening type to avoid Any",
484
+ "list-any": "🤔 Consider tightening element type",
485
+ "verbose-list-append-loop": "🔧 Use list comprehension [x for x in items]",
486
+ "set-add-loop": "🔧 Use set comprehension {x for x in items}",
487
+ "manual-dict-get-assign": "🔧 Use dict.get(key, default)",
488
+ "list-extend-from-loop": "🔧 Use list.extend(iterable) directly",
489
+ "join-list-comprehension": "🔧 Use generator expression instead",
490
+ "manual-sum-loop": "🔧 Use sum(iterable) instead of manual loop",
491
+ "membership-test-list-literal": "🔧 Use set literal for O(1) lookup",
492
+ "deep-dict-access": "🤔 Extract to helper or use dataclass",
493
+ "long-tuple-unpacking": "🤔 Use namedtuple or dataclass",
494
+ "chained-dict-get": "🤔 Consider walrus operator or get() with default",
495
+ "nested-attribute-guard-chain": "🤔 Use getattr with default",
496
+ "isinstance-return-ladder": "🏗️ Consider dispatch table or polymorphism",
497
+ "manual-str-join": "🔧 Use ''.join(iterable)",
498
+ "comprehension-used-but-ignored-result": "🔧 Use for loop or fix logic",
499
+ "duplicated-if-condition": "🔧 Remove duplicate elif condition",
500
+ // TypeScript/JavaScript slop rules
501
+ "ts-for-index-length": "🔧 Use for-of or .forEach() instead of index loop",
502
+ "ts-while-index-length": "🔧 Use for-of or array methods instead of while loop",
503
+ "ts-manual-min-max": "🔧 Use Math.min() or Math.max()",
504
+ "ts-array-map-ceremony": "🔧 Use array directly, remove unnecessary mapping",
505
+ "ts-boolean-return-if-else": "🔧 Simplify to return !!condition or Boolean(condition)",
506
+ "ts-json-stringify-parse": "🔧 Use structuredClone or proper copy method",
507
+ "ts-pointless-bool-cast": "🔧 Remove Boolean() wrapper",
508
+ "ts-double-negation": "🔧 Use Boolean(value) or truthiness directly",
509
+ "ts-unnecessary-array-concat": "🔧 Use push(item) or spread [...arr, item]",
510
+ "ts-defensive-null-guard": "🤔 Consider removing - may be overly defensive",
511
+ "ts-optional-chain-opportunity": "🔧 Use optional chaining obj?.prop?.nested",
512
+ "ts-explicit-undefined-check": "🔧 Use x === undefined or truthiness",
513
+ "ts-array-length-check": "🔧 Use truthiness: if (arr) instead of length check",
514
+ "ts-unnecessary-array-from": "🔧 Iterate directly without Array.from()",
515
+ "ts-redundant-filter-map": "🔧 Use flatMap or single pass transformation",
516
+ "ts-unnecessary-ternary-boolean": "🔧 Use Boolean(cond) or cond directly",
517
+ "ts-typeof-equality": "🤔 Consider instanceof or proper type guards",
518
+ "ts-manual-array-contains": "🔧 Use arr.includes(x) instead of indexOf",
519
+ "ts-slice-copy": "🔧 Use spread [...arr] instead of slice()",
520
+ "ts-parseint-no-radix": "🔧 Add radix: parseInt(x, 10)",
521
+ "ts-isnan-check": "🔧 Use Number.isNaN(x) instead of x !== x",
522
+ "ts-void-zero": "🔧 Use undefined directly instead of void 0",
523
+ "ts-function-constructor": "🏗️ Avoid dynamic code evaluation",
524
+ "ts-unnecessary-bind": "🔧 Remove .bind(this) in arrow function context",
525
+ "ts-empty-array-check": "🔧 Use !arr.length or truthiness",
526
+ "ts-array-every-some": "🔧 Use arr.every(x => !!x.prop) directly",
527
+ "ts-string-split-index": "🔧 Use destructuring or named variables",
528
+ "ts-nested-ternary": "🔧 Extract to if/else or named variables",
529
+ "ts-unnecessary-else-return": "🔧 Remove redundant else after return",
530
+ "ts-object-hasown-check": "🔧 Use Object.hasOwn(obj, key)",
531
+ "ts-delete-property": "🤔 Consider setting to undefined or restructuring",
532
+ "ts-in-operator-loop": "🔧 Use Object.keys/entries/values for iteration",
533
+ "ts-array-concat-spread": "🔧 Use push(...items) or spread directly",
534
+ "ts-unnecessary-array-isarray": "🔧 Remove redundant Array.isArray check",
535
+ "ts-nullish-coalescing-opportunity": "🔧 Use x ?? default instead of ternary",
536
+ "ts-optional-chaining-default": "🔧 Use obj?.prop ?? default",
537
+ };
538
+ return instructions[issue.rule] || "🤔 Evaluate: fix if trivial, mark FP if rule is wrong";
539
+ }
540
+ // --- Handler ---
541
+ export async function handleFixSimplified(args, ctx, clients, pi) {
542
+ const cwd = ctx.cwd || process.cwd();
543
+ // Parse command args: supports "[path] [--false-positive 'type:file:line']"
544
+ const argsTrimmed = args.trim();
545
+ let targetPath = ".";
546
+ let falsePositive;
547
+ if (argsTrimmed) {
548
+ // Check for --false-positive flag
549
+ const fpMatch = argsTrimmed.match(/--false-positive\s+['"]?([^'"\s]+)['"]?/);
550
+ if (fpMatch) {
551
+ falsePositive = fpMatch[1];
552
+ targetPath = argsTrimmed.replace(fpMatch[0], "").trim() || ".";
553
+ }
554
+ else {
555
+ targetPath = argsTrimmed;
556
+ }
557
+ }
558
+ // Handle false positive marking
559
+ if (falsePositive) {
560
+ const ignores = loadIgnoreFile(cwd);
561
+ ignores.push({
562
+ pattern: falsePositive,
563
+ addedAt: new Date().toISOString(),
564
+ reason: "User marked as false positive",
565
+ });
566
+ saveIgnoreFile(cwd, ignores);
567
+ ctx.ui.notify(`Marked ${falsePositive} as false positive`, "info");
568
+ return;
569
+ }
570
+ // Get changed files
571
+ let files;
572
+ if (targetPath !== "." && nodeFs.existsSync(targetPath)) {
573
+ files = [targetPath];
574
+ }
575
+ else {
576
+ files = getChangedFiles(cwd);
577
+ }
578
+ if (files.length === 0) {
579
+ ctx.ui.notify("No changed files found. Edit some files first, or specify a path.", "warning");
580
+ return;
581
+ }
582
+ // Apply file-level exclusions
583
+ const filesToScan = files.filter(shouldScanFile);
584
+ const skippedCount = files.length - filesToScan.length;
585
+ ctx.ui.notify(`Analyzing ${filesToScan.length} file(s)${skippedCount > 0 ? ` (${skippedCount} skipped)` : ""}...`, "info");
586
+ // Load ignores
587
+ const ignores = loadIgnoreFile(cwd);
588
+ // STEP 1: Auto-fix with Biome (silent - no reporting of these issues)
589
+ let biomeFixed = 0;
590
+ if (clients.biome.isAvailable()) {
591
+ ctx.ui.notify("🔧 Running Biome auto-fix...", "info");
592
+ biomeFixed = await autoFixWithBiome(files, clients);
593
+ if (biomeFixed > 0) {
594
+ ctx.ui.notify(`✅ Biome auto-fixed ${biomeFixed} issue(s)`, "info");
595
+ }
596
+ }
597
+ // STEP 2: Detect structural issues with AST-grep (these need human decisions)
598
+ const structuralIssues = await detectStructuralIssues(filesToScan, cwd, ignores, clients);
599
+ // STEP 3: Apply safe ast-grep fixes that biome doesn't handle
600
+ let astGrepFixed = 0;
601
+ if (structuralIssues.length > 0) {
602
+ astGrepFixed = await applySafeAstGrepFixes(structuralIssues, cwd);
603
+ if (astGrepFixed > 0) {
604
+ ctx.ui.notify(`🤖 Fixed ${astGrepFixed} structural issue(s)`, "info");
605
+ }
606
+ }
607
+ const totalFixed = biomeFixed + astGrepFixed;
608
+ // If no structural issues remain, just confirm success
609
+ if (structuralIssues.length === 0) {
610
+ ctx.ui.notify(`✅ /lens-booboo-fix complete — ${totalFixed} issue(s) auto-fixed, no structural issues remain.`, "info");
611
+ return;
612
+ }
613
+ // Generate imperative prompt for AI to fix remaining issues
614
+ const actionPrompt = generateActionPrompt(structuralIssues, totalFixed, filesToScan, cwd);
615
+ // Send the prompt to the AI via pi's message API
616
+ // This triggers the AI to respond and act on the issues
617
+ pi.sendUserMessage(actionPrompt);
618
+ }