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.
Files changed (182) hide show
  1. package/dashboard/dist/components/AuditPane.d.ts +30 -0
  2. package/dashboard/dist/components/AuditPane.js +127 -0
  3. package/dashboard/dist/components/AuditPane.test.d.ts +1 -0
  4. package/dashboard/dist/components/AuditPane.test.js +339 -0
  5. package/dashboard/dist/components/CompliancePane.d.ts +39 -0
  6. package/dashboard/dist/components/CompliancePane.js +96 -0
  7. package/dashboard/dist/components/CompliancePane.test.d.ts +1 -0
  8. package/dashboard/dist/components/CompliancePane.test.js +183 -0
  9. package/dashboard/dist/components/SSOPane.d.ts +36 -0
  10. package/dashboard/dist/components/SSOPane.js +71 -0
  11. package/dashboard/dist/components/SSOPane.test.d.ts +1 -0
  12. package/dashboard/dist/components/SSOPane.test.js +155 -0
  13. package/dashboard/dist/components/UsagePane.d.ts +13 -0
  14. package/dashboard/dist/components/UsagePane.js +51 -0
  15. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  16. package/dashboard/dist/components/UsagePane.test.js +142 -0
  17. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  18. package/dashboard/dist/components/WorkspaceDocsPane.js +130 -0
  19. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  20. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  21. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  22. package/dashboard/dist/components/WorkspacePane.js +17 -0
  23. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  24. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  25. package/dashboard/dist/components/ZeroRetentionPane.d.ts +44 -0
  26. package/dashboard/dist/components/ZeroRetentionPane.js +83 -0
  27. package/dashboard/dist/components/ZeroRetentionPane.test.d.ts +1 -0
  28. package/dashboard/dist/components/ZeroRetentionPane.test.js +160 -0
  29. package/package.json +1 -1
  30. package/server/lib/access-control-doc.js +541 -0
  31. package/server/lib/access-control-doc.test.js +672 -0
  32. package/server/lib/adr-generator.js +423 -0
  33. package/server/lib/adr-generator.test.js +586 -0
  34. package/server/lib/agent-progress-monitor.js +223 -0
  35. package/server/lib/agent-progress-monitor.test.js +202 -0
  36. package/server/lib/architecture-command.js +450 -0
  37. package/server/lib/architecture-command.test.js +754 -0
  38. package/server/lib/ast-analyzer.js +324 -0
  39. package/server/lib/ast-analyzer.test.js +437 -0
  40. package/server/lib/audit-attribution.js +191 -0
  41. package/server/lib/audit-attribution.test.js +359 -0
  42. package/server/lib/audit-classifier.js +202 -0
  43. package/server/lib/audit-classifier.test.js +209 -0
  44. package/server/lib/audit-command.js +275 -0
  45. package/server/lib/audit-command.test.js +325 -0
  46. package/server/lib/audit-exporter.js +380 -0
  47. package/server/lib/audit-exporter.test.js +464 -0
  48. package/server/lib/audit-logger.js +236 -0
  49. package/server/lib/audit-logger.test.js +364 -0
  50. package/server/lib/audit-query.js +257 -0
  51. package/server/lib/audit-query.test.js +352 -0
  52. package/server/lib/audit-storage.js +269 -0
  53. package/server/lib/audit-storage.test.js +272 -0
  54. package/server/lib/auth-system.test.js +4 -1
  55. package/server/lib/boundary-detector.js +427 -0
  56. package/server/lib/boundary-detector.test.js +320 -0
  57. package/server/lib/budget-alerts.js +138 -0
  58. package/server/lib/budget-alerts.test.js +235 -0
  59. package/server/lib/bulk-repo-init.js +342 -0
  60. package/server/lib/bulk-repo-init.test.js +388 -0
  61. package/server/lib/candidates-tracker.js +210 -0
  62. package/server/lib/candidates-tracker.test.js +300 -0
  63. package/server/lib/checkpoint-manager.js +251 -0
  64. package/server/lib/checkpoint-manager.test.js +474 -0
  65. package/server/lib/circular-detector.js +337 -0
  66. package/server/lib/circular-detector.test.js +353 -0
  67. package/server/lib/cohesion-analyzer.js +310 -0
  68. package/server/lib/cohesion-analyzer.test.js +447 -0
  69. package/server/lib/compliance-checklist.js +866 -0
  70. package/server/lib/compliance-checklist.test.js +476 -0
  71. package/server/lib/compliance-command.js +616 -0
  72. package/server/lib/compliance-command.test.js +551 -0
  73. package/server/lib/compliance-reporter.js +692 -0
  74. package/server/lib/compliance-reporter.test.js +707 -0
  75. package/server/lib/contract-testing.js +625 -0
  76. package/server/lib/contract-testing.test.js +342 -0
  77. package/server/lib/conversion-planner.js +469 -0
  78. package/server/lib/conversion-planner.test.js +361 -0
  79. package/server/lib/convert-command.js +351 -0
  80. package/server/lib/convert-command.test.js +608 -0
  81. package/server/lib/coupling-calculator.js +189 -0
  82. package/server/lib/coupling-calculator.test.js +509 -0
  83. package/server/lib/data-flow-doc.js +665 -0
  84. package/server/lib/data-flow-doc.test.js +659 -0
  85. package/server/lib/dependency-graph.js +367 -0
  86. package/server/lib/dependency-graph.test.js +516 -0
  87. package/server/lib/duplication-detector.js +349 -0
  88. package/server/lib/duplication-detector.test.js +401 -0
  89. package/server/lib/ephemeral-storage.js +249 -0
  90. package/server/lib/ephemeral-storage.test.js +254 -0
  91. package/server/lib/evidence-collector.js +627 -0
  92. package/server/lib/evidence-collector.test.js +901 -0
  93. package/server/lib/example-service.js +616 -0
  94. package/server/lib/example-service.test.js +397 -0
  95. package/server/lib/flow-diagram-generator.js +474 -0
  96. package/server/lib/flow-diagram-generator.test.js +446 -0
  97. package/server/lib/idp-manager.js +626 -0
  98. package/server/lib/idp-manager.test.js +587 -0
  99. package/server/lib/impact-scorer.js +184 -0
  100. package/server/lib/impact-scorer.test.js +211 -0
  101. package/server/lib/memory-exclusion.js +326 -0
  102. package/server/lib/memory-exclusion.test.js +241 -0
  103. package/server/lib/mermaid-generator.js +358 -0
  104. package/server/lib/mermaid-generator.test.js +301 -0
  105. package/server/lib/messaging-patterns.js +750 -0
  106. package/server/lib/messaging-patterns.test.js +213 -0
  107. package/server/lib/mfa-handler.js +452 -0
  108. package/server/lib/mfa-handler.test.js +490 -0
  109. package/server/lib/microservice-template.js +386 -0
  110. package/server/lib/microservice-template.test.js +325 -0
  111. package/server/lib/new-project-microservice.js +450 -0
  112. package/server/lib/new-project-microservice.test.js +600 -0
  113. package/server/lib/oauth-flow.js +375 -0
  114. package/server/lib/oauth-flow.test.js +487 -0
  115. package/server/lib/oauth-registry.js +190 -0
  116. package/server/lib/oauth-registry.test.js +306 -0
  117. package/server/lib/readme-generator.js +490 -0
  118. package/server/lib/readme-generator.test.js +493 -0
  119. package/server/lib/refactor-command.js +326 -0
  120. package/server/lib/refactor-command.test.js +528 -0
  121. package/server/lib/refactor-executor.js +254 -0
  122. package/server/lib/refactor-executor.test.js +305 -0
  123. package/server/lib/refactor-observer.js +292 -0
  124. package/server/lib/refactor-observer.test.js +422 -0
  125. package/server/lib/refactor-progress.js +193 -0
  126. package/server/lib/refactor-progress.test.js +251 -0
  127. package/server/lib/refactor-reporter.js +237 -0
  128. package/server/lib/refactor-reporter.test.js +247 -0
  129. package/server/lib/repo-dependency-tracker.js +261 -0
  130. package/server/lib/repo-dependency-tracker.test.js +350 -0
  131. package/server/lib/retention-policy.js +281 -0
  132. package/server/lib/retention-policy.test.js +486 -0
  133. package/server/lib/role-mapper.js +236 -0
  134. package/server/lib/role-mapper.test.js +395 -0
  135. package/server/lib/saml-provider.js +765 -0
  136. package/server/lib/saml-provider.test.js +643 -0
  137. package/server/lib/security-policy-generator.js +682 -0
  138. package/server/lib/security-policy-generator.test.js +544 -0
  139. package/server/lib/semantic-analyzer.js +198 -0
  140. package/server/lib/semantic-analyzer.test.js +474 -0
  141. package/server/lib/sensitive-detector.js +112 -0
  142. package/server/lib/sensitive-detector.test.js +209 -0
  143. package/server/lib/service-interaction-diagram.js +700 -0
  144. package/server/lib/service-interaction-diagram.test.js +638 -0
  145. package/server/lib/service-scaffold.js +486 -0
  146. package/server/lib/service-scaffold.test.js +373 -0
  147. package/server/lib/service-summary.js +553 -0
  148. package/server/lib/service-summary.test.js +619 -0
  149. package/server/lib/session-purge.js +460 -0
  150. package/server/lib/session-purge.test.js +312 -0
  151. package/server/lib/shared-kernel.js +578 -0
  152. package/server/lib/shared-kernel.test.js +255 -0
  153. package/server/lib/sso-command.js +544 -0
  154. package/server/lib/sso-command.test.js +552 -0
  155. package/server/lib/sso-session.js +492 -0
  156. package/server/lib/sso-session.test.js +670 -0
  157. package/server/lib/traefik-config.js +282 -0
  158. package/server/lib/traefik-config.test.js +312 -0
  159. package/server/lib/usage-command.js +218 -0
  160. package/server/lib/usage-command.test.js +391 -0
  161. package/server/lib/usage-formatter.js +192 -0
  162. package/server/lib/usage-formatter.test.js +267 -0
  163. package/server/lib/usage-history.js +122 -0
  164. package/server/lib/usage-history.test.js +206 -0
  165. package/server/lib/workspace-command.js +249 -0
  166. package/server/lib/workspace-command.test.js +264 -0
  167. package/server/lib/workspace-config.js +270 -0
  168. package/server/lib/workspace-config.test.js +312 -0
  169. package/server/lib/workspace-docs-command.js +547 -0
  170. package/server/lib/workspace-docs-command.test.js +692 -0
  171. package/server/lib/workspace-memory.js +451 -0
  172. package/server/lib/workspace-memory.test.js +403 -0
  173. package/server/lib/workspace-scanner.js +452 -0
  174. package/server/lib/workspace-scanner.test.js +677 -0
  175. package/server/lib/workspace-test-runner.js +315 -0
  176. package/server/lib/workspace-test-runner.test.js +294 -0
  177. package/server/lib/zero-retention-command.js +439 -0
  178. package/server/lib/zero-retention-command.test.js +448 -0
  179. package/server/lib/zero-retention.js +322 -0
  180. package/server/lib/zero-retention.test.js +258 -0
  181. package/server/package-lock.json +14 -0
  182. 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
+ });