pi-lens 3.6.2 → 3.6.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.
- package/CHANGELOG.md +10 -2
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/file-time.test.ts +0 -276
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/format-service.test.ts +0 -339
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/__tests__/formatters.test.ts +0 -401
- package/clients/agent-behavior-client.js +0 -110
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/agent-behavior-client.test.ts +0 -116
- package/clients/amain-types.js +0 -164
- package/clients/architect-client.js +0 -291
- package/clients/ast-grep-client.js +0 -253
- package/clients/ast-grep-parser.js +0 -84
- package/clients/ast-grep-rule-manager.js +0 -89
- package/clients/ast-grep-types.js +0 -9
- package/clients/auto-loop.js +0 -131
- package/clients/biome-client.js +0 -420
- package/clients/biome-client.test.js +0 -144
- package/clients/biome-client.test.ts +0 -163
- package/clients/cache/rule-cache.js +0 -72
- package/clients/cache-manager.js +0 -245
- package/clients/cache-manager.test.js +0 -197
- package/clients/cache-manager.test.ts +0 -299
- package/clients/complexity-client.js +0 -675
- package/clients/complexity-client.test.js +0 -234
- package/clients/complexity-client.test.ts +0 -255
- package/clients/config-validator.js +0 -465
- package/clients/dependency-checker.js +0 -325
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dependency-checker.test.ts +0 -71
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
- package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
- package/clients/dispatch/debug.log +0 -1
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.edge.test.ts +0 -100
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.format.test.ts +0 -58
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.inline.test.ts +0 -93
- package/clients/dispatch/dispatcher.js +0 -381
- package/clients/dispatch/dispatcher.test.js +0 -116
- package/clients/dispatch/dispatcher.test.ts +0 -149
- package/clients/dispatch/integration.js +0 -108
- package/clients/dispatch/plan.js +0 -183
- package/clients/dispatch/runners/architect.js +0 -83
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/architect.test.ts +0 -162
- package/clients/dispatch/runners/ast-grep-napi.js +0 -405
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
- package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
- package/clients/dispatch/runners/ast-grep.js +0 -157
- package/clients/dispatch/runners/biome.js +0 -55
- package/clients/dispatch/runners/config-validation.js +0 -67
- package/clients/dispatch/runners/go-vet.js +0 -48
- package/clients/dispatch/runners/index.js +0 -47
- package/clients/dispatch/runners/lsp.js +0 -102
- package/clients/dispatch/runners/oxlint.js +0 -67
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/oxlint.test.ts +0 -303
- package/clients/dispatch/runners/pyright.js +0 -100
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/pyright.test.ts +0 -121
- package/clients/dispatch/runners/python-slop.js +0 -97
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/python-slop.test.ts +0 -298
- package/clients/dispatch/runners/ruff.js +0 -48
- package/clients/dispatch/runners/rust-clippy.js +0 -102
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
- package/clients/dispatch/runners/shellcheck.js +0 -147
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/shellcheck.test.ts +0 -129
- package/clients/dispatch/runners/similarity.js +0 -230
- package/clients/dispatch/runners/spellcheck.js +0 -106
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/spellcheck.test.ts +0 -214
- package/clients/dispatch/runners/tree-sitter.js +0 -246
- package/clients/dispatch/runners/ts-lsp.js +0 -125
- package/clients/dispatch/runners/ts-slop.js +0 -113
- package/clients/dispatch/runners/type-safety.js +0 -142
- package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
- package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
- package/clients/dispatch/runners/utils.js +0 -51
- package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
- package/clients/dispatch/types.js +0 -16
- package/clients/dispatch/utils/format-utils.js +0 -44
- package/clients/dogfood.test.js +0 -201
- package/clients/dogfood.test.ts +0 -269
- package/clients/file-kinds.js +0 -177
- package/clients/file-kinds.test.js +0 -169
- package/clients/file-kinds.test.ts +0 -210
- package/clients/file-time.js +0 -152
- package/clients/file-utils.js +0 -40
- package/clients/fix-scanners.js +0 -204
- package/clients/format-service.js +0 -184
- package/clients/formatters.js +0 -488
- package/clients/go-client.js +0 -203
- package/clients/go-client.test.js +0 -127
- package/clients/go-client.test.ts +0 -143
- package/clients/installer/index.js +0 -403
- package/clients/interviewer-templates.js +0 -75
- package/clients/interviewer.js +0 -173
- package/clients/jscpd-client.js +0 -196
- package/clients/jscpd-client.test.js +0 -127
- package/clients/jscpd-client.test.ts +0 -145
- package/clients/knip-client.js +0 -239
- package/clients/knip-client.test.js +0 -112
- package/clients/knip-client.test.ts +0 -128
- package/clients/latency-logger.js +0 -40
- package/clients/lsp/__tests__/client.test.js +0 -310
- package/clients/lsp/__tests__/client.test.ts +0 -412
- package/clients/lsp/__tests__/config.test.js +0 -167
- package/clients/lsp/__tests__/config.test.ts +0 -217
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/integration.test.ts +0 -160
- package/clients/lsp/__tests__/launch.test.js +0 -313
- package/clients/lsp/__tests__/launch.test.ts +0 -394
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/server.test.ts +0 -332
- package/clients/lsp/__tests__/service.test.js +0 -438
- package/clients/lsp/__tests__/service.test.ts +0 -530
- package/clients/lsp/client.js +0 -350
- package/clients/lsp/config.js +0 -112
- package/clients/lsp/index.js +0 -318
- package/clients/lsp/installer/index.js +0 -391
- package/clients/lsp/interactive-install.js +0 -221
- package/clients/lsp/language.js +0 -170
- package/clients/lsp/launch.js +0 -329
- package/clients/lsp/lsp/launch.js +0 -116
- package/clients/lsp/lsp/server.js +0 -532
- package/clients/lsp/lsp-index.js +0 -10
- package/clients/lsp/path-utils.js +0 -5
- package/clients/lsp/server.js +0 -725
- package/clients/lsp/test-py-spawn/requirements.txt +0 -1
- package/clients/lsp/test-py-spawn/test.py +0 -3
- package/clients/lsp/test-py-svc/requirements.txt +0 -1
- package/clients/lsp/test-py-svc/test.py +0 -3
- package/clients/lsp/test-python-project/requirements.txt +0 -1
- package/clients/lsp/test-python-project/test.py +0 -5
- package/clients/metrics-client.js +0 -107
- package/clients/metrics-client.test.js +0 -128
- package/clients/metrics-client.test.ts +0 -163
- package/clients/metrics-history.js +0 -367
- package/clients/path-utils.js +0 -142
- package/clients/pipeline.js +0 -272
- package/clients/production-readiness.js +0 -522
- package/clients/project-index.js +0 -255
- package/clients/project-metadata.js +0 -531
- package/clients/ruff-client.js +0 -325
- package/clients/ruff-client.test.js +0 -132
- package/clients/ruff-client.test.ts +0 -153
- package/clients/rules-scanner.js +0 -97
- package/clients/runner-tracker.js +0 -152
- package/clients/rust-client.js +0 -205
- package/clients/rust-client.test.js +0 -108
- package/clients/rust-client.test.ts +0 -130
- package/clients/safe-spawn-async.js +0 -163
- package/clients/safe-spawn.js +0 -241
- package/clients/sanitize.js +0 -291
- package/clients/sanitize.test.js +0 -177
- package/clients/sanitize.test.ts +0 -223
- package/clients/scan-architectural-debt.js +0 -167
- package/clients/scan-utils.js +0 -83
- package/clients/secrets-scanner.js +0 -119
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/secrets-scanner.test.ts +0 -113
- package/clients/sg-runner.js +0 -292
- package/clients/state-matrix.js +0 -160
- package/clients/subprocess-client.js +0 -65
- package/clients/symbol-types.js +0 -5
- package/clients/test-runner-client.js +0 -523
- package/clients/test-runner-client.test.js +0 -192
- package/clients/test-runner-client.test.ts +0 -253
- package/clients/test-utils.js +0 -27
- package/clients/test-utils.ts +0 -36
- package/clients/todo-scanner.js +0 -200
- package/clients/todo-scanner.test.js +0 -301
- package/clients/todo-scanner.test.ts +0 -352
- package/clients/tool-availability.js +0 -207
- package/clients/tree-sitter-client.js +0 -601
- package/clients/tree-sitter-query-loader.js +0 -355
- package/clients/tree-sitter-symbol-extractor.js +0 -289
- package/clients/ts-service.js +0 -129
- package/clients/type-coverage-client.js +0 -127
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/type-coverage-client.test.ts +0 -125
- package/clients/type-safety-client.js +0 -138
- package/clients/types.js +0 -11
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.codefix.test.ts +0 -186
- package/clients/typescript-client.js +0 -509
- package/clients/typescript-client.test.js +0 -105
- package/clients/typescript-client.test.ts +0 -126
- package/commands/booboo.js +0 -1007
- package/commands/fix-from-booboo.js +0 -398
- package/commands/fix-simplified.js +0 -618
- package/commands/rate.js +0 -281
- package/commands/rate.test.js +0 -119
- package/commands/rate.test.ts +0 -131
- package/commands/refactor.js +0 -130
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Metrics History Tracker for pi-lens
|
|
3
|
-
*
|
|
4
|
-
* Persists complexity metrics per commit to track trends over time.
|
|
5
|
-
* Captures snapshots passively (session start) and explicitly (/lens-metrics).
|
|
6
|
-
*
|
|
7
|
-
* Storage: .pi-lens/metrics-history.json
|
|
8
|
-
*/
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
// --- Constants ---
|
|
12
|
-
const HISTORY_FILE = ".pi-lens/metrics-history.json";
|
|
13
|
-
const MAX_HISTORY_PER_FILE = 20;
|
|
14
|
-
// --- Git Helpers ---
|
|
15
|
-
/**
|
|
16
|
-
* Get current git commit hash (short)
|
|
17
|
-
*/
|
|
18
|
-
function getCurrentCommit() {
|
|
19
|
-
try {
|
|
20
|
-
const { execSync } = require("node:child_process");
|
|
21
|
-
return execSync("git rev-parse --short HEAD", {
|
|
22
|
-
encoding: "utf-8",
|
|
23
|
-
timeout: 5000,
|
|
24
|
-
}).trim();
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return "unknown";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// --- History Management ---
|
|
31
|
-
/**
|
|
32
|
-
* Load history from disk (or return empty)
|
|
33
|
-
*/
|
|
34
|
-
export function loadHistory() {
|
|
35
|
-
const historyPath = path.join(process.cwd(), HISTORY_FILE);
|
|
36
|
-
if (!fs.existsSync(historyPath)) {
|
|
37
|
-
return {
|
|
38
|
-
version: 1,
|
|
39
|
-
files: {},
|
|
40
|
-
capturedAt: new Date().toISOString(),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
try {
|
|
44
|
-
const content = fs.readFileSync(historyPath, "utf-8");
|
|
45
|
-
return JSON.parse(content);
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return {
|
|
49
|
-
version: 1,
|
|
50
|
-
files: {},
|
|
51
|
-
capturedAt: new Date().toISOString(),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Save history to disk
|
|
57
|
-
*/
|
|
58
|
-
export function saveHistory(history) {
|
|
59
|
-
const historyDir = path.join(process.cwd(), ".pi-lens");
|
|
60
|
-
if (!fs.existsSync(historyDir)) {
|
|
61
|
-
fs.mkdirSync(historyDir, { recursive: true });
|
|
62
|
-
}
|
|
63
|
-
history.capturedAt = new Date().toISOString();
|
|
64
|
-
const historyPath = path.join(historyDir, "metrics-history.json");
|
|
65
|
-
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
|
|
66
|
-
}
|
|
67
|
-
// In-memory cache to avoid loading/saving on every capture
|
|
68
|
-
let pendingHistory = null;
|
|
69
|
-
let saveTimer = null;
|
|
70
|
-
const SAVE_DEBOUNCE_MS = 5000; // Save at most every 5 seconds
|
|
71
|
-
/**
|
|
72
|
-
* Capture a snapshot for a file's current metrics
|
|
73
|
-
* Auto-saves to disk (debounced) for passive tracking
|
|
74
|
-
*/
|
|
75
|
-
export function captureSnapshot(filePath, metrics) {
|
|
76
|
-
// Use in-memory cache if available, otherwise load from disk
|
|
77
|
-
if (!pendingHistory) {
|
|
78
|
-
pendingHistory = loadHistory();
|
|
79
|
-
}
|
|
80
|
-
const relativePath = path.relative(process.cwd(), filePath);
|
|
81
|
-
const commit = getCurrentCommit();
|
|
82
|
-
const snapshot = {
|
|
83
|
-
commit,
|
|
84
|
-
timestamp: new Date().toISOString(),
|
|
85
|
-
mi: Math.round(metrics.maintainabilityIndex * 10) / 10,
|
|
86
|
-
cognitive: metrics.cognitiveComplexity,
|
|
87
|
-
nesting: metrics.maxNestingDepth,
|
|
88
|
-
lines: metrics.linesOfCode,
|
|
89
|
-
maxCyclomatic: metrics.maxCyclomatic,
|
|
90
|
-
entropy: Math.round(metrics.entropy * 100) / 100,
|
|
91
|
-
};
|
|
92
|
-
const existing = pendingHistory.files[relativePath];
|
|
93
|
-
if (existing) {
|
|
94
|
-
// Skip if same commit + same MI (no change worth recording)
|
|
95
|
-
const latest = existing.latest;
|
|
96
|
-
if (latest.commit === commit && latest.mi === snapshot.mi) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
// Append to history (cap at MAX_HISTORY_PER_FILE)
|
|
100
|
-
existing.history.push(snapshot);
|
|
101
|
-
if (existing.history.length > MAX_HISTORY_PER_FILE) {
|
|
102
|
-
existing.history = existing.history.slice(-MAX_HISTORY_PER_FILE);
|
|
103
|
-
}
|
|
104
|
-
existing.latest = snapshot;
|
|
105
|
-
existing.trend = computeTrend(existing.history);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
// New file
|
|
109
|
-
pendingHistory.files[relativePath] = {
|
|
110
|
-
latest: snapshot,
|
|
111
|
-
history: [snapshot],
|
|
112
|
-
trend: "stable",
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
// Debounced save to disk
|
|
116
|
-
if (saveTimer)
|
|
117
|
-
clearTimeout(saveTimer);
|
|
118
|
-
saveTimer = setTimeout(() => {
|
|
119
|
-
if (pendingHistory) {
|
|
120
|
-
saveHistory(pendingHistory);
|
|
121
|
-
pendingHistory = null;
|
|
122
|
-
}
|
|
123
|
-
}, SAVE_DEBOUNCE_MS);
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Capture snapshots for multiple files (explicit, immediate save)
|
|
127
|
-
* Used by /lens-metrics for batch capture
|
|
128
|
-
*/
|
|
129
|
-
export function captureSnapshots(files) {
|
|
130
|
-
const history = loadHistory();
|
|
131
|
-
for (const file of files) {
|
|
132
|
-
const relativePath = path.relative(process.cwd(), file.filePath);
|
|
133
|
-
const commit = getCurrentCommit();
|
|
134
|
-
const snapshot = {
|
|
135
|
-
commit,
|
|
136
|
-
timestamp: new Date().toISOString(),
|
|
137
|
-
mi: Math.round(file.metrics.maintainabilityIndex * 10) / 10,
|
|
138
|
-
cognitive: file.metrics.cognitiveComplexity,
|
|
139
|
-
nesting: file.metrics.maxNestingDepth,
|
|
140
|
-
lines: file.metrics.linesOfCode,
|
|
141
|
-
maxCyclomatic: file.metrics.maxCyclomatic,
|
|
142
|
-
entropy: Math.round(file.metrics.entropy * 100) / 100,
|
|
143
|
-
};
|
|
144
|
-
const existing = history.files[relativePath];
|
|
145
|
-
if (existing) {
|
|
146
|
-
existing.history.push(snapshot);
|
|
147
|
-
if (existing.history.length > MAX_HISTORY_PER_FILE) {
|
|
148
|
-
existing.history = existing.history.slice(-MAX_HISTORY_PER_FILE);
|
|
149
|
-
}
|
|
150
|
-
existing.latest = snapshot;
|
|
151
|
-
existing.trend = computeTrend(existing.history);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
history.files[relativePath] = {
|
|
155
|
-
latest: snapshot,
|
|
156
|
-
history: [snapshot],
|
|
157
|
-
trend: "stable",
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
saveHistory(history);
|
|
162
|
-
return history;
|
|
163
|
-
}
|
|
164
|
-
// --- Trend Analysis ---
|
|
165
|
-
/**
|
|
166
|
-
* Compute trend direction from history snapshots
|
|
167
|
-
* Uses last 3 snapshots for stability (or 2 if only 2 available)
|
|
168
|
-
*/
|
|
169
|
-
export function computeTrend(history) {
|
|
170
|
-
if (history.length < 2)
|
|
171
|
-
return "stable";
|
|
172
|
-
const recent = history.slice(-3);
|
|
173
|
-
const first = recent[0];
|
|
174
|
-
const last = recent[recent.length - 1];
|
|
175
|
-
// Use MI as primary indicator, cognitive as secondary
|
|
176
|
-
const miDelta = last.mi - first.mi;
|
|
177
|
-
const cogDelta = last.cognitive - first.cognitive;
|
|
178
|
-
// Thresholds (MI changes < 2 are noise)
|
|
179
|
-
if (miDelta > 2)
|
|
180
|
-
return "improving";
|
|
181
|
-
if (miDelta < -2)
|
|
182
|
-
return "regressing";
|
|
183
|
-
// If MI is stable, check cognitive
|
|
184
|
-
if (cogDelta < -10)
|
|
185
|
-
return "improving";
|
|
186
|
-
if (cogDelta > 10)
|
|
187
|
-
return "regressing";
|
|
188
|
-
return "stable";
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Get delta between current snapshot and previous
|
|
192
|
-
*/
|
|
193
|
-
export function getDelta(history) {
|
|
194
|
-
if (!history || history.history.length < 2)
|
|
195
|
-
return null;
|
|
196
|
-
const current = history.history[history.history.length - 1];
|
|
197
|
-
const previous = history.history[history.history.length - 2];
|
|
198
|
-
return {
|
|
199
|
-
mi: Math.round((current.mi - previous.mi) * 10) / 10,
|
|
200
|
-
cognitive: current.cognitive - previous.cognitive,
|
|
201
|
-
trend: history.trend,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Get trend emoji for display
|
|
206
|
-
*/
|
|
207
|
-
export function getTrendEmoji(trend) {
|
|
208
|
-
switch (trend) {
|
|
209
|
-
case "improving":
|
|
210
|
-
return "📈";
|
|
211
|
-
case "regressing":
|
|
212
|
-
return "📉";
|
|
213
|
-
default:
|
|
214
|
-
return "➡️";
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Get trend summary across all files
|
|
219
|
-
*/
|
|
220
|
-
export function getTrendSummary(history) {
|
|
221
|
-
let improving = 0;
|
|
222
|
-
let regressing = 0;
|
|
223
|
-
let stable = 0;
|
|
224
|
-
const regressions = [];
|
|
225
|
-
for (const [file, fileHistory] of Object.entries(history.files)) {
|
|
226
|
-
switch (fileHistory.trend) {
|
|
227
|
-
case "improving":
|
|
228
|
-
improving++;
|
|
229
|
-
break;
|
|
230
|
-
case "regressing": {
|
|
231
|
-
regressing++;
|
|
232
|
-
const delta = getDelta(fileHistory);
|
|
233
|
-
if (delta) {
|
|
234
|
-
regressions.push({ file, miDelta: delta.mi });
|
|
235
|
-
}
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
default:
|
|
239
|
-
stable++;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Sort regressions by MI delta (worst first)
|
|
243
|
-
regressions.sort((a, b) => a.miDelta - b.miDelta);
|
|
244
|
-
return {
|
|
245
|
-
improving,
|
|
246
|
-
regressing,
|
|
247
|
-
stable,
|
|
248
|
-
worstRegressions: regressions.slice(0, 5),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Format trend for metrics table
|
|
253
|
-
*/
|
|
254
|
-
export function formatTrendCell(filePath, history) {
|
|
255
|
-
const relativePath = path.relative(process.cwd(), filePath);
|
|
256
|
-
const fileHistory = history.files[relativePath];
|
|
257
|
-
if (!fileHistory || fileHistory.history.length < 2) {
|
|
258
|
-
return "—"; // No history
|
|
259
|
-
}
|
|
260
|
-
const delta = getDelta(fileHistory);
|
|
261
|
-
if (!delta)
|
|
262
|
-
return "—";
|
|
263
|
-
const emoji = getTrendEmoji(delta.trend);
|
|
264
|
-
const miSign = delta.mi > 0 ? "+" : "";
|
|
265
|
-
const miColor = delta.mi > 0 ? "🟢" : delta.mi < 0 ? "🔴" : "⚪";
|
|
266
|
-
return `${emoji} ${miColor}${miSign}${delta.mi}`;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Calculate Technical Debt Index for the project.
|
|
270
|
-
* Score: 0 = perfect, 100 = maximum debt.
|
|
271
|
-
*/
|
|
272
|
-
export function computeTDI(history) {
|
|
273
|
-
const files = Object.values(history.files);
|
|
274
|
-
if (files.length === 0) {
|
|
275
|
-
return {
|
|
276
|
-
score: 0,
|
|
277
|
-
grade: "N/A",
|
|
278
|
-
avgMI: 100,
|
|
279
|
-
totalCognitive: 0,
|
|
280
|
-
filesAnalyzed: 0,
|
|
281
|
-
filesWithDebt: 0,
|
|
282
|
-
byCategory: {
|
|
283
|
-
maintainability: 0,
|
|
284
|
-
cognitive: 0,
|
|
285
|
-
nesting: 0,
|
|
286
|
-
maxCyclomatic: 0,
|
|
287
|
-
entropy: 0,
|
|
288
|
-
},
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
let totalMI = 0;
|
|
292
|
-
let totalCognitive = 0;
|
|
293
|
-
let _totalNesting = 0;
|
|
294
|
-
let filesWithDebt = 0;
|
|
295
|
-
let debtFromMI = 0;
|
|
296
|
-
let debtFromCognitive = 0;
|
|
297
|
-
let debtFromNesting = 0;
|
|
298
|
-
let debtFromMaxCyclomatic = 0; // NEW
|
|
299
|
-
let debtFromEntropy = 0; // NEW
|
|
300
|
-
for (const file of files) {
|
|
301
|
-
const snap = file.latest;
|
|
302
|
-
totalMI += snap.mi;
|
|
303
|
-
totalCognitive += snap.cognitive;
|
|
304
|
-
_totalNesting += snap.nesting;
|
|
305
|
-
// Accumulate debt points
|
|
306
|
-
let fileDebt = 0;
|
|
307
|
-
// MI debt: 0 at MI=100, max at MI=0
|
|
308
|
-
const miDebt = Math.max(0, (100 - snap.mi) / 100);
|
|
309
|
-
debtFromMI += miDebt;
|
|
310
|
-
// Cognitive debt: 0 at 0, max at 500+
|
|
311
|
-
const cogDebt = Math.min(1, snap.cognitive / 200);
|
|
312
|
-
debtFromCognitive += cogDebt;
|
|
313
|
-
// Nesting debt: 0 at 1-3, max at 10+
|
|
314
|
-
const nestDebt = Math.min(1, Math.max(0, snap.nesting - 3) / 7);
|
|
315
|
-
debtFromNesting += nestDebt;
|
|
316
|
-
// Max Cyclomatic debt: 0 at max<=10, 1 at max>=30
|
|
317
|
-
const maxCycDebt = Math.min(1, Math.max(0, snap.maxCyclomatic - 10) / 20);
|
|
318
|
-
debtFromMaxCyclomatic += maxCycDebt;
|
|
319
|
-
// Entropy debt: 0 at entropy<=4.0, 1 at entropy>=7.0
|
|
320
|
-
const entropyDebt = Math.min(1, Math.max(0, snap.entropy - 4.0) / 3.0);
|
|
321
|
-
debtFromEntropy += entropyDebt;
|
|
322
|
-
fileDebt = miDebt + cogDebt + nestDebt + maxCycDebt + entropyDebt;
|
|
323
|
-
if (fileDebt > 0.5)
|
|
324
|
-
filesWithDebt++; // Lowered threshold since we have more factors
|
|
325
|
-
}
|
|
326
|
-
const avgMI = totalMI / files.length;
|
|
327
|
-
// Normalize to 0-100 scale
|
|
328
|
-
const avgMIDebt = debtFromMI / files.length; // 0-1
|
|
329
|
-
const avgCogDebt = debtFromCognitive / files.length; // 0-1
|
|
330
|
-
const avgNestDebt = debtFromNesting / files.length; // 0-1
|
|
331
|
-
const avgMaxCycDebt = debtFromMaxCyclomatic / files.length; // NEW
|
|
332
|
-
const avgEntropyDebt = debtFromEntropy / files.length; // NEW
|
|
333
|
-
// Weighted: MI (45%), cognitive (30%), nesting (10%), maxCyc (10%), entropy (5%)
|
|
334
|
-
const rawScore = avgMIDebt * 45 +
|
|
335
|
-
avgCogDebt * 30 +
|
|
336
|
-
avgNestDebt * 10 +
|
|
337
|
-
avgMaxCycDebt * 10 +
|
|
338
|
-
avgEntropyDebt * 5;
|
|
339
|
-
const score = Math.round(rawScore * 100) / 100;
|
|
340
|
-
// Grade
|
|
341
|
-
let grade;
|
|
342
|
-
if (score <= 15)
|
|
343
|
-
grade = "A";
|
|
344
|
-
else if (score <= 30)
|
|
345
|
-
grade = "B";
|
|
346
|
-
else if (score <= 50)
|
|
347
|
-
grade = "C";
|
|
348
|
-
else if (score <= 70)
|
|
349
|
-
grade = "D";
|
|
350
|
-
else
|
|
351
|
-
grade = "F";
|
|
352
|
-
return {
|
|
353
|
-
score,
|
|
354
|
-
grade,
|
|
355
|
-
avgMI: Math.round(avgMI * 10) / 10,
|
|
356
|
-
totalCognitive,
|
|
357
|
-
filesAnalyzed: files.length,
|
|
358
|
-
filesWithDebt,
|
|
359
|
-
byCategory: {
|
|
360
|
-
maintainability: Math.round(avgMIDebt * 100),
|
|
361
|
-
cognitive: Math.round(avgCogDebt * 100),
|
|
362
|
-
nesting: Math.round(avgNestDebt * 100),
|
|
363
|
-
maxCyclomatic: Math.round(avgMaxCycDebt * 100),
|
|
364
|
-
entropy: Math.round(avgEntropyDebt * 100),
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
|
-
}
|
package/clients/path-utils.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path utilities for pi-lens
|
|
3
|
-
*
|
|
4
|
-
* Handles cross-platform path normalization, particularly
|
|
5
|
-
* Windows case-insensitivity issues when using paths as Map keys.
|
|
6
|
-
*
|
|
7
|
-
* Approach (inspired by OpenCode's Filesystem.normalizePath):
|
|
8
|
-
* - On Windows: try realpathSync.native() for canonical casing
|
|
9
|
-
* - Falls back to lowercase for files that don't exist yet
|
|
10
|
-
* - On non-Windows: return path as-is (case-sensitive filesystem)
|
|
11
|
-
* - Always convert backslashes to forward slashes for Map key consistency
|
|
12
|
-
*/
|
|
13
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
14
|
-
import { dirname, win32 } from "node:path";
|
|
15
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
|
-
/**
|
|
17
|
-
* Detect if a path is a Windows path (has drive letter or UNC prefix).
|
|
18
|
-
*/
|
|
19
|
-
function isWindowsPath(filePath) {
|
|
20
|
-
return /^[A-Za-z]:/.test(filePath) || filePath.startsWith("\\\\");
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Normalize a file path for consistent Map key usage.
|
|
24
|
-
*
|
|
25
|
-
* On Windows:
|
|
26
|
-
* - If the file exists: uses realpathSync.native() to get the canonical
|
|
27
|
-
* filesystem path (actual casing, resolved symlinks)
|
|
28
|
-
* - If the file doesn't exist: resolves the path and lowercases
|
|
29
|
-
* (needed for new files where we haven't written yet)
|
|
30
|
-
*
|
|
31
|
-
* On non-Windows: returns path as-is (case-sensitive filesystem).
|
|
32
|
-
*
|
|
33
|
-
* Always converts backslashes to forward slashes for consistent Map keys.
|
|
34
|
-
*/
|
|
35
|
-
export function normalizeFilePath(filePath) {
|
|
36
|
-
// Convert backslashes to forward slashes first
|
|
37
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
38
|
-
if (process.platform !== "win32" && !isWindowsPath(normalized)) {
|
|
39
|
-
return normalized;
|
|
40
|
-
}
|
|
41
|
-
// Windows: try realpathSync.native() for canonical casing
|
|
42
|
-
// This resolves symlinks and returns the actual filesystem casing
|
|
43
|
-
try {
|
|
44
|
-
const canonical = realpathSync.native(filePath);
|
|
45
|
-
return canonical.replace(/\\/g, "/");
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
// File doesn't exist yet (new file) — resolve path and lowercase
|
|
49
|
-
// We need to walk up the directory tree to find the nearest existing
|
|
50
|
-
// parent, resolve its casing, then append the non-existent parts
|
|
51
|
-
try {
|
|
52
|
-
return resolveNonExisting(filePath);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// Last resort: just lowercase the resolved path
|
|
56
|
-
const resolved = win32.normalize(win32.resolve(filePath));
|
|
57
|
-
return resolved.replace(/\\/g, "/").toLowerCase();
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Resolve a non-existing path by finding the nearest existing parent,
|
|
63
|
-
* getting its canonical casing, then appending the non-existent parts lowercased.
|
|
64
|
-
*
|
|
65
|
-
* Example: C:\Users\Foo\newdir\file.ts
|
|
66
|
-
* - C:\Users\Foo exists → realpathSync gives C:\Users\Foo
|
|
67
|
-
* - newdir\file.ts doesn't exist → lowercased
|
|
68
|
-
* - Result: C:/Users/Foo/newdir/file.ts
|
|
69
|
-
*/
|
|
70
|
-
function resolveNonExisting(filePath) {
|
|
71
|
-
const resolved = win32.resolve(filePath);
|
|
72
|
-
let current = resolved;
|
|
73
|
-
const nonExistentParts = [];
|
|
74
|
-
// Walk up until we find an existing directory
|
|
75
|
-
while (true) {
|
|
76
|
-
if (existsSync(current)) {
|
|
77
|
-
// Found existing ancestor — get its canonical casing
|
|
78
|
-
const canonical = realpathSync.native(current);
|
|
79
|
-
if (nonExistentParts.length === 0) {
|
|
80
|
-
return canonical.replace(/\\/g, "/");
|
|
81
|
-
}
|
|
82
|
-
// Append non-existent parts (lowercased for consistency)
|
|
83
|
-
const tail = nonExistentParts.reverse().join("/").toLowerCase();
|
|
84
|
-
const base = canonical.replace(/\\/g, "/");
|
|
85
|
-
return base.endsWith("/") ? base + tail : `${base}/${tail}`;
|
|
86
|
-
}
|
|
87
|
-
const parent = dirname(current);
|
|
88
|
-
if (parent === current) {
|
|
89
|
-
// Reached filesystem root without finding existing dir
|
|
90
|
-
// Fall back to full lowercase
|
|
91
|
-
throw new Error("No existing parent found");
|
|
92
|
-
}
|
|
93
|
-
nonExistentParts.push(win32.basename(current));
|
|
94
|
-
current = parent;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Convert a file:// URI to a normalized path.
|
|
99
|
-
* Handles URL decoding and Windows drive letter normalization.
|
|
100
|
-
*/
|
|
101
|
-
export function uriToPath(uri) {
|
|
102
|
-
try {
|
|
103
|
-
const filePath = fileURLToPath(uri);
|
|
104
|
-
return normalizeFilePath(filePath);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// Not a valid file:// URI, treat as plain path
|
|
108
|
-
return normalizeFilePath(uri);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Convert a path to a file:// URI.
|
|
113
|
-
* Does NOT normalize the path - URIs preserve original casing.
|
|
114
|
-
*/
|
|
115
|
-
export function pathToUri(filePath) {
|
|
116
|
-
return pathToFileURL(filePath).href;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Normalize a Map key lookup for file paths.
|
|
120
|
-
* Use this when getting/setting values in Maps that use file paths as keys.
|
|
121
|
-
*/
|
|
122
|
-
export function normalizeMapKey(filePath) {
|
|
123
|
-
return normalizeFilePath(filePath);
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Compare two file paths for equality, handling Windows case-insensitivity
|
|
127
|
-
* and mixed separators (backslash vs forward slash).
|
|
128
|
-
*/
|
|
129
|
-
export function pathsEqual(a, b) {
|
|
130
|
-
return normalizeFilePath(a) === normalizeFilePath(b);
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Check if `child` is under `parent` directory.
|
|
134
|
-
* Separator-agnostic and case-insensitive on Windows.
|
|
135
|
-
*/
|
|
136
|
-
export function isUnderDir(child, parent) {
|
|
137
|
-
const normChild = normalizeFilePath(child);
|
|
138
|
-
const normParent = normalizeFilePath(parent);
|
|
139
|
-
// Ensure parent ends with / for prefix matching
|
|
140
|
-
const parentPrefix = normParent.endsWith("/") ? normParent : `${normParent}/`;
|
|
141
|
-
return normChild === normParent || normChild.startsWith(parentPrefix);
|
|
142
|
-
}
|