pi-lens 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +14 -15
  3. package/index.ts +41 -34
  4. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to pi-lens will be documented in this file.
4
4
 
5
+ ## [2.0.1] - 2026-03-25
6
+
7
+ ### Fixed
8
+ - **ast-grep in `/lens-booboo` was silently dropping all results** — newer ast-grep versions exit `0` with `--json` even when issues are found; fixed the exit code check.
9
+ - **Renamed "Design Smells" to "ast-grep"** in booboo report — the scan runs all 65 rules (security, correctness, style, design), not just design smells.
10
+
11
+ ### Changed
12
+ - **Stronger real-time feedback messages** — all messages now use severity emoji and imperative language:
13
+ - `🔴 Fix N TypeScript error(s) — these must be resolved`
14
+ - `🧹 Remove N unused import(s) — they are dead code`
15
+ - `🔴 You introduced N new structural violation(s) — fix before moving on`
16
+ - `🟠 You introduced N new Biome violation(s) — fix before moving on`
17
+ - `🟡 Complexity issues — refactor when you get a chance`
18
+ - `🟠 This file has N duplicate block(s) — extract to shared utilities`
19
+ - `🔴 Do not redefine — N function(s) already exist elsewhere`
20
+ - **Biome fix command is now a real bash command** — `npx @biomejs/biome check --write <file>` instead of `/lens-format` (which is a pi UI command, not runnable from agent tools).
21
+ - **Complexity warnings skip test files in real-time** — same exclusion as lens-booboo.
22
+
5
23
  ## [2.0.0] - 2026-03-25
6
24
 
7
25
  ### Added
