tlc-claude-code 1.2.29 → 1.4.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/dashboard/dist/components/AuditPane.d.ts +30 -0
- package/dashboard/dist/components/AuditPane.js +127 -0
- package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
- package/dashboard/dist/components/AuditPane.test.js +339 -0
- package/dashboard/dist/components/CompliancePane.d.ts +39 -0
- package/dashboard/dist/components/CompliancePane.js +96 -0
- package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
- package/dashboard/dist/components/CompliancePane.test.js +183 -0
- package/dashboard/dist/components/SSOPane.d.ts +36 -0
- package/dashboard/dist/components/SSOPane.js +71 -0
- package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
- package/dashboard/dist/components/SSOPane.test.js +155 -0
- package/dashboard/dist/components/UsagePane.d.ts +13 -0
- package/dashboard/dist/components/UsagePane.js +51 -0
- package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
- package/dashboard/dist/components/UsagePane.test.js +142 -0
- package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
- package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
- package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
- package/dashboard/dist/components/WorkspacePane.js +17 -0
- package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
- package/dashboard/dist/components/WorkspacePane.test.js +84 -0
- package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
- package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
- package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
- package/package.json +1 -1
- package/server/lib/access-control-doc.js +541 -0
- package/server/lib/access-control-doc.test.js +672 -0
- package/server/lib/adr-generator.js +423 -0
- package/server/lib/adr-generator.test.js +586 -0
- package/server/lib/agent-progress-monitor.js +223 -0
- package/server/lib/agent-progress-monitor.test.js +202 -0
- package/server/lib/architecture-command.js +450 -0
- package/server/lib/architecture-command.test.js +754 -0
- package/server/lib/ast-analyzer.js +324 -0
- package/server/lib/ast-analyzer.test.js +437 -0
- package/server/lib/audit-attribution.js +191 -0
- package/server/lib/audit-attribution.test.js +359 -0
- package/server/lib/audit-classifier.js +202 -0
- package/server/lib/audit-classifier.test.js +209 -0
- package/server/lib/audit-command.js +275 -0
- package/server/lib/audit-command.test.js +325 -0
- package/server/lib/audit-exporter.js +380 -0
- package/server/lib/audit-exporter.test.js +464 -0
- package/server/lib/audit-logger.js +236 -0
- package/server/lib/audit-logger.test.js +364 -0
- package/server/lib/audit-query.js +257 -0
- package/server/lib/audit-query.test.js +352 -0
- package/server/lib/audit-storage.js +269 -0
- package/server/lib/audit-storage.test.js +272 -0
- package/server/lib/auth-system.test.js +4 -1
- package/server/lib/boundary-detector.js +427 -0
- package/server/lib/boundary-detector.test.js +320 -0
- package/server/lib/budget-alerts.js +138 -0
- package/server/lib/budget-alerts.test.js +235 -0
- package/server/lib/bulk-repo-init.js +342 -0
- package/server/lib/bulk-repo-init.test.js +388 -0
- package/server/lib/candidates-tracker.js +210 -0
- package/server/lib/candidates-tracker.test.js +300 -0
- package/server/lib/checkpoint-manager.js +251 -0
- package/server/lib/checkpoint-manager.test.js +474 -0
- package/server/lib/circular-detector.js +337 -0
- package/server/lib/circular-detector.test.js +353 -0
- package/server/lib/cohesion-analyzer.js +310 -0
- package/server/lib/cohesion-analyzer.test.js +447 -0
- package/server/lib/compliance-checklist.js +866 -0
- package/server/lib/compliance-checklist.test.js +476 -0
- package/server/lib/compliance-command.js +616 -0
- package/server/lib/compliance-command.test.js +551 -0
- package/server/lib/compliance-reporter.js +692 -0
- package/server/lib/compliance-reporter.test.js +707 -0
- package/server/lib/contract-testing.js +625 -0
- package/server/lib/contract-testing.test.js +342 -0
- package/server/lib/conversion-planner.js +469 -0
- package/server/lib/conversion-planner.test.js +361 -0
- package/server/lib/convert-command.js +351 -0
- package/server/lib/convert-command.test.js +608 -0
- package/server/lib/coupling-calculator.js +189 -0
- package/server/lib/coupling-calculator.test.js +509 -0
- package/server/lib/data-flow-doc.js +665 -0
- package/server/lib/data-flow-doc.test.js +659 -0
- package/server/lib/dependency-graph.js +367 -0
- package/server/lib/dependency-graph.test.js +516 -0
- package/server/lib/duplication-detector.js +349 -0
- package/server/lib/duplication-detector.test.js +401 -0
- package/server/lib/ephemeral-storage.js +249 -0
- package/server/lib/ephemeral-storage.test.js +254 -0
- package/server/lib/evidence-collector.js +627 -0
- package/server/lib/evidence-collector.test.js +901 -0
- package/server/lib/example-service.js +616 -0
- package/server/lib/example-service.test.js +397 -0
- package/server/lib/flow-diagram-generator.js +474 -0
- package/server/lib/flow-diagram-generator.test.js +446 -0
- package/server/lib/idp-manager.js +626 -0
- package/server/lib/idp-manager.test.js +587 -0
- package/server/lib/impact-scorer.js +184 -0
- package/server/lib/impact-scorer.test.js +211 -0
- package/server/lib/memory-exclusion.js +326 -0
- package/server/lib/memory-exclusion.test.js +241 -0
- package/server/lib/mermaid-generator.js +358 -0
- package/server/lib/mermaid-generator.test.js +301 -0
- package/server/lib/messaging-patterns.js +750 -0
- package/server/lib/messaging-patterns.test.js +213 -0
- package/server/lib/mfa-handler.js +452 -0
- package/server/lib/mfa-handler.test.js +490 -0
- package/server/lib/microservice-template.js +386 -0
- package/server/lib/microservice-template.test.js +325 -0
- package/server/lib/new-project-microservice.js +450 -0
- package/server/lib/new-project-microservice.test.js +600 -0
- package/server/lib/oauth-flow.js +375 -0
- package/server/lib/oauth-flow.test.js +487 -0
- package/server/lib/oauth-registry.js +190 -0
- package/server/lib/oauth-registry.test.js +306 -0
- package/server/lib/readme-generator.js +490 -0
- package/server/lib/readme-generator.test.js +493 -0
- package/server/lib/refactor-command.js +326 -0
- package/server/lib/refactor-command.test.js +528 -0
- package/server/lib/refactor-executor.js +254 -0
- package/server/lib/refactor-executor.test.js +305 -0
- package/server/lib/refactor-observer.js +292 -0
- package/server/lib/refactor-observer.test.js +422 -0
- package/server/lib/refactor-progress.js +193 -0
- package/server/lib/refactor-progress.test.js +251 -0
- package/server/lib/refactor-reporter.js +237 -0
- package/server/lib/refactor-reporter.test.js +247 -0
- package/server/lib/repo-dependency-tracker.js +261 -0
- package/server/lib/repo-dependency-tracker.test.js +350 -0
- package/server/lib/retention-policy.js +281 -0
- package/server/lib/retention-policy.test.js +486 -0
- package/server/lib/role-mapper.js +236 -0
- package/server/lib/role-mapper.test.js +395 -0
- package/server/lib/saml-provider.js +765 -0
- package/server/lib/saml-provider.test.js +643 -0
- package/server/lib/security-policy-generator.js +682 -0
- package/server/lib/security-policy-generator.test.js +544 -0
- package/server/lib/semantic-analyzer.js +198 -0
- package/server/lib/semantic-analyzer.test.js +474 -0
- package/server/lib/sensitive-detector.js +112 -0
- package/server/lib/sensitive-detector.test.js +209 -0
- package/server/lib/service-interaction-diagram.js +700 -0
- package/server/lib/service-interaction-diagram.test.js +638 -0
- package/server/lib/service-scaffold.js +486 -0
- package/server/lib/service-scaffold.test.js +373 -0
- package/server/lib/service-summary.js +553 -0
- package/server/lib/service-summary.test.js +619 -0
- package/server/lib/session-purge.js +460 -0
- package/server/lib/session-purge.test.js +312 -0
- package/server/lib/shared-kernel.js +578 -0
- package/server/lib/shared-kernel.test.js +255 -0
- package/server/lib/sso-command.js +544 -0
- package/server/lib/sso-command.test.js +552 -0
- package/server/lib/sso-session.js +492 -0
- package/server/lib/sso-session.test.js +670 -0
- package/server/lib/traefik-config.js +282 -0
- package/server/lib/traefik-config.test.js +312 -0
- package/server/lib/usage-command.js +218 -0
- package/server/lib/usage-command.test.js +391 -0
- package/server/lib/usage-formatter.js +192 -0
- package/server/lib/usage-formatter.test.js +267 -0
- package/server/lib/usage-history.js +122 -0
- package/server/lib/usage-history.test.js +206 -0
- package/server/lib/workspace-command.js +249 -0
- package/server/lib/workspace-command.test.js +264 -0
- package/server/lib/workspace-config.js +270 -0
- package/server/lib/workspace-config.test.js +312 -0
- package/server/lib/workspace-docs-command.js +547 -0
- package/server/lib/workspace-docs-command.test.js +692 -0
- package/server/lib/workspace-memory.js +451 -0
- package/server/lib/workspace-memory.test.js +403 -0
- package/server/lib/workspace-scanner.js +452 -0
- package/server/lib/workspace-scanner.test.js +677 -0
- package/server/lib/workspace-test-runner.js +315 -0
- package/server/lib/workspace-test-runner.test.js +294 -0
- package/server/lib/zero-retention-command.js +439 -0
- package/server/lib/zero-retention-command.test.js +448 -0
- package/server/lib/zero-retention.js +322 -0
- package/server/lib/zero-retention.test.js +258 -0
- package/server/package-lock.json +14 -0
- package/server/package.json +1 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Impact Scorer
|
|
3
|
+
* Calculate priority score for refactoring opportunities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
class ImpactScorer {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.weights = {
|
|
11
|
+
complexityReduction: options.complexityWeight || 0.30,
|
|
12
|
+
blastRadius: options.blastRadiusWeight || 0.25,
|
|
13
|
+
changeFrequency: options.frequencyWeight || 0.25,
|
|
14
|
+
risk: options.riskWeight || 0.20,
|
|
15
|
+
};
|
|
16
|
+
this.exec = options.exec || this.defaultExec.bind(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
defaultExec(command) {
|
|
20
|
+
try {
|
|
21
|
+
return execSync(command, { encoding: 'utf-8' });
|
|
22
|
+
} catch {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Calculate impact score for a refactoring opportunity
|
|
29
|
+
* @param {Object} opportunity - Refactoring opportunity
|
|
30
|
+
* @returns {Object} Score breakdown and total
|
|
31
|
+
*/
|
|
32
|
+
score(opportunity) {
|
|
33
|
+
const complexityScore = this.scoreComplexityReduction(opportunity);
|
|
34
|
+
const blastRadiusScore = this.scoreBlastRadius(opportunity);
|
|
35
|
+
const frequencyScore = this.scoreChangeFrequency(opportunity);
|
|
36
|
+
const riskScore = this.scoreRisk(opportunity);
|
|
37
|
+
|
|
38
|
+
const total = Math.round(
|
|
39
|
+
complexityScore * this.weights.complexityReduction +
|
|
40
|
+
blastRadiusScore * this.weights.blastRadius +
|
|
41
|
+
frequencyScore * this.weights.changeFrequency +
|
|
42
|
+
riskScore * this.weights.risk
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
total: Math.min(100, Math.max(0, total)),
|
|
47
|
+
breakdown: {
|
|
48
|
+
complexityReduction: complexityScore,
|
|
49
|
+
blastRadius: blastRadiusScore,
|
|
50
|
+
changeFrequency: frequencyScore,
|
|
51
|
+
risk: riskScore,
|
|
52
|
+
},
|
|
53
|
+
weights: this.weights,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Score based on complexity reduction potential
|
|
59
|
+
* Higher complexity = higher score (more to gain)
|
|
60
|
+
*/
|
|
61
|
+
scoreComplexityReduction(opportunity) {
|
|
62
|
+
const { complexity, targetComplexity } = opportunity;
|
|
63
|
+
|
|
64
|
+
if (!complexity) return 50; // Default middle score
|
|
65
|
+
|
|
66
|
+
const reduction = complexity - (targetComplexity || 1);
|
|
67
|
+
|
|
68
|
+
// Scale: 0-5 reduction = 0-50, 5-15 = 50-80, 15+ = 80-100
|
|
69
|
+
if (reduction <= 0) return 20;
|
|
70
|
+
if (reduction <= 5) return 20 + (reduction * 10);
|
|
71
|
+
if (reduction <= 15) return 70 + ((reduction - 5) * 2);
|
|
72
|
+
return 90 + Math.min(10, (reduction - 15));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Score based on blast radius (files affected)
|
|
77
|
+
*/
|
|
78
|
+
scoreBlastRadius(opportunity) {
|
|
79
|
+
const { filesAffected, linesAffected } = opportunity;
|
|
80
|
+
|
|
81
|
+
// More files affected = higher impact
|
|
82
|
+
let score = 30; // base
|
|
83
|
+
|
|
84
|
+
if (filesAffected) {
|
|
85
|
+
if (filesAffected === 1) score = 40;
|
|
86
|
+
else if (filesAffected <= 3) score = 60;
|
|
87
|
+
else if (filesAffected <= 10) score = 80;
|
|
88
|
+
else score = 95;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Adjust for lines affected
|
|
92
|
+
if (linesAffected) {
|
|
93
|
+
if (linesAffected > 100) score = Math.min(100, score + 10);
|
|
94
|
+
if (linesAffected > 500) score = Math.min(100, score + 10);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return score;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Score based on change frequency from git history
|
|
102
|
+
*/
|
|
103
|
+
scoreChangeFrequency(opportunity) {
|
|
104
|
+
const { filePath, changeCount } = opportunity;
|
|
105
|
+
|
|
106
|
+
// If changeCount provided, use it
|
|
107
|
+
if (changeCount !== undefined) {
|
|
108
|
+
if (changeCount === 0) return 30;
|
|
109
|
+
if (changeCount <= 5) return 50;
|
|
110
|
+
if (changeCount <= 20) return 70;
|
|
111
|
+
if (changeCount <= 50) return 85;
|
|
112
|
+
return 95;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Otherwise try to get from git
|
|
116
|
+
if (filePath) {
|
|
117
|
+
try {
|
|
118
|
+
const output = this.exec(`git log --oneline "${filePath}" 2>/dev/null | wc -l`);
|
|
119
|
+
const commits = parseInt(output.trim(), 10) || 0;
|
|
120
|
+
|
|
121
|
+
if (commits === 0) return 30;
|
|
122
|
+
if (commits <= 5) return 50;
|
|
123
|
+
if (commits <= 20) return 70;
|
|
124
|
+
if (commits <= 50) return 85;
|
|
125
|
+
return 95;
|
|
126
|
+
} catch {
|
|
127
|
+
return 50; // Default if git fails
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return 50;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Score based on risk (test coverage, criticality)
|
|
136
|
+
* Lower coverage = higher risk = higher priority to refactor safely
|
|
137
|
+
*/
|
|
138
|
+
scoreRisk(opportunity) {
|
|
139
|
+
const { testCoverage, isCritical } = opportunity;
|
|
140
|
+
|
|
141
|
+
let score = 50;
|
|
142
|
+
|
|
143
|
+
// Lower test coverage = higher risk score
|
|
144
|
+
if (testCoverage !== undefined) {
|
|
145
|
+
if (testCoverage >= 80) score = 30;
|
|
146
|
+
else if (testCoverage >= 60) score = 50;
|
|
147
|
+
else if (testCoverage >= 40) score = 70;
|
|
148
|
+
else if (testCoverage >= 20) score = 85;
|
|
149
|
+
else score = 95;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Critical paths get higher score
|
|
153
|
+
if (isCritical) {
|
|
154
|
+
score = Math.min(100, score + 15);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return score;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Score multiple opportunities and sort by impact
|
|
162
|
+
* @param {Array} opportunities - Array of opportunities
|
|
163
|
+
* @returns {Array} Sorted opportunities with scores
|
|
164
|
+
*/
|
|
165
|
+
scoreAll(opportunities) {
|
|
166
|
+
return opportunities
|
|
167
|
+
.map(opp => ({
|
|
168
|
+
...opp,
|
|
169
|
+
impact: this.score(opp),
|
|
170
|
+
}))
|
|
171
|
+
.sort((a, b) => b.impact.total - a.impact.total);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get priority tier from score
|
|
176
|
+
*/
|
|
177
|
+
static getTier(score) {
|
|
178
|
+
if (score >= 80) return 'high';
|
|
179
|
+
if (score >= 50) return 'medium';
|
|
180
|
+
return 'low';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = { ImpactScorer };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Impact Scorer Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
+
|
|
7
|
+
describe('ImpactScorer', () => {
|
|
8
|
+
describe('complexity reduction scoring', () => {
|
|
9
|
+
it('high complexity reduction gives high score', async () => {
|
|
10
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
11
|
+
const scorer = new ImpactScorer();
|
|
12
|
+
|
|
13
|
+
const result = scorer.score({
|
|
14
|
+
complexity: 25,
|
|
15
|
+
targetComplexity: 5,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(result.breakdown.complexityReduction).toBeGreaterThan(70);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('low complexity reduction gives low score', async () => {
|
|
22
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
23
|
+
const scorer = new ImpactScorer();
|
|
24
|
+
|
|
25
|
+
const result = scorer.score({
|
|
26
|
+
complexity: 3,
|
|
27
|
+
targetComplexity: 2,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(result.breakdown.complexityReduction).toBeLessThan(50);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('blast radius scoring', () => {
|
|
35
|
+
it('many files affected gives higher score', async () => {
|
|
36
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
37
|
+
const scorer = new ImpactScorer();
|
|
38
|
+
|
|
39
|
+
const result = scorer.score({
|
|
40
|
+
filesAffected: 15,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result.breakdown.blastRadius).toBeGreaterThan(80);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('single file gives lower score', async () => {
|
|
47
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
48
|
+
const scorer = new ImpactScorer();
|
|
49
|
+
|
|
50
|
+
const result = scorer.score({
|
|
51
|
+
filesAffected: 1,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result.breakdown.blastRadius).toBeLessThan(60);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('change frequency scoring', () => {
|
|
59
|
+
it('frequently changed files get higher score', async () => {
|
|
60
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
61
|
+
const scorer = new ImpactScorer();
|
|
62
|
+
|
|
63
|
+
const result = scorer.score({
|
|
64
|
+
changeCount: 100,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(result.breakdown.changeFrequency).toBeGreaterThan(80);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('rarely changed files get lower score', async () => {
|
|
71
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
72
|
+
const scorer = new ImpactScorer();
|
|
73
|
+
|
|
74
|
+
const result = scorer.score({
|
|
75
|
+
changeCount: 2,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(result.breakdown.changeFrequency).toBeLessThan(60);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('risk scoring', () => {
|
|
83
|
+
it('low test coverage gives higher risk score', async () => {
|
|
84
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
85
|
+
const scorer = new ImpactScorer();
|
|
86
|
+
|
|
87
|
+
const result = scorer.score({
|
|
88
|
+
testCoverage: 10,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(result.breakdown.risk).toBeGreaterThan(80);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('high test coverage gives lower risk score', async () => {
|
|
95
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
96
|
+
const scorer = new ImpactScorer();
|
|
97
|
+
|
|
98
|
+
const result = scorer.score({
|
|
99
|
+
testCoverage: 90,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(result.breakdown.risk).toBeLessThan(50);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('critical paths get boosted score', async () => {
|
|
106
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
107
|
+
const scorer = new ImpactScorer();
|
|
108
|
+
|
|
109
|
+
const regular = scorer.score({ testCoverage: 50 });
|
|
110
|
+
const critical = scorer.score({ testCoverage: 50, isCritical: true });
|
|
111
|
+
|
|
112
|
+
expect(critical.breakdown.risk).toBeGreaterThan(regular.breakdown.risk);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('combined scoring', () => {
|
|
117
|
+
it('combines factors into single 0-100 score', async () => {
|
|
118
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
119
|
+
const scorer = new ImpactScorer();
|
|
120
|
+
|
|
121
|
+
const result = scorer.score({
|
|
122
|
+
complexity: 20,
|
|
123
|
+
filesAffected: 5,
|
|
124
|
+
changeCount: 30,
|
|
125
|
+
testCoverage: 40,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(result.total).toBeGreaterThanOrEqual(0);
|
|
129
|
+
expect(result.total).toBeLessThanOrEqual(100);
|
|
130
|
+
expect(typeof result.total).toBe('number');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('returns breakdown of individual scores', async () => {
|
|
134
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
135
|
+
const scorer = new ImpactScorer();
|
|
136
|
+
|
|
137
|
+
const result = scorer.score({
|
|
138
|
+
complexity: 10,
|
|
139
|
+
filesAffected: 3,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result.breakdown).toBeDefined();
|
|
143
|
+
expect(result.breakdown.complexityReduction).toBeDefined();
|
|
144
|
+
expect(result.breakdown.blastRadius).toBeDefined();
|
|
145
|
+
expect(result.breakdown.changeFrequency).toBeDefined();
|
|
146
|
+
expect(result.breakdown.risk).toBeDefined();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('git history', () => {
|
|
151
|
+
it('handles missing git history gracefully', async () => {
|
|
152
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
153
|
+
|
|
154
|
+
const execMock = vi.fn().mockReturnValue('');
|
|
155
|
+
const scorer = new ImpactScorer({ exec: execMock });
|
|
156
|
+
|
|
157
|
+
const result = scorer.score({
|
|
158
|
+
filePath: 'nonexistent.js',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(result.total).toBeGreaterThanOrEqual(0);
|
|
162
|
+
// Empty git output returns 0 commits = score 30
|
|
163
|
+
expect(result.breakdown.changeFrequency).toBe(30);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('uses git log for file history when available', async () => {
|
|
167
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
168
|
+
|
|
169
|
+
const execMock = vi.fn().mockReturnValue('25\n');
|
|
170
|
+
const scorer = new ImpactScorer({ exec: execMock });
|
|
171
|
+
|
|
172
|
+
const result = scorer.score({
|
|
173
|
+
filePath: 'src/api/users.js',
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(execMock).toHaveBeenCalledWith(expect.stringContaining('git log'));
|
|
177
|
+
expect(result.breakdown.changeFrequency).toBeGreaterThan(60);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('scoreAll', () => {
|
|
182
|
+
it('scores and sorts multiple opportunities', async () => {
|
|
183
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
184
|
+
const scorer = new ImpactScorer();
|
|
185
|
+
|
|
186
|
+
const opportunities = [
|
|
187
|
+
{ complexity: 5, filesAffected: 1 },
|
|
188
|
+
{ complexity: 30, filesAffected: 10, changeCount: 50 },
|
|
189
|
+
{ complexity: 10, filesAffected: 3 },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const scored = scorer.scoreAll(opportunities);
|
|
193
|
+
|
|
194
|
+
expect(scored).toHaveLength(3);
|
|
195
|
+
expect(scored[0].impact.total).toBeGreaterThanOrEqual(scored[1].impact.total);
|
|
196
|
+
expect(scored[1].impact.total).toBeGreaterThanOrEqual(scored[2].impact.total);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe('getTier', () => {
|
|
201
|
+
it('returns correct priority tier', async () => {
|
|
202
|
+
const { ImpactScorer } = await import('./impact-scorer.js');
|
|
203
|
+
|
|
204
|
+
expect(ImpactScorer.getTier(90)).toBe('high');
|
|
205
|
+
expect(ImpactScorer.getTier(80)).toBe('high');
|
|
206
|
+
expect(ImpactScorer.getTier(65)).toBe('medium');
|
|
207
|
+
expect(ImpactScorer.getTier(50)).toBe('medium');
|
|
208
|
+
expect(ImpactScorer.getTier(30)).toBe('low');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|