@eduardbar/drift 0.9.1 → 1.0.0
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/.github/workflows/publish-vscode.yml +76 -0
- package/AGENTS.md +30 -12
- package/README.md +1 -1
- package/ROADMAP.md +130 -98
- package/dist/analyzer.d.ts +4 -38
- package/dist/analyzer.js +85 -1543
- package/dist/cli.js +47 -4
- package/dist/config.js +1 -1
- package/dist/fix.d.ts +13 -0
- package/dist/fix.js +120 -0
- package/dist/git/blame.d.ts +22 -0
- package/dist/git/blame.js +227 -0
- package/dist/git/helpers.d.ts +36 -0
- package/dist/git/helpers.js +152 -0
- package/dist/git/trend.d.ts +21 -0
- package/dist/git/trend.js +80 -0
- package/dist/git.d.ts +0 -4
- package/dist/git.js +2 -2
- package/dist/report.js +620 -293
- package/dist/rules/phase0-basic.d.ts +11 -0
- package/dist/rules/phase0-basic.js +176 -0
- package/dist/rules/phase1-complexity.d.ts +31 -0
- package/dist/rules/phase1-complexity.js +277 -0
- package/dist/rules/phase2-crossfile.d.ts +27 -0
- package/dist/rules/phase2-crossfile.js +122 -0
- package/dist/rules/phase3-arch.d.ts +31 -0
- package/dist/rules/phase3-arch.js +148 -0
- package/dist/rules/phase5-ai.d.ts +8 -0
- package/dist/rules/phase5-ai.js +262 -0
- package/dist/rules/phase8-semantic.d.ts +22 -0
- package/dist/rules/phase8-semantic.js +109 -0
- package/dist/rules/shared.d.ts +7 -0
- package/dist/rules/shared.js +27 -0
- package/package.json +8 -3
- package/packages/vscode-drift/.vscodeignore +9 -0
- package/packages/vscode-drift/LICENSE +21 -0
- package/packages/vscode-drift/README.md +64 -0
- package/packages/vscode-drift/images/icon.png +0 -0
- package/packages/vscode-drift/images/icon.svg +30 -0
- package/packages/vscode-drift/package-lock.json +485 -0
- package/packages/vscode-drift/package.json +119 -0
- package/packages/vscode-drift/src/analyzer.ts +38 -0
- package/packages/vscode-drift/src/diagnostics.ts +55 -0
- package/packages/vscode-drift/src/extension.ts +111 -0
- package/packages/vscode-drift/src/statusbar.ts +47 -0
- package/packages/vscode-drift/src/treeview.ts +108 -0
- package/packages/vscode-drift/tsconfig.json +18 -0
- package/packages/vscode-drift/vscode-drift-0.1.0.vsix +0 -0
- package/packages/vscode-drift/vscode-drift-0.1.1.vsix +0 -0
- package/src/analyzer.ts +124 -1773
- package/src/cli.ts +53 -4
- package/src/config.ts +1 -1
- package/src/fix.ts +154 -0
- package/src/git/blame.ts +279 -0
- package/src/git/helpers.ts +198 -0
- package/src/git/trend.ts +116 -0
- package/src/git.ts +2 -2
- package/src/report.ts +631 -296
- package/src/rules/phase0-basic.ts +187 -0
- package/src/rules/phase1-complexity.ts +302 -0
- package/src/rules/phase2-crossfile.ts +149 -0
- package/src/rules/phase3-arch.ts +179 -0
- package/src/rules/phase5-ai.ts +292 -0
- package/src/rules/phase8-semantic.ts +132 -0
- package/src/rules/shared.ts +39 -0
- package/tests/helpers.ts +45 -0
- package/tests/rules.test.ts +1269 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FileReport, DriftConfig, TrendDataPoint, DriftTrendReport } from '../types.js';
|
|
2
|
+
export declare class TrendAnalyzer {
|
|
3
|
+
private readonly projectPath;
|
|
4
|
+
private readonly config;
|
|
5
|
+
private readonly analyzeProjectFn;
|
|
6
|
+
constructor(projectPath: string, analyzeProjectFn: (targetPath: string, config?: DriftConfig) => FileReport[], config?: DriftConfig);
|
|
7
|
+
static calculateMovingAverage(data: TrendDataPoint[], windowSize: number): number[];
|
|
8
|
+
static linearRegression(data: TrendDataPoint[]): {
|
|
9
|
+
slope: number;
|
|
10
|
+
intercept: number;
|
|
11
|
+
r2: number;
|
|
12
|
+
};
|
|
13
|
+
/** Generate a simple horizontal ASCII bar chart (one bar per data point). */
|
|
14
|
+
static generateTrendChart(data: TrendDataPoint[]): string;
|
|
15
|
+
analyzeTrend(options: {
|
|
16
|
+
period?: 'week' | 'month' | 'quarter' | 'year';
|
|
17
|
+
since?: string;
|
|
18
|
+
until?: string;
|
|
19
|
+
}): Promise<DriftTrendReport>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=trend.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { assertGitRepo, analyzeHistoricalCommits } from './helpers.js';
|
|
2
|
+
import { buildReport } from '../reporter.js';
|
|
3
|
+
export class TrendAnalyzer {
|
|
4
|
+
projectPath;
|
|
5
|
+
config;
|
|
6
|
+
analyzeProjectFn;
|
|
7
|
+
constructor(projectPath, analyzeProjectFn, config) {
|
|
8
|
+
this.projectPath = projectPath;
|
|
9
|
+
this.analyzeProjectFn = analyzeProjectFn;
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
// --- Static utility methods -----------------------------------------------
|
|
13
|
+
static calculateMovingAverage(data, windowSize) {
|
|
14
|
+
return data.map((_, i) => {
|
|
15
|
+
const start = Math.max(0, i - windowSize + 1);
|
|
16
|
+
const window = data.slice(start, i + 1);
|
|
17
|
+
return window.reduce((s, p) => s + p.score, 0) / window.length;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
static linearRegression(data) {
|
|
21
|
+
const n = data.length;
|
|
22
|
+
if (n < 2)
|
|
23
|
+
return { slope: 0, intercept: data[0]?.score ?? 0, r2: 0 };
|
|
24
|
+
const xs = data.map((_, i) => i);
|
|
25
|
+
const ys = data.map(p => p.score);
|
|
26
|
+
const xMean = xs.reduce((s, x) => s + x, 0) / n;
|
|
27
|
+
const yMean = ys.reduce((s, y) => s + y, 0) / n;
|
|
28
|
+
const ssXX = xs.reduce((s, x) => s + (x - xMean) ** 2, 0);
|
|
29
|
+
const ssXY = xs.reduce((s, x, i) => s + (x - xMean) * (ys[i] - yMean), 0);
|
|
30
|
+
const ssYY = ys.reduce((s, y) => s + (y - yMean) ** 2, 0);
|
|
31
|
+
const slope = ssXX === 0 ? 0 : ssXY / ssXX;
|
|
32
|
+
const intercept = yMean - slope * xMean;
|
|
33
|
+
const r2 = ssYY === 0 ? 1 : (ssXY ** 2) / (ssXX * ssYY);
|
|
34
|
+
return { slope, intercept, r2 };
|
|
35
|
+
}
|
|
36
|
+
/** Generate a simple horizontal ASCII bar chart (one bar per data point). */
|
|
37
|
+
static generateTrendChart(data) {
|
|
38
|
+
if (data.length === 0)
|
|
39
|
+
return '(no data)';
|
|
40
|
+
const maxScore = Math.max(...data.map(p => p.score), 1);
|
|
41
|
+
const chartWidth = 40;
|
|
42
|
+
const lines = data.map(p => {
|
|
43
|
+
const barLen = Math.round((p.score / maxScore) * chartWidth);
|
|
44
|
+
const bar = '█'.repeat(barLen);
|
|
45
|
+
const dateStr = p.date.toISOString().slice(0, 10);
|
|
46
|
+
return `${dateStr} │${bar.padEnd(chartWidth)} ${p.score.toFixed(1)}`;
|
|
47
|
+
});
|
|
48
|
+
return lines.join('\n');
|
|
49
|
+
}
|
|
50
|
+
// --- Instance method -------------------------------------------------------
|
|
51
|
+
async analyzeTrend(options) {
|
|
52
|
+
assertGitRepo(this.projectPath);
|
|
53
|
+
const periodDays = {
|
|
54
|
+
week: 7, month: 30, quarter: 90, year: 365,
|
|
55
|
+
};
|
|
56
|
+
const days = periodDays[options.period ?? 'month'] ?? 30;
|
|
57
|
+
const sinceDate = options.since
|
|
58
|
+
? new Date(options.since)
|
|
59
|
+
: new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
60
|
+
const historicalAnalyses = await analyzeHistoricalCommits(sinceDate, this.projectPath, 100, this.analyzeProjectFn, this.config, 10);
|
|
61
|
+
const trendPoints = historicalAnalyses.map(h => ({
|
|
62
|
+
date: h.commitDate,
|
|
63
|
+
score: h.averageScore,
|
|
64
|
+
fileCount: h.files.length,
|
|
65
|
+
avgIssuesPerFile: h.files.length > 0
|
|
66
|
+
? h.files.reduce((s, f) => s + f.issues.length, 0) / h.files.length
|
|
67
|
+
: 0,
|
|
68
|
+
}));
|
|
69
|
+
const regression = TrendAnalyzer.linearRegression(trendPoints);
|
|
70
|
+
// Current state report
|
|
71
|
+
const currentFiles = this.analyzeProjectFn(this.projectPath, this.config);
|
|
72
|
+
const baseReport = buildReport(this.projectPath, currentFiles);
|
|
73
|
+
return {
|
|
74
|
+
...baseReport,
|
|
75
|
+
trend: trendPoints,
|
|
76
|
+
regression,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=trend.js.map
|
package/dist/git.d.ts
CHANGED
|
@@ -12,8 +12,4 @@ export declare function extractFilesAtRef(projectPath: string, ref: string): str
|
|
|
12
12
|
* Clean up a temporary directory created by extractFilesAtRef.
|
|
13
13
|
*/
|
|
14
14
|
export declare function cleanupTempDir(tempDir: string): void;
|
|
15
|
-
/**
|
|
16
|
-
* Get the short hash of a git ref for display purposes.
|
|
17
|
-
*/
|
|
18
|
-
export declare function resolveRefHash(projectPath: string, ref: string): string;
|
|
19
15
|
//# sourceMappingURL=git.d.ts.map
|
package/dist/git.js
CHANGED
|
@@ -38,7 +38,7 @@ export function extractFilesAtRef(projectPath, ref) {
|
|
|
38
38
|
const tsFiles = fileList
|
|
39
39
|
.split('\n')
|
|
40
40
|
.map(f => f.trim())
|
|
41
|
-
.filter(f => f.endsWith('.ts') && !f.endsWith('.d.ts'));
|
|
41
|
+
.filter(f => (f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js') || f.endsWith('.jsx')) && !f.endsWith('.d.ts'));
|
|
42
42
|
if (tsFiles.length === 0) {
|
|
43
43
|
throw new Error(`No TypeScript files found at ref '${ref}'`);
|
|
44
44
|
}
|
|
@@ -73,7 +73,7 @@ export function cleanupTempDir(tempDir) {
|
|
|
73
73
|
/**
|
|
74
74
|
* Get the short hash of a git ref for display purposes.
|
|
75
75
|
*/
|
|
76
|
-
|
|
76
|
+
function resolveRefHash(projectPath, ref) {
|
|
77
77
|
try {
|
|
78
78
|
return execSync(`git rev-parse --short ${ref}`, { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
79
79
|
}
|