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,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
|
+
});
|