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,254 @@
1
+ /**
2
+ * Refactor Executor
3
+ * Apply refactoring changes with interactive confirmation
4
+ */
5
+
6
+ const { CheckpointManager } = require('./checkpoint-manager.js');
7
+
8
+ class RefactorExecutor {
9
+ constructor(options = {}) {
10
+ this.checkpointManager = options.checkpointManager || new CheckpointManager();
11
+ this.testCommand = options.testCommand || 'npm test';
12
+ this.maxAutofixAttempts = options.maxAutofixAttempts || 3;
13
+ this.interactive = options.interactive !== false;
14
+ this.prompt = options.prompt || this.defaultPrompt.bind(this);
15
+ this.exec = options.exec || this.defaultExec.bind(this);
16
+ this.writeFile = options.writeFile || require('fs').promises.writeFile;
17
+ this.readFile = options.readFile || require('fs').promises.readFile;
18
+
19
+ this.appliedChanges = [];
20
+ this.checkpoint = null;
21
+ }
22
+
23
+ defaultPrompt(question) {
24
+ // In real implementation, would use readline
25
+ return Promise.resolve('y');
26
+ }
27
+
28
+ async defaultExec(command) {
29
+ const { execSync } = require('child_process');
30
+ return execSync(command, { encoding: 'utf-8' });
31
+ }
32
+
33
+ /**
34
+ * Execute a list of refactorings
35
+ * @param {Array} refactorings - List of refactoring operations
36
+ * @returns {Object} Execution result
37
+ */
38
+ async execute(refactorings) {
39
+ this.appliedChanges = [];
40
+
41
+ // Create checkpoint before starting
42
+ this.checkpoint = await this.checkpointManager.create();
43
+
44
+ const results = {
45
+ applied: [],
46
+ skipped: [],
47
+ failed: [],
48
+ rolledBack: false,
49
+ };
50
+
51
+ for (const refactor of refactorings) {
52
+ try {
53
+ const result = await this.executeOne(refactor);
54
+
55
+ if (result.applied) {
56
+ results.applied.push(refactor);
57
+ this.appliedChanges.push({
58
+ refactor,
59
+ timestamp: new Date(),
60
+ });
61
+ } else if (result.skipped) {
62
+ results.skipped.push(refactor);
63
+ } else if (result.failed) {
64
+ results.failed.push({ refactor, error: result.error });
65
+
66
+ // Rollback on failure
67
+ await this.rollback();
68
+ results.rolledBack = true;
69
+ break;
70
+ }
71
+ } catch (error) {
72
+ results.failed.push({ refactor, error: error.message });
73
+ await this.rollback();
74
+ results.rolledBack = true;
75
+ break;
76
+ }
77
+ }
78
+
79
+ return results;
80
+ }
81
+
82
+ /**
83
+ * Execute a single refactoring
84
+ */
85
+ async executeOne(refactor) {
86
+ // Interactive confirmation
87
+ if (this.interactive) {
88
+ const description = this.describeRefactor(refactor);
89
+ const answer = await this.prompt(`${description}\nApply? [Y/n/skip]: `);
90
+
91
+ if (answer.toLowerCase() === 'n') {
92
+ return { applied: false, skipped: true };
93
+ }
94
+ if (answer.toLowerCase() === 'skip') {
95
+ return { applied: false, skipped: true };
96
+ }
97
+ }
98
+
99
+ // Apply the refactoring
100
+ await this.applyRefactor(refactor);
101
+
102
+ // Run tests
103
+ const testResult = await this.runTests();
104
+
105
+ if (!testResult.success) {
106
+ // Try autofix
107
+ const fixed = await this.attemptAutofix(refactor, testResult);
108
+ if (!fixed) {
109
+ return { applied: false, failed: true, error: 'Tests failed after autofix attempts' };
110
+ }
111
+ }
112
+
113
+ return { applied: true };
114
+ }
115
+
116
+ /**
117
+ * Describe a refactoring in plain English
118
+ */
119
+ describeRefactor(refactor) {
120
+ switch (refactor.type) {
121
+ case 'extract':
122
+ return `Extract "${refactor.name}" from "${refactor.source}" (${refactor.lines} lines)`;
123
+ case 'rename':
124
+ return `Rename "${refactor.oldName}" to "${refactor.newName}" in ${refactor.files?.length || 1} file(s)`;
125
+ case 'split':
126
+ return `Split "${refactor.source}" into ${refactor.targets?.length || 2} files`;
127
+ case 'inline':
128
+ return `Inline "${refactor.name}" into "${refactor.target}"`;
129
+ default:
130
+ return `Apply ${refactor.type} refactoring`;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Apply a refactoring to the codebase
136
+ */
137
+ async applyRefactor(refactor) {
138
+ switch (refactor.type) {
139
+ case 'extract':
140
+ await this.applyExtract(refactor);
141
+ break;
142
+ case 'rename':
143
+ await this.applyRename(refactor);
144
+ break;
145
+ case 'split':
146
+ await this.applySplit(refactor);
147
+ break;
148
+ default:
149
+ if (refactor.changes) {
150
+ for (const change of refactor.changes) {
151
+ await this.writeFile(change.file, change.content);
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ async applyExtract(refactor) {
158
+ const { source, name, startLine, endLine, newFile } = refactor;
159
+
160
+ const content = await this.readFile(source, 'utf-8');
161
+ const lines = content.split('\n');
162
+
163
+ // Extract the function body
164
+ const extractedLines = lines.slice(startLine - 1, endLine);
165
+ const extractedCode = extractedLines.join('\n');
166
+
167
+ // Create new file with extracted function
168
+ const newContent = `/**
169
+ * Extracted from ${source}
170
+ */
171
+
172
+ ${extractedCode}
173
+
174
+ module.exports = { ${name} };
175
+ `;
176
+
177
+ if (newFile) {
178
+ await this.writeFile(newFile, newContent);
179
+ }
180
+
181
+ // Update original file - replace with import/call
182
+ const updatedLines = [
183
+ ...lines.slice(0, startLine - 1),
184
+ `const { ${name} } = require('${newFile}');`,
185
+ ...lines.slice(endLine),
186
+ ];
187
+
188
+ await this.writeFile(source, updatedLines.join('\n'));
189
+ }
190
+
191
+ async applyRename(refactor) {
192
+ const { oldName, newName, files } = refactor;
193
+
194
+ for (const file of files || []) {
195
+ const content = await this.readFile(file, 'utf-8');
196
+ const updated = content.replace(new RegExp(oldName, 'g'), newName);
197
+ await this.writeFile(file, updated);
198
+ }
199
+ }
200
+
201
+ async applySplit(refactor) {
202
+ const { source, targets } = refactor;
203
+
204
+ for (const target of targets || []) {
205
+ await this.writeFile(target.file, target.content);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Run tests and return result
211
+ */
212
+ async runTests() {
213
+ try {
214
+ await this.exec(this.testCommand);
215
+ return { success: true };
216
+ } catch (error) {
217
+ return { success: false, error: error.message };
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Attempt to autofix failing tests
223
+ */
224
+ async attemptAutofix(refactor, testResult) {
225
+ for (let attempt = 1; attempt <= this.maxAutofixAttempts; attempt++) {
226
+ // In real implementation, would analyze error and fix
227
+ // For now, just re-run tests
228
+ const result = await this.runTests();
229
+ if (result.success) {
230
+ return true;
231
+ }
232
+ }
233
+ return false;
234
+ }
235
+
236
+ /**
237
+ * Rollback to checkpoint
238
+ */
239
+ async rollback() {
240
+ if (this.checkpoint) {
241
+ await this.checkpointManager.rollback(this.checkpoint);
242
+ this.checkpoint = null;
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Get log of applied changes
248
+ */
249
+ getLog() {
250
+ return this.appliedChanges;
251
+ }
252
+ }
253
+
254
+ module.exports = { RefactorExecutor };
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Refactor Executor Tests
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+
7
+ describe('RefactorExecutor', () => {
8
+ beforeEach(() => {
9
+ vi.resetAllMocks();
10
+ });
11
+
12
+ describe('extract refactoring', () => {
13
+ it('applies extract function refactoring', async () => {
14
+ const { RefactorExecutor } = await import('./refactor-executor.js');
15
+
16
+ const writeFileMock = vi.fn().mockResolvedValue();
17
+ const readFileMock = vi.fn().mockResolvedValue(`
18
+ function main() {
19
+ const x = validateEmail(email);
20
+ return x;
21
+ }
22
+
23
+ function validateEmail(email) {
24
+ if (!email) return false;
25
+ return email.includes('@');
26
+ }
27
+ `);
28
+ const execMock = vi.fn().mockResolvedValue('');
29
+ const checkpointManager = {
30
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
31
+ rollback: vi.fn().mockResolvedValue(),
32
+ };
33
+
34
+ const executor = new RefactorExecutor({
35
+ checkpointManager,
36
+ writeFile: writeFileMock,
37
+ readFile: readFileMock,
38
+ exec: execMock,
39
+ interactive: false,
40
+ });
41
+
42
+ const result = await executor.execute([{
43
+ type: 'extract',
44
+ source: 'src/main.js',
45
+ name: 'validateEmail',
46
+ startLine: 6,
47
+ endLine: 9,
48
+ newFile: 'src/validators.js',
49
+ }]);
50
+
51
+ expect(writeFileMock).toHaveBeenCalled();
52
+ expect(result.applied).toHaveLength(1);
53
+ });
54
+ });
55
+
56
+ describe('rename refactoring', () => {
57
+ it('applies rename refactoring across files', async () => {
58
+ const { RefactorExecutor } = await import('./refactor-executor.js');
59
+
60
+ const writeFileMock = vi.fn().mockResolvedValue();
61
+ const readFileMock = vi.fn().mockResolvedValue('const oldName = 1;');
62
+ const execMock = vi.fn().mockResolvedValue('');
63
+ const checkpointManager = {
64
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
65
+ rollback: vi.fn().mockResolvedValue(),
66
+ };
67
+
68
+ const executor = new RefactorExecutor({
69
+ checkpointManager,
70
+ writeFile: writeFileMock,
71
+ readFile: readFileMock,
72
+ exec: execMock,
73
+ interactive: false,
74
+ });
75
+
76
+ const result = await executor.execute([{
77
+ type: 'rename',
78
+ oldName: 'oldName',
79
+ newName: 'newName',
80
+ files: ['file1.js', 'file2.js'],
81
+ }]);
82
+
83
+ expect(writeFileMock).toHaveBeenCalledTimes(2);
84
+ expect(result.applied).toHaveLength(1);
85
+ });
86
+ });
87
+
88
+ describe('interactive mode', () => {
89
+ it('pauses for confirmation in interactive mode', async () => {
90
+ const { RefactorExecutor } = await import('./refactor-executor.js');
91
+
92
+ const promptMock = vi.fn().mockResolvedValue('y');
93
+ const writeFileMock = vi.fn().mockResolvedValue();
94
+ const readFileMock = vi.fn().mockResolvedValue('code');
95
+ const execMock = vi.fn().mockResolvedValue('');
96
+ const checkpointManager = {
97
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
98
+ rollback: vi.fn().mockResolvedValue(),
99
+ };
100
+
101
+ const executor = new RefactorExecutor({
102
+ checkpointManager,
103
+ writeFile: writeFileMock,
104
+ readFile: readFileMock,
105
+ exec: execMock,
106
+ prompt: promptMock,
107
+ interactive: true,
108
+ });
109
+
110
+ await executor.execute([{
111
+ type: 'rename',
112
+ oldName: 'x',
113
+ newName: 'count',
114
+ files: ['file.js'],
115
+ }]);
116
+
117
+ expect(promptMock).toHaveBeenCalled();
118
+ });
119
+
120
+ it('skips when user says skip', async () => {
121
+ const { RefactorExecutor } = await import('./refactor-executor.js');
122
+
123
+ const promptMock = vi.fn().mockResolvedValue('skip');
124
+ const writeFileMock = vi.fn();
125
+ const checkpointManager = {
126
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
127
+ rollback: vi.fn().mockResolvedValue(),
128
+ };
129
+
130
+ const executor = new RefactorExecutor({
131
+ checkpointManager,
132
+ writeFile: writeFileMock,
133
+ prompt: promptMock,
134
+ interactive: true,
135
+ });
136
+
137
+ const result = await executor.execute([{
138
+ type: 'rename',
139
+ oldName: 'x',
140
+ newName: 'count',
141
+ files: [],
142
+ }]);
143
+
144
+ expect(result.skipped).toHaveLength(1);
145
+ expect(result.applied).toHaveLength(0);
146
+ });
147
+ });
148
+
149
+ describe('test running', () => {
150
+ it('runs test command after each change', async () => {
151
+ const { RefactorExecutor } = await import('./refactor-executor.js');
152
+
153
+ const execMock = vi.fn().mockResolvedValue('');
154
+ const checkpointManager = {
155
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
156
+ rollback: vi.fn().mockResolvedValue(),
157
+ };
158
+
159
+ const executor = new RefactorExecutor({
160
+ checkpointManager,
161
+ testCommand: 'npm test',
162
+ exec: execMock,
163
+ interactive: false,
164
+ writeFile: vi.fn().mockResolvedValue(),
165
+ readFile: vi.fn().mockResolvedValue('code'),
166
+ });
167
+
168
+ await executor.execute([{
169
+ type: 'rename',
170
+ oldName: 'x',
171
+ newName: 'y',
172
+ files: ['file.js'],
173
+ }]);
174
+
175
+ expect(execMock).toHaveBeenCalledWith('npm test');
176
+ });
177
+ });
178
+
179
+ describe('autofix attempts', () => {
180
+ it('attempts autofix on failure up to 3 times', async () => {
181
+ const { RefactorExecutor } = await import('./refactor-executor.js');
182
+
183
+ let callCount = 0;
184
+ const execMock = vi.fn().mockImplementation(() => {
185
+ callCount++;
186
+ if (callCount <= 3) throw new Error('Test failed');
187
+ return '';
188
+ });
189
+
190
+ const checkpointManager = {
191
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
192
+ rollback: vi.fn().mockResolvedValue(),
193
+ };
194
+
195
+ const executor = new RefactorExecutor({
196
+ checkpointManager,
197
+ exec: execMock,
198
+ maxAutofixAttempts: 3,
199
+ interactive: false,
200
+ writeFile: vi.fn().mockResolvedValue(),
201
+ readFile: vi.fn().mockResolvedValue('code'),
202
+ });
203
+
204
+ await executor.execute([{
205
+ type: 'rename',
206
+ oldName: 'x',
207
+ newName: 'y',
208
+ files: ['file.js'],
209
+ }]);
210
+
211
+ // 1 initial + 3 autofix attempts = 4 calls
212
+ expect(execMock).toHaveBeenCalledTimes(4);
213
+ });
214
+
215
+ it('rolls back after 3 failed attempts', async () => {
216
+ const { RefactorExecutor } = await import('./refactor-executor.js');
217
+
218
+ const execMock = vi.fn().mockImplementation(() => {
219
+ throw new Error('Test failed');
220
+ });
221
+
222
+ const checkpointManager = {
223
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
224
+ rollback: vi.fn().mockResolvedValue(),
225
+ };
226
+
227
+ const executor = new RefactorExecutor({
228
+ checkpointManager,
229
+ exec: execMock,
230
+ maxAutofixAttempts: 3,
231
+ interactive: false,
232
+ writeFile: vi.fn().mockResolvedValue(),
233
+ readFile: vi.fn().mockResolvedValue('code'),
234
+ });
235
+
236
+ const result = await executor.execute([{
237
+ type: 'rename',
238
+ oldName: 'x',
239
+ newName: 'y',
240
+ files: ['file.js'],
241
+ }]);
242
+
243
+ expect(checkpointManager.rollback).toHaveBeenCalled();
244
+ expect(result.rolledBack).toBe(true);
245
+ });
246
+ });
247
+
248
+ describe('change tracking', () => {
249
+ it('tracks all applied changes in log', async () => {
250
+ const { RefactorExecutor } = await import('./refactor-executor.js');
251
+
252
+ const execMock = vi.fn().mockResolvedValue('');
253
+ const checkpointManager = {
254
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
255
+ rollback: vi.fn().mockResolvedValue(),
256
+ };
257
+
258
+ const executor = new RefactorExecutor({
259
+ checkpointManager,
260
+ exec: execMock,
261
+ interactive: false,
262
+ writeFile: vi.fn().mockResolvedValue(),
263
+ readFile: vi.fn().mockResolvedValue('code'),
264
+ });
265
+
266
+ await executor.execute([
267
+ { type: 'rename', oldName: 'a', newName: 'b', files: ['f1.js'] },
268
+ { type: 'rename', oldName: 'c', newName: 'd', files: ['f2.js'] },
269
+ ]);
270
+
271
+ const log = executor.getLog();
272
+ expect(log).toHaveLength(2);
273
+ expect(log[0].refactor.oldName).toBe('a');
274
+ expect(log[1].refactor.oldName).toBe('c');
275
+ });
276
+ });
277
+
278
+ describe('checkpoint management', () => {
279
+ it('creates checkpoint before starting', async () => {
280
+ const { RefactorExecutor } = await import('./refactor-executor.js');
281
+
282
+ const checkpointManager = {
283
+ create: vi.fn().mockResolvedValue({ id: 'test' }),
284
+ rollback: vi.fn().mockResolvedValue(),
285
+ };
286
+
287
+ const executor = new RefactorExecutor({
288
+ checkpointManager,
289
+ exec: vi.fn().mockResolvedValue(''),
290
+ interactive: false,
291
+ writeFile: vi.fn().mockResolvedValue(),
292
+ readFile: vi.fn().mockResolvedValue('code'),
293
+ });
294
+
295
+ await executor.execute([{
296
+ type: 'rename',
297
+ oldName: 'x',
298
+ newName: 'y',
299
+ files: ['file.js'],
300
+ }]);
301
+
302
+ expect(checkpointManager.create).toHaveBeenCalled();
303
+ });
304
+ });
305
+ });