pi-lens 2.2.7 → 2.2.8
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/metrics-history.js +78 -0
- package/clients/metrics-history.ts +101 -0
- package/index.ts +34 -0
- package/package.json +1 -1
|
@@ -261,3 +261,81 @@ export function formatTrendCell(filePath, history) {
|
|
|
261
261
|
const miColor = delta.mi > 0 ? "🟢" : delta.mi < 0 ? "🔴" : "⚪";
|
|
262
262
|
return `${emoji} ${miColor}${miSign}${delta.mi}`;
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Calculate Technical Debt Index for the project.
|
|
266
|
+
* Score: 0 = perfect, 100 = maximum debt.
|
|
267
|
+
*/
|
|
268
|
+
export function computeTDI(history) {
|
|
269
|
+
const files = Object.values(history.files);
|
|
270
|
+
if (files.length === 0) {
|
|
271
|
+
return {
|
|
272
|
+
score: 0,
|
|
273
|
+
grade: "N/A",
|
|
274
|
+
avgMI: 100,
|
|
275
|
+
totalCognitive: 0,
|
|
276
|
+
filesAnalyzed: 0,
|
|
277
|
+
filesWithDebt: 0,
|
|
278
|
+
byCategory: { complexity: 0, maintainability: 0, nesting: 0 },
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
let totalMI = 0;
|
|
282
|
+
let totalCognitive = 0;
|
|
283
|
+
let totalNesting = 0;
|
|
284
|
+
let filesWithDebt = 0;
|
|
285
|
+
let debtFromMI = 0;
|
|
286
|
+
let debtFromCognitive = 0;
|
|
287
|
+
let debtFromNesting = 0;
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
const snap = file.latest;
|
|
290
|
+
totalMI += snap.mi;
|
|
291
|
+
totalCognitive += snap.cognitive;
|
|
292
|
+
totalNesting += snap.nesting;
|
|
293
|
+
// Accumulate debt points
|
|
294
|
+
let fileDebt = 0;
|
|
295
|
+
// MI debt: 0 at MI=100, max at MI=0
|
|
296
|
+
const miDebt = Math.max(0, (100 - snap.mi) / 100);
|
|
297
|
+
debtFromMI += miDebt;
|
|
298
|
+
// Cognitive debt: 0 at 0, max at 500+
|
|
299
|
+
const cogDebt = Math.min(1, snap.cognitive / 200);
|
|
300
|
+
debtFromCognitive += cogDebt;
|
|
301
|
+
// Nesting debt: 0 at 1-3, max at 10+
|
|
302
|
+
const nestDebt = Math.min(1, Math.max(0, snap.nesting - 3) / 7);
|
|
303
|
+
debtFromNesting += nestDebt;
|
|
304
|
+
fileDebt = miDebt + cogDebt + nestDebt;
|
|
305
|
+
if (fileDebt > 1)
|
|
306
|
+
filesWithDebt++; // File has at least some debt
|
|
307
|
+
}
|
|
308
|
+
const avgMI = totalMI / files.length;
|
|
309
|
+
// Normalize to 0-100 scale
|
|
310
|
+
const avgMIDebt = debtFromMI / files.length; // 0-1
|
|
311
|
+
const avgCogDebt = debtFromCognitive / files.length; // 0-1
|
|
312
|
+
const avgNestDebt = debtFromNesting / files.length; // 0-1
|
|
313
|
+
// Weighted: MI matters most (50%), cognitive (35%), nesting (15%)
|
|
314
|
+
const rawScore = avgMIDebt * 50 + avgCogDebt * 35 + avgNestDebt * 15;
|
|
315
|
+
const score = Math.round(rawScore * 100) / 100;
|
|
316
|
+
// Grade
|
|
317
|
+
let grade;
|
|
318
|
+
if (score <= 15)
|
|
319
|
+
grade = "A";
|
|
320
|
+
else if (score <= 30)
|
|
321
|
+
grade = "B";
|
|
322
|
+
else if (score <= 50)
|
|
323
|
+
grade = "C";
|
|
324
|
+
else if (score <= 70)
|
|
325
|
+
grade = "D";
|
|
326
|
+
else
|
|
327
|
+
grade = "F";
|
|
328
|
+
return {
|
|
329
|
+
score,
|
|
330
|
+
grade,
|
|
331
|
+
avgMI: Math.round(avgMI * 10) / 10,
|
|
332
|
+
totalCognitive,
|
|
333
|
+
filesAnalyzed: files.length,
|
|
334
|
+
filesWithDebt,
|
|
335
|
+
byCategory: {
|
|
336
|
+
complexity: Math.round(avgCogDebt * 100),
|
|
337
|
+
maintainability: Math.round(avgMIDebt * 100),
|
|
338
|
+
nesting: Math.round(avgNestDebt * 100),
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
@@ -348,3 +348,104 @@ export function formatTrendCell(
|
|
|
348
348
|
|
|
349
349
|
return `${emoji} ${miColor}${miSign}${delta.mi}`;
|
|
350
350
|
}
|
|
351
|
+
|
|
352
|
+
// --- Technical Debt Index (TDI) ---
|
|
353
|
+
|
|
354
|
+
export interface ProjectTDI {
|
|
355
|
+
score: number; // 0-100, higher = more debt
|
|
356
|
+
grade: string; // A-F
|
|
357
|
+
avgMI: number;
|
|
358
|
+
totalCognitive: number;
|
|
359
|
+
filesAnalyzed: number;
|
|
360
|
+
filesWithDebt: number;
|
|
361
|
+
byCategory: {
|
|
362
|
+
complexity: number;
|
|
363
|
+
maintainability: number;
|
|
364
|
+
nesting: number;
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Calculate Technical Debt Index for the project.
|
|
370
|
+
* Score: 0 = perfect, 100 = maximum debt.
|
|
371
|
+
*/
|
|
372
|
+
export function computeTDI(history: MetricsHistory): ProjectTDI {
|
|
373
|
+
const files = Object.values(history.files);
|
|
374
|
+
if (files.length === 0) {
|
|
375
|
+
return {
|
|
376
|
+
score: 0,
|
|
377
|
+
grade: "N/A",
|
|
378
|
+
avgMI: 100,
|
|
379
|
+
totalCognitive: 0,
|
|
380
|
+
filesAnalyzed: 0,
|
|
381
|
+
filesWithDebt: 0,
|
|
382
|
+
byCategory: { complexity: 0, maintainability: 0, nesting: 0 },
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let totalMI = 0;
|
|
387
|
+
let totalCognitive = 0;
|
|
388
|
+
let totalNesting = 0;
|
|
389
|
+
let filesWithDebt = 0;
|
|
390
|
+
let debtFromMI = 0;
|
|
391
|
+
let debtFromCognitive = 0;
|
|
392
|
+
let debtFromNesting = 0;
|
|
393
|
+
|
|
394
|
+
for (const file of files) {
|
|
395
|
+
const snap = file.latest;
|
|
396
|
+
totalMI += snap.mi;
|
|
397
|
+
totalCognitive += snap.cognitive;
|
|
398
|
+
totalNesting += snap.nesting;
|
|
399
|
+
|
|
400
|
+
// Accumulate debt points
|
|
401
|
+
let fileDebt = 0;
|
|
402
|
+
|
|
403
|
+
// MI debt: 0 at MI=100, max at MI=0
|
|
404
|
+
const miDebt = Math.max(0, (100 - snap.mi) / 100);
|
|
405
|
+
debtFromMI += miDebt;
|
|
406
|
+
|
|
407
|
+
// Cognitive debt: 0 at 0, max at 500+
|
|
408
|
+
const cogDebt = Math.min(1, snap.cognitive / 200);
|
|
409
|
+
debtFromCognitive += cogDebt;
|
|
410
|
+
|
|
411
|
+
// Nesting debt: 0 at 1-3, max at 10+
|
|
412
|
+
const nestDebt = Math.min(1, Math.max(0, snap.nesting - 3) / 7);
|
|
413
|
+
debtFromNesting += nestDebt;
|
|
414
|
+
|
|
415
|
+
fileDebt = miDebt + cogDebt + nestDebt;
|
|
416
|
+
if (fileDebt > 1) filesWithDebt++; // File has at least some debt
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const avgMI = totalMI / files.length;
|
|
420
|
+
|
|
421
|
+
// Normalize to 0-100 scale
|
|
422
|
+
const avgMIDebt = debtFromMI / files.length; // 0-1
|
|
423
|
+
const avgCogDebt = debtFromCognitive / files.length; // 0-1
|
|
424
|
+
const avgNestDebt = debtFromNesting / files.length; // 0-1
|
|
425
|
+
|
|
426
|
+
// Weighted: MI matters most (50%), cognitive (35%), nesting (15%)
|
|
427
|
+
const rawScore = avgMIDebt * 50 + avgCogDebt * 35 + avgNestDebt * 15;
|
|
428
|
+
const score = Math.round(rawScore * 100) / 100;
|
|
429
|
+
|
|
430
|
+
// Grade
|
|
431
|
+
let grade: string;
|
|
432
|
+
if (score <= 15) grade = "A";
|
|
433
|
+
else if (score <= 30) grade = "B";
|
|
434
|
+
else if (score <= 50) grade = "C";
|
|
435
|
+
else if (score <= 70) grade = "D";
|
|
436
|
+
else grade = "F";
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
score,
|
|
440
|
+
grade,
|
|
441
|
+
avgMI: Math.round(avgMI * 10) / 10,
|
|
442
|
+
totalCognitive,
|
|
443
|
+
filesAnalyzed: files.length,
|
|
444
|
+
filesWithDebt,
|
|
445
|
+
byCategory: {
|
|
446
|
+
complexity: Math.round(avgCogDebt * 100),
|
|
447
|
+
maintainability: Math.round(avgMIDebt * 100),
|
|
448
|
+
nesting: Math.round(avgNestDebt * 100),
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
}
|
package/index.ts
CHANGED
|
@@ -634,6 +634,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
634
634
|
},
|
|
635
635
|
});
|
|
636
636
|
|
|
637
|
+
pi.registerCommand("lens-tdi", {
|
|
638
|
+
description:
|
|
639
|
+
"Show Technical Debt Index (TDI) and project health trend. Usage: /lens-tdi",
|
|
640
|
+
handler: async (_args, ctx) => {
|
|
641
|
+
const { loadHistory, computeTDI } = await import(
|
|
642
|
+
"./clients/metrics-history.js"
|
|
643
|
+
);
|
|
644
|
+
const history = loadHistory();
|
|
645
|
+
const tdi = computeTDI(history);
|
|
646
|
+
|
|
647
|
+
const lines = [
|
|
648
|
+
`📊 TECHNICAL DEBT INDEX: ${tdi.score}/100 (${tdi.grade})`,
|
|
649
|
+
``,
|
|
650
|
+
`Files analyzed: ${tdi.filesAnalyzed}`,
|
|
651
|
+
`Files with debt: ${tdi.filesWithDebt}`,
|
|
652
|
+
`Avg MI: ${tdi.avgMI}`,
|
|
653
|
+
`Total cognitive complexity: ${tdi.totalCognitive}`,
|
|
654
|
+
``,
|
|
655
|
+
`Debt breakdown:`,
|
|
656
|
+
` Maintainability: ${tdi.byCategory.maintainability}%`,
|
|
657
|
+
` Complexity: ${tdi.byCategory.complexity}%`,
|
|
658
|
+
` Nesting: ${tdi.byCategory.nesting}%`,
|
|
659
|
+
``,
|
|
660
|
+
tdi.score <= 30
|
|
661
|
+
? "✅ Codebase is healthy!"
|
|
662
|
+
: tdi.score <= 60
|
|
663
|
+
? "⚠️ Moderate debt — consider refactoring"
|
|
664
|
+
: "🔴 High debt — run /lens-booboo-refactor",
|
|
665
|
+
];
|
|
666
|
+
|
|
667
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
|
|
637
671
|
pi.registerCommand("lens-format", {
|
|
638
672
|
description:
|
|
639
673
|
"Apply Biome formatting to files. Usage: /lens-format [file-path] or /lens-format --all",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lens",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.8",
|
|
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": {
|