@defai.digital/ax-cli 3.4.5 → 3.5.2
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/LICENSE +2 -6
- package/README.md +91 -3
- package/dist/analyzers/ast/parser.d.ts +59 -0
- package/dist/analyzers/ast/parser.js +293 -0
- package/dist/analyzers/ast/parser.js.map +1 -0
- package/dist/analyzers/ast/types.d.ts +107 -0
- package/dist/analyzers/ast/types.js +7 -0
- package/dist/analyzers/ast/types.js.map +1 -0
- package/dist/analyzers/code-smells/base-smell-detector.d.ts +30 -0
- package/dist/analyzers/code-smells/base-smell-detector.js +44 -0
- package/dist/analyzers/code-smells/base-smell-detector.js.map +1 -0
- package/dist/analyzers/code-smells/code-smell-analyzer.d.ts +30 -0
- package/dist/analyzers/code-smells/code-smell-analyzer.js +167 -0
- package/dist/analyzers/code-smells/code-smell-analyzer.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js +66 -0
- package/dist/analyzers/code-smells/detectors/data-clumps-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/dead-code-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js +53 -0
- package/dist/analyzers/code-smells/detectors/dead-code-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js +51 -0
- package/dist/analyzers/code-smells/detectors/duplicate-code-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js +64 -0
- package/dist/analyzers/code-smells/detectors/feature-envy-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.d.ts +11 -0
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js +56 -0
- package/dist/analyzers/code-smells/detectors/inappropriate-intimacy-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/large-class-detector.d.ts +13 -0
- package/dist/analyzers/code-smells/detectors/large-class-detector.js +58 -0
- package/dist/analyzers/code-smells/detectors/large-class-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/long-method-detector.d.ts +12 -0
- package/dist/analyzers/code-smells/detectors/long-method-detector.js +52 -0
- package/dist/analyzers/code-smells/detectors/long-method-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.d.ts +12 -0
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js +50 -0
- package/dist/analyzers/code-smells/detectors/long-parameter-list-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.d.ts +12 -0
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js +54 -0
- package/dist/analyzers/code-smells/detectors/magic-numbers-detector.js.map +1 -0
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.d.ts +13 -0
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js +71 -0
- package/dist/analyzers/code-smells/detectors/nested-conditionals-detector.js.map +1 -0
- package/dist/analyzers/code-smells/index.d.ts +16 -0
- package/dist/analyzers/code-smells/index.js +19 -0
- package/dist/analyzers/code-smells/index.js.map +1 -0
- package/dist/analyzers/code-smells/types.d.ts +82 -0
- package/dist/analyzers/code-smells/types.js +30 -0
- package/dist/analyzers/code-smells/types.js.map +1 -0
- package/dist/analyzers/dependency/circular-detector.d.ts +17 -0
- package/dist/analyzers/dependency/circular-detector.js +71 -0
- package/dist/analyzers/dependency/circular-detector.js.map +1 -0
- package/dist/analyzers/dependency/coupling-calculator.d.ts +24 -0
- package/dist/analyzers/dependency/coupling-calculator.js +86 -0
- package/dist/analyzers/dependency/coupling-calculator.js.map +1 -0
- package/dist/analyzers/dependency/dependency-analyzer.d.ts +40 -0
- package/dist/analyzers/dependency/dependency-analyzer.js +214 -0
- package/dist/analyzers/dependency/dependency-analyzer.js.map +1 -0
- package/dist/analyzers/dependency/dependency-graph.d.ts +57 -0
- package/dist/analyzers/dependency/dependency-graph.js +186 -0
- package/dist/analyzers/dependency/dependency-graph.js.map +1 -0
- package/dist/analyzers/dependency/index.d.ts +8 -0
- package/dist/analyzers/dependency/index.js +8 -0
- package/dist/analyzers/dependency/index.js.map +1 -0
- package/dist/analyzers/dependency/types.d.ts +105 -0
- package/dist/analyzers/dependency/types.js +5 -0
- package/dist/analyzers/dependency/types.js.map +1 -0
- package/dist/analyzers/git/churn-calculator.d.ts +34 -0
- package/dist/analyzers/git/churn-calculator.js +214 -0
- package/dist/analyzers/git/churn-calculator.js.map +1 -0
- package/dist/analyzers/git/git-analyzer.d.ts +19 -0
- package/dist/analyzers/git/git-analyzer.js +71 -0
- package/dist/analyzers/git/git-analyzer.js.map +1 -0
- package/dist/analyzers/git/hotspot-detector.d.ts +34 -0
- package/dist/analyzers/git/hotspot-detector.js +170 -0
- package/dist/analyzers/git/hotspot-detector.js.map +1 -0
- package/dist/analyzers/git/index.d.ts +7 -0
- package/dist/analyzers/git/index.js +7 -0
- package/dist/analyzers/git/index.js.map +1 -0
- package/dist/analyzers/git/types.d.ts +88 -0
- package/dist/analyzers/git/types.js +5 -0
- package/dist/analyzers/git/types.js.map +1 -0
- package/dist/analyzers/metrics/halstead-calculator.d.ts +30 -0
- package/dist/analyzers/metrics/halstead-calculator.js +150 -0
- package/dist/analyzers/metrics/halstead-calculator.js.map +1 -0
- package/dist/analyzers/metrics/index.d.ts +9 -0
- package/dist/analyzers/metrics/index.js +9 -0
- package/dist/analyzers/metrics/index.js.map +1 -0
- package/dist/analyzers/metrics/maintainability-calculator.d.ts +17 -0
- package/dist/analyzers/metrics/maintainability-calculator.js +46 -0
- package/dist/analyzers/metrics/maintainability-calculator.js.map +1 -0
- package/dist/analyzers/metrics/metrics-analyzer.d.ts +32 -0
- package/dist/analyzers/metrics/metrics-analyzer.js +140 -0
- package/dist/analyzers/metrics/metrics-analyzer.js.map +1 -0
- package/dist/analyzers/metrics/types.d.ts +67 -0
- package/dist/analyzers/metrics/types.js +5 -0
- package/dist/analyzers/metrics/types.js.map +1 -0
- package/dist/analyzers/security/base-detector.d.ts +58 -0
- package/dist/analyzers/security/base-detector.js +104 -0
- package/dist/analyzers/security/base-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/command-injection-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/command-injection-detector.js +84 -0
- package/dist/analyzers/security/detectors/command-injection-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/hardcoded-secrets-detector.d.ts +16 -0
- package/dist/analyzers/security/detectors/hardcoded-secrets-detector.js +140 -0
- package/dist/analyzers/security/detectors/hardcoded-secrets-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/insecure-deserialization-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/insecure-deserialization-detector.js +109 -0
- package/dist/analyzers/security/detectors/insecure-deserialization-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/insecure-random-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/insecure-random-detector.js +61 -0
- package/dist/analyzers/security/detectors/insecure-random-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/path-traversal-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/path-traversal-detector.js +82 -0
- package/dist/analyzers/security/detectors/path-traversal-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/sql-injection-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/sql-injection-detector.js +88 -0
- package/dist/analyzers/security/detectors/sql-injection-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/weak-crypto-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/weak-crypto-detector.js +104 -0
- package/dist/analyzers/security/detectors/weak-crypto-detector.js.map +1 -0
- package/dist/analyzers/security/detectors/xss-detector.d.ts +12 -0
- package/dist/analyzers/security/detectors/xss-detector.js +90 -0
- package/dist/analyzers/security/detectors/xss-detector.js.map +1 -0
- package/dist/analyzers/security/index.d.ts +16 -0
- package/dist/analyzers/security/index.js +18 -0
- package/dist/analyzers/security/index.js.map +1 -0
- package/dist/analyzers/security/security-analyzer.d.ts +38 -0
- package/dist/analyzers/security/security-analyzer.js +215 -0
- package/dist/analyzers/security/security-analyzer.js.map +1 -0
- package/dist/analyzers/security/types.d.ts +95 -0
- package/dist/analyzers/security/types.js +7 -0
- package/dist/analyzers/security/types.js.map +1 -0
- package/dist/commands/memory.js +1 -1
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/setup.js +6 -1
- package/dist/commands/setup.js.map +1 -1
- package/dist/hooks/use-enhanced-input.d.ts +0 -1
- package/dist/hooks/use-enhanced-input.js.map +1 -1
- package/dist/mcp/health.js +4 -2
- package/dist/mcp/health.js.map +1 -1
- package/dist/mcp/validation.js +12 -6
- package/dist/mcp/validation.js.map +1 -1
- package/dist/tools/analysis-tools.d.ts +73 -0
- package/dist/tools/analysis-tools.js +422 -0
- package/dist/tools/analysis-tools.js.map +1 -0
- package/dist/tools/bash.js +2 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/ui/components/chat-history.js +1 -1
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-interface.js +3 -2
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/confirmation-dialog.js +1 -1
- package/dist/ui/components/confirmation-dialog.js.map +1 -1
- package/dist/ui/components/welcome-panel.js +1 -1
- package/dist/ui/components/welcome-panel.js.map +1 -1
- package/dist/ui/hooks/use-chat-reducer.d.ts +61 -0
- package/dist/ui/hooks/use-chat-reducer.js +118 -0
- package/dist/ui/hooks/use-chat-reducer.js.map +1 -0
- package/dist/ui/hooks/use-enhanced-input.d.ts +40 -0
- package/dist/ui/hooks/use-enhanced-input.js +254 -0
- package/dist/ui/hooks/use-enhanced-input.js.map +1 -0
- package/dist/ui/hooks/use-input-handler.d.ts +46 -0
- package/dist/ui/hooks/use-input-handler.js +1434 -0
- package/dist/ui/hooks/use-input-handler.js.map +1 -0
- package/dist/ui/hooks/use-input-history.d.ts +9 -0
- package/dist/ui/hooks/use-input-history.js +117 -0
- package/dist/ui/hooks/use-input-history.js.map +1 -0
- package/dist/utils/config-loader.js +3 -3
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/parallel-analyzer.js +7 -11
- package/dist/utils/parallel-analyzer.js.map +1 -1
- package/dist/utils/paste-collapse.d.ts +46 -0
- package/dist/utils/paste-collapse.js +77 -0
- package/dist/utils/paste-collapse.js.map +1 -0
- package/dist/utils/settings-manager.js +16 -2
- package/dist/utils/settings-manager.js.map +1 -1
- package/dist/utils/streaming-analyzer.js +9 -21
- package/dist/utils/streaming-analyzer.js.map +1 -1
- package/package.json +5 -5
- package/vitest.config.ts +1 -0
- package/dist/commands/weather.d.ts +0 -8
- package/dist/commands/weather.js +0 -160
- package/dist/commands/weather.js.map +0 -1
- package/dist/grok/client.d.ts +0 -144
- package/dist/grok/client.js +0 -237
- package/dist/grok/client.js.map +0 -1
- package/dist/grok/tools.d.ts +0 -8
- package/dist/grok/tools.js +0 -318
- package/dist/grok/tools.js.map +0 -1
- package/dist/grok/types.d.ts +0 -291
- package/dist/grok/types.js +0 -127
- package/dist/grok/types.js.map +0 -1
- package/dist/tools/morph-editor.d.ts +0 -36
- package/dist/tools/morph-editor.js +0 -308
- package/dist/tools/morph-editor.js.map +0 -1
- package/dist/ui/components/session-recovery.d.ts +0 -12
- package/dist/ui/components/session-recovery.js +0 -93
- package/dist/ui/components/session-recovery.js.map +0 -1
- package/dist/utils/model-config.d.ts +0 -28
- package/dist/utils/model-config.js +0 -43
- package/dist/utils/model-config.js.map +0 -1
- package/dist/utils/tool-helpers.d.ts +0 -25
- package/dist/utils/tool-helpers.js +0 -79
- package/dist/utils/tool-helpers.js.map +0 -1
- /package/{automatosx.config.json → ax.config.json} +0 -0
- /package/{config → config-defaults}/messages.yaml +0 -0
- /package/{config → config-defaults}/models.yaml +0 -0
- /package/{config → config-defaults}/prompts.yaml +0 -0
- /package/{config → config-defaults}/settings.yaml +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Analyzer Types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Dependency node representing a file
|
|
6
|
+
*/
|
|
7
|
+
export interface DependencyNode {
|
|
8
|
+
readonly filePath: string;
|
|
9
|
+
readonly imports: ReadonlyArray<ImportEdge>;
|
|
10
|
+
readonly exports: ReadonlyArray<ExportEdge>;
|
|
11
|
+
readonly size: number;
|
|
12
|
+
readonly loc: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Import edge in dependency graph
|
|
16
|
+
*/
|
|
17
|
+
export interface ImportEdge {
|
|
18
|
+
readonly from: string;
|
|
19
|
+
readonly to: string;
|
|
20
|
+
readonly importedSymbols: ReadonlyArray<string>;
|
|
21
|
+
readonly isDynamic: boolean;
|
|
22
|
+
readonly isTypeOnly: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Export edge
|
|
26
|
+
*/
|
|
27
|
+
export interface ExportEdge {
|
|
28
|
+
readonly from: string;
|
|
29
|
+
readonly symbols: ReadonlyArray<string>;
|
|
30
|
+
readonly isDefault: boolean;
|
|
31
|
+
readonly isReExport: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Circular dependency cycle
|
|
35
|
+
*/
|
|
36
|
+
export interface CircularDependency {
|
|
37
|
+
readonly cycle: ReadonlyArray<string>;
|
|
38
|
+
readonly length: number;
|
|
39
|
+
readonly severity: 'critical' | 'high' | 'medium' | 'low';
|
|
40
|
+
readonly impact: number;
|
|
41
|
+
readonly description: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Coupling metrics for a file
|
|
45
|
+
*/
|
|
46
|
+
export interface CouplingMetrics {
|
|
47
|
+
readonly file: string;
|
|
48
|
+
readonly afferentCoupling: number;
|
|
49
|
+
readonly efferentCoupling: number;
|
|
50
|
+
readonly instability: number;
|
|
51
|
+
readonly abstractness: number;
|
|
52
|
+
readonly distanceFromMainSequence: number;
|
|
53
|
+
readonly zone: 'useless' | 'painful' | 'balanced';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Dependency analysis result
|
|
57
|
+
*/
|
|
58
|
+
export interface DependencyAnalysisResult {
|
|
59
|
+
readonly graph: DependencyGraph;
|
|
60
|
+
readonly circularDependencies: ReadonlyArray<CircularDependency>;
|
|
61
|
+
readonly couplingMetrics: ReadonlyArray<CouplingMetrics>;
|
|
62
|
+
readonly orphanedFiles: ReadonlyArray<string>;
|
|
63
|
+
readonly hubFiles: ReadonlyArray<string>;
|
|
64
|
+
readonly summary: DependencySummary;
|
|
65
|
+
readonly timestamp: Date;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Summary statistics
|
|
69
|
+
*/
|
|
70
|
+
export interface DependencySummary {
|
|
71
|
+
readonly totalFiles: number;
|
|
72
|
+
readonly totalDependencies: number;
|
|
73
|
+
readonly averageAfferentCoupling: number;
|
|
74
|
+
readonly averageEfferentCoupling: number;
|
|
75
|
+
readonly averageInstability: number;
|
|
76
|
+
readonly circularDependencyCount: number;
|
|
77
|
+
readonly maxCycleLength: number;
|
|
78
|
+
readonly healthScore: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Dependency graph interface
|
|
82
|
+
*/
|
|
83
|
+
export interface DependencyGraph {
|
|
84
|
+
addNode(node: DependencyNode): void;
|
|
85
|
+
addEdge(from: string, to: string): void;
|
|
86
|
+
getNode(file: string): DependencyNode | undefined;
|
|
87
|
+
getNodes(): DependencyNode[];
|
|
88
|
+
getAfferentDependencies(file: string): string[];
|
|
89
|
+
getEfferentDependencies(file: string): string[];
|
|
90
|
+
getTotalEdges(): number;
|
|
91
|
+
hasPath(from: string, to: string): boolean;
|
|
92
|
+
topologicalSort(): {
|
|
93
|
+
sorted: string[];
|
|
94
|
+
hasCycle: boolean;
|
|
95
|
+
};
|
|
96
|
+
getStronglyConnectedComponents(): string[][];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Analysis options
|
|
100
|
+
*/
|
|
101
|
+
export interface DependencyAnalysisOptions {
|
|
102
|
+
readonly includeNodeModules?: boolean;
|
|
103
|
+
readonly maxDepth?: number;
|
|
104
|
+
readonly ignorePatterns?: readonly string[];
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/analyzers/dependency/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Churn Calculator
|
|
3
|
+
*
|
|
4
|
+
* Calculates file churn metrics from git history
|
|
5
|
+
*/
|
|
6
|
+
import type { FileChurn, GitAnalysisOptions, ContributorStats } from './types.js';
|
|
7
|
+
export declare class ChurnCalculator {
|
|
8
|
+
private repositoryPath;
|
|
9
|
+
constructor(repositoryPath: string);
|
|
10
|
+
/**
|
|
11
|
+
* Calculate churn for all files
|
|
12
|
+
*/
|
|
13
|
+
calculateChurn(options?: GitAnalysisOptions): Promise<FileChurn[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Calculate contributor statistics
|
|
16
|
+
*/
|
|
17
|
+
calculateContributorStats(options?: GitAnalysisOptions): Promise<ContributorStats[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Get git log with file stats
|
|
20
|
+
*/
|
|
21
|
+
private getGitLog;
|
|
22
|
+
/**
|
|
23
|
+
* Parse git log output
|
|
24
|
+
*/
|
|
25
|
+
private parseGitLog;
|
|
26
|
+
/**
|
|
27
|
+
* Check if file should be included based on patterns
|
|
28
|
+
*/
|
|
29
|
+
private shouldIncludeFile;
|
|
30
|
+
/**
|
|
31
|
+
* Simple pattern matching (supports * wildcard)
|
|
32
|
+
*/
|
|
33
|
+
private matchPattern;
|
|
34
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Churn Calculator
|
|
3
|
+
*
|
|
4
|
+
* Calculates file churn metrics from git history
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
export class ChurnCalculator {
|
|
8
|
+
repositoryPath;
|
|
9
|
+
constructor(repositoryPath) {
|
|
10
|
+
this.repositoryPath = repositoryPath;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Calculate churn for all files
|
|
14
|
+
*/
|
|
15
|
+
async calculateChurn(options = {}) {
|
|
16
|
+
const gitLogOutput = this.getGitLog(options);
|
|
17
|
+
const fileChurnMap = new Map();
|
|
18
|
+
// Parse git log output
|
|
19
|
+
const commits = this.parseGitLog(gitLogOutput);
|
|
20
|
+
for (const commit of commits) {
|
|
21
|
+
for (const file of commit.files) {
|
|
22
|
+
if (!this.shouldIncludeFile(file.path, options))
|
|
23
|
+
continue;
|
|
24
|
+
if (!fileChurnMap.has(file.path)) {
|
|
25
|
+
fileChurnMap.set(file.path, {
|
|
26
|
+
commits: 0,
|
|
27
|
+
additions: 0,
|
|
28
|
+
deletions: 0,
|
|
29
|
+
lastModified: commit.date,
|
|
30
|
+
authors: new Set(),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const churn = fileChurnMap.get(file.path);
|
|
34
|
+
churn.commits++;
|
|
35
|
+
churn.additions += file.additions;
|
|
36
|
+
churn.deletions += file.deletions;
|
|
37
|
+
churn.authors.add(commit.author);
|
|
38
|
+
// Update last modified date
|
|
39
|
+
if (commit.date > churn.lastModified) {
|
|
40
|
+
churn.lastModified = commit.date;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Convert to FileChurn array
|
|
45
|
+
const result = [];
|
|
46
|
+
for (const [filePath, data] of fileChurnMap.entries()) {
|
|
47
|
+
result.push(Object.freeze({
|
|
48
|
+
filePath,
|
|
49
|
+
commitCount: data.commits,
|
|
50
|
+
additions: data.additions,
|
|
51
|
+
deletions: data.deletions,
|
|
52
|
+
totalChurn: data.additions + data.deletions,
|
|
53
|
+
lastModified: data.lastModified,
|
|
54
|
+
authors: Object.freeze(Array.from(data.authors)),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
// Sort by total churn (descending)
|
|
58
|
+
return result.sort((a, b) => b.totalChurn - a.totalChurn);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Calculate contributor statistics
|
|
62
|
+
*/
|
|
63
|
+
async calculateContributorStats(options = {}) {
|
|
64
|
+
const gitLogOutput = this.getGitLog(options);
|
|
65
|
+
const commits = this.parseGitLog(gitLogOutput);
|
|
66
|
+
const contributorMap = new Map();
|
|
67
|
+
for (const commit of commits) {
|
|
68
|
+
if (!contributorMap.has(commit.author)) {
|
|
69
|
+
contributorMap.set(commit.author, {
|
|
70
|
+
commits: 0,
|
|
71
|
+
files: new Set(),
|
|
72
|
+
added: 0,
|
|
73
|
+
deleted: 0,
|
|
74
|
+
firstCommit: commit.date,
|
|
75
|
+
lastCommit: commit.date,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const stats = contributorMap.get(commit.author);
|
|
79
|
+
stats.commits++;
|
|
80
|
+
for (const file of commit.files) {
|
|
81
|
+
if (this.shouldIncludeFile(file.path, options)) {
|
|
82
|
+
stats.files.add(file.path);
|
|
83
|
+
stats.added += file.additions;
|
|
84
|
+
stats.deleted += file.deletions;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Update date range
|
|
88
|
+
if (commit.date < stats.firstCommit) {
|
|
89
|
+
stats.firstCommit = commit.date;
|
|
90
|
+
}
|
|
91
|
+
if (commit.date > stats.lastCommit) {
|
|
92
|
+
stats.lastCommit = commit.date;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Convert to ContributorStats array
|
|
96
|
+
const result = [];
|
|
97
|
+
for (const [author, data] of contributorMap.entries()) {
|
|
98
|
+
result.push(Object.freeze({
|
|
99
|
+
author,
|
|
100
|
+
commitCount: data.commits,
|
|
101
|
+
filesChanged: data.files.size,
|
|
102
|
+
linesAdded: data.added,
|
|
103
|
+
linesDeleted: data.deleted,
|
|
104
|
+
firstCommit: data.firstCommit,
|
|
105
|
+
lastCommit: data.lastCommit,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
// Sort by commit count (descending)
|
|
109
|
+
return result.sort((a, b) => b.commitCount - a.commitCount);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get git log with file stats
|
|
113
|
+
*/
|
|
114
|
+
getGitLog(options) {
|
|
115
|
+
const args = ['log', '--numstat', '--pretty=format:%H|%an|%ad|%s'];
|
|
116
|
+
if (options.since) {
|
|
117
|
+
args.push(`--since="${options.since}"`);
|
|
118
|
+
}
|
|
119
|
+
if (options.until) {
|
|
120
|
+
args.push(`--until="${options.until}"`);
|
|
121
|
+
}
|
|
122
|
+
if (options.branch) {
|
|
123
|
+
args.push(options.branch);
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
return execSync(`git ${args.join(' ')}`, {
|
|
127
|
+
cwd: this.repositoryPath,
|
|
128
|
+
encoding: 'utf-8',
|
|
129
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
130
|
+
stdio: ['pipe', 'pipe', 'ignore'], // Ignore stderr to suppress shell warnings
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// If git command fails, return empty string
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Parse git log output
|
|
140
|
+
*/
|
|
141
|
+
parseGitLog(output) {
|
|
142
|
+
const commits = [];
|
|
143
|
+
const lines = output.split('\n');
|
|
144
|
+
let currentCommit = null;
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
if (!line.trim())
|
|
147
|
+
continue;
|
|
148
|
+
// Commit line: hash|author|date|message
|
|
149
|
+
if (line.includes('|')) {
|
|
150
|
+
const parts = line.split('|');
|
|
151
|
+
if (parts.length >= 4) {
|
|
152
|
+
// Save previous commit
|
|
153
|
+
if (currentCommit) {
|
|
154
|
+
commits.push(currentCommit);
|
|
155
|
+
}
|
|
156
|
+
// Start new commit
|
|
157
|
+
currentCommit = {
|
|
158
|
+
hash: parts[0],
|
|
159
|
+
author: parts[1],
|
|
160
|
+
date: new Date(parts[2]),
|
|
161
|
+
message: parts[3],
|
|
162
|
+
files: [],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (currentCommit) {
|
|
167
|
+
// File stat line: additions deletions filename
|
|
168
|
+
const parts = line.split('\t');
|
|
169
|
+
if (parts.length >= 3) {
|
|
170
|
+
const additions = parts[0] === '-' ? 0 : parseInt(parts[0], 10);
|
|
171
|
+
const deletions = parts[1] === '-' ? 0 : parseInt(parts[1], 10);
|
|
172
|
+
const path = parts[2];
|
|
173
|
+
currentCommit.files.push({ path, additions, deletions });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Add last commit
|
|
178
|
+
if (currentCommit) {
|
|
179
|
+
commits.push(currentCommit);
|
|
180
|
+
}
|
|
181
|
+
return commits;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Check if file should be included based on patterns
|
|
185
|
+
*/
|
|
186
|
+
shouldIncludeFile(filePath, options) {
|
|
187
|
+
// Check exclude patterns
|
|
188
|
+
if (options.excludePatterns) {
|
|
189
|
+
for (const pattern of options.excludePatterns) {
|
|
190
|
+
if (this.matchPattern(filePath, pattern)) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Check include patterns
|
|
196
|
+
if (options.includePatterns && options.includePatterns.length > 0) {
|
|
197
|
+
for (const pattern of options.includePatterns) {
|
|
198
|
+
if (this.matchPattern(filePath, pattern)) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Simple pattern matching (supports * wildcard)
|
|
208
|
+
*/
|
|
209
|
+
matchPattern(filePath, pattern) {
|
|
210
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
211
|
+
return regex.test(filePath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=churn-calculator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churn-calculator.js","sourceRoot":"","sources":["../../../src/analyzers/git/churn-calculator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Main orchestrator for Git history analysis
|
|
5
|
+
*/
|
|
6
|
+
import type { GitAnalysisResult, GitAnalysisOptions } from './types.js';
|
|
7
|
+
export declare class GitAnalyzer {
|
|
8
|
+
private churnCalculator;
|
|
9
|
+
private hotspotDetector;
|
|
10
|
+
constructor(repositoryPath: string);
|
|
11
|
+
/**
|
|
12
|
+
* Perform complete Git analysis
|
|
13
|
+
*/
|
|
14
|
+
analyze(options?: GitAnalysisOptions): Promise<GitAnalysisResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Calculate summary statistics
|
|
17
|
+
*/
|
|
18
|
+
private calculateSummary;
|
|
19
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Main orchestrator for Git history analysis
|
|
5
|
+
*/
|
|
6
|
+
import { ChurnCalculator } from './churn-calculator.js';
|
|
7
|
+
import { HotspotDetector } from './hotspot-detector.js';
|
|
8
|
+
export class GitAnalyzer {
|
|
9
|
+
churnCalculator;
|
|
10
|
+
hotspotDetector;
|
|
11
|
+
constructor(repositoryPath) {
|
|
12
|
+
this.churnCalculator = new ChurnCalculator(repositoryPath);
|
|
13
|
+
this.hotspotDetector = new HotspotDetector();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Perform complete Git analysis
|
|
17
|
+
*/
|
|
18
|
+
async analyze(options = {}) {
|
|
19
|
+
const timestamp = new Date();
|
|
20
|
+
// Calculate churn metrics
|
|
21
|
+
const churnMetrics = await this.churnCalculator.calculateChurn(options);
|
|
22
|
+
// Calculate contributor statistics
|
|
23
|
+
const contributors = await this.churnCalculator.calculateContributorStats(options);
|
|
24
|
+
// Detect hotspots
|
|
25
|
+
const hotspots = await this.hotspotDetector.detectHotspots(churnMetrics, options);
|
|
26
|
+
// Calculate summary
|
|
27
|
+
const summary = this.calculateSummary(churnMetrics, hotspots, contributors);
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
hotspots: Object.freeze(hotspots),
|
|
30
|
+
churnMetrics: Object.freeze(churnMetrics),
|
|
31
|
+
contributors: Object.freeze(contributors),
|
|
32
|
+
summary: Object.freeze(summary),
|
|
33
|
+
timestamp,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Calculate summary statistics
|
|
38
|
+
*/
|
|
39
|
+
calculateSummary(churnMetrics, hotspots, contributors) {
|
|
40
|
+
const totalCommits = contributors.reduce((sum, c) => sum + c.commitCount, 0);
|
|
41
|
+
const averageChurn = churnMetrics.length > 0
|
|
42
|
+
? churnMetrics.reduce((sum, c) => sum + c.totalChurn, 0) / churnMetrics.length
|
|
43
|
+
: 0;
|
|
44
|
+
const topContributor = contributors.length > 0
|
|
45
|
+
? contributors[0].author
|
|
46
|
+
: 'Unknown';
|
|
47
|
+
// Determine date range
|
|
48
|
+
let earliestDate = new Date();
|
|
49
|
+
let latestDate = new Date(0);
|
|
50
|
+
for (const contributor of contributors) {
|
|
51
|
+
if (contributor.firstCommit < earliestDate) {
|
|
52
|
+
earliestDate = contributor.firstCommit;
|
|
53
|
+
}
|
|
54
|
+
if (contributor.lastCommit > latestDate) {
|
|
55
|
+
latestDate = contributor.lastCommit;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
totalCommits,
|
|
60
|
+
filesAnalyzed: churnMetrics.length,
|
|
61
|
+
hotspotCount: hotspots.length,
|
|
62
|
+
averageChurn: Math.round(averageChurn),
|
|
63
|
+
topContributor,
|
|
64
|
+
dateRange: {
|
|
65
|
+
from: earliestDate,
|
|
66
|
+
to: latestDate,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=git-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-analyzer.js","sourceRoot":"","sources":["../../../src/analyzers/git/git-analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hotspot Detector
|
|
3
|
+
*
|
|
4
|
+
* Identifies code hotspots using churn × complexity formula
|
|
5
|
+
*/
|
|
6
|
+
import type { FileChurn, CodeHotspot, GitAnalysisOptions } from './types.js';
|
|
7
|
+
export declare class HotspotDetector {
|
|
8
|
+
private astParser;
|
|
9
|
+
constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Detect hotspots from churn metrics
|
|
12
|
+
*/
|
|
13
|
+
detectHotspots(churnMetrics: readonly FileChurn[], options?: GitAnalysisOptions): Promise<CodeHotspot[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Get complexity metrics from file
|
|
16
|
+
*/
|
|
17
|
+
private getComplexityMetrics;
|
|
18
|
+
/**
|
|
19
|
+
* Normalize complexity to 0-100 scale
|
|
20
|
+
*/
|
|
21
|
+
private normalizeComplexity;
|
|
22
|
+
/**
|
|
23
|
+
* Calculate severity based on hotspot score
|
|
24
|
+
*/
|
|
25
|
+
private calculateSeverity;
|
|
26
|
+
/**
|
|
27
|
+
* Generate reason for hotspot
|
|
28
|
+
*/
|
|
29
|
+
private generateReason;
|
|
30
|
+
/**
|
|
31
|
+
* Generate recommendation
|
|
32
|
+
*/
|
|
33
|
+
private generateRecommendation;
|
|
34
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hotspot Detector
|
|
3
|
+
*
|
|
4
|
+
* Identifies code hotspots using churn × complexity formula
|
|
5
|
+
*/
|
|
6
|
+
import { ASTParser } from '../ast/parser.js';
|
|
7
|
+
export class HotspotDetector {
|
|
8
|
+
astParser;
|
|
9
|
+
constructor() {
|
|
10
|
+
this.astParser = new ASTParser();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Detect hotspots from churn metrics
|
|
14
|
+
*/
|
|
15
|
+
async detectHotspots(churnMetrics, options = {}) {
|
|
16
|
+
const threshold = options.hotspotThreshold ?? 70;
|
|
17
|
+
const hotspots = [];
|
|
18
|
+
// Calculate max values for normalization
|
|
19
|
+
const maxChurn = Math.max(...churnMetrics.map(c => c.totalChurn), 1);
|
|
20
|
+
const maxCommits = Math.max(...churnMetrics.map(c => c.commitCount), 1);
|
|
21
|
+
for (const churn of churnMetrics) {
|
|
22
|
+
try {
|
|
23
|
+
// Get complexity metrics from AST
|
|
24
|
+
const complexity = await this.getComplexityMetrics(churn.filePath);
|
|
25
|
+
if (!complexity)
|
|
26
|
+
continue;
|
|
27
|
+
// Normalize churn (0-100)
|
|
28
|
+
const normalizedChurn = (churn.totalChurn / maxChurn) * 100;
|
|
29
|
+
const normalizedCommits = (churn.commitCount / maxCommits) * 100;
|
|
30
|
+
const churnScore = (normalizedChurn * 0.6 + normalizedCommits * 0.4);
|
|
31
|
+
// Normalize complexity (0-100)
|
|
32
|
+
const complexityScore = this.normalizeComplexity(complexity.average, complexity.max);
|
|
33
|
+
// Calculate hotspot score using weighted formula
|
|
34
|
+
// Churn: 40%, Complexity: 30%, Commit Frequency: 30%
|
|
35
|
+
const hotspotScore = Math.round(churnScore * 0.4 +
|
|
36
|
+
complexityScore * 0.3 +
|
|
37
|
+
normalizedCommits * 0.3);
|
|
38
|
+
// Only include if above threshold
|
|
39
|
+
if (hotspotScore >= threshold) {
|
|
40
|
+
const severity = this.calculateSeverity(hotspotScore);
|
|
41
|
+
const reason = this.generateReason(churn, complexity, hotspotScore);
|
|
42
|
+
const recommendation = this.generateRecommendation(churn, complexity, severity);
|
|
43
|
+
hotspots.push(Object.freeze({
|
|
44
|
+
filePath: churn.filePath,
|
|
45
|
+
hotspotScore,
|
|
46
|
+
churnScore: Math.round(churnScore),
|
|
47
|
+
complexityScore: Math.round(complexityScore),
|
|
48
|
+
commitCount: churn.commitCount,
|
|
49
|
+
totalChurn: churn.totalChurn,
|
|
50
|
+
averageComplexity: complexity.average,
|
|
51
|
+
maxComplexity: complexity.max,
|
|
52
|
+
severity,
|
|
53
|
+
reason,
|
|
54
|
+
recommendation,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// Skip files that can't be analyzed
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Sort by hotspot score (descending)
|
|
64
|
+
return hotspots.sort((a, b) => b.hotspotScore - a.hotspotScore);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get complexity metrics from file
|
|
68
|
+
*/
|
|
69
|
+
async getComplexityMetrics(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
const ast = this.astParser.parseFile(filePath);
|
|
72
|
+
const complexities = [];
|
|
73
|
+
// Collect function complexities
|
|
74
|
+
for (const func of ast.functions) {
|
|
75
|
+
complexities.push(func.complexity);
|
|
76
|
+
}
|
|
77
|
+
// Collect method complexities
|
|
78
|
+
for (const cls of ast.classes) {
|
|
79
|
+
for (const method of cls.methods) {
|
|
80
|
+
complexities.push(method.complexity);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (complexities.length === 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const average = complexities.reduce((sum, c) => sum + c, 0) / complexities.length;
|
|
87
|
+
const max = Math.max(...complexities);
|
|
88
|
+
return { average, max };
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Normalize complexity to 0-100 scale
|
|
96
|
+
*/
|
|
97
|
+
normalizeComplexity(average, max) {
|
|
98
|
+
// McCabe complexity thresholds:
|
|
99
|
+
// 1-10: Simple
|
|
100
|
+
// 11-20: Moderate
|
|
101
|
+
// 21-50: Complex
|
|
102
|
+
// 50+: Very complex
|
|
103
|
+
const avgScore = Math.min(100, (average / 20) * 100);
|
|
104
|
+
const maxScore = Math.min(100, (max / 50) * 100);
|
|
105
|
+
// Weight average more than max
|
|
106
|
+
return avgScore * 0.7 + maxScore * 0.3;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Calculate severity based on hotspot score
|
|
110
|
+
*/
|
|
111
|
+
calculateSeverity(score) {
|
|
112
|
+
if (score >= 90)
|
|
113
|
+
return 'CRITICAL';
|
|
114
|
+
if (score >= 80)
|
|
115
|
+
return 'HIGH';
|
|
116
|
+
if (score >= 70)
|
|
117
|
+
return 'MEDIUM';
|
|
118
|
+
return 'LOW';
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Generate reason for hotspot
|
|
122
|
+
*/
|
|
123
|
+
generateReason(churn, complexity, score) {
|
|
124
|
+
const reasons = [];
|
|
125
|
+
if (churn.commitCount > 20) {
|
|
126
|
+
reasons.push(`high change frequency (${churn.commitCount} commits)`);
|
|
127
|
+
}
|
|
128
|
+
if (churn.totalChurn > 500) {
|
|
129
|
+
reasons.push(`high churn (${churn.totalChurn} lines changed)`);
|
|
130
|
+
}
|
|
131
|
+
if (complexity.average > 10) {
|
|
132
|
+
reasons.push(`high average complexity (${complexity.average.toFixed(1)})`);
|
|
133
|
+
}
|
|
134
|
+
if (complexity.max > 20) {
|
|
135
|
+
reasons.push(`very complex functions (max: ${complexity.max})`);
|
|
136
|
+
}
|
|
137
|
+
if (churn.authors.length > 5) {
|
|
138
|
+
reasons.push(`many contributors (${churn.authors.length})`);
|
|
139
|
+
}
|
|
140
|
+
return `Hotspot (score: ${score}/100): ${reasons.join(', ')}`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Generate recommendation
|
|
144
|
+
*/
|
|
145
|
+
generateRecommendation(churn, complexity, severity) {
|
|
146
|
+
const recommendations = [];
|
|
147
|
+
if (complexity.max > 20) {
|
|
148
|
+
recommendations.push('Reduce complexity by breaking down complex functions');
|
|
149
|
+
}
|
|
150
|
+
if (churn.commitCount > 30) {
|
|
151
|
+
recommendations.push('Stabilize this file - consider refactoring to reduce change frequency');
|
|
152
|
+
}
|
|
153
|
+
if (churn.authors.length > 8) {
|
|
154
|
+
recommendations.push('Establish clear ownership and coding standards');
|
|
155
|
+
}
|
|
156
|
+
if (complexity.average > 15) {
|
|
157
|
+
recommendations.push('Simplify logic and improve code structure');
|
|
158
|
+
}
|
|
159
|
+
if (recommendations.length === 0) {
|
|
160
|
+
recommendations.push('Monitor this file for further changes and consider refactoring');
|
|
161
|
+
}
|
|
162
|
+
const prefix = severity === 'CRITICAL' || severity === 'HIGH'
|
|
163
|
+
? 'URGENT: '
|
|
164
|
+
: severity === 'MEDIUM'
|
|
165
|
+
? 'Important: '
|
|
166
|
+
: '';
|
|
167
|
+
return prefix + recommendations.join('. ') + '.';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=hotspot-detector.js.map
|