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,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Candidates Tracker Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
|
|
7
|
+
describe('CandidatesTracker', () => {
|
|
8
|
+
describe('file creation', () => {
|
|
9
|
+
it('creates file if not exists', async () => {
|
|
10
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
11
|
+
|
|
12
|
+
const writeFileMock = vi.fn().mockResolvedValue();
|
|
13
|
+
const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
|
|
14
|
+
const mkdirMock = vi.fn().mockResolvedValue();
|
|
15
|
+
|
|
16
|
+
const tracker = new CandidatesTracker({
|
|
17
|
+
readFile: readFileMock,
|
|
18
|
+
writeFile: writeFileMock,
|
|
19
|
+
mkdir: mkdirMock,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await tracker.add([
|
|
23
|
+
{ file: 'test.js', startLine: 10, description: 'Test issue', impact: 85 },
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
expect(writeFileMock).toHaveBeenCalled();
|
|
27
|
+
expect(mkdirMock).toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('priority sections', () => {
|
|
32
|
+
it('appends new candidates to correct priority section', async () => {
|
|
33
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
34
|
+
|
|
35
|
+
let savedContent = '';
|
|
36
|
+
const writeFileMock = vi.fn().mockImplementation((path, content) => {
|
|
37
|
+
savedContent = content;
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
});
|
|
40
|
+
const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
|
|
41
|
+
const mkdirMock = vi.fn().mockResolvedValue();
|
|
42
|
+
|
|
43
|
+
const tracker = new CandidatesTracker({
|
|
44
|
+
readFile: readFileMock,
|
|
45
|
+
writeFile: writeFileMock,
|
|
46
|
+
mkdir: mkdirMock,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await tracker.add([
|
|
50
|
+
{ file: 'high.js', startLine: 1, description: 'High priority', impact: 90 },
|
|
51
|
+
{ file: 'medium.js', startLine: 1, description: 'Medium priority', impact: 65 },
|
|
52
|
+
{ file: 'low.js', startLine: 1, description: 'Low priority', impact: 30 },
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
expect(savedContent).toContain('High Priority');
|
|
56
|
+
expect(savedContent).toContain('high.js:1');
|
|
57
|
+
expect(savedContent).toContain('Medium Priority');
|
|
58
|
+
expect(savedContent).toContain('medium.js:1');
|
|
59
|
+
expect(savedContent).toContain('Low Priority');
|
|
60
|
+
expect(savedContent).toContain('low.js:1');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('correctly categorizes by impact score', async () => {
|
|
64
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
65
|
+
const tracker = new CandidatesTracker({});
|
|
66
|
+
|
|
67
|
+
expect(tracker.getTier(90)).toBe('high');
|
|
68
|
+
expect(tracker.getTier(80)).toBe('high');
|
|
69
|
+
expect(tracker.getTier(79)).toBe('medium');
|
|
70
|
+
expect(tracker.getTier(50)).toBe('medium');
|
|
71
|
+
expect(tracker.getTier(49)).toBe('low');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('deduplication', () => {
|
|
76
|
+
it('deduplicates by file:line key', async () => {
|
|
77
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
78
|
+
|
|
79
|
+
const existingContent = `# Refactor Candidates
|
|
80
|
+
|
|
81
|
+
## High Priority (Impact 80+)
|
|
82
|
+
|
|
83
|
+
- [ ] test.js:10 - Existing issue (Impact: 85)
|
|
84
|
+
|
|
85
|
+
## Medium Priority (Impact 50-79)
|
|
86
|
+
|
|
87
|
+
_None_
|
|
88
|
+
|
|
89
|
+
## Low Priority (Impact <50)
|
|
90
|
+
|
|
91
|
+
_None_
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
let savedContent = '';
|
|
95
|
+
const writeFileMock = vi.fn().mockImplementation((path, content) => {
|
|
96
|
+
savedContent = content;
|
|
97
|
+
return Promise.resolve();
|
|
98
|
+
});
|
|
99
|
+
const readFileMock = vi.fn().mockResolvedValue(existingContent);
|
|
100
|
+
const mkdirMock = vi.fn().mockResolvedValue();
|
|
101
|
+
|
|
102
|
+
const tracker = new CandidatesTracker({
|
|
103
|
+
readFile: readFileMock,
|
|
104
|
+
writeFile: writeFileMock,
|
|
105
|
+
mkdir: mkdirMock,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await tracker.add([
|
|
109
|
+
{ file: 'test.js', startLine: 10, description: 'Updated issue', impact: 90 },
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// Should only have one entry for test.js:10
|
|
113
|
+
const matches = savedContent.match(/test\.js:10/g);
|
|
114
|
+
expect(matches).toHaveLength(1);
|
|
115
|
+
expect(savedContent).toContain('Updated issue');
|
|
116
|
+
expect(savedContent).toContain('Impact: 90');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('impact score updates', () => {
|
|
121
|
+
it('updates impact scores on re-analysis', async () => {
|
|
122
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
123
|
+
|
|
124
|
+
const existingContent = `# Refactor Candidates
|
|
125
|
+
|
|
126
|
+
## High Priority (Impact 80+)
|
|
127
|
+
|
|
128
|
+
- [ ] test.js:10 - Old description (Impact: 85)
|
|
129
|
+
|
|
130
|
+
## Medium Priority (Impact 50-79)
|
|
131
|
+
|
|
132
|
+
_None_
|
|
133
|
+
|
|
134
|
+
## Low Priority (Impact <50)
|
|
135
|
+
|
|
136
|
+
_None_
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
let savedContent = '';
|
|
140
|
+
const tracker = new CandidatesTracker({
|
|
141
|
+
readFile: vi.fn().mockResolvedValue(existingContent),
|
|
142
|
+
writeFile: vi.fn().mockImplementation((p, c) => { savedContent = c; return Promise.resolve(); }),
|
|
143
|
+
mkdir: vi.fn().mockResolvedValue(),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await tracker.add([
|
|
147
|
+
{ file: 'test.js', startLine: 10, description: 'New description', impact: 95 },
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
expect(savedContent).toContain('Impact: 95');
|
|
151
|
+
expect(savedContent).toContain('New description');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('completion marking', () => {
|
|
156
|
+
it('marks candidate as complete after refactoring', async () => {
|
|
157
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
158
|
+
|
|
159
|
+
const existingContent = `# Refactor Candidates
|
|
160
|
+
|
|
161
|
+
## High Priority (Impact 80+)
|
|
162
|
+
|
|
163
|
+
- [ ] test.js:10 - Test issue (Impact: 85)
|
|
164
|
+
|
|
165
|
+
## Medium Priority (Impact 50-79)
|
|
166
|
+
|
|
167
|
+
_None_
|
|
168
|
+
|
|
169
|
+
## Low Priority (Impact <50)
|
|
170
|
+
|
|
171
|
+
_None_
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
let savedContent = '';
|
|
175
|
+
const tracker = new CandidatesTracker({
|
|
176
|
+
readFile: vi.fn().mockResolvedValue(existingContent),
|
|
177
|
+
writeFile: vi.fn().mockImplementation((p, c) => { savedContent = c; return Promise.resolve(); }),
|
|
178
|
+
mkdir: vi.fn().mockResolvedValue(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await tracker.markComplete('test.js', 10);
|
|
182
|
+
|
|
183
|
+
expect(savedContent).toContain('[x] test.js:10');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('notes preservation', () => {
|
|
188
|
+
it('preserves manual notes in file', async () => {
|
|
189
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
190
|
+
|
|
191
|
+
const existingContent = `# Refactor Candidates
|
|
192
|
+
|
|
193
|
+
## High Priority (Impact 80+)
|
|
194
|
+
|
|
195
|
+
- [ ] test.js:10 - Test issue (Impact: 85)
|
|
196
|
+
|
|
197
|
+
## Medium Priority (Impact 50-79)
|
|
198
|
+
|
|
199
|
+
_None_
|
|
200
|
+
|
|
201
|
+
## Low Priority (Impact <50)
|
|
202
|
+
|
|
203
|
+
_None_
|
|
204
|
+
|
|
205
|
+
## Notes
|
|
206
|
+
|
|
207
|
+
This is a manual note that should be preserved.
|
|
208
|
+
Another line of notes.
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
let savedContent = '';
|
|
212
|
+
const tracker = new CandidatesTracker({
|
|
213
|
+
readFile: vi.fn().mockResolvedValue(existingContent),
|
|
214
|
+
writeFile: vi.fn().mockImplementation((p, c) => { savedContent = c; return Promise.resolve(); }),
|
|
215
|
+
mkdir: vi.fn().mockResolvedValue(),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await tracker.add([
|
|
219
|
+
{ file: 'new.js', startLine: 5, description: 'New issue', impact: 70 },
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
expect(savedContent).toContain('## Notes');
|
|
223
|
+
expect(savedContent).toContain('manual note that should be preserved');
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('parsing', () => {
|
|
228
|
+
it('parses existing candidates correctly', async () => {
|
|
229
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
230
|
+
|
|
231
|
+
const content = `# Refactor Candidates
|
|
232
|
+
|
|
233
|
+
## High Priority (Impact 80+)
|
|
234
|
+
|
|
235
|
+
- [ ] src/api.js:10-25 - Extract validation (Impact: 85)
|
|
236
|
+
- [x] src/utils.js:5 - Rename variable (Impact: 82)
|
|
237
|
+
|
|
238
|
+
## Medium Priority (Impact 50-79)
|
|
239
|
+
|
|
240
|
+
- [ ] src/helpers.js:30 - Simplify logic (Impact: 65)
|
|
241
|
+
|
|
242
|
+
## Low Priority (Impact <50)
|
|
243
|
+
|
|
244
|
+
_None_
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
const tracker = new CandidatesTracker({
|
|
248
|
+
readFile: vi.fn().mockResolvedValue(content),
|
|
249
|
+
writeFile: vi.fn().mockResolvedValue(),
|
|
250
|
+
mkdir: vi.fn().mockResolvedValue(),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const data = await tracker.load();
|
|
254
|
+
|
|
255
|
+
expect(data.high).toHaveLength(2);
|
|
256
|
+
expect(data.high[0].file).toBe('src/api.js');
|
|
257
|
+
expect(data.high[0].startLine).toBe(10);
|
|
258
|
+
expect(data.high[0].endLine).toBe(25);
|
|
259
|
+
expect(data.high[0].completed).toBe(false);
|
|
260
|
+
expect(data.high[1].completed).toBe(true);
|
|
261
|
+
expect(data.medium).toHaveLength(1);
|
|
262
|
+
expect(data.low).toHaveLength(0);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('formatting', () => {
|
|
267
|
+
it('formats line ranges correctly', async () => {
|
|
268
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
269
|
+
const tracker = new CandidatesTracker({});
|
|
270
|
+
|
|
271
|
+
const line = tracker.formatCandidate({
|
|
272
|
+
completed: false,
|
|
273
|
+
file: 'test.js',
|
|
274
|
+
startLine: 10,
|
|
275
|
+
endLine: 20,
|
|
276
|
+
description: 'Test',
|
|
277
|
+
impact: 85,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
expect(line).toContain('test.js:10-20');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('formats single line correctly', async () => {
|
|
284
|
+
const { CandidatesTracker } = await import('./candidates-tracker.js');
|
|
285
|
+
const tracker = new CandidatesTracker({});
|
|
286
|
+
|
|
287
|
+
const line = tracker.formatCandidate({
|
|
288
|
+
completed: false,
|
|
289
|
+
file: 'test.js',
|
|
290
|
+
startLine: 10,
|
|
291
|
+
endLine: 10,
|
|
292
|
+
description: 'Test',
|
|
293
|
+
impact: 85,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(line).toContain('test.js:10 -');
|
|
297
|
+
expect(line).not.toContain('10-10');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint Manager
|
|
3
|
+
* Create and manage git-based checkpoints for safe refactoring
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
class CheckpointManager {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.exec = options.exec || this.defaultExec.bind(this);
|
|
13
|
+
this.readFile = options.readFile || this.defaultReadFile.bind(this);
|
|
14
|
+
this.writeFile = options.writeFile || this.defaultWriteFile.bind(this);
|
|
15
|
+
this.stateFile = options.stateFile || '.tlc/checkpoint.json';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default exec implementation
|
|
20
|
+
*/
|
|
21
|
+
async defaultExec(command) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
try {
|
|
24
|
+
const stdout = execSync(command, { encoding: 'utf-8' });
|
|
25
|
+
resolve({ stdout });
|
|
26
|
+
} catch (error) {
|
|
27
|
+
reject(error);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Default read file implementation
|
|
34
|
+
*/
|
|
35
|
+
async defaultReadFile(filePath) {
|
|
36
|
+
return fs.promises.readFile(filePath, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default write file implementation
|
|
41
|
+
*/
|
|
42
|
+
async defaultWriteFile(filePath, content) {
|
|
43
|
+
const dir = path.dirname(filePath);
|
|
44
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
45
|
+
await fs.promises.writeFile(filePath, content, 'utf-8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a new checkpoint
|
|
50
|
+
* @param {Object} options - Creation options
|
|
51
|
+
* @returns {Object} Checkpoint info
|
|
52
|
+
*/
|
|
53
|
+
async create(options = {}) {
|
|
54
|
+
// Check for existing checkpoint
|
|
55
|
+
if (!options.force) {
|
|
56
|
+
const existing = await this.load();
|
|
57
|
+
if (existing) {
|
|
58
|
+
throw new Error('Checkpoint already exists. Use rollback() first or force: true');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Get current branch
|
|
63
|
+
let originalBranch;
|
|
64
|
+
let wasDetached = false;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const { stdout } = await this.exec('git branch --show-current');
|
|
68
|
+
originalBranch = stdout.trim();
|
|
69
|
+
|
|
70
|
+
if (!originalBranch) {
|
|
71
|
+
// Detached HEAD
|
|
72
|
+
const { stdout: headCommit } = await this.exec('git rev-parse HEAD');
|
|
73
|
+
originalBranch = headCommit.trim();
|
|
74
|
+
wasDetached = true;
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error('Failed to get current branch: ' + error.message);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for uncommitted changes
|
|
81
|
+
const { stdout: status } = await this.exec('git status --porcelain');
|
|
82
|
+
const hasChanges = status.trim().length > 0;
|
|
83
|
+
let hasStash = false;
|
|
84
|
+
let stashRef = null;
|
|
85
|
+
|
|
86
|
+
// Stash changes if any
|
|
87
|
+
if (hasChanges) {
|
|
88
|
+
await this.exec('git stash push -m "TLC checkpoint stash"');
|
|
89
|
+
hasStash = true;
|
|
90
|
+
stashRef = 'stash@{0}';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Create refactor branch
|
|
94
|
+
const timestamp = Date.now();
|
|
95
|
+
let branch = `refactor/${timestamp}`;
|
|
96
|
+
let attempts = 0;
|
|
97
|
+
|
|
98
|
+
while (attempts < 3) {
|
|
99
|
+
try {
|
|
100
|
+
await this.exec(`git checkout -b ${branch}`);
|
|
101
|
+
break;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.message.includes('already exists')) {
|
|
104
|
+
attempts++;
|
|
105
|
+
branch = `refactor/${timestamp}-${attempts}`;
|
|
106
|
+
} else {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get commit hash
|
|
113
|
+
const { stdout: commitHash } = await this.exec('git rev-parse HEAD');
|
|
114
|
+
|
|
115
|
+
const checkpoint = {
|
|
116
|
+
id: `checkpoint-${timestamp}`,
|
|
117
|
+
branch,
|
|
118
|
+
originalBranch,
|
|
119
|
+
wasDetached,
|
|
120
|
+
hasStash,
|
|
121
|
+
stashRef,
|
|
122
|
+
commitHash: commitHash.trim(),
|
|
123
|
+
createdAt: new Date(),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Save checkpoint state
|
|
127
|
+
await this.save(checkpoint);
|
|
128
|
+
|
|
129
|
+
return checkpoint;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Rollback to checkpoint state
|
|
134
|
+
* @param {Object} checkpoint - Checkpoint to rollback to
|
|
135
|
+
*/
|
|
136
|
+
async rollback(checkpoint) {
|
|
137
|
+
// Checkout original branch
|
|
138
|
+
await this.exec(`git checkout ${checkpoint.originalBranch}`);
|
|
139
|
+
|
|
140
|
+
// Delete refactor branch
|
|
141
|
+
try {
|
|
142
|
+
await this.exec(`git branch -D ${checkpoint.branch}`);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Branch might not exist
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Pop stash if we stashed
|
|
148
|
+
if (checkpoint.hasStash) {
|
|
149
|
+
try {
|
|
150
|
+
await this.exec('git stash pop');
|
|
151
|
+
} catch (error) {
|
|
152
|
+
// Stash might be empty or conflict
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Clear checkpoint state
|
|
157
|
+
await this.clear();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Commit current changes
|
|
162
|
+
* @param {string} message - Commit message
|
|
163
|
+
*/
|
|
164
|
+
async commit(message) {
|
|
165
|
+
await this.exec('git add -A');
|
|
166
|
+
await this.exec(`git commit -m "${message.replace(/"/g, '\\"')}"`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Merge refactor branch back to original
|
|
171
|
+
* @param {Object} checkpoint - Checkpoint info
|
|
172
|
+
* @param {Object} options - Merge options
|
|
173
|
+
*/
|
|
174
|
+
async merge(checkpoint, options = {}) {
|
|
175
|
+
// Checkout original branch
|
|
176
|
+
await this.exec(`git checkout ${checkpoint.originalBranch}`);
|
|
177
|
+
|
|
178
|
+
// Merge refactor branch
|
|
179
|
+
await this.exec(`git merge ${checkpoint.branch}`);
|
|
180
|
+
|
|
181
|
+
// Cleanup if requested
|
|
182
|
+
if (options.cleanup) {
|
|
183
|
+
await this.exec(`git branch -d ${checkpoint.branch}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Clear checkpoint state
|
|
187
|
+
await this.clear();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Save checkpoint state to file
|
|
192
|
+
*/
|
|
193
|
+
async save(checkpoint) {
|
|
194
|
+
await this.writeFile(this.stateFile, JSON.stringify(checkpoint, null, 2));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Load existing checkpoint state
|
|
199
|
+
* @returns {Object|null} Checkpoint or null if none exists
|
|
200
|
+
*/
|
|
201
|
+
async load() {
|
|
202
|
+
try {
|
|
203
|
+
const content = await this.readFile(this.stateFile);
|
|
204
|
+
return JSON.parse(content);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Clear checkpoint state
|
|
212
|
+
*/
|
|
213
|
+
async clear() {
|
|
214
|
+
try {
|
|
215
|
+
await fs.promises.unlink(this.stateFile);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
// File might not exist
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get current checkpoint status
|
|
223
|
+
* @returns {Object} Status info
|
|
224
|
+
*/
|
|
225
|
+
async status() {
|
|
226
|
+
const checkpoint = await this.load();
|
|
227
|
+
|
|
228
|
+
if (!checkpoint) {
|
|
229
|
+
return {
|
|
230
|
+
active: false,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Get current branch
|
|
235
|
+
const { stdout: currentBranch } = await this.exec('git branch --show-current');
|
|
236
|
+
|
|
237
|
+
// Check for uncommitted changes
|
|
238
|
+
const { stdout: status } = await this.exec('git status --porcelain');
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
active: true,
|
|
242
|
+
branch: checkpoint.branch,
|
|
243
|
+
originalBranch: checkpoint.originalBranch,
|
|
244
|
+
isOnRefactorBranch: currentBranch.trim() === checkpoint.branch,
|
|
245
|
+
hasUncommittedChanges: status.trim().length > 0,
|
|
246
|
+
createdAt: checkpoint.createdAt,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
module.exports = { CheckpointManager };
|