pi-lens 2.2.3 → 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.
@@ -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
@@ -12,6 +12,7 @@ const typeSafetyRunner = {
12
12
  appliesTo: ["jsts"],
13
13
  priority: 20,
14
14
  enabledByDefault: true,
15
+ skipIfUnchanged: true, // Skip type-coverage if file unchanged
15
16
  async run(ctx) {
16
17
  // Only check TypeScript files
17
18
  if (!ctx.filePath.match(/\.tsx?$/)) {
@@ -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",
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": {