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.
- package/CHANGELOG.md +10 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- 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
|
-
}
|