pi-lens 1.3.4 → 1.3.5
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 +14 -0
- package/README.md +7 -7
- package/index.ts +102 -197
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-lens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.5.0] - 2026-03-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Real-time jscpd duplicate detection**: Code duplication is now detected on every write. Duplicates involving the edited file are shown to the agent in real-time.
|
|
9
|
+
- **`/lens-review` command**: Combined code review: design smells + complexity metrics in one command.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **Consistent command prefix**: All commands now start with `lens-`.
|
|
13
|
+
- `/find-todos` → `/lens-todos`
|
|
14
|
+
- `/dead-code` → `/lens-dead-code`
|
|
15
|
+
- `/check-deps` → `/lens-deps`
|
|
16
|
+
- `/format` → `/lens-format`
|
|
17
|
+
- `/design-review` + `/lens-metrics` → `/lens-review`
|
|
18
|
+
|
|
5
19
|
## [1.4.0] - 2026-03-23
|
|
6
20
|
|
|
7
21
|
### Added
|
package/README.md
CHANGED
|
@@ -12,10 +12,11 @@ Real-time code quality feedback for [pi](https://github.com/mariozechner/pi-codi
|
|
|
12
12
|
|---|---|
|
|
13
13
|
| **TypeScript LSP** | Type errors and warnings, using the project's `tsconfig.json` (walks up from the file to find it; falls back to `ES2020 + DOM` defaults) |
|
|
14
14
|
| **ast-grep** | 60+ structural rules: `no-var`, `no-eval`, `no-debugger`, `no-as-any`, `prefer-template`, `no-throw-string`, `no-hardcoded-secrets`, `no-return-await`, nested ternaries, strict equality, and more |
|
|
15
|
-
| **Biome** | Lint + format for JS/TS/JSX/TSX/CSS/JSON. Auto-
|
|
15
|
+
| **Biome** | Lint + format for JS/TS/JSX/TSX/CSS/JSON. Auto-fix disabled by default, use `/lens-format` to apply |
|
|
16
16
|
| **Ruff** | Lint + format for Python. Auto-fixes on every write by default |
|
|
17
17
|
| **Test Runner** | Runs corresponding test file when you edit source code (vitest, jest, pytest). Silent if no test file exists. |
|
|
18
18
|
| **Complexity Metrics** | AST-based analysis: Maintainability Index, Cyclomatic/Cognitive Complexity, Halstead Volume, nesting depth, function length. |
|
|
19
|
+
| **jscpd** | Code duplication detection. Warns when editing a file that has duplicates with other files in the project. |
|
|
19
20
|
|
|
20
21
|
### Pre-write hints
|
|
21
22
|
|
|
@@ -68,12 +69,11 @@ Example:
|
|
|
68
69
|
|
|
69
70
|
| Command | Description |
|
|
70
71
|
|---|---|
|
|
71
|
-
| `/
|
|
72
|
-
| `/dead-code` | Find unused exports/files/dependencies (requires knip) |
|
|
73
|
-
| `/
|
|
74
|
-
| `/format [file
|
|
75
|
-
| `/
|
|
76
|
-
| `/lens-metrics [path]` | Full project complexity scan (Maintainability Index, Cognitive/Cyclomatic Complexity, Halstead Volume) |
|
|
72
|
+
| `/lens-todos [path]` | Scan for TODO/FIXME/HACK annotations |
|
|
73
|
+
| `/lens-dead-code` | Find unused exports/files/dependencies (requires knip) |
|
|
74
|
+
| `/lens-deps` | Circular dependency scan (requires madge) |
|
|
75
|
+
| `/lens-format [file\|--all]` | Apply Biome formatting |
|
|
76
|
+
| `/lens-review [path]` | Code review: design smells + complexity metrics |
|
|
77
77
|
|
|
78
78
|
### On-demand tools
|
|
79
79
|
|
package/index.ts
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
* - Warns when target file already has existing violations
|
|
12
12
|
*
|
|
13
13
|
* Auto-fix on write (enable with --autofix-ruff flag, Biome auto-fix disabled by default):
|
|
14
|
-
* - Biome: feedback only by default, use /format to apply fixes
|
|
14
|
+
* - Biome: feedback only by default, use /lens-format to apply fixes
|
|
15
15
|
* - Ruff: applies --fix + format (lint + format fixes)
|
|
16
16
|
*
|
|
17
17
|
* On-demand commands:
|
|
18
|
-
* - /format - Apply Biome formatting
|
|
19
|
-
* - /
|
|
20
|
-
* - /dead-code - Find unused exports/dependencies (requires knip)
|
|
21
|
-
* - /
|
|
18
|
+
* - /lens-format - Apply Biome formatting
|
|
19
|
+
* - /lens-todos - Scan for TODO/FIXME/HACK annotations
|
|
20
|
+
* - /lens-dead-code - Find unused exports/dependencies (requires knip)
|
|
21
|
+
* - /lens-deps - Full circular dependency scan (requires madge)
|
|
22
22
|
*
|
|
23
23
|
* External dependencies:
|
|
24
24
|
* - npm: @biomejs/biome, @ast-grep/cli, knip, madge
|
|
@@ -154,9 +154,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
154
154
|
|
|
155
155
|
// --- Commands ---
|
|
156
156
|
|
|
157
|
-
pi.registerCommand("
|
|
157
|
+
pi.registerCommand("lens-todos", {
|
|
158
158
|
description:
|
|
159
|
-
"Scan for TODO/FIXME/HACK annotations. Usage: /
|
|
159
|
+
"Scan for TODO/FIXME/HACK annotations. Usage: /lens-todos [path]",
|
|
160
160
|
handler: async (args, ctx) => {
|
|
161
161
|
const targetPath = args.trim() || ctx.cwd || process.cwd();
|
|
162
162
|
ctx.ui.notify("🔍 Scanning for TODOs...", "info");
|
|
@@ -172,8 +172,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
172
172
|
},
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
pi.registerCommand("dead-code", {
|
|
176
|
-
description: "Check for unused exports, files, and dependencies",
|
|
175
|
+
pi.registerCommand("lens-dead-code", {
|
|
176
|
+
description: "Check for unused exports, files, and dependencies. Usage: /lens-dead-code [path]",
|
|
177
177
|
handler: async (args, ctx) => {
|
|
178
178
|
if (!knipClient.isAvailable()) {
|
|
179
179
|
ctx.ui.notify("Knip not installed. Run: npm install -D knip", "error");
|
|
@@ -192,8 +192,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
192
192
|
},
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
pi.registerCommand("
|
|
196
|
-
description: "Check for circular dependencies
|
|
195
|
+
pi.registerCommand("lens-deps", {
|
|
196
|
+
description: "Check for circular dependencies. Usage: /lens-deps [path]",
|
|
197
197
|
handler: async (args, ctx) => {
|
|
198
198
|
if (!depChecker.isAvailable()) {
|
|
199
199
|
ctx.ui.notify(
|
|
@@ -215,95 +215,78 @@ export default function (pi: ExtensionAPI) {
|
|
|
215
215
|
},
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
pi.registerCommand("
|
|
218
|
+
pi.registerCommand("lens-review", {
|
|
219
219
|
description:
|
|
220
|
-
"
|
|
220
|
+
"Code review: design smells + complexity metrics. Usage: /lens-review [path]",
|
|
221
221
|
handler: async (args, ctx) => {
|
|
222
|
-
if (!astGrepClient.isAvailable()) {
|
|
223
|
-
ctx.ui.notify(
|
|
224
|
-
"ast-grep not installed. Run: npm i -D @ast-grep/cli",
|
|
225
|
-
"error",
|
|
226
|
-
);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
222
|
const targetPath = args.trim() || ctx.cwd || process.cwd();
|
|
231
|
-
ctx.ui.notify("🔍
|
|
223
|
+
ctx.ui.notify("🔍 Running code review...", "info");
|
|
232
224
|
|
|
233
|
-
const
|
|
234
|
-
typeof __dirname !== "undefined" ? __dirname : ".",
|
|
235
|
-
"rules",
|
|
236
|
-
"ast-grep-rules",
|
|
237
|
-
".sgconfig.yml",
|
|
238
|
-
);
|
|
225
|
+
const parts: string[] = [];
|
|
239
226
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
"
|
|
244
|
-
"
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
encoding: "utf-8",
|
|
249
|
-
timeout: 30000,
|
|
250
|
-
shell: true,
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
const output = result.stdout || result.stderr || "";
|
|
254
|
-
if (!output.trim() || result.status !== 1) {
|
|
255
|
-
ctx.ui.notify("✓ No design smells found", "info");
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
227
|
+
// Part 1: Design smells via ast-grep
|
|
228
|
+
if (astGrepClient.isAvailable()) {
|
|
229
|
+
const configPath = path.join(
|
|
230
|
+
typeof __dirname !== "undefined" ? __dirname : ".",
|
|
231
|
+
"rules",
|
|
232
|
+
"ast-grep-rules",
|
|
233
|
+
".sgconfig.yml",
|
|
234
|
+
);
|
|
258
235
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
issues.push({
|
|
272
|
-
line: lineNum + 1,
|
|
273
|
-
rule: ruleId,
|
|
274
|
-
message: message,
|
|
275
|
-
});
|
|
276
|
-
} catch {
|
|
277
|
-
// Skip unparseable lines
|
|
278
|
-
}
|
|
279
|
-
}
|
|
236
|
+
try {
|
|
237
|
+
const result = require("node:child_process").spawnSync("npx", [
|
|
238
|
+
"sg",
|
|
239
|
+
"scan",
|
|
240
|
+
"--config", configPath,
|
|
241
|
+
"--json",
|
|
242
|
+
targetPath,
|
|
243
|
+
], {
|
|
244
|
+
encoding: "utf-8",
|
|
245
|
+
timeout: 30000,
|
|
246
|
+
shell: true,
|
|
247
|
+
});
|
|
280
248
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
249
|
+
const output = result.stdout || result.stderr || "";
|
|
250
|
+
if (output.trim() && result.status === 1) {
|
|
251
|
+
let issues: Array<{line: number; rule: string; message: string}> = [];
|
|
252
|
+
const lines = output.split("\n").filter((l: string) => l.trim());
|
|
253
|
+
|
|
254
|
+
for (const line of lines) {
|
|
255
|
+
try {
|
|
256
|
+
const item = JSON.parse(line);
|
|
257
|
+
const ruleId = item.ruleId || item.name || "unknown";
|
|
258
|
+
const ruleDesc = astGrepClient.getRuleDescription?.(ruleId);
|
|
259
|
+
const message = ruleDesc?.message || item.message || ruleId;
|
|
260
|
+
const lineNum = item.labels?.[0]?.range?.start?.line ||
|
|
261
|
+
item.spans?.[0]?.range?.start?.line || 0;
|
|
262
|
+
|
|
263
|
+
issues.push({
|
|
264
|
+
line: lineNum + 1,
|
|
265
|
+
rule: ruleId,
|
|
266
|
+
message: message,
|
|
267
|
+
});
|
|
268
|
+
} catch {
|
|
269
|
+
// Skip unparseable lines
|
|
270
|
+
}
|
|
271
|
+
}
|
|
285
272
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
273
|
+
if (issues.length > 0) {
|
|
274
|
+
let report = `[Design Smells] ${issues.length} issue(s) found:\n`;
|
|
275
|
+
for (const issue of issues.slice(0, 20)) {
|
|
276
|
+
report += ` L${issue.line}: ${issue.rule} — ${issue.message}\n`;
|
|
277
|
+
}
|
|
278
|
+
if (issues.length > 20) {
|
|
279
|
+
report += ` ... and ${issues.length - 20} more\n`;
|
|
280
|
+
}
|
|
281
|
+
parts.push(report);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
} catch (err: any) {
|
|
285
|
+
// ast-grep scan failed, skip
|
|
292
286
|
}
|
|
293
|
-
ctx.ui.notify(report, "info");
|
|
294
|
-
} catch (err: any) {
|
|
295
|
-
ctx.ui.notify(`Design review failed: ${err.message}`, "error");
|
|
296
287
|
}
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
288
|
|
|
300
|
-
|
|
301
|
-
description: "Scan project for complexity metrics (maintainability, cognitive complexity, etc.). Usage: /lens-metrics [path]",
|
|
302
|
-
handler: async (args, ctx) => {
|
|
303
|
-
const targetPath = args.trim() || ctx.cwd || process.cwd();
|
|
304
|
-
ctx.ui.notify("🔍 Scanning project metrics...", "info");
|
|
305
|
-
|
|
306
|
-
const startTime = Date.now();
|
|
289
|
+
// Part 2: Complexity metrics
|
|
307
290
|
const results: import("./clients/complexity-client.js").FileComplexity[] = [];
|
|
308
291
|
|
|
309
292
|
const scanDir = (dir: string) => {
|
|
@@ -325,127 +308,49 @@ export default function (pi: ExtensionAPI) {
|
|
|
325
308
|
};
|
|
326
309
|
|
|
327
310
|
scanDir(targetPath);
|
|
328
|
-
const duration = Date.now() - startTime;
|
|
329
311
|
|
|
330
|
-
if (results.length
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
312
|
+
if (results.length > 0) {
|
|
313
|
+
const avgMI = results.reduce((a, b) => a + b.maintainabilityIndex, 0) / results.length;
|
|
314
|
+
const avgCognitive = results.reduce((a, b) => a + b.cognitiveComplexity, 0) / results.length;
|
|
315
|
+
const avgCyclomatic = results.reduce((a, b) => a + b.cyclomaticComplexity, 0) / results.length;
|
|
316
|
+
const maxNesting = Math.max(...results.map(r => r.maxNestingDepth));
|
|
334
317
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const avgCognitive = results.reduce((a, b) => a + b.cognitiveComplexity, 0) / results.length;
|
|
338
|
-
const avgCyclomatic = results.reduce((a, b) => a + b.cyclomaticComplexity, 0) / results.length;
|
|
339
|
-
const maxNesting = Math.max(...results.map(r => r.maxNestingDepth));
|
|
340
|
-
const avgHalstead = results.reduce((a, b) => a + b.halsteadVolume, 0) / results.length;
|
|
341
|
-
|
|
342
|
-
// Find problem files
|
|
343
|
-
const lowMI = results.filter(r => r.maintainabilityIndex < 60).sort((a, b) => a.maintainabilityIndex - b.maintainabilityIndex);
|
|
344
|
-
const highCognitive = results.filter(r => r.cognitiveComplexity > 20).sort((a, b) => b.cognitiveComplexity - a.cognitiveComplexity);
|
|
345
|
-
const highNesting = results.filter(r => r.maxNestingDepth > 5).sort((a, b) => b.maxNestingDepth - a.maxNestingDepth);
|
|
346
|
-
|
|
347
|
-
// Build markdown report
|
|
348
|
-
const now = new Date();
|
|
349
|
-
const timestamp = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
350
|
-
const dateStr = now.toISOString().slice(0, 10);
|
|
351
|
-
const timeStr = now.toISOString().slice(11, 19);
|
|
352
|
-
|
|
353
|
-
let report = `# Lens Metrics Report\n\n`;
|
|
354
|
-
report += `**Date:** ${dateStr} ${timeStr}\n`;
|
|
355
|
-
report += `**Files scanned:** ${results.length}\n`;
|
|
356
|
-
report += `**Scan time:** ${duration}ms\n\n`;
|
|
357
|
-
report += `## Aggregate\n\n`;
|
|
358
|
-
report += `| Metric | Value |\n`;
|
|
359
|
-
report += `|--------|-------|\n`;
|
|
360
|
-
report += `| Maintainability Index | ${avgMI.toFixed(1)} (avg) |\n`;
|
|
361
|
-
report += `| Cognitive Complexity | ${avgCognitive.toFixed(1)} (avg) |\n`;
|
|
362
|
-
report += `| Cyclomatic Complexity | ${avgCyclomatic.toFixed(1)} (avg) |\n`;
|
|
363
|
-
report += `| Halstead Volume | ${avgHalstead.toFixed(1)} (avg) |\n`;
|
|
364
|
-
report += `| Max Nesting Depth | ${maxNesting} levels |\n\n`;
|
|
365
|
-
|
|
366
|
-
if (lowMI.length > 0) {
|
|
367
|
-
report += `## Low Maintainability (MI < 60)\n\n`;
|
|
368
|
-
report += `| File | MI |\n`;
|
|
369
|
-
report += `|------|-----|\n`;
|
|
370
|
-
for (const f of lowMI) {
|
|
371
|
-
report += `| ${f.filePath} | ${f.maintainabilityIndex.toFixed(1)} |\n`;
|
|
372
|
-
}
|
|
373
|
-
report += `\n`;
|
|
374
|
-
}
|
|
318
|
+
const lowMI = results.filter(r => r.maintainabilityIndex < 60).sort((a, b) => a.maintainabilityIndex - b.maintainabilityIndex);
|
|
319
|
+
const highCognitive = results.filter(r => r.cognitiveComplexity > 20).sort((a, b) => b.cognitiveComplexity - a.cognitiveComplexity);
|
|
375
320
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
report += `| File | Cognitive | Cyclomatic | Max Nesting |\n`;
|
|
379
|
-
report += `|------|-----------|------------|-------------|\n`;
|
|
380
|
-
for (const f of highCognitive) {
|
|
381
|
-
report += `| ${f.filePath} | ${f.cognitiveComplexity} | ${f.cyclomaticComplexity} | ${f.maxNestingDepth} |\n`;
|
|
382
|
-
}
|
|
383
|
-
report += `\n`;
|
|
384
|
-
}
|
|
321
|
+
let summary = `[Complexity] ${results.length} file(s) scanned\n`;
|
|
322
|
+
summary += ` Maintainability: ${avgMI.toFixed(1)} avg | Cognitive: ${avgCognitive.toFixed(1)} avg | Max Nesting: ${maxNesting} levels\n`;
|
|
385
323
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
324
|
+
if (lowMI.length > 0) {
|
|
325
|
+
summary += `\n Low Maintainability (MI < 60):\n`;
|
|
326
|
+
for (const f of lowMI.slice(0, 5)) {
|
|
327
|
+
summary += ` ✗ ${f.filePath}: MI ${f.maintainabilityIndex.toFixed(1)}\n`;
|
|
328
|
+
}
|
|
329
|
+
if (lowMI.length > 5) summary += ` ... and ${lowMI.length - 5} more\n`;
|
|
392
330
|
}
|
|
393
|
-
report += `\n`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// All files sorted by MI
|
|
397
|
-
report += `## All Files\n\n`;
|
|
398
|
-
report += `| File | MI | Cognitive | Cyclomatic | Nesting | Halstead |\n`;
|
|
399
|
-
report += `|------|-----|-----------|------------|---------|----------|\n`;
|
|
400
|
-
for (const f of results.sort((a, b) => a.maintainabilityIndex - b.maintainabilityIndex)) {
|
|
401
|
-
report += `| ${f.filePath} | ${f.maintainabilityIndex.toFixed(1)} | ${f.cognitiveComplexity} | ${f.cyclomaticComplexity} | ${f.maxNestingDepth} | ${f.halsteadVolume.toFixed(0)} |\n`;
|
|
402
|
-
}
|
|
403
331
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const timestampedPath = path.join(metricsDir, `metrics-${timestamp}.md`);
|
|
411
|
-
const latestPath = path.join(metricsDir, "latest.md");
|
|
412
|
-
fs.writeFileSync(timestampedPath, report);
|
|
413
|
-
fs.writeFileSync(latestPath, report);
|
|
414
|
-
|
|
415
|
-
// Build summary for UI (shorter)
|
|
416
|
-
let summary = `[Lens Metrics] ${results.length} file(s) scanned in ${duration}ms\n\n`;
|
|
417
|
-
summary += `── Aggregate ──\n`;
|
|
418
|
-
summary += ` Maintainability Index: ${avgMI.toFixed(1)} (avg)\n`;
|
|
419
|
-
summary += ` Cognitive Complexity: ${avgCognitive.toFixed(1)} (avg)\n`;
|
|
420
|
-
summary += ` Cyclomatic Complexity: ${avgCyclomatic.toFixed(1)} (avg)\n`;
|
|
421
|
-
summary += ` Halstead Volume: ${avgHalstead.toFixed(1)} (avg)\n`;
|
|
422
|
-
summary += ` Max Nesting Depth: ${maxNesting} levels\n`;
|
|
423
|
-
|
|
424
|
-
if (lowMI.length > 0) {
|
|
425
|
-
summary += `\n── Low Maintainability (MI < 60) ──\n`;
|
|
426
|
-
for (const f of lowMI.slice(0, 5)) {
|
|
427
|
-
summary += ` ✗ ${f.filePath}: MI ${f.maintainabilityIndex.toFixed(1)}\n`;
|
|
332
|
+
if (highCognitive.length > 0) {
|
|
333
|
+
summary += `\n High Cognitive Complexity (> 20):\n`;
|
|
334
|
+
for (const f of highCognitive.slice(0, 5)) {
|
|
335
|
+
summary += ` ⚠ ${f.filePath}: ${f.cognitiveComplexity}\n`;
|
|
336
|
+
}
|
|
337
|
+
if (highCognitive.length > 5) summary += ` ... and ${highCognitive.length - 5} more\n`;
|
|
428
338
|
}
|
|
429
|
-
if (lowMI.length > 5) summary += ` ... and ${lowMI.length - 5} more\n`;
|
|
430
|
-
}
|
|
431
339
|
|
|
432
|
-
|
|
433
|
-
summary += `\n── High Cognitive Complexity (> 20) ──\n`;
|
|
434
|
-
for (const f of highCognitive.slice(0, 5)) {
|
|
435
|
-
summary += ` ⚠ ${f.filePath}: ${f.cognitiveComplexity}\n`;
|
|
436
|
-
}
|
|
437
|
-
if (highCognitive.length > 5) summary += ` ... and ${highCognitive.length - 5} more\n`;
|
|
340
|
+
parts.push(summary);
|
|
438
341
|
}
|
|
439
342
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
343
|
+
if (parts.length === 0) {
|
|
344
|
+
ctx.ui.notify("✓ Code review clean", "info");
|
|
345
|
+
} else {
|
|
346
|
+
ctx.ui.notify(parts.join("\n\n"), "info");
|
|
347
|
+
}
|
|
443
348
|
},
|
|
444
349
|
});
|
|
445
350
|
|
|
446
|
-
pi.registerCommand("format", {
|
|
351
|
+
pi.registerCommand("lens-format", {
|
|
447
352
|
description:
|
|
448
|
-
"Apply Biome formatting to files. Usage: /format [file-path] or /format --all",
|
|
353
|
+
"Apply Biome formatting to files. Usage: /lens-format [file-path] or /lens-format --all",
|
|
449
354
|
handler: async (args, ctx) => {
|
|
450
355
|
if (!biomeClient.isAvailable()) {
|
|
451
356
|
ctx.ui.notify(
|
|
@@ -871,7 +776,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
871
776
|
const fixable = biomeDiags.filter((d) => d.fixable);
|
|
872
777
|
lspOutput += `\n\n${biomeClient.formatDiagnostics(biomeDiags, filePath)}`;
|
|
873
778
|
if (fixable.length > 0) {
|
|
874
|
-
lspOutput += `\n\n[Biome] ${fixable.length} fixable — enable --autofix-biome flag or run /format`;
|
|
779
|
+
lspOutput += `\n\n[Biome] ${fixable.length} fixable — enable --autofix-biome flag or run /lens-format`;
|
|
875
780
|
}
|
|
876
781
|
}
|
|
877
782
|
}
|
package/package.json
CHANGED