pi-lens 2.2.6 ā 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/commands/fix.js +39 -23
- package/commands/fix.ts +45 -33
- package/commands/refactor.js +7 -5
- package/commands/refactor.ts +7 -5
- 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/commands/fix.js
CHANGED
|
@@ -112,41 +112,57 @@ function generatePlan(results, session, _isTsProject, prevCounts) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
nodeFs.writeFileSync(reportPath, tsvRows.join("\n"), "utf-8");
|
|
115
|
-
// --- Build
|
|
115
|
+
// --- Build actionable list for terminal (no TSV reading needed) ---
|
|
116
116
|
const lines = [];
|
|
117
|
-
lines.push(`š FIX PLAN ā Iteration ${session.iteration}/${MAX_ITERATIONS} ā ${totalFixable} issues`);
|
|
118
|
-
|
|
119
|
-
// Summary by category with counts
|
|
117
|
+
lines.push(`š FIX PLAN ā Iteration ${session.iteration}/${MAX_ITERATIONS} ā ${totalFixable} issues:\n`);
|
|
118
|
+
// Duplicates
|
|
120
119
|
if (filteredDups.length > 0) {
|
|
121
|
-
|
|
120
|
+
for (const clone of filteredDups.slice(0, 10)) {
|
|
121
|
+
lines.push(`š ${clone.fileA}:${clone.startA} ā ${clone.lines} dup from ${clone.fileB}:${clone.startB}`);
|
|
122
|
+
}
|
|
123
|
+
if (filteredDups.length > 10)
|
|
124
|
+
lines.push(` ... +${filteredDups.length - 10} more`);
|
|
125
|
+
lines.push("");
|
|
122
126
|
}
|
|
127
|
+
// Dead code
|
|
123
128
|
if (filteredDeadCode.length > 0) {
|
|
124
|
-
|
|
129
|
+
for (const issue of filteredDeadCode.slice(0, 10)) {
|
|
130
|
+
lines.push(`šļø ${issue.file || issue.name} ā ${issue.name} unused`);
|
|
131
|
+
}
|
|
132
|
+
if (filteredDeadCode.length > 10)
|
|
133
|
+
lines.push(` ... +${filteredDeadCode.length - 10} more`);
|
|
134
|
+
lines.push("");
|
|
125
135
|
}
|
|
136
|
+
// AST lint
|
|
126
137
|
if (agentTasks.length > 0) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
for (const t of agentTasks) {
|
|
130
|
-
grouped.set(t.rule, (grouped.get(t.rule) ?? 0) + 1);
|
|
138
|
+
for (const issue of agentTasks.slice(0, 15)) {
|
|
139
|
+
lines.push(`šØ ${issue.file}:${issue.line} ā ${issue.rule}`);
|
|
131
140
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
lines.push(` ${rule}: ${count}`);
|
|
136
|
-
}
|
|
137
|
-
if (sorted.length > 5)
|
|
138
|
-
lines.push(` ... and ${sorted.length - 5} more rules`);
|
|
141
|
+
if (agentTasks.length > 15)
|
|
142
|
+
lines.push(` ... +${agentTasks.length - 15} more`);
|
|
143
|
+
lines.push("");
|
|
139
144
|
}
|
|
145
|
+
// Biome
|
|
140
146
|
if (filteredBiome.length > 0) {
|
|
141
|
-
|
|
147
|
+
for (const issue of filteredBiome.slice(0, 10)) {
|
|
148
|
+
lines.push(`š ${issue.file}:${issue.line} ā ${issue.rule}`);
|
|
149
|
+
}
|
|
150
|
+
if (filteredBiome.length > 10)
|
|
151
|
+
lines.push(` ... +${filteredBiome.length - 10} more`);
|
|
152
|
+
lines.push("");
|
|
142
153
|
}
|
|
154
|
+
// AI Slop
|
|
143
155
|
if (filteredSlop.length > 0) {
|
|
144
|
-
|
|
156
|
+
for (const { file, warnings } of filteredSlop.slice(0, 5)) {
|
|
157
|
+
lines.push(`š¤ ${file} ā ${warnings[0]}`);
|
|
158
|
+
}
|
|
159
|
+
if (filteredSlop.length > 5)
|
|
160
|
+
lines.push(` ... +${filteredSlop.length - 5} more`);
|
|
161
|
+
lines.push("");
|
|
145
162
|
}
|
|
146
|
-
lines.push("
|
|
147
|
-
lines.push("
|
|
148
|
-
lines.push(
|
|
149
|
-
lines.push('š« **False positive**: `/lens-booboo-fix --false-positive "type:file:line"`');
|
|
163
|
+
lines.push("---");
|
|
164
|
+
lines.push("š Fix items above, then run `/lens-booboo-fix --loop`");
|
|
165
|
+
lines.push('š« False positive: `/lens-booboo-fix --false-positive "type:file:line"`');
|
|
150
166
|
return lines.join("\n");
|
|
151
167
|
}
|
|
152
168
|
// --- Main handler ---
|
package/commands/fix.ts
CHANGED
|
@@ -183,56 +183,68 @@ function generatePlan(
|
|
|
183
183
|
|
|
184
184
|
nodeFs.writeFileSync(reportPath, tsvRows.join("\n"), "utf-8");
|
|
185
185
|
|
|
186
|
-
// --- Build
|
|
186
|
+
// --- Build actionable list for terminal (no TSV reading needed) ---
|
|
187
187
|
const lines: string[] = [];
|
|
188
188
|
lines.push(
|
|
189
|
-
`š FIX PLAN ā Iteration ${session.iteration}/${MAX_ITERATIONS} ā ${totalFixable} issues`,
|
|
189
|
+
`š FIX PLAN ā Iteration ${session.iteration}/${MAX_ITERATIONS} ā ${totalFixable} issues:\n`,
|
|
190
190
|
);
|
|
191
|
-
lines.push(`š Full plan: .pi-lens/reports/fix-plan.tsv\n`);
|
|
192
191
|
|
|
193
|
-
//
|
|
192
|
+
// Duplicates
|
|
194
193
|
if (filteredDups.length > 0) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
for (const clone of filteredDups.slice(0, 10)) {
|
|
195
|
+
lines.push(
|
|
196
|
+
`š ${clone.fileA}:${clone.startA} ā ${clone.lines} dup from ${clone.fileB}:${clone.startB}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (filteredDups.length > 10)
|
|
200
|
+
lines.push(` ... +${filteredDups.length - 10} more`);
|
|
201
|
+
lines.push("");
|
|
198
202
|
}
|
|
203
|
+
|
|
204
|
+
// Dead code
|
|
199
205
|
if (filteredDeadCode.length > 0) {
|
|
200
|
-
|
|
201
|
-
`šļø
|
|
202
|
-
|
|
206
|
+
for (const issue of filteredDeadCode.slice(0, 10)) {
|
|
207
|
+
lines.push(`šļø ${issue.file || issue.name} ā ${issue.name} unused`);
|
|
208
|
+
}
|
|
209
|
+
if (filteredDeadCode.length > 10)
|
|
210
|
+
lines.push(` ... +${filteredDeadCode.length - 10} more`);
|
|
211
|
+
lines.push("");
|
|
203
212
|
}
|
|
213
|
+
|
|
214
|
+
// AST lint
|
|
204
215
|
if (agentTasks.length > 0) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
for (const t of agentTasks) {
|
|
208
|
-
grouped.set(t.rule, (grouped.get(t.rule) ?? 0) + 1);
|
|
209
|
-
}
|
|
210
|
-
const sorted = [...grouped.entries()].sort((a, b) => b[1] - a[1]);
|
|
211
|
-
lines.push(`šØ Lint: ${agentTasks.length} item(s)`);
|
|
212
|
-
for (const [rule, count] of sorted.slice(0, 5)) {
|
|
213
|
-
lines.push(` ${rule}: ${count}`);
|
|
216
|
+
for (const issue of agentTasks.slice(0, 15)) {
|
|
217
|
+
lines.push(`šØ ${issue.file}:${issue.line} ā ${issue.rule}`);
|
|
214
218
|
}
|
|
215
|
-
if (
|
|
216
|
-
lines.push(` ...
|
|
219
|
+
if (agentTasks.length > 15)
|
|
220
|
+
lines.push(` ... +${agentTasks.length - 15} more`);
|
|
221
|
+
lines.push("");
|
|
217
222
|
}
|
|
223
|
+
|
|
224
|
+
// Biome
|
|
218
225
|
if (filteredBiome.length > 0) {
|
|
219
|
-
|
|
226
|
+
for (const issue of filteredBiome.slice(0, 10)) {
|
|
227
|
+
lines.push(`š ${issue.file}:${issue.line} ā ${issue.rule}`);
|
|
228
|
+
}
|
|
229
|
+
if (filteredBiome.length > 10)
|
|
230
|
+
lines.push(` ... +${filteredBiome.length - 10} more`);
|
|
231
|
+
lines.push("");
|
|
220
232
|
}
|
|
233
|
+
|
|
234
|
+
// AI Slop
|
|
221
235
|
if (filteredSlop.length > 0) {
|
|
222
|
-
|
|
223
|
-
`š¤
|
|
224
|
-
|
|
236
|
+
for (const { file, warnings } of filteredSlop.slice(0, 5)) {
|
|
237
|
+
lines.push(`š¤ ${file} ā ${warnings[0]}`);
|
|
238
|
+
}
|
|
239
|
+
if (filteredSlop.length > 5)
|
|
240
|
+
lines.push(` ... +${filteredSlop.length - 5} more`);
|
|
241
|
+
lines.push("");
|
|
225
242
|
}
|
|
226
243
|
|
|
227
|
-
lines.push("
|
|
228
|
-
lines.push(
|
|
229
|
-
"š **Read plan**: `read .pi-lens/reports/fix-plan.tsv` for full details",
|
|
230
|
-
);
|
|
231
|
-
lines.push(
|
|
232
|
-
"š **Fix & loop**: Fix items, then run `/lens-booboo-fix --loop`",
|
|
233
|
-
);
|
|
244
|
+
lines.push("---");
|
|
245
|
+
lines.push("š Fix items above, then run `/lens-booboo-fix --loop`");
|
|
234
246
|
lines.push(
|
|
235
|
-
'š«
|
|
247
|
+
'š« False positive: `/lens-booboo-fix --false-positive "type:file:line"`',
|
|
236
248
|
);
|
|
237
249
|
|
|
238
250
|
return lines.join("\n");
|
package/commands/refactor.js
CHANGED
|
@@ -83,16 +83,18 @@ export async function handleRefactor(args, ctx, clients, pi, skipRules, ruleActi
|
|
|
83
83
|
: "";
|
|
84
84
|
// First violation line for quick reference
|
|
85
85
|
const firstViolationLine = issues.length > 0 ? issues[0].line : null;
|
|
86
|
-
// ---
|
|
86
|
+
// --- Full ranked list in terminal (agent won't read TSV) ---
|
|
87
87
|
const topFiles = scored
|
|
88
|
-
.slice(0,
|
|
88
|
+
.slice(0, 15)
|
|
89
89
|
.map((f, i) => {
|
|
90
90
|
const name = path.relative(targetPath, f.file).replace(/\\/g, "/");
|
|
91
|
-
|
|
91
|
+
const m = metricsByFile.get(f.file);
|
|
92
|
+
const mi = m ? `MI:${m.mi.toFixed(0)}` : "";
|
|
93
|
+
return ` ${i + 1}. ${name} (${f.score} pts${mi ? `, ${mi}` : ""})`;
|
|
92
94
|
})
|
|
93
95
|
.join("\n");
|
|
94
96
|
ctx.ui.notify(`šļø Worst: ${relFile} (score: ${score}) ā ${scored.length} files with debt`, "info");
|
|
95
|
-
console.log(`\nš
|
|
97
|
+
console.log(`\nš Ranked by debt score:\n${topFiles}${scored.length > 15 ? `\n ... and ${scored.length - 15} more` : ""}\n`);
|
|
96
98
|
// --- Steer message for agent ---
|
|
97
99
|
const steer = [
|
|
98
100
|
`šļø BOOBOO REFACTOR ā worst offender identified`,
|
|
@@ -107,7 +109,7 @@ export async function handleRefactor(args, ctx, clients, pi, skipRules, ruleActi
|
|
|
107
109
|
: "",
|
|
108
110
|
firstViolationLine ? `First violation at line ${firstViolationLine}` : "",
|
|
109
111
|
"",
|
|
110
|
-
`š
|
|
112
|
+
`š Read \`${relFile}\` when ready to implement`,
|
|
111
113
|
"",
|
|
112
114
|
"**Your job**:",
|
|
113
115
|
"1. Analyze this code ā what's the most impactful refactoring for this file?",
|
package/commands/refactor.ts
CHANGED
|
@@ -148,12 +148,14 @@ export async function handleRefactor(
|
|
|
148
148
|
// First violation line for quick reference
|
|
149
149
|
const firstViolationLine = issues.length > 0 ? issues[0].line : null;
|
|
150
150
|
|
|
151
|
-
// ---
|
|
151
|
+
// --- Full ranked list in terminal (agent won't read TSV) ---
|
|
152
152
|
const topFiles = scored
|
|
153
|
-
.slice(0,
|
|
153
|
+
.slice(0, 15)
|
|
154
154
|
.map((f, i) => {
|
|
155
155
|
const name = path.relative(targetPath, f.file).replace(/\\/g, "/");
|
|
156
|
-
|
|
156
|
+
const m = metricsByFile.get(f.file);
|
|
157
|
+
const mi = m ? `MI:${m.mi.toFixed(0)}` : "";
|
|
158
|
+
return ` ${i + 1}. ${name} (${f.score} pts${mi ? `, ${mi}` : ""})`;
|
|
157
159
|
})
|
|
158
160
|
.join("\n");
|
|
159
161
|
|
|
@@ -162,7 +164,7 @@ export async function handleRefactor(
|
|
|
162
164
|
"info",
|
|
163
165
|
);
|
|
164
166
|
console.log(
|
|
165
|
-
`\nš
|
|
167
|
+
`\nš Ranked by debt score:\n${topFiles}${scored.length > 15 ? `\n ... and ${scored.length - 15} more` : ""}\n`,
|
|
166
168
|
);
|
|
167
169
|
|
|
168
170
|
// --- Steer message for agent ---
|
|
@@ -179,7 +181,7 @@ export async function handleRefactor(
|
|
|
179
181
|
: "",
|
|
180
182
|
firstViolationLine ? `First violation at line ${firstViolationLine}` : "",
|
|
181
183
|
"",
|
|
182
|
-
`š
|
|
184
|
+
`š Read \`${relFile}\` when ready to implement`,
|
|
183
185
|
"",
|
|
184
186
|
"**Your job**:",
|
|
185
187
|
"1. Analyze this code ā what's the most impactful refactoring for this file?",
|
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": {
|