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.
Files changed (207) hide show
  1. package/CHANGELOG.md +10 -2
  2. package/package.json +4 -4
  3. package/tsconfig.json +1 -1
  4. package/clients/__tests__/file-time.test.js +0 -216
  5. package/clients/__tests__/file-time.test.ts +0 -276
  6. package/clients/__tests__/format-service.test.js +0 -245
  7. package/clients/__tests__/format-service.test.ts +0 -339
  8. package/clients/__tests__/formatters.test.js +0 -271
  9. package/clients/__tests__/formatters.test.ts +0 -401
  10. package/clients/agent-behavior-client.js +0 -110
  11. package/clients/agent-behavior-client.test.js +0 -94
  12. package/clients/agent-behavior-client.test.ts +0 -116
  13. package/clients/amain-types.js +0 -164
  14. package/clients/architect-client.js +0 -291
  15. package/clients/ast-grep-client.js +0 -253
  16. package/clients/ast-grep-parser.js +0 -84
  17. package/clients/ast-grep-rule-manager.js +0 -89
  18. package/clients/ast-grep-types.js +0 -9
  19. package/clients/auto-loop.js +0 -131
  20. package/clients/biome-client.js +0 -420
  21. package/clients/biome-client.test.js +0 -144
  22. package/clients/biome-client.test.ts +0 -163
  23. package/clients/cache/rule-cache.js +0 -72
  24. package/clients/cache-manager.js +0 -245
  25. package/clients/cache-manager.test.js +0 -197
  26. package/clients/cache-manager.test.ts +0 -299
  27. package/clients/complexity-client.js +0 -675
  28. package/clients/complexity-client.test.js +0 -234
  29. package/clients/complexity-client.test.ts +0 -255
  30. package/clients/config-validator.js +0 -465
  31. package/clients/dependency-checker.js +0 -325
  32. package/clients/dependency-checker.test.js +0 -60
  33. package/clients/dependency-checker.test.ts +0 -71
  34. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  35. package/clients/dispatch/__tests__/autofix-integration.test.ts +0 -300
  36. package/clients/dispatch/__tests__/runner-registration.test.js +0 -234
  37. package/clients/dispatch/__tests__/runner-registration.test.ts +0 -286
  38. package/clients/dispatch/debug.log +0 -1
  39. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  40. package/clients/dispatch/dispatcher.edge.test.ts +0 -100
  41. package/clients/dispatch/dispatcher.format.test.js +0 -46
  42. package/clients/dispatch/dispatcher.format.test.ts +0 -58
  43. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  44. package/clients/dispatch/dispatcher.inline.test.ts +0 -93
  45. package/clients/dispatch/dispatcher.js +0 -381
  46. package/clients/dispatch/dispatcher.test.js +0 -116
  47. package/clients/dispatch/dispatcher.test.ts +0 -149
  48. package/clients/dispatch/integration.js +0 -108
  49. package/clients/dispatch/plan.js +0 -183
  50. package/clients/dispatch/runners/architect.js +0 -83
  51. package/clients/dispatch/runners/architect.test.js +0 -138
  52. package/clients/dispatch/runners/architect.test.ts +0 -162
  53. package/clients/dispatch/runners/ast-grep-napi.js +0 -405
  54. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -107
  55. package/clients/dispatch/runners/ast-grep-napi.test.ts +0 -129
  56. package/clients/dispatch/runners/ast-grep.js +0 -157
  57. package/clients/dispatch/runners/biome.js +0 -55
  58. package/clients/dispatch/runners/config-validation.js +0 -67
  59. package/clients/dispatch/runners/go-vet.js +0 -48
  60. package/clients/dispatch/runners/index.js +0 -47
  61. package/clients/dispatch/runners/lsp.js +0 -102
  62. package/clients/dispatch/runners/oxlint.js +0 -67
  63. package/clients/dispatch/runners/oxlint.test.js +0 -230
  64. package/clients/dispatch/runners/oxlint.test.ts +0 -303
  65. package/clients/dispatch/runners/pyright.js +0 -100
  66. package/clients/dispatch/runners/pyright.test.js +0 -98
  67. package/clients/dispatch/runners/pyright.test.ts +0 -121
  68. package/clients/dispatch/runners/python-slop.js +0 -97
  69. package/clients/dispatch/runners/python-slop.test.js +0 -203
  70. package/clients/dispatch/runners/python-slop.test.ts +0 -298
  71. package/clients/dispatch/runners/ruff.js +0 -48
  72. package/clients/dispatch/runners/rust-clippy.js +0 -102
  73. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  74. package/clients/dispatch/runners/scan_codebase.test.ts +0 -105
  75. package/clients/dispatch/runners/shellcheck.js +0 -147
  76. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  77. package/clients/dispatch/runners/shellcheck.test.ts +0 -129
  78. package/clients/dispatch/runners/similarity.js +0 -230
  79. package/clients/dispatch/runners/spellcheck.js +0 -106
  80. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  81. package/clients/dispatch/runners/spellcheck.test.ts +0 -214
  82. package/clients/dispatch/runners/tree-sitter.js +0 -246
  83. package/clients/dispatch/runners/ts-lsp.js +0 -125
  84. package/clients/dispatch/runners/ts-slop.js +0 -113
  85. package/clients/dispatch/runners/type-safety.js +0 -142
  86. package/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -134
  87. package/clients/dispatch/runners/utils/runner-helpers.js +0 -115
  88. package/clients/dispatch/runners/utils.js +0 -51
  89. package/clients/dispatch/runners/yaml-rule-parser.js +0 -360
  90. package/clients/dispatch/types.js +0 -16
  91. package/clients/dispatch/utils/format-utils.js +0 -44
  92. package/clients/dogfood.test.js +0 -201
  93. package/clients/dogfood.test.ts +0 -269
  94. package/clients/file-kinds.js +0 -177
  95. package/clients/file-kinds.test.js +0 -169
  96. package/clients/file-kinds.test.ts +0 -210
  97. package/clients/file-time.js +0 -152
  98. package/clients/file-utils.js +0 -40
  99. package/clients/fix-scanners.js +0 -204
  100. package/clients/format-service.js +0 -184
  101. package/clients/formatters.js +0 -488
  102. package/clients/go-client.js +0 -203
  103. package/clients/go-client.test.js +0 -127
  104. package/clients/go-client.test.ts +0 -143
  105. package/clients/installer/index.js +0 -403
  106. package/clients/interviewer-templates.js +0 -75
  107. package/clients/interviewer.js +0 -173
  108. package/clients/jscpd-client.js +0 -196
  109. package/clients/jscpd-client.test.js +0 -127
  110. package/clients/jscpd-client.test.ts +0 -145
  111. package/clients/knip-client.js +0 -239
  112. package/clients/knip-client.test.js +0 -112
  113. package/clients/knip-client.test.ts +0 -128
  114. package/clients/latency-logger.js +0 -40
  115. package/clients/lsp/__tests__/client.test.js +0 -310
  116. package/clients/lsp/__tests__/client.test.ts +0 -412
  117. package/clients/lsp/__tests__/config.test.js +0 -167
  118. package/clients/lsp/__tests__/config.test.ts +0 -217
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/error-recovery.test.ts +0 -279
  121. package/clients/lsp/__tests__/integration.test.js +0 -127
  122. package/clients/lsp/__tests__/integration.test.ts +0 -160
  123. package/clients/lsp/__tests__/launch.test.js +0 -313
  124. package/clients/lsp/__tests__/launch.test.ts +0 -394
  125. package/clients/lsp/__tests__/server.test.js +0 -259
  126. package/clients/lsp/__tests__/server.test.ts +0 -332
  127. package/clients/lsp/__tests__/service.test.js +0 -438
  128. package/clients/lsp/__tests__/service.test.ts +0 -530
  129. package/clients/lsp/client.js +0 -350
  130. package/clients/lsp/config.js +0 -112
  131. package/clients/lsp/index.js +0 -318
  132. package/clients/lsp/installer/index.js +0 -391
  133. package/clients/lsp/interactive-install.js +0 -221
  134. package/clients/lsp/language.js +0 -170
  135. package/clients/lsp/launch.js +0 -329
  136. package/clients/lsp/lsp/launch.js +0 -116
  137. package/clients/lsp/lsp/server.js +0 -532
  138. package/clients/lsp/lsp-index.js +0 -10
  139. package/clients/lsp/path-utils.js +0 -5
  140. package/clients/lsp/server.js +0 -725
  141. package/clients/lsp/test-py-spawn/requirements.txt +0 -1
  142. package/clients/lsp/test-py-spawn/test.py +0 -3
  143. package/clients/lsp/test-py-svc/requirements.txt +0 -1
  144. package/clients/lsp/test-py-svc/test.py +0 -3
  145. package/clients/lsp/test-python-project/requirements.txt +0 -1
  146. package/clients/lsp/test-python-project/test.py +0 -5
  147. package/clients/metrics-client.js +0 -107
  148. package/clients/metrics-client.test.js +0 -128
  149. package/clients/metrics-client.test.ts +0 -163
  150. package/clients/metrics-history.js +0 -367
  151. package/clients/path-utils.js +0 -142
  152. package/clients/pipeline.js +0 -272
  153. package/clients/production-readiness.js +0 -522
  154. package/clients/project-index.js +0 -255
  155. package/clients/project-metadata.js +0 -531
  156. package/clients/ruff-client.js +0 -325
  157. package/clients/ruff-client.test.js +0 -132
  158. package/clients/ruff-client.test.ts +0 -153
  159. package/clients/rules-scanner.js +0 -97
  160. package/clients/runner-tracker.js +0 -152
  161. package/clients/rust-client.js +0 -205
  162. package/clients/rust-client.test.js +0 -108
  163. package/clients/rust-client.test.ts +0 -130
  164. package/clients/safe-spawn-async.js +0 -163
  165. package/clients/safe-spawn.js +0 -241
  166. package/clients/sanitize.js +0 -291
  167. package/clients/sanitize.test.js +0 -177
  168. package/clients/sanitize.test.ts +0 -223
  169. package/clients/scan-architectural-debt.js +0 -167
  170. package/clients/scan-utils.js +0 -83
  171. package/clients/secrets-scanner.js +0 -119
  172. package/clients/secrets-scanner.test.js +0 -100
  173. package/clients/secrets-scanner.test.ts +0 -113
  174. package/clients/sg-runner.js +0 -292
  175. package/clients/state-matrix.js +0 -160
  176. package/clients/subprocess-client.js +0 -65
  177. package/clients/symbol-types.js +0 -5
  178. package/clients/test-runner-client.js +0 -523
  179. package/clients/test-runner-client.test.js +0 -192
  180. package/clients/test-runner-client.test.ts +0 -253
  181. package/clients/test-utils.js +0 -27
  182. package/clients/test-utils.ts +0 -36
  183. package/clients/todo-scanner.js +0 -200
  184. package/clients/todo-scanner.test.js +0 -301
  185. package/clients/todo-scanner.test.ts +0 -352
  186. package/clients/tool-availability.js +0 -207
  187. package/clients/tree-sitter-client.js +0 -601
  188. package/clients/tree-sitter-query-loader.js +0 -355
  189. package/clients/tree-sitter-symbol-extractor.js +0 -289
  190. package/clients/ts-service.js +0 -129
  191. package/clients/type-coverage-client.js +0 -127
  192. package/clients/type-coverage-client.test.js +0 -105
  193. package/clients/type-coverage-client.test.ts +0 -125
  194. package/clients/type-safety-client.js +0 -138
  195. package/clients/types.js +0 -11
  196. package/clients/typescript-client.codefix.test.js +0 -157
  197. package/clients/typescript-client.codefix.test.ts +0 -186
  198. package/clients/typescript-client.js +0 -509
  199. package/clients/typescript-client.test.js +0 -105
  200. package/clients/typescript-client.test.ts +0 -126
  201. package/commands/booboo.js +0 -1007
  202. package/commands/fix-from-booboo.js +0 -398
  203. package/commands/fix-simplified.js +0 -618
  204. package/commands/rate.js +0 -281
  205. package/commands/rate.test.js +0 -119
  206. package/commands/rate.test.ts +0 -131
  207. 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
- }
@@ -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
- }