pi-lens 2.2.2 → 2.2.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/clients/dispatch/dispatcher.js +34 -1
- package/clients/dispatch/dispatcher.ts +39 -0
- package/clients/dispatch/runners/ast-grep.js +1 -0
- package/clients/dispatch/runners/ast-grep.ts +1 -0
- package/clients/dispatch/runners/pyright.js +32 -6
- package/clients/dispatch/runners/pyright.ts +35 -6
- package/clients/dispatch/runners/ruff.js +41 -8
- package/clients/dispatch/runners/ruff.ts +46 -9
- package/clients/dispatch/runners/type-safety.js +1 -0
- package/clients/dispatch/runners/type-safety.ts +1 -0
- package/clients/dispatch/types.ts +4 -0
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - OutputSemantic: How to display (blocking, warning, silent, etc.)
|
|
14
14
|
* - BaselineStore: Track pre-existing issues for delta mode
|
|
15
15
|
*/
|
|
16
|
+
import * as fs from "node:fs";
|
|
16
17
|
import { detectFileKind } from "../file-kinds.js";
|
|
17
18
|
// --- In-Memory Baseline Store ---
|
|
18
19
|
export function createBaselineStore() {
|
|
@@ -31,6 +32,8 @@ export function createBaselineStore() {
|
|
|
31
32
|
}
|
|
32
33
|
// --- Runner Registry ---
|
|
33
34
|
const globalRegistry = new Map();
|
|
35
|
+
// Track last-run mtime per file+runner to skip unchanged files
|
|
36
|
+
const lastRunMtimes = new Map(); // key: `${runnerId}:${filePath}` -> mtimeMs
|
|
34
37
|
export function registerRunner(runner) {
|
|
35
38
|
if (globalRegistry.has(runner.id)) {
|
|
36
39
|
console.error(`[dispatch] Duplicate runner: ${runner.id}`);
|
|
@@ -41,17 +44,31 @@ export function registerRunner(runner) {
|
|
|
41
44
|
export function getRunner(id) {
|
|
42
45
|
return globalRegistry.get(id);
|
|
43
46
|
}
|
|
44
|
-
export function getRunnersForKind(kind) {
|
|
47
|
+
export function getRunnersForKind(kind, filePath) {
|
|
45
48
|
if (!kind)
|
|
46
49
|
return [];
|
|
47
50
|
const runners = [];
|
|
51
|
+
const isTestFile = filePath ? isTest(filePath) : false;
|
|
48
52
|
for (const runner of globalRegistry.values()) {
|
|
53
|
+
// Skip runners that shouldn't run on test files
|
|
54
|
+
if (isTestFile && runner.skipTestFiles)
|
|
55
|
+
continue;
|
|
49
56
|
if (runner.appliesTo.includes(kind) || runner.appliesTo.length === 0) {
|
|
50
57
|
runners.push(runner);
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
return runners.sort((a, b) => a.priority - b.priority);
|
|
54
61
|
}
|
|
62
|
+
function isTest(filePath) {
|
|
63
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
64
|
+
return (normalized.includes(".test.") ||
|
|
65
|
+
normalized.includes(".spec.") ||
|
|
66
|
+
normalized.includes("/test/") ||
|
|
67
|
+
normalized.includes("/tests/") ||
|
|
68
|
+
normalized.includes("__tests__/") ||
|
|
69
|
+
normalized.includes("test-utils") ||
|
|
70
|
+
normalized.startsWith("test-"));
|
|
71
|
+
}
|
|
55
72
|
export function listRunners() {
|
|
56
73
|
return Array.from(globalRegistry.values());
|
|
57
74
|
}
|
|
@@ -206,6 +223,22 @@ export async function dispatchForFile(ctx, groups) {
|
|
|
206
223
|
}
|
|
207
224
|
// --- Run Single Runner ---
|
|
208
225
|
async function runRunner(ctx, runner, defaultSemantic) {
|
|
226
|
+
// Skip if file unchanged (optimization for expensive runners)
|
|
227
|
+
if (runner.skipIfUnchanged) {
|
|
228
|
+
const cacheKey = `${runner.id}:${ctx.filePath}`;
|
|
229
|
+
try {
|
|
230
|
+
const stats = fs.statSync(ctx.filePath);
|
|
231
|
+
const lastMtime = lastRunMtimes.get(cacheKey);
|
|
232
|
+
if (lastMtime && stats.mtimeMs <= lastMtime) {
|
|
233
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
234
|
+
}
|
|
235
|
+
// Update mtime after run (below)
|
|
236
|
+
lastRunMtimes.set(cacheKey, stats.mtimeMs);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// File doesn't exist or stat failed, continue with run
|
|
240
|
+
}
|
|
241
|
+
}
|
|
209
242
|
try {
|
|
210
243
|
const result = await runner.run(ctx);
|
|
211
244
|
return {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* - BaselineStore: Track pre-existing issues for delta mode
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import * as fs from "node:fs";
|
|
17
18
|
import type { FileKind } from "../file-kinds.js";
|
|
18
19
|
import { detectFileKind } from "../file-kinds.js";
|
|
19
20
|
|
|
@@ -51,6 +52,9 @@ export function createBaselineStore(): BaselineStore {
|
|
|
51
52
|
|
|
52
53
|
const globalRegistry = new Map<string, RunnerDefinition>();
|
|
53
54
|
|
|
55
|
+
// Track last-run mtime per file+runner to skip unchanged files
|
|
56
|
+
const lastRunMtimes = new Map<string, number>(); // key: `${runnerId}:${filePath}` -> mtimeMs
|
|
57
|
+
|
|
54
58
|
export function registerRunner(runner: RunnerDefinition): void {
|
|
55
59
|
if (globalRegistry.has(runner.id)) {
|
|
56
60
|
console.error(`[dispatch] Duplicate runner: ${runner.id}`);
|
|
@@ -65,10 +69,16 @@ export function getRunner(id: string): RunnerDefinition | undefined {
|
|
|
65
69
|
|
|
66
70
|
export function getRunnersForKind(
|
|
67
71
|
kind: FileKind | undefined,
|
|
72
|
+
filePath?: string,
|
|
68
73
|
): RunnerDefinition[] {
|
|
69
74
|
if (!kind) return [];
|
|
70
75
|
const runners: RunnerDefinition[] = [];
|
|
76
|
+
const isTestFile = filePath ? isTest(filePath) : false;
|
|
77
|
+
|
|
71
78
|
for (const runner of globalRegistry.values()) {
|
|
79
|
+
// Skip runners that shouldn't run on test files
|
|
80
|
+
if (isTestFile && runner.skipTestFiles) continue;
|
|
81
|
+
|
|
72
82
|
if (runner.appliesTo.includes(kind) || runner.appliesTo.length === 0) {
|
|
73
83
|
runners.push(runner);
|
|
74
84
|
}
|
|
@@ -76,6 +86,19 @@ export function getRunnersForKind(
|
|
|
76
86
|
return runners.sort((a, b) => a.priority - b.priority);
|
|
77
87
|
}
|
|
78
88
|
|
|
89
|
+
function isTest(filePath: string): boolean {
|
|
90
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
91
|
+
return (
|
|
92
|
+
normalized.includes(".test.") ||
|
|
93
|
+
normalized.includes(".spec.") ||
|
|
94
|
+
normalized.includes("/test/") ||
|
|
95
|
+
normalized.includes("/tests/") ||
|
|
96
|
+
normalized.includes("__tests__/") ||
|
|
97
|
+
normalized.includes("test-utils") ||
|
|
98
|
+
normalized.startsWith("test-")
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
79
102
|
export function listRunners(): RunnerDefinition[] {
|
|
80
103
|
return Array.from(globalRegistry.values());
|
|
81
104
|
}
|
|
@@ -288,6 +311,22 @@ async function runRunner(
|
|
|
288
311
|
runner: RunnerDefinition,
|
|
289
312
|
defaultSemantic: OutputSemantic,
|
|
290
313
|
): Promise<RunnerResult> {
|
|
314
|
+
// Skip if file unchanged (optimization for expensive runners)
|
|
315
|
+
if (runner.skipIfUnchanged) {
|
|
316
|
+
const cacheKey = `${runner.id}:${ctx.filePath}`;
|
|
317
|
+
try {
|
|
318
|
+
const stats = fs.statSync(ctx.filePath);
|
|
319
|
+
const lastMtime = lastRunMtimes.get(cacheKey);
|
|
320
|
+
if (lastMtime && stats.mtimeMs <= lastMtime) {
|
|
321
|
+
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
322
|
+
}
|
|
323
|
+
// Update mtime after run (below)
|
|
324
|
+
lastRunMtimes.set(cacheKey, stats.mtimeMs);
|
|
325
|
+
} catch {
|
|
326
|
+
// File doesn't exist or stat failed, continue with run
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
291
330
|
try {
|
|
292
331
|
const result = await runner.run(ctx);
|
|
293
332
|
return {
|
|
@@ -13,6 +13,7 @@ const astGrepRunner = {
|
|
|
13
13
|
appliesTo: ["jsts", "python", "go", "rust", "cxx"],
|
|
14
14
|
priority: 30,
|
|
15
15
|
enabledByDefault: false,
|
|
16
|
+
skipTestFiles: true, // Many rules are noisy in tests
|
|
16
17
|
async run(ctx) {
|
|
17
18
|
// Check if ast-grep is available
|
|
18
19
|
const check = spawnSync("sg", ["--version"], {
|
|
@@ -21,6 +21,7 @@ const astGrepRunner: RunnerDefinition = {
|
|
|
21
21
|
appliesTo: ["jsts", "python", "go", "rust", "cxx"],
|
|
22
22
|
priority: 30,
|
|
23
23
|
enabledByDefault: false,
|
|
24
|
+
skipTestFiles: true, // Many rules are noisy in tests
|
|
24
25
|
|
|
25
26
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
26
27
|
// Check if ast-grep is available
|
|
@@ -7,18 +7,44 @@
|
|
|
7
7
|
* Requires: pyright (pip install pyright or npm install -g pyright)
|
|
8
8
|
*/
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
10
12
|
// Cache pyright availability check
|
|
11
13
|
let pyrightAvailable = null;
|
|
12
|
-
|
|
14
|
+
let pyrightCommand = null;
|
|
15
|
+
/**
|
|
16
|
+
* Find pyright command, checking venv first, then global.
|
|
17
|
+
* Looks in .venv/bin, venv/bin (Unix), .venv/Scripts, venv/Scripts (Windows)
|
|
18
|
+
*/
|
|
19
|
+
function findPyrightCommand(cwd) {
|
|
20
|
+
// Check common venv locations
|
|
21
|
+
const venvPaths = [
|
|
22
|
+
".venv/bin/pyright",
|
|
23
|
+
"venv/bin/pyright",
|
|
24
|
+
".venv/Scripts/pyright.exe",
|
|
25
|
+
"venv/Scripts/pyright.exe",
|
|
26
|
+
];
|
|
27
|
+
for (const venvPath of venvPaths) {
|
|
28
|
+
const fullPath = path.join(cwd, venvPath);
|
|
29
|
+
if (fs.existsSync(fullPath)) {
|
|
30
|
+
return `"${fullPath}"`; // Quote for Windows paths with spaces
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Fall back to global
|
|
34
|
+
return "pyright";
|
|
35
|
+
}
|
|
36
|
+
function isPyrightAvailable(cwd) {
|
|
13
37
|
if (pyrightAvailable !== null)
|
|
14
38
|
return pyrightAvailable;
|
|
15
|
-
|
|
16
|
-
const check = spawnSync(
|
|
39
|
+
const command = findPyrightCommand(cwd || process.cwd());
|
|
40
|
+
const check = spawnSync(command, ["--version"], {
|
|
17
41
|
encoding: "utf-8",
|
|
18
42
|
timeout: 5000,
|
|
19
43
|
shell: true,
|
|
20
44
|
});
|
|
21
45
|
pyrightAvailable = !check.error && check.status === 0;
|
|
46
|
+
if (pyrightAvailable)
|
|
47
|
+
pyrightCommand = command;
|
|
22
48
|
return pyrightAvailable;
|
|
23
49
|
}
|
|
24
50
|
const pyrightRunner = {
|
|
@@ -28,11 +54,11 @@ const pyrightRunner = {
|
|
|
28
54
|
enabledByDefault: true,
|
|
29
55
|
async run(ctx) {
|
|
30
56
|
// Skip if pyright is not installed
|
|
31
|
-
if (!isPyrightAvailable()) {
|
|
57
|
+
if (!isPyrightAvailable(ctx.cwd || process.cwd())) {
|
|
32
58
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
33
59
|
}
|
|
34
|
-
// Run pyright with JSON output (use
|
|
35
|
-
const result = spawnSync(
|
|
60
|
+
// Run pyright with JSON output (use venv-local or global command)
|
|
61
|
+
const result = spawnSync(pyrightCommand, ["--outputjson", ctx.filePath], {
|
|
36
62
|
encoding: "utf-8",
|
|
37
63
|
timeout: 60000,
|
|
38
64
|
shell: true,
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
11
13
|
import type {
|
|
12
14
|
Diagnostic,
|
|
13
15
|
DispatchContext,
|
|
@@ -17,17 +19,44 @@ import type {
|
|
|
17
19
|
|
|
18
20
|
// Cache pyright availability check
|
|
19
21
|
let pyrightAvailable: boolean | null = null;
|
|
22
|
+
let pyrightCommand: string | null = null;
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Find pyright command, checking venv first, then global.
|
|
26
|
+
* Looks in .venv/bin, venv/bin (Unix), .venv/Scripts, venv/Scripts (Windows)
|
|
27
|
+
*/
|
|
28
|
+
function findPyrightCommand(cwd: string): string {
|
|
29
|
+
// Check common venv locations
|
|
30
|
+
const venvPaths = [
|
|
31
|
+
".venv/bin/pyright",
|
|
32
|
+
"venv/bin/pyright",
|
|
33
|
+
".venv/Scripts/pyright.exe",
|
|
34
|
+
"venv/Scripts/pyright.exe",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
for (const venvPath of venvPaths) {
|
|
38
|
+
const fullPath = path.join(cwd, venvPath);
|
|
39
|
+
if (fs.existsSync(fullPath)) {
|
|
40
|
+
return `"${fullPath}"`; // Quote for Windows paths with spaces
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Fall back to global
|
|
45
|
+
return "pyright";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isPyrightAvailable(cwd?: string): boolean {
|
|
22
49
|
if (pyrightAvailable !== null) return pyrightAvailable;
|
|
23
50
|
|
|
24
|
-
|
|
25
|
-
|
|
51
|
+
const command = findPyrightCommand(cwd || process.cwd());
|
|
52
|
+
|
|
53
|
+
const check = spawnSync(command, ["--version"], {
|
|
26
54
|
encoding: "utf-8",
|
|
27
55
|
timeout: 5000,
|
|
28
56
|
shell: true,
|
|
29
57
|
});
|
|
30
58
|
pyrightAvailable = !check.error && check.status === 0;
|
|
59
|
+
if (pyrightAvailable) pyrightCommand = command;
|
|
31
60
|
return pyrightAvailable;
|
|
32
61
|
}
|
|
33
62
|
|
|
@@ -39,12 +68,12 @@ const pyrightRunner: RunnerDefinition = {
|
|
|
39
68
|
|
|
40
69
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
41
70
|
// Skip if pyright is not installed
|
|
42
|
-
if (!isPyrightAvailable()) {
|
|
71
|
+
if (!isPyrightAvailable(ctx.cwd || process.cwd())) {
|
|
43
72
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
44
73
|
}
|
|
45
74
|
|
|
46
|
-
// Run pyright with JSON output (use
|
|
47
|
-
const result = spawnSync(
|
|
75
|
+
// Run pyright with JSON output (use venv-local or global command)
|
|
76
|
+
const result = spawnSync(pyrightCommand!, ["--outputjson", ctx.filePath], {
|
|
48
77
|
encoding: "utf-8",
|
|
49
78
|
timeout: 60000,
|
|
50
79
|
shell: true,
|
|
@@ -2,29 +2,62 @@
|
|
|
2
2
|
* Ruff runner for dispatch system
|
|
3
3
|
*
|
|
4
4
|
* Ruff handles both formatting and linting for Python files.
|
|
5
|
+
* Supports venv-local installations.
|
|
5
6
|
*/
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
7
10
|
import { stripAnsi } from "../../sanitize.js";
|
|
11
|
+
// Cache ruff availability check
|
|
12
|
+
let ruffAvailable = null;
|
|
13
|
+
let ruffCommand = null;
|
|
14
|
+
/**
|
|
15
|
+
* Find ruff command, checking venv first, then global.
|
|
16
|
+
*/
|
|
17
|
+
function findRuffCommand(cwd) {
|
|
18
|
+
const venvPaths = [
|
|
19
|
+
".venv/bin/ruff",
|
|
20
|
+
"venv/bin/ruff",
|
|
21
|
+
".venv/Scripts/ruff.exe",
|
|
22
|
+
"venv/Scripts/ruff.exe",
|
|
23
|
+
];
|
|
24
|
+
for (const venvPath of venvPaths) {
|
|
25
|
+
const fullPath = path.join(cwd, venvPath);
|
|
26
|
+
if (fs.existsSync(fullPath)) {
|
|
27
|
+
return `"${fullPath}"`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return "ruff";
|
|
31
|
+
}
|
|
32
|
+
function isRuffAvailable(cwd) {
|
|
33
|
+
if (ruffAvailable !== null)
|
|
34
|
+
return ruffAvailable;
|
|
35
|
+
const command = findRuffCommand(cwd || process.cwd());
|
|
36
|
+
const check = spawnSync(command, ["--version"], {
|
|
37
|
+
encoding: "utf-8",
|
|
38
|
+
timeout: 5000,
|
|
39
|
+
shell: true,
|
|
40
|
+
});
|
|
41
|
+
ruffAvailable = !check.error && check.status === 0;
|
|
42
|
+
if (ruffAvailable)
|
|
43
|
+
ruffCommand = command;
|
|
44
|
+
return ruffAvailable;
|
|
45
|
+
}
|
|
8
46
|
const ruffRunner = {
|
|
9
47
|
id: "ruff-lint",
|
|
10
48
|
appliesTo: ["python"],
|
|
11
49
|
priority: 10,
|
|
12
50
|
enabledByDefault: true,
|
|
13
51
|
async run(ctx) {
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
encoding: "utf-8",
|
|
17
|
-
timeout: 5000,
|
|
18
|
-
shell: true,
|
|
19
|
-
});
|
|
20
|
-
if (check.error || check.status !== 0) {
|
|
52
|
+
// Skip if ruff is not installed
|
|
53
|
+
if (!isRuffAvailable(ctx.cwd || process.cwd())) {
|
|
21
54
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
22
55
|
}
|
|
23
56
|
// Run ruff check
|
|
24
57
|
const args = ctx.autofix
|
|
25
58
|
? ["check", "--fix", ctx.filePath]
|
|
26
59
|
: ["check", ctx.filePath];
|
|
27
|
-
const result = spawnSync(
|
|
60
|
+
const result = spawnSync(ruffCommand, args, {
|
|
28
61
|
encoding: "utf-8",
|
|
29
62
|
timeout: 30000,
|
|
30
63
|
shell: true,
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Ruff runner for dispatch system
|
|
3
3
|
*
|
|
4
4
|
* Ruff handles both formatting and linting for Python files.
|
|
5
|
+
* Supports venv-local installations.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { spawnSync } from "node:child_process";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
8
11
|
import { stripAnsi } from "../../sanitize.js";
|
|
9
12
|
import type {
|
|
10
13
|
Diagnostic,
|
|
@@ -13,6 +16,46 @@ import type {
|
|
|
13
16
|
RunnerResult,
|
|
14
17
|
} from "../types.js";
|
|
15
18
|
|
|
19
|
+
// Cache ruff availability check
|
|
20
|
+
let ruffAvailable: boolean | null = null;
|
|
21
|
+
let ruffCommand: string | null = null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Find ruff command, checking venv first, then global.
|
|
25
|
+
*/
|
|
26
|
+
function findRuffCommand(cwd: string): string {
|
|
27
|
+
const venvPaths = [
|
|
28
|
+
".venv/bin/ruff",
|
|
29
|
+
"venv/bin/ruff",
|
|
30
|
+
".venv/Scripts/ruff.exe",
|
|
31
|
+
"venv/Scripts/ruff.exe",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const venvPath of venvPaths) {
|
|
35
|
+
const fullPath = path.join(cwd, venvPath);
|
|
36
|
+
if (fs.existsSync(fullPath)) {
|
|
37
|
+
return `"${fullPath}"`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return "ruff";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isRuffAvailable(cwd?: string): boolean {
|
|
45
|
+
if (ruffAvailable !== null) return ruffAvailable;
|
|
46
|
+
|
|
47
|
+
const command = findRuffCommand(cwd || process.cwd());
|
|
48
|
+
const check = spawnSync(command, ["--version"], {
|
|
49
|
+
encoding: "utf-8",
|
|
50
|
+
timeout: 5000,
|
|
51
|
+
shell: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
ruffAvailable = !check.error && check.status === 0;
|
|
55
|
+
if (ruffAvailable) ruffCommand = command;
|
|
56
|
+
return ruffAvailable;
|
|
57
|
+
}
|
|
58
|
+
|
|
16
59
|
const ruffRunner: RunnerDefinition = {
|
|
17
60
|
id: "ruff-lint",
|
|
18
61
|
appliesTo: ["python"],
|
|
@@ -20,14 +63,8 @@ const ruffRunner: RunnerDefinition = {
|
|
|
20
63
|
enabledByDefault: true,
|
|
21
64
|
|
|
22
65
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
encoding: "utf-8",
|
|
26
|
-
timeout: 5000,
|
|
27
|
-
shell: true,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (check.error || check.status !== 0) {
|
|
66
|
+
// Skip if ruff is not installed
|
|
67
|
+
if (!isRuffAvailable(ctx.cwd || process.cwd())) {
|
|
31
68
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
32
69
|
}
|
|
33
70
|
|
|
@@ -36,7 +73,7 @@ const ruffRunner: RunnerDefinition = {
|
|
|
36
73
|
? ["check", "--fix", ctx.filePath]
|
|
37
74
|
: ["check", ctx.filePath];
|
|
38
75
|
|
|
39
|
-
const result = spawnSync(
|
|
76
|
+
const result = spawnSync(ruffCommand!, args, {
|
|
40
77
|
encoding: "utf-8",
|
|
41
78
|
timeout: 30000,
|
|
42
79
|
shell: true,
|
|
@@ -20,6 +20,7 @@ const typeSafetyRunner: RunnerDefinition = {
|
|
|
20
20
|
appliesTo: ["jsts"],
|
|
21
21
|
priority: 20,
|
|
22
22
|
enabledByDefault: true,
|
|
23
|
+
skipIfUnchanged: true, // Skip type-coverage if file unchanged
|
|
23
24
|
|
|
24
25
|
async run(ctx: DispatchContext): Promise<RunnerResult> {
|
|
25
26
|
// Only check TypeScript files
|
|
@@ -96,6 +96,10 @@ export interface RunnerDefinition {
|
|
|
96
96
|
appliesTo: readonly FileKind[];
|
|
97
97
|
priority: number;
|
|
98
98
|
enabledByDefault: boolean;
|
|
99
|
+
/** Skip this runner for test files (false positive reduction) */
|
|
100
|
+
skipTestFiles?: boolean;
|
|
101
|
+
/** Skip if file unchanged since last run (check mtime) */
|
|
102
|
+
skipIfUnchanged?: boolean;
|
|
99
103
|
/** Check if runner should run */
|
|
100
104
|
when?: (ctx: DispatchContext) => Promise<boolean> | boolean;
|
|
101
105
|
/** Execute the runner */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lens",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Real-time code quality feedback for pi — TypeScript LSP, Biome, ast-grep, Ruff, complexity metrics, duplicate detection. Includes automated fix loop (/lens-booboo-fix) and interactive architectural refactoring (/lens-booboo-refactor) with browser-based interviews.",
|
|
6
6
|
"repository": {
|