package/README.md CHANGED
@@ -24,30 +24,29 @@ Real-time code quality feedback for [pi](https://github.com/mariozechner/pi-codi
24
24
  ast-grep and Biome run in **delta mode** — only violations *introduced by the current edit* are shown. Pre-existing issues are silent. Fixed violations are acknowledged.
25
25
 
26
26
  ```
27
- [TypeScript] 2 issue(s):
28
- [error] L10: Type 'string' is not assignable to type 'number'
27
+ 🔴 Fix 2 TypeScript error(s) — these must be resolved:
28
+ L10: Type 'string' is not assignable to type 'number'
29
29
 
30
- [ast-grep] +1 new issue(s) introduced:
31
- no-var: Use 'const' or 'let' instead of 'var' (L23) [fixable]
30
+ 🔴 You introduced 1 new structural violation(s) — fix before moving on:
31
+ no-var: Use 'const' or 'let' instead of 'var' (L23)
32
32
  → var has function scope and can lead to unexpected hoisting behavior.
33
- (18 total)
33
+ (18 total remaining)
34
34
 
35
- [ast-grep] Fixed: no-console-log (-1)
35
+ ast-grep: fixed no-console-log (-1)
36
36
 
37
- [Biome] +1 new issue(s) introduced:
37
+ 🟠 You introduced 1 new Biome violation(s) — fix before moving on:
38
38
  L23:5 [style/useConst] This let declares a variable that is only assigned once.
39
- 1 fixable run /lens-format
40
- (4 total)
39
+ Auto-fixable: `npx @biomejs/biome check --write utils.ts`
40
+ (4 total remaining)
41
41
 
42
- [jscpd] 1 duplicate block(s) involving utils.ts:
43
- 15 lines helpers.ts:20
44
- → Extract duplicated code to a shared utility function
42
+ 🟠 This file has 1 duplicate block(s) extract to shared utilities:
43
+ 15 lines duplicated with helpers.ts:20
45
44
 
46
- [Duplicate Exports] 1 function(s) already exist:
45
+ 🔴 Do not redefine — 1 function(s) already exist elsewhere:
47
46
  formatDate (already in helpers.ts)
48
- → Import the existing function instead of redefining it
47
+ → Import the existing function instead
49
48
 
50
- [Complexity Warnings]
49
+ 🟡 Complexity issues — refactor when you get a chance:
51
50
  ⚠ Maintainability dropped to 55 — extract logic into helper functions
52
51
  ⚠ AI-style comments (6) — remove hand-holding comments
53
52
  ⚠ Many try/catch blocks (7) — consolidate error handling
package/index.ts CHANGED
@@ -254,7 +254,7 @@ export default function (pi: ExtensionAPI) {
254
254
  });
255
255
 
256
256
  const output = result.stdout || result.stderr || "";
257
- if (output.trim() && result.status === 1) {
257
+ if (output.trim() && result.status !== undefined) {
258
258
  let issues: Array<{line: number; rule: string; message: string}> = [];
259
259
  const lines = output.split("\n").filter((l: string) => l.trim());
260
260
 
@@ -279,7 +279,7 @@ export default function (pi: ExtensionAPI) {
279
279
 
280
280
  if (issues.length > 0) {
281
281
  // UI summary (truncated)
282
- let report = `[Design Smells] ${issues.length} issue(s) found:\n`;
282
+ let report = `[ast-grep] ${issues.length} issue(s) found:\n`;
283
283
  for (const issue of issues.slice(0, 20)) {
284
284
  report += ` L${issue.line}: ${issue.rule} — ${issue.message}\n`;
285
285
  }
@@ -289,7 +289,7 @@ export default function (pi: ExtensionAPI) {
289
289
  parts.push(report);
290
290
 
291
291
  // Full report for file
292
- let fullSection = `## Design Smells\n\n**${issues.length} issue(s) found**\n\n`;
292
+ let fullSection = `## ast-grep (Structural Issues)\n\n**${issues.length} issue(s) found**\n\n`;
293
293
  fullSection += "| Line | Rule | Message |\n|------|------|--------|\n";
294
294
  for (const issue of issues) {
295
295
  fullSection += `| ${issue.line} | ${issue.rule} | ${issue.message} |\n`;
@@ -1103,18 +1103,26 @@ export default function (pi: ExtensionAPI) {
1103
1103
  const otherDiags = diags.filter(d => d.code !== 6133 && d.code !== 6196);
1104
1104
 
1105
1105
  if (unusedImports.length > 0) {
1106
- lspOutput += `\n\n[Unused Imports] ${unusedImports.length} imported but never used:\n`;
1106
+ lspOutput += `\n\n🧹 Remove ${unusedImports.length} unused import(s) they are dead code:\n`;
1107
1107
  for (const d of unusedImports.slice(0, 10)) {
1108
1108
  lspOutput += ` L${d.range.start.line + 1}: ${d.message}\n`;
1109
1109
  }
1110
- lspOutput += ` → Remove unused imports to reduce noise\n`;
1111
1110
  }
1112
1111
 
1113
1112
  if (otherDiags.length > 0) {
1114
- lspOutput += `\n\n[TypeScript] ${otherDiags.length} issue(s):\n`;
1115
- for (const d of otherDiags.slice(0, 10)) {
1116
- const label = d.severity === 2 ? "Warning" : "Error";
1117
- lspOutput += ` [${label}] L${d.range.start.line + 1}: ${d.message}\n`;
1113
+ const errors = otherDiags.filter(d => d.severity !== 2);
1114
+ const warnings = otherDiags.filter(d => d.severity === 2);
1115
+ if (errors.length > 0) {
1116
+ lspOutput += `\n\n🔴 Fix ${errors.length} TypeScript error(s) — these must be resolved:\n`;
1117
+ for (const d of errors.slice(0, 10)) {
1118
+ lspOutput += ` L${d.range.start.line + 1}: ${d.message}\n`;
1119
+ }
1120
+ }
1121
+ if (warnings.length > 0) {
1122
+ lspOutput += `\n\n🟡 ${warnings.length} TypeScript warning(s) — address before moving on:\n`;
1123
+ for (const d of warnings.slice(0, 10)) {
1124
+ lspOutput += ` L${d.range.start.line + 1}: ${d.message}\n`;
1125
+ }
1118
1126
  }
1119
1127
  }
1120
1128
  }
@@ -1157,10 +1165,10 @@ export default function (pi: ExtensionAPI) {
1157
1165
  }
1158
1166
  } else {
1159
1167
  if (diags.length > 0)
1160
- lspOutput += `\n\n${ruffClient.formatDiagnostics(diags)}`;
1168
+ lspOutput += `\n\n🟠 Fix ${diags.length} Ruff issue(s):\n${ruffClient.formatDiagnostics(diags)}`;
1161
1169
  if (fmtReport) lspOutput += `\n\n${fmtReport}`;
1162
1170
  if (fixable.length > 0 || hasFormatIssues) {
1163
- lspOutput += `\n\n[Ruff] ${fixable.length} fixable enable --autofix-ruff flag to auto-fix`;
1171
+ lspOutput += `\n Enable --autofix-ruff to auto-fix ${fixable.length} of these on every write`;
1164
1172
  }
1165
1173
  }
1166
1174
  }
@@ -1195,17 +1203,16 @@ export default function (pi: ExtensionAPI) {
1195
1203
  }
1196
1204
 
1197
1205
  if (newViolations.length > 0) {
1198
- lspOutput += `\n\n[ast-grep] +${newViolations.length} new issue(s) introduced:\n`;
1206
+ const hasFixable = newViolations.some(v => v.fix);
1207
+ lspOutput += `\n\n🔴 You introduced ${newViolations.length} new structural violation(s) — fix before moving on:\n`;
1199
1208
  lspOutput += astGrepClient.formatDiagnostics(newViolations);
1209
+ if (hasFixable) lspOutput += `\n Some are fixable — check the → hints above`;
1200
1210
  }
1201
1211
  if (fixedRules.length > 0) {
1202
- lspOutput += `\n\n[ast-grep] Fixed: ${fixedRules.join(", ")}`;
1212
+ lspOutput += `\n\nast-grep: fixed ${fixedRules.join(", ")}`;
1203
1213
  }
1204
- // Show total count quietly so context isn't lost
1205
- if (after.length > 0 && newViolations.length === 0 && fixedRules.length === 0) {
1206
- // no change — show nothing
1207
- } else if (after.length > 0) {
1208
- lspOutput += `\n (${after.length} total)`;
1214
+ if (after.length > 0 && newViolations.length > 0) {
1215
+ lspOutput += `\n (${after.length} total remaining)`;
1209
1216
  }
1210
1217
  }
1211
1218
 
@@ -1253,17 +1260,15 @@ export default function (pi: ExtensionAPI) {
1253
1260
  } else {
1254
1261
  if (newDiags.length > 0) {
1255
1262
  const fixable = newDiags.filter((d) => d.fixable);
1256
- lspOutput += `\n\n[Biome] +${newDiags.length} new issue(s) introduced:\n`;
1263
+ lspOutput += `\n\n🟠 You introduced ${newDiags.length} new Biome violation(s) — fix before moving on:\n`;
1257
1264
  lspOutput += biomeClient.formatDiagnostics(newDiags, filePath);
1258
- if (fixable.length > 0) lspOutput += `\n\n[Biome] ${fixable.length} fixable run /lens-format`;
1265
+ if (fixable.length > 0) lspOutput += `\n Auto-fixable: \`npx @biomejs/biome check --write ${path.basename(filePath)}\``;
1259
1266
  }
1260
1267
  if (fixedRules.length > 0) {
1261
- lspOutput += `\n\n[Biome] Fixed: ${fixedRules.join(", ")}`;
1268
+ lspOutput += `\n\nBiome: fixed ${fixedRules.join(", ")}`;
1262
1269
  }
1263
- if (after.length > 0 && newDiags.length === 0 && fixedRules.length === 0) {
1264
- // no change show nothing
1265
- } else if (after.length > 0) {
1266
- lspOutput += `\n (${after.length} total)`;
1270
+ if (after.length > 0 && newDiags.length > 0) {
1271
+ lspOutput += `\n (${after.length} total remaining)`;
1267
1272
  }
1268
1273
  }
1269
1274
  }
@@ -1291,11 +1296,14 @@ export default function (pi: ExtensionAPI) {
1291
1296
  if (metrics) {
1292
1297
  const warnings = complexityClient.checkThresholds(metrics);
1293
1298
  if (warnings.length > 0) {
1294
- let warningReport = `[Complexity Warnings]\n`;
1295
- for (const w of warnings) {
1296
- warningReport += ` ⚠ ${w}\n`;
1299
+ const isTestFile = /\.(test|spec)\.[jt]sx?$/.test(filePath);
1300
+ if (!isTestFile) {
1301
+ let warningReport = `🟡 Complexity issues — refactor when you get a chance:\n`;
1302
+ for (const w of warnings) {
1303
+ warningReport += ` ⚠ ${w}\n`;
1304
+ }
1305
+ lspOutput += `\n\n${warningReport}`;
1297
1306
  }
1298
- lspOutput += `\n\n${warningReport}`;
1299
1307
  }
1300
1308
  }
1301
1309
  }
@@ -1335,17 +1343,16 @@ export default function (pi: ExtensionAPI) {
1335
1343
  );
1336
1344
  if (fileClones.length > 0) {
1337
1345
  dbg(` jscpd: ${fileClones.length} duplicate(s) involving ${filePath}`);
1338
- let dupReport = `[jscpd] ${fileClones.length} duplicate block(s) involving ${path.basename(filePath)}:\n`;
1346
+ let dupReport = `🟠 This file has ${fileClones.length} duplicate block(s) extract to shared utilities:\n`;
1339
1347
  for (const clone of fileClones.slice(0, 3)) {
1340
1348
  const other = path.resolve(clone.fileA) === path.resolve(filePath)
1341
1349
  ? `${path.basename(clone.fileB)}:${clone.startB}`
1342
1350
  : `${path.basename(clone.fileA)}:${clone.startA}`;
1343
- dupReport += ` ${clone.lines} lines ${other}\n`;
1351
+ dupReport += ` ${clone.lines} lines duplicated with ${other}\n`;
1344
1352
  }
1345
1353
  if (fileClones.length > 3) {
1346
1354
  dupReport += ` ... and ${fileClones.length - 3} more\n`;
1347
1355
  }
1348
- dupReport += ` → Extract duplicated code to a shared utility function\n`;
1349
1356
  lspOutput += `\n\n${dupReport}`;
1350
1357
  }
1351
1358
  }
@@ -1365,11 +1372,11 @@ export default function (pi: ExtensionAPI) {
1365
1372
  }
1366
1373
  if (dupes.length > 0) {
1367
1374
  dbg(` duplicate exports: ${dupes.length} found`);
1368
- let exportReport = `[Duplicate Exports] ${dupes.length} function(s) already exist:\n`;
1375
+ let exportReport = `🔴 Do not redefine — ${dupes.length} function(s) already exist elsewhere:\n`;
1369
1376
  for (const dupe of dupes.slice(0, 5)) {
1370
1377
  exportReport += ` ${dupe}\n`;
1371
1378
  }
1372
- exportReport += ` → Import the existing function instead of redefining it\n`;
1379
+ exportReport += ` → Import the existing function instead\n`;
1373
1380
  lspOutput += `\n\n${exportReport}`;
1374
1381
  }
1375
1382
  // Update cache with new exports
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-lens",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Real-time code feedback for pi — TypeScript LSP, Biome, ast-grep, Ruff, TODO scanner, dead code, duplicate detection, type coverage",
5
5
  "repository": {
6
6
  "type": "git",