pi-lens 3.6.2 → 3.6.4

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 (207) hide show
  1. package/CHANGELOG.md +10 -2
  2. package/package.json +4 -4
  3. package/tsconfig.json +1 -1
  4. package/clients/__tests__/file-time.test.js +0 -216
  5. package/clients/__tests__/file-time.test.ts +0 -276
  6. package/clients/__tests__/format-service.test.js +0 -245
  7. package/clients/__tests__/format-service.test.ts +0 -339
  8. package/clients/__tests__/formatters.test.js +0 -271
  9. package/clients/__tests__/formatters.test.ts +0 -401
  10. package/clients/agent-behavior-client.js +0 -110
  11. package/clients/agent-behavior-client.test.js +0 -94
  12. package/clients/agent-behavior-client.test.ts +0 -116
  13. package/clients/amain-types.js +0 -164
  14. package/clients/architect-client.js +0 -291
  15. package/clients/ast-grep-client.js +0 -253
  16. package/clients/ast-grep-parser.js +0 -84
  17. package/clients/ast-grep-rule-manager.js +0 -89
  18. package/clients/ast-grep-types.js +0 -9
  19. package/clients/auto-loop.js +0 -131
  20. package/clients/biome-client.js +0 -420
  21. package/clients/biome-client.test.js +0 -144
  22. package/clients/biome-client.test.ts +0 -163
  23. package/clients/cache/rule-cache.js +0 -72
  24. package/clients/cache-manager.js +0 -245
  25. package/clients/cache-manager.test.js +0 -197
  26. package/clients/cache-manager.test.ts +0 -299
  27. package/clients/complexity-client.js +0 -675
  28. package/clients/complexity-client.test.js +0 -234
  29. package/clients/complexity-client.test.ts +0 -255
  30. package/clients/config-validator.js +0 -465
  31. package/clients/dependency-checker.js +0 -325
  32. package/clients/dependency-checker.test.js +0 -60
  33. package/clients/dependency-checker.test.ts +0 -71
  34. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  35. package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
  36. package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
  37. package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
  38. package/clients/dispatch/debug.log +0 -1
  39. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  40. package/clients/dispatch/dispatcher.edge.test.ts +0 -100
  41. package/clients/dispatch/dispatcher.format.test.js +0 -46
  42. package/clients/dispatch/dispatcher.format.test.ts +0 -58
  43. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  44. package/clients/dispatch/dispatcher.inline.test.ts +0 -93
  45. package/clients/dispatch/dispatcher.js +0 -381
  46. package/clients/dispatch/dispatcher.test.js +0 -116
  47. package/clients/dispatch/dispatcher.test.ts +0 -149
  48. package/clients/dispatch/integration.js +0 -108
  49. package/clients/dispatch/plan.js +0 -183
  50. package/clients/dispatch/runners/architect.js +0 -83
  51. package/clients/dispatch/runners/architect.test.js +0 -138
  52. package/clients/dispatch/runners/architect.test.ts +0 -162
  53. package/clients/dispatch/runners/ast-grep-napi.js +0 -405
  54. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
  55. package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
  56. package/clients/dispatch/runners/ast-grep.js +0 -157
  57. package/clients/dispatch/runners/biome.js +0 -55
  58. package/clients/dispatch/runners/config-validation.js +0 -67
  59. package/clients/dispatch/runners/go-vet.js +0 -48
  60. package/clients/dispatch/runners/index.js +0 -47
  61. package/clients/dispatch/runners/lsp.js +0 -102
  62. package/clients/dispatch/runners/oxlint.js +0 -67
  63. package/clients/dispatch/runners/oxlint.test.js +0 -230
  64. package/clients/dispatch/runners/oxlint.test.ts +0 -303
  65. package/clients/dispatch/runners/pyright.js +0 -100
  66. package/clients/dispatch/runners/pyright.test.js +0 -98
  67. package/clients/dispatch/runners/pyright.test.ts +0 -121
  68. package/clients/dispatch/runners/python-slop.js +0 -97
  69. package/clients/dispatch/runners/python-slop.test.js +0 -203
  70. package/clients/dispatch/runners/python-slop.test.ts +0 -298
  71. package/clients/dispatch/runners/ruff.js +0 -48
  72. package/clients/dispatch/runners/rust-clippy.js +0 -102
  73. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  74. package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
  75. package/clients/dispatch/runners/shellcheck.js +0 -147
  76. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  77. package/clients/dispatch/runners/shellcheck.test.ts +0 -129
  78. package/clients/dispatch/runners/similarity.js +0 -230
  79. package/clients/dispatch/runners/spellcheck.js +0 -106
  80. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  81. package/clients/dispatch/runners/spellcheck.test.ts +0 -214
  82. package/clients/dispatch/runners/tree-sitter.js +0 -246
  83. package/clients/dispatch/runners/ts-lsp.js +0 -125
  84. package/clients/dispatch/runners/ts-slop.js +0 -113
  85. package/clients/dispatch/runners/type-safety.js +0 -142
  86. package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
  87. package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
  88. package/clients/dispatch/runners/utils.js +0 -51
  89. package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
  90. package/clients/dispatch/types.js +0 -16
  91. package/clients/dispatch/utils/format-utils.js +0 -44
  92. package/clients/dogfood.test.js +0 -201
  93. package/clients/dogfood.test.ts +0 -269
  94. package/clients/file-kinds.js +0 -177
  95. package/clients/file-kinds.test.js +0 -169
  96. package/clients/file-kinds.test.ts +0 -210
  97. package/clients/file-time.js +0 -152
  98. package/clients/file-utils.js +0 -40
  99. package/clients/fix-scanners.js +0 -204
  100. package/clients/format-service.js +0 -184
  101. package/clients/formatters.js +0 -488
  102. package/clients/go-client.js +0 -203
  103. package/clients/go-client.test.js +0 -127
  104. package/clients/go-client.test.ts +0 -143
  105. package/clients/installer/index.js +0 -403
  106. package/clients/interviewer-templates.js +0 -75
  107. package/clients/interviewer.js +0 -173
  108. package/clients/jscpd-client.js +0 -196
  109. package/clients/jscpd-client.test.js +0 -127
  110. package/clients/jscpd-client.test.ts +0 -145
  111. package/clients/knip-client.js +0 -239
  112. package/clients/knip-client.test.js +0 -112
  113. package/clients/knip-client.test.ts +0 -128
  114. package/clients/latency-logger.js +0 -40
  115. package/clients/lsp/__tests__/client.test.js +0 -310
  116. package/clients/lsp/__tests__/client.test.ts +0 -412
  117. package/clients/lsp/__tests__/config.test.js +0 -167
  118. package/clients/lsp/__tests__/config.test.ts +0 -217
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
  121. package/clients/lsp/__tests__/integration.test.js +0 -127
  122. package/clients/lsp/__tests__/integration.test.ts +0 -160
  123. package/clients/lsp/__tests__/launch.test.js +0 -313
  124. package/clients/lsp/__tests__/launch.test.ts +0 -394
  125. package/clients/lsp/__tests__/server.test.js +0 -259
  126. package/clients/lsp/__tests__/server.test.ts +0 -332
  127. package/clients/lsp/__tests__/service.test.js +0 -438
  128. package/clients/lsp/__tests__/service.test.ts +0 -530
  129. package/clients/lsp/client.js +0 -350
  130. package/clients/lsp/config.js +0 -112
  131. package/clients/lsp/index.js +0 -318
  132. package/clients/lsp/installer/index.js +0 -391
  133. package/clients/lsp/interactive-install.js +0 -221
  134. package/clients/lsp/language.js +0 -170
  135. package/clients/lsp/launch.js +0 -329
  136. package/clients/lsp/lsp/launch.js +0 -116
  137. package/clients/lsp/lsp/server.js +0 -532
  138. package/clients/lsp/lsp-index.js +0 -10
  139. package/clients/lsp/path-utils.js +0 -5
  140. package/clients/lsp/server.js +0 -725
  141. package/clients/lsp/test-py-spawn/requirements.txt +0 -1
  142. package/clients/lsp/test-py-spawn/test.py +0 -3
  143. package/clients/lsp/test-py-svc/requirements.txt +0 -1
  144. package/clients/lsp/test-py-svc/test.py +0 -3
  145. package/clients/lsp/test-python-project/requirements.txt +0 -1
  146. package/clients/lsp/test-python-project/test.py +0 -5
  147. package/clients/metrics-client.js +0 -107
  148. package/clients/metrics-client.test.js +0 -128
  149. package/clients/metrics-client.test.ts +0 -163
  150. package/clients/metrics-history.js +0 -367
  151. package/clients/path-utils.js +0 -142
  152. package/clients/pipeline.js +0 -272
  153. package/clients/production-readiness.js +0 -522
  154. package/clients/project-index.js +0 -255
  155. package/clients/project-metadata.js +0 -531
  156. package/clients/ruff-client.js +0 -325
  157. package/clients/ruff-client.test.js +0 -132
  158. package/clients/ruff-client.test.ts +0 -153
  159. package/clients/rules-scanner.js +0 -97
  160. package/clients/runner-tracker.js +0 -152
  161. package/clients/rust-client.js +0 -205
  162. package/clients/rust-client.test.js +0 -108
  163. package/clients/rust-client.test.ts +0 -130
  164. package/clients/safe-spawn-async.js +0 -163
  165. package/clients/safe-spawn.js +0 -241
  166. package/clients/sanitize.js +0 -291
  167. package/clients/sanitize.test.js +0 -177
  168. package/clients/sanitize.test.ts +0 -223
  169. package/clients/scan-architectural-debt.js +0 -167
  170. package/clients/scan-utils.js +0 -83
  171. package/clients/secrets-scanner.js +0 -119
  172. package/clients/secrets-scanner.test.js +0 -100
  173. package/clients/secrets-scanner.test.ts +0 -113
  174. package/clients/sg-runner.js +0 -292
  175. package/clients/state-matrix.js +0 -160
  176. package/clients/subprocess-client.js +0 -65
  177. package/clients/symbol-types.js +0 -5
  178. package/clients/test-runner-client.js +0 -523
  179. package/clients/test-runner-client.test.js +0 -192
  180. package/clients/test-runner-client.test.ts +0 -253
  181. package/clients/test-utils.js +0 -27
  182. package/clients/test-utils.ts +0 -36
  183. package/clients/todo-scanner.js +0 -200
  184. package/clients/todo-scanner.test.js +0 -301
  185. package/clients/todo-scanner.test.ts +0 -352
  186. package/clients/tool-availability.js +0 -207
  187. package/clients/tree-sitter-client.js +0 -601
  188. package/clients/tree-sitter-query-loader.js +0 -355
  189. package/clients/tree-sitter-symbol-extractor.js +0 -289
  190. package/clients/ts-service.js +0 -129
  191. package/clients/type-coverage-client.js +0 -127
  192. package/clients/type-coverage-client.test.js +0 -105
  193. package/clients/type-coverage-client.test.ts +0 -125
  194. package/clients/type-safety-client.js +0 -138
  195. package/clients/types.js +0 -11
  196. package/clients/typescript-client.codefix.test.js +0 -157
  197. package/clients/typescript-client.codefix.test.ts +0 -186
  198. package/clients/typescript-client.js +0 -509
  199. package/clients/typescript-client.test.js +0 -105
  200. package/clients/typescript-client.test.ts +0 -126
  201. package/commands/booboo.js +0 -1007
  202. package/commands/fix-from-booboo.js +0 -398
  203. package/commands/fix-simplified.js +0 -618
  204. package/commands/rate.js +0 -281
  205. package/commands/rate.test.js +0 -119
  206. package/commands/rate.test.ts +0 -131
  207. package/commands/refactor.js +0 -130
@@ -1,618 +0,0 @@
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
- }