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
package/clients/ruff-client.js
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ruff Client for pi-lens
|
|
3
|
-
*
|
|
4
|
-
* Fast Python linting and formatting via Ruff CLI.
|
|
5
|
-
* Replaces flake8, pylint, isort, black, pyupgrade.
|
|
6
|
-
*
|
|
7
|
-
* Requires: pip install ruff
|
|
8
|
-
* Docs: https://docs.astral.sh/ruff/
|
|
9
|
-
*/
|
|
10
|
-
import * as fs from "node:fs";
|
|
11
|
-
import * as path from "node:path";
|
|
12
|
-
import { isFileKind } from "./file-kinds.js";
|
|
13
|
-
import { safeSpawn } from "./safe-spawn.js";
|
|
14
|
-
// --- Client ---
|
|
15
|
-
export class RuffClient {
|
|
16
|
-
constructor(verbose = false) {
|
|
17
|
-
this.ruffAvailable = null;
|
|
18
|
-
this.log = verbose
|
|
19
|
-
? (msg) => console.error(`[ruff] ${msg}`)
|
|
20
|
-
: () => { };
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Check if ruff CLI is available, auto-install if not
|
|
24
|
-
*/
|
|
25
|
-
async ensureAvailable() {
|
|
26
|
-
// Fast path: already checked
|
|
27
|
-
if (this.ruffAvailable !== null)
|
|
28
|
-
return this.ruffAvailable;
|
|
29
|
-
// Check if available in PATH
|
|
30
|
-
const result = safeSpawn("ruff", ["--version"], {
|
|
31
|
-
timeout: 5000,
|
|
32
|
-
});
|
|
33
|
-
this.ruffAvailable = !result.error && result.status === 0;
|
|
34
|
-
if (this.ruffAvailable) {
|
|
35
|
-
this.log(`Ruff found: ${result.stdout.trim()}`);
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
// Auto-install via pi-lens installer
|
|
39
|
-
this.log("Ruff not found, attempting auto-install...");
|
|
40
|
-
const { ensureTool } = await import("./installer/index.js");
|
|
41
|
-
const installedPath = await ensureTool("ruff");
|
|
42
|
-
if (installedPath) {
|
|
43
|
-
this.log(`Ruff auto-installed: ${installedPath}`);
|
|
44
|
-
this.ruffAvailable = true;
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
this.log("Ruff auto-install failed");
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Check if ruff CLI is available (legacy sync method)
|
|
52
|
-
* Prefer ensureAvailable() for auto-install behavior
|
|
53
|
-
*/
|
|
54
|
-
isAvailable() {
|
|
55
|
-
if (this.ruffAvailable !== null)
|
|
56
|
-
return this.ruffAvailable;
|
|
57
|
-
try {
|
|
58
|
-
const result = safeSpawn("ruff", ["--version"], {
|
|
59
|
-
timeout: 5000,
|
|
60
|
-
});
|
|
61
|
-
this.ruffAvailable = !result.error && result.status === 0;
|
|
62
|
-
if (this.ruffAvailable) {
|
|
63
|
-
this.log(`Ruff found: ${result.stdout.trim()}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
void err;
|
|
68
|
-
this.ruffAvailable = false;
|
|
69
|
-
}
|
|
70
|
-
return this.ruffAvailable;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Check if a file is a Python file
|
|
74
|
-
*/
|
|
75
|
-
isPythonFile(filePath) {
|
|
76
|
-
return isFileKind(filePath, "python");
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Lint a Python file
|
|
80
|
-
*/
|
|
81
|
-
checkFile(filePath) {
|
|
82
|
-
if (!this.isAvailable())
|
|
83
|
-
return [];
|
|
84
|
-
const absolutePath = path.resolve(filePath);
|
|
85
|
-
if (!fs.existsSync(absolutePath))
|
|
86
|
-
return [];
|
|
87
|
-
try {
|
|
88
|
-
const result = safeSpawn("ruff", [
|
|
89
|
-
"check",
|
|
90
|
-
"--output-format",
|
|
91
|
-
"json",
|
|
92
|
-
"--target-version",
|
|
93
|
-
"py310",
|
|
94
|
-
absolutePath,
|
|
95
|
-
], {
|
|
96
|
-
timeout: 10000,
|
|
97
|
-
});
|
|
98
|
-
// ruff exits 1 when it finds issues (normal)
|
|
99
|
-
const output = result.stdout || "";
|
|
100
|
-
if (!output.trim())
|
|
101
|
-
return [];
|
|
102
|
-
return this.parseOutput(output, absolutePath);
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
105
|
-
this.log(`Check error: ${err.message}`);
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Check if file has formatting issues (ruff format --check)
|
|
111
|
-
*/
|
|
112
|
-
checkFormatting(filePath) {
|
|
113
|
-
if (!this.isAvailable())
|
|
114
|
-
return "";
|
|
115
|
-
const absolutePath = path.resolve(filePath);
|
|
116
|
-
if (!fs.existsSync(absolutePath))
|
|
117
|
-
return "";
|
|
118
|
-
try {
|
|
119
|
-
const result = safeSpawn("ruff", ["format", "--check", "--diff", absolutePath], {
|
|
120
|
-
timeout: 10000,
|
|
121
|
-
});
|
|
122
|
-
// ruff format --check exits 1 when changes needed
|
|
123
|
-
if (result.status === 0)
|
|
124
|
-
return "";
|
|
125
|
-
const diff = result.stdout || "";
|
|
126
|
-
if (!diff.trim())
|
|
127
|
-
return "";
|
|
128
|
-
// Count lines that would change
|
|
129
|
-
const diffLines = diff
|
|
130
|
-
.split("\n")
|
|
131
|
-
.filter((l) => l.startsWith("+") || l.startsWith("-")).length;
|
|
132
|
-
return `[Ruff Format] ${diffLines} line(s) would change — run 'ruff format ${path.basename(filePath)}' to fix`;
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
void err;
|
|
136
|
-
return "";
|
|
137
|
-
} // Intentionally return empty string on diff failure
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Auto-fix linting issues (writes to disk)
|
|
141
|
-
*/
|
|
142
|
-
fixFile(filePath) {
|
|
143
|
-
if (!this.isAvailable())
|
|
144
|
-
return {
|
|
145
|
-
success: false,
|
|
146
|
-
changed: false,
|
|
147
|
-
fixed: 0,
|
|
148
|
-
error: "Ruff not available",
|
|
149
|
-
};
|
|
150
|
-
const absolutePath = path.resolve(filePath);
|
|
151
|
-
if (!fs.existsSync(absolutePath))
|
|
152
|
-
return {
|
|
153
|
-
success: false,
|
|
154
|
-
changed: false,
|
|
155
|
-
fixed: 0,
|
|
156
|
-
error: "File not found",
|
|
157
|
-
};
|
|
158
|
-
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
159
|
-
try {
|
|
160
|
-
const beforeDiags = this.checkFile(filePath);
|
|
161
|
-
const fixableCount = beforeDiags.filter((d) => d.fixable).length;
|
|
162
|
-
const result = safeSpawn("ruff", ["check", "--fix", absolutePath], {
|
|
163
|
-
timeout: 15000,
|
|
164
|
-
});
|
|
165
|
-
if (result.error) {
|
|
166
|
-
return {
|
|
167
|
-
success: false,
|
|
168
|
-
changed: false,
|
|
169
|
-
fixed: 0,
|
|
170
|
-
error: result.error.message,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
const fixed = fs.readFileSync(absolutePath, "utf-8");
|
|
174
|
-
const changed = content !== fixed;
|
|
175
|
-
if (changed) {
|
|
176
|
-
this.log(`Fixed ${fixableCount} issue(s) in ${path.basename(filePath)}`);
|
|
177
|
-
}
|
|
178
|
-
return { success: true, changed, fixed: fixableCount };
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
return { success: false, changed: false, fixed: 0, error: err.message };
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Fix multiple Python files at once (much faster than file-by-file)
|
|
186
|
-
*/
|
|
187
|
-
fixFiles(filePaths) {
|
|
188
|
-
if (!this.isAvailable()) {
|
|
189
|
-
return {
|
|
190
|
-
success: false,
|
|
191
|
-
fixed: 0,
|
|
192
|
-
changed: 0,
|
|
193
|
-
error: "Ruff not available",
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
// Filter to existing Python files
|
|
197
|
-
const validFiles = filePaths
|
|
198
|
-
.map((f) => path.resolve(f))
|
|
199
|
-
.filter((f) => fs.existsSync(f) && f.endsWith(".py"));
|
|
200
|
-
if (validFiles.length === 0) {
|
|
201
|
-
return { success: true, fixed: 0, changed: 0 };
|
|
202
|
-
}
|
|
203
|
-
try {
|
|
204
|
-
// Count fixable issues before fixing
|
|
205
|
-
let totalFixable = 0;
|
|
206
|
-
for (const file of validFiles) {
|
|
207
|
-
const diags = this.checkFile(file);
|
|
208
|
-
totalFixable += diags.filter((d) => d.fixable).length;
|
|
209
|
-
}
|
|
210
|
-
// Run ruff once on all files - much faster than per file
|
|
211
|
-
const result = safeSpawn("ruff", ["check", "--fix", ...validFiles], {
|
|
212
|
-
timeout: 60000, // Longer timeout for batch
|
|
213
|
-
});
|
|
214
|
-
if (result.error) {
|
|
215
|
-
return {
|
|
216
|
-
success: false,
|
|
217
|
-
fixed: 0,
|
|
218
|
-
changed: 0,
|
|
219
|
-
error: result.error.message,
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
this.log(`Fixed ${totalFixable} issue(s) in ${validFiles.length} file(s)`);
|
|
223
|
-
return { success: true, fixed: totalFixable, changed: validFiles.length };
|
|
224
|
-
}
|
|
225
|
-
catch (err) {
|
|
226
|
-
return {
|
|
227
|
-
success: false,
|
|
228
|
-
fixed: 0,
|
|
229
|
-
changed: 0,
|
|
230
|
-
error: err.message,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Format a Python file (writes to disk)
|
|
236
|
-
*/
|
|
237
|
-
formatFile(filePath) {
|
|
238
|
-
if (!this.isAvailable())
|
|
239
|
-
return { success: false, changed: false, error: "Ruff not available" };
|
|
240
|
-
const absolutePath = path.resolve(filePath);
|
|
241
|
-
if (!fs.existsSync(absolutePath))
|
|
242
|
-
return { success: false, changed: false, error: "File not found" };
|
|
243
|
-
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
244
|
-
try {
|
|
245
|
-
const result = safeSpawn("ruff", ["format", absolutePath], {
|
|
246
|
-
timeout: 10000,
|
|
247
|
-
});
|
|
248
|
-
if (result.error) {
|
|
249
|
-
return { success: false, changed: false, error: result.error.message };
|
|
250
|
-
}
|
|
251
|
-
const formatted = fs.readFileSync(absolutePath, "utf-8");
|
|
252
|
-
const changed = content !== formatted;
|
|
253
|
-
if (changed) {
|
|
254
|
-
this.log(`Formatted ${path.basename(filePath)}`);
|
|
255
|
-
}
|
|
256
|
-
return { success: true, changed };
|
|
257
|
-
}
|
|
258
|
-
catch (err) {
|
|
259
|
-
return { success: false, changed: false, error: err.message };
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Format diagnostics for LLM consumption
|
|
264
|
-
*/
|
|
265
|
-
formatDiagnostics(diags) {
|
|
266
|
-
if (diags.length === 0)
|
|
267
|
-
return "";
|
|
268
|
-
const errors = diags.filter((d) => d.severity === "error");
|
|
269
|
-
const warnings = diags.filter((d) => d.severity === "warning");
|
|
270
|
-
const fixable = diags.filter((d) => d.fixable);
|
|
271
|
-
let result = `[Ruff] ${diags.length} issue(s)`;
|
|
272
|
-
if (errors.length)
|
|
273
|
-
result += ` — ${errors.length} error(s)`;
|
|
274
|
-
if (warnings.length)
|
|
275
|
-
result += ` — ${warnings.length} warning(s)`;
|
|
276
|
-
if (fixable.length)
|
|
277
|
-
result += ` — ${fixable.length} auto-fixable`;
|
|
278
|
-
result += ":\n";
|
|
279
|
-
for (const d of diags.slice(0, 15)) {
|
|
280
|
-
const loc = d.line === d.endLine
|
|
281
|
-
? `L${d.line}:${d.column}-${d.endColumn}`
|
|
282
|
-
: `L${d.line}:${d.column}-L${d.endLine}:${d.endColumn}`;
|
|
283
|
-
const fix = d.fixable ? " [fixable]" : "";
|
|
284
|
-
result += ` [${d.rule}] ${loc} ${d.message}${fix}\n`;
|
|
285
|
-
}
|
|
286
|
-
if (diags.length > 15) {
|
|
287
|
-
result += ` ... and ${diags.length - 15} more\n`;
|
|
288
|
-
}
|
|
289
|
-
if (fixable.length > 0) {
|
|
290
|
-
result += `\n Run 'ruff check --fix ${path.basename(diags[0].file)}' to auto-fix ${fixable.length} issue(s)\n`;
|
|
291
|
-
}
|
|
292
|
-
return result;
|
|
293
|
-
}
|
|
294
|
-
// --- Internal ---
|
|
295
|
-
parseOutput(output, filterFile) {
|
|
296
|
-
if (!output.trim())
|
|
297
|
-
return [];
|
|
298
|
-
try {
|
|
299
|
-
const items = JSON.parse(output);
|
|
300
|
-
const diagnostics = [];
|
|
301
|
-
for (const item of items) {
|
|
302
|
-
// Filter to single file if requested
|
|
303
|
-
if (filterFile && path.resolve(item.filename) !== filterFile)
|
|
304
|
-
continue;
|
|
305
|
-
diagnostics.push({
|
|
306
|
-
line: item.location.row - 1, // ruff is 1-indexed
|
|
307
|
-
column: item.location.column - 1,
|
|
308
|
-
endLine: item.end_location.row - 1,
|
|
309
|
-
endColumn: item.end_location.column - 1,
|
|
310
|
-
severity: item.code?.startsWith("E") ? "error" : "warning",
|
|
311
|
-
message: item.message,
|
|
312
|
-
rule: item.code || "unknown",
|
|
313
|
-
file: item.filename,
|
|
314
|
-
fixable: item.fix !== null,
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
return diagnostics;
|
|
318
|
-
}
|
|
319
|
-
catch (err) {
|
|
320
|
-
void err;
|
|
321
|
-
this.log("Failed to parse ruff JSON output");
|
|
322
|
-
return [];
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { RuffClient } from "./ruff-client.js";
|
|
3
|
-
import { createTempFile, setupTestEnvironment } from "./test-utils.js";
|
|
4
|
-
describe("RuffClient", () => {
|
|
5
|
-
let client;
|
|
6
|
-
let tmpDir;
|
|
7
|
-
let cleanup;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
client = new RuffClient();
|
|
10
|
-
({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-ruff-test-"));
|
|
11
|
-
});
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
cleanup();
|
|
14
|
-
});
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
cleanup();
|
|
17
|
-
});
|
|
18
|
-
describe("isPythonFile", () => {
|
|
19
|
-
it("should recognize Python files", () => {
|
|
20
|
-
expect(client.isPythonFile("test.py")).toBe(true);
|
|
21
|
-
expect(client.isPythonFile("module.py")).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
it("should not recognize non-Python files", () => {
|
|
24
|
-
expect(client.isPythonFile("test.ts")).toBe(false);
|
|
25
|
-
expect(client.isPythonFile("test.js")).toBe(false);
|
|
26
|
-
expect(client.isPythonFile("test.txt")).toBe(false);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
describe("isAvailable", () => {
|
|
30
|
-
it("should check ruff availability", () => {
|
|
31
|
-
const available = client.isAvailable();
|
|
32
|
-
expect(typeof available).toBe("boolean");
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
describe("checkFile", () => {
|
|
36
|
-
it("should return empty array for non-existent files", () => {
|
|
37
|
-
if (!client.isAvailable())
|
|
38
|
-
return;
|
|
39
|
-
const result = client.checkFile("/nonexistent/file.py");
|
|
40
|
-
expect(result).toEqual([]);
|
|
41
|
-
});
|
|
42
|
-
it("should detect lint issues in Python code", () => {
|
|
43
|
-
if (!client.isAvailable())
|
|
44
|
-
return;
|
|
45
|
-
const content = `
|
|
46
|
-
import os
|
|
47
|
-
import sys
|
|
48
|
-
|
|
49
|
-
x = 1
|
|
50
|
-
`;
|
|
51
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
52
|
-
const result = client.checkFile(filePath);
|
|
53
|
-
// Should detect unused imports
|
|
54
|
-
expect(result.some((d) => d.rule === "F401" || d.message.includes("unused"))).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
it("should return array of diagnostics", () => {
|
|
57
|
-
if (!client.isAvailable())
|
|
58
|
-
return;
|
|
59
|
-
const content = `
|
|
60
|
-
def foo():
|
|
61
|
-
x = undefined_variable
|
|
62
|
-
return x
|
|
63
|
-
`;
|
|
64
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
65
|
-
const result = client.checkFile(filePath);
|
|
66
|
-
// Should return an array
|
|
67
|
-
expect(Array.isArray(result)).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
describe("formatDiagnostics", () => {
|
|
71
|
-
it("should format diagnostics for display", () => {
|
|
72
|
-
const diags = [
|
|
73
|
-
{
|
|
74
|
-
line: 1,
|
|
75
|
-
column: 0,
|
|
76
|
-
endLine: 1,
|
|
77
|
-
endColumn: 10,
|
|
78
|
-
severity: "error",
|
|
79
|
-
message: "Undefined name 'x'",
|
|
80
|
-
rule: "F821",
|
|
81
|
-
file: "test.py",
|
|
82
|
-
fixable: false,
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
const formatted = client.formatDiagnostics(diags);
|
|
86
|
-
expect(formatted).toContain("Ruff");
|
|
87
|
-
expect(formatted).toContain("F821");
|
|
88
|
-
expect(formatted).toContain("Undefined name");
|
|
89
|
-
});
|
|
90
|
-
it("should show fixable count", () => {
|
|
91
|
-
const diags = [
|
|
92
|
-
{
|
|
93
|
-
line: 1,
|
|
94
|
-
column: 0,
|
|
95
|
-
endLine: 1,
|
|
96
|
-
endColumn: 10,
|
|
97
|
-
severity: "warning",
|
|
98
|
-
message: "Unused import",
|
|
99
|
-
rule: "F401",
|
|
100
|
-
file: "test.py",
|
|
101
|
-
fixable: true,
|
|
102
|
-
},
|
|
103
|
-
];
|
|
104
|
-
const formatted = client.formatDiagnostics(diags);
|
|
105
|
-
expect(formatted).toContain("fixable");
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
describe("checkFormatting", () => {
|
|
109
|
-
it("should detect formatting issues", () => {
|
|
110
|
-
if (!client.isAvailable())
|
|
111
|
-
return;
|
|
112
|
-
const content = `x=1
|
|
113
|
-
y=2
|
|
114
|
-
`;
|
|
115
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
116
|
-
const result = client.checkFormatting(filePath);
|
|
117
|
-
// Should suggest formatting (missing spaces around =)
|
|
118
|
-
expect(typeof result).toBe("string");
|
|
119
|
-
});
|
|
120
|
-
it("should return empty string for well-formatted code", () => {
|
|
121
|
-
if (!client.isAvailable())
|
|
122
|
-
return;
|
|
123
|
-
const content = `x = 1
|
|
124
|
-
y = 2
|
|
125
|
-
`;
|
|
126
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
127
|
-
const result = client.checkFormatting(filePath);
|
|
128
|
-
// Well-formatted code should return empty or minimal output
|
|
129
|
-
expect(result).toBe("");
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
});
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { RuffClient } from "./ruff-client.js";
|
|
3
|
-
import { createTempFile, setupTestEnvironment } from "./test-utils.js";
|
|
4
|
-
|
|
5
|
-
describe("RuffClient", () => {
|
|
6
|
-
let client: RuffClient;
|
|
7
|
-
let tmpDir: string;
|
|
8
|
-
let cleanup: () => void;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
client = new RuffClient();
|
|
12
|
-
({ tmpDir, cleanup } = setupTestEnvironment("pi-lens-ruff-test-"));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
cleanup();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
cleanup();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("isPythonFile", () => {
|
|
24
|
-
it("should recognize Python files", () => {
|
|
25
|
-
expect(client.isPythonFile("test.py")).toBe(true);
|
|
26
|
-
expect(client.isPythonFile("module.py")).toBe(true);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should not recognize non-Python files", () => {
|
|
30
|
-
expect(client.isPythonFile("test.ts")).toBe(false);
|
|
31
|
-
expect(client.isPythonFile("test.js")).toBe(false);
|
|
32
|
-
expect(client.isPythonFile("test.txt")).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe("isAvailable", () => {
|
|
37
|
-
it("should check ruff availability", () => {
|
|
38
|
-
const available = client.isAvailable();
|
|
39
|
-
expect(typeof available).toBe("boolean");
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("checkFile", () => {
|
|
44
|
-
it("should return empty array for non-existent files", () => {
|
|
45
|
-
if (!client.isAvailable()) return;
|
|
46
|
-
const result = client.checkFile("/nonexistent/file.py");
|
|
47
|
-
expect(result).toEqual([]);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should detect lint issues in Python code", () => {
|
|
51
|
-
if (!client.isAvailable()) return;
|
|
52
|
-
|
|
53
|
-
const content = `
|
|
54
|
-
import os
|
|
55
|
-
import sys
|
|
56
|
-
|
|
57
|
-
x = 1
|
|
58
|
-
`;
|
|
59
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
60
|
-
const result = client.checkFile(filePath);
|
|
61
|
-
|
|
62
|
-
// Should detect unused imports
|
|
63
|
-
expect(
|
|
64
|
-
result.some((d) => d.rule === "F401" || d.message.includes("unused")),
|
|
65
|
-
).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("should return array of diagnostics", () => {
|
|
69
|
-
if (!client.isAvailable()) return;
|
|
70
|
-
|
|
71
|
-
const content = `
|
|
72
|
-
def foo():
|
|
73
|
-
x = undefined_variable
|
|
74
|
-
return x
|
|
75
|
-
`;
|
|
76
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
77
|
-
const result = client.checkFile(filePath);
|
|
78
|
-
|
|
79
|
-
// Should return an array
|
|
80
|
-
expect(Array.isArray(result)).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
describe("formatDiagnostics", () => {
|
|
85
|
-
it("should format diagnostics for display", () => {
|
|
86
|
-
const diags = [
|
|
87
|
-
{
|
|
88
|
-
line: 1,
|
|
89
|
-
column: 0,
|
|
90
|
-
endLine: 1,
|
|
91
|
-
endColumn: 10,
|
|
92
|
-
severity: "error" as const,
|
|
93
|
-
message: "Undefined name 'x'",
|
|
94
|
-
rule: "F821",
|
|
95
|
-
file: "test.py",
|
|
96
|
-
fixable: false,
|
|
97
|
-
},
|
|
98
|
-
];
|
|
99
|
-
|
|
100
|
-
const formatted = client.formatDiagnostics(diags);
|
|
101
|
-
expect(formatted).toContain("Ruff");
|
|
102
|
-
expect(formatted).toContain("F821");
|
|
103
|
-
expect(formatted).toContain("Undefined name");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should show fixable count", () => {
|
|
107
|
-
const diags = [
|
|
108
|
-
{
|
|
109
|
-
line: 1,
|
|
110
|
-
column: 0,
|
|
111
|
-
endLine: 1,
|
|
112
|
-
endColumn: 10,
|
|
113
|
-
severity: "warning" as const,
|
|
114
|
-
message: "Unused import",
|
|
115
|
-
rule: "F401",
|
|
116
|
-
file: "test.py",
|
|
117
|
-
fixable: true,
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
const formatted = client.formatDiagnostics(diags);
|
|
122
|
-
expect(formatted).toContain("fixable");
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe("checkFormatting", () => {
|
|
127
|
-
it("should detect formatting issues", () => {
|
|
128
|
-
if (!client.isAvailable()) return;
|
|
129
|
-
|
|
130
|
-
const content = `x=1
|
|
131
|
-
y=2
|
|
132
|
-
`;
|
|
133
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
134
|
-
const result = client.checkFormatting(filePath);
|
|
135
|
-
|
|
136
|
-
// Should suggest formatting (missing spaces around =)
|
|
137
|
-
expect(typeof result).toBe("string");
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("should return empty string for well-formatted code", () => {
|
|
141
|
-
if (!client.isAvailable()) return;
|
|
142
|
-
|
|
143
|
-
const content = `x = 1
|
|
144
|
-
y = 2
|
|
145
|
-
`;
|
|
146
|
-
const filePath = createTempFile(tmpDir, "test.py", content);
|
|
147
|
-
const result = client.checkFormatting(filePath);
|
|
148
|
-
|
|
149
|
-
// Well-formatted code should return empty or minimal output
|
|
150
|
-
expect(result).toBe("");
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
});
|
package/clients/rules-scanner.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project rules scanner for pi-lens.
|
|
3
|
-
*
|
|
4
|
-
* Scans for rule files that other tools/agents may have left:
|
|
5
|
-
* - .claude/rules/ — Claude Code rule files
|
|
6
|
-
* - .agents/rules/ — Generic agent rule files
|
|
7
|
-
* - .cursorrules — Cursor IDE rules
|
|
8
|
-
* - CLAUDE.md — Claude Code project context
|
|
9
|
-
* - AGENTS.md — Generic agent context
|
|
10
|
-
*
|
|
11
|
-
* These are surfaced in the system prompt so the agent knows
|
|
12
|
-
* to read them when relevant. pi-lens architect.yaml handles
|
|
13
|
-
* the automated regex-based checks separately.
|
|
14
|
-
*/
|
|
15
|
-
import * as fs from "node:fs";
|
|
16
|
-
import * as path from "node:path";
|
|
17
|
-
const RULE_DIRS = [
|
|
18
|
-
{ dir: ".claude/rules", source: ".claude/rules" },
|
|
19
|
-
{ dir: ".agents/rules", source: ".agents/rules" },
|
|
20
|
-
];
|
|
21
|
-
const RULE_FILES = [
|
|
22
|
-
{ file: "CLAUDE.md", source: "root" },
|
|
23
|
-
{ file: "AGENTS.md", source: "root" },
|
|
24
|
-
{ file: ".cursorrules", source: "root" },
|
|
25
|
-
];
|
|
26
|
-
function findMarkdownFiles(dir, baseDir) {
|
|
27
|
-
const results = [];
|
|
28
|
-
if (!fs.existsSync(dir))
|
|
29
|
-
return results;
|
|
30
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
31
|
-
for (const entry of entries) {
|
|
32
|
-
const fullPath = path.join(dir, entry.name);
|
|
33
|
-
if (entry.isDirectory()) {
|
|
34
|
-
results.push(...findMarkdownFiles(fullPath, baseDir));
|
|
35
|
-
}
|
|
36
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
37
|
-
results.push({
|
|
38
|
-
source: path.relative(baseDir, dir) || path.basename(baseDir),
|
|
39
|
-
name: entry.name,
|
|
40
|
-
filePath: fullPath,
|
|
41
|
-
relativePath: path.relative(baseDir, fullPath).replace(/\\/g, "/"),
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
export function scanProjectRules(cwd) {
|
|
48
|
-
const rules = [];
|
|
49
|
-
// Scan rule directories
|
|
50
|
-
for (const { dir, source } of RULE_DIRS) {
|
|
51
|
-
const dirPath = path.join(cwd, dir);
|
|
52
|
-
if (fs.existsSync(dirPath)) {
|
|
53
|
-
const found = findMarkdownFiles(dirPath, path.join(cwd, dir));
|
|
54
|
-
for (const rule of found) {
|
|
55
|
-
rules.push({
|
|
56
|
-
source,
|
|
57
|
-
name: rule.name,
|
|
58
|
-
filePath: rule.filePath,
|
|
59
|
-
relativePath: `${dir}/${rule.relativePath}`,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// Check for root-level rule files
|
|
65
|
-
for (const { file, source } of RULE_FILES) {
|
|
66
|
-
const filePath = path.join(cwd, file);
|
|
67
|
-
if (fs.existsSync(filePath)) {
|
|
68
|
-
rules.push({
|
|
69
|
-
source,
|
|
70
|
-
name: file,
|
|
71
|
-
filePath,
|
|
72
|
-
relativePath: file,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
rules,
|
|
78
|
-
hasCustomRules: rules.length > 0,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
export function formatRulesForPrompt(result) {
|
|
82
|
-
if (!result.hasCustomRules)
|
|
83
|
-
return "";
|
|
84
|
-
// Group by source
|
|
85
|
-
const bySource = new Map();
|
|
86
|
-
for (const rule of result.rules) {
|
|
87
|
-
const existing = bySource.get(rule.source) ?? [];
|
|
88
|
-
existing.push(rule);
|
|
89
|
-
bySource.set(rule.source, existing);
|
|
90
|
-
}
|
|
91
|
-
const sections = [];
|
|
92
|
-
for (const [source, rules] of bySource) {
|
|
93
|
-
const list = rules.map((r) => `- \`${r.relativePath}\``).join("\n");
|
|
94
|
-
sections.push(`From ${source}/:\n${list}`);
|
|
95
|
-
}
|
|
96
|
-
return sections.join("\n\n");
|
|
97
|
-
}
|