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,474 @@
1
+ /**
2
+ * Checkpoint Manager Tests
3
+ * Task 5: Create and manage git-based checkpoints for safe refactoring
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+
8
+ describe('CheckpointManager', () => {
9
+ let mockExec;
10
+ let mockFs;
11
+
12
+ beforeEach(() => {
13
+ vi.resetAllMocks();
14
+ });
15
+
16
+ describe('createCheckpoint', () => {
17
+ it('creates stash with uncommitted changes', async () => {
18
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
19
+
20
+ const execMock = vi.fn()
21
+ .mockResolvedValueOnce({ stdout: 'main\n' }) // git branch --show-current
22
+ .mockResolvedValueOnce({ stdout: ' M file.js\n' }) // git status
23
+ .mockResolvedValueOnce({ stdout: '' }) // git stash
24
+ .mockResolvedValueOnce({ stdout: '' }) // git checkout -b
25
+ .mockResolvedValueOnce({ stdout: 'abc123' }); // git rev-parse HEAD
26
+
27
+ const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
28
+ const writeFileMock = vi.fn().mockResolvedValue();
29
+
30
+ const manager = new CheckpointManager({
31
+ exec: execMock,
32
+ readFile: readFileMock,
33
+ writeFile: writeFileMock,
34
+ });
35
+
36
+ const checkpoint = await manager.create();
37
+
38
+ expect(execMock).toHaveBeenCalledWith('git status --porcelain');
39
+ expect(execMock).toHaveBeenCalledWith(expect.stringContaining('git stash push'));
40
+ expect(checkpoint.hasStash).toBe(true);
41
+ });
42
+
43
+ it('creates branch with naming pattern refactor/{timestamp}', async () => {
44
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
45
+
46
+ const execMock = vi.fn()
47
+ .mockResolvedValueOnce({ stdout: 'main\n' }) // git branch --show-current
48
+ .mockResolvedValueOnce({ stdout: '' }) // git status (clean)
49
+ .mockResolvedValueOnce({ stdout: '' }) // git checkout -b
50
+ .mockResolvedValueOnce({ stdout: 'abc123' }); // git rev-parse HEAD
51
+
52
+ const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
53
+ const writeFileMock = vi.fn().mockResolvedValue();
54
+
55
+ const manager = new CheckpointManager({
56
+ exec: execMock,
57
+ readFile: readFileMock,
58
+ writeFile: writeFileMock,
59
+ });
60
+
61
+ const checkpoint = await manager.create();
62
+
63
+ expect(execMock).toHaveBeenCalledWith(
64
+ expect.stringMatching(/git checkout -b refactor\/\d+/)
65
+ );
66
+ expect(checkpoint.branch).toMatch(/^refactor\/\d+$/);
67
+ });
68
+
69
+ it('stores original branch for rollback', async () => {
70
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
71
+
72
+ const execMock = vi.fn()
73
+ .mockResolvedValueOnce({ stdout: 'feature/my-feature\n' }) // git branch --show-current
74
+ .mockResolvedValueOnce({ stdout: '' }) // git status
75
+ .mockResolvedValueOnce({ stdout: '' }) // git checkout -b
76
+ .mockResolvedValueOnce({ stdout: 'abc123' }); // git rev-parse HEAD
77
+
78
+ const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
79
+ const writeFileMock = vi.fn().mockResolvedValue();
80
+
81
+ const manager = new CheckpointManager({
82
+ exec: execMock,
83
+ readFile: readFileMock,
84
+ writeFile: writeFileMock,
85
+ });
86
+
87
+ const checkpoint = await manager.create();
88
+
89
+ expect(checkpoint.originalBranch).toBe('feature/my-feature');
90
+ });
91
+
92
+ it('handles already clean working directory', async () => {
93
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
94
+
95
+ const execMock = vi.fn()
96
+ .mockResolvedValueOnce({ stdout: 'main\n' }) // git branch --show-current
97
+ .mockResolvedValueOnce({ stdout: '' }) // git status (clean)
98
+ .mockResolvedValueOnce({ stdout: '' }) // git checkout -b
99
+ .mockResolvedValueOnce({ stdout: 'abc123' }); // git rev-parse HEAD
100
+
101
+ const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
102
+ const writeFileMock = vi.fn().mockResolvedValue();
103
+
104
+ const manager = new CheckpointManager({
105
+ exec: execMock,
106
+ readFile: readFileMock,
107
+ writeFile: writeFileMock,
108
+ });
109
+
110
+ const checkpoint = await manager.create();
111
+
112
+ expect(checkpoint.hasStash).toBe(false);
113
+ expect(execMock).not.toHaveBeenCalledWith(expect.stringContaining('git stash'));
114
+ });
115
+
116
+ it('handles existing branch with same name', async () => {
117
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
118
+
119
+ const execMock = vi.fn()
120
+ .mockResolvedValueOnce({ stdout: 'main\n' }) // git branch --show-current
121
+ .mockResolvedValueOnce({ stdout: '' }) // git status
122
+ .mockRejectedValueOnce(new Error('branch already exists')) // first checkout -b fails
123
+ .mockResolvedValueOnce({ stdout: '' }) // second checkout -b with suffix
124
+ .mockResolvedValueOnce({ stdout: 'abc123' }); // git rev-parse HEAD
125
+
126
+ const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
127
+ const writeFileMock = vi.fn().mockResolvedValue();
128
+
129
+ const manager = new CheckpointManager({
130
+ exec: execMock,
131
+ readFile: readFileMock,
132
+ writeFile: writeFileMock,
133
+ });
134
+
135
+ const checkpoint = await manager.create();
136
+
137
+ // Should have tried with a different name
138
+ expect(checkpoint.branch).toBeDefined();
139
+ });
140
+
141
+ it('reports checkpoint state accurately', async () => {
142
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
143
+
144
+ const execMock = vi.fn()
145
+ .mockResolvedValueOnce({ stdout: 'main\n' }) // git branch --show-current
146
+ .mockResolvedValueOnce({ stdout: ' M file.js\n' }) // git status
147
+ .mockResolvedValueOnce({ stdout: '' }) // git stash
148
+ .mockResolvedValueOnce({ stdout: '' }) // git checkout -b
149
+ .mockResolvedValueOnce({ stdout: 'abc123' }); // git rev-parse HEAD
150
+
151
+ const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
152
+ const writeFileMock = vi.fn().mockResolvedValue();
153
+
154
+ const manager = new CheckpointManager({
155
+ exec: execMock,
156
+ readFile: readFileMock,
157
+ writeFile: writeFileMock,
158
+ });
159
+
160
+ const checkpoint = await manager.create();
161
+
162
+ expect(checkpoint).toMatchObject({
163
+ id: expect.any(String),
164
+ branch: expect.stringMatching(/^refactor\//),
165
+ originalBranch: 'main',
166
+ hasStash: true,
167
+ commitHash: 'abc123',
168
+ createdAt: expect.any(Date),
169
+ });
170
+ });
171
+ });
172
+
173
+ describe('rollback', () => {
174
+ it('deletes branch and pops stash on rollback', async () => {
175
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
176
+
177
+ const execMock = vi.fn()
178
+ .mockResolvedValue({ stdout: '' });
179
+
180
+ const manager = new CheckpointManager({ exec: execMock });
181
+
182
+ const checkpoint = {
183
+ id: 'test-123',
184
+ branch: 'refactor/12345',
185
+ originalBranch: 'main',
186
+ hasStash: true,
187
+ stashRef: 'stash@{0}',
188
+ };
189
+
190
+ await manager.rollback(checkpoint);
191
+
192
+ expect(execMock).toHaveBeenCalledWith('git checkout main');
193
+ expect(execMock).toHaveBeenCalledWith('git branch -D refactor/12345');
194
+ expect(execMock).toHaveBeenCalledWith('git stash pop');
195
+ });
196
+
197
+ it('skips stash pop if no stash was created', async () => {
198
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
199
+
200
+ const execMock = vi.fn()
201
+ .mockResolvedValue({ stdout: '' });
202
+
203
+ const manager = new CheckpointManager({ exec: execMock });
204
+
205
+ const checkpoint = {
206
+ id: 'test-123',
207
+ branch: 'refactor/12345',
208
+ originalBranch: 'main',
209
+ hasStash: false,
210
+ };
211
+
212
+ await manager.rollback(checkpoint);
213
+
214
+ expect(execMock).toHaveBeenCalledWith('git checkout main');
215
+ expect(execMock).toHaveBeenCalledWith('git branch -D refactor/12345');
216
+ expect(execMock).not.toHaveBeenCalledWith(expect.stringContaining('git stash pop'));
217
+ });
218
+
219
+ it('handles rollback when already on original branch', async () => {
220
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
221
+
222
+ const execMock = vi.fn()
223
+ .mockResolvedValueOnce({ stdout: 'main\n' }) // current branch
224
+ .mockResolvedValue({ stdout: '' });
225
+
226
+ const manager = new CheckpointManager({ exec: execMock });
227
+
228
+ const checkpoint = {
229
+ id: 'test-123',
230
+ branch: 'refactor/12345',
231
+ originalBranch: 'main',
232
+ hasStash: false,
233
+ };
234
+
235
+ await manager.rollback(checkpoint);
236
+
237
+ // Should still delete the refactor branch
238
+ expect(execMock).toHaveBeenCalledWith('git branch -D refactor/12345');
239
+ });
240
+ });
241
+
242
+ describe('commit', () => {
243
+ it('commits checkpoint changes to refactor branch', async () => {
244
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
245
+
246
+ const execMock = vi.fn()
247
+ .mockResolvedValue({ stdout: '' });
248
+
249
+ const manager = new CheckpointManager({ exec: execMock });
250
+
251
+ await manager.commit('Refactored validateEmail function');
252
+
253
+ expect(execMock).toHaveBeenCalledWith('git add -A');
254
+ expect(execMock).toHaveBeenCalledWith(
255
+ expect.stringContaining('git commit -m "Refactored validateEmail function"')
256
+ );
257
+ });
258
+ });
259
+
260
+ describe('merge', () => {
261
+ it('merges refactor branch back to original', async () => {
262
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
263
+
264
+ const execMock = vi.fn()
265
+ .mockResolvedValue({ stdout: '' });
266
+
267
+ const manager = new CheckpointManager({ exec: execMock });
268
+
269
+ const checkpoint = {
270
+ id: 'test-123',
271
+ branch: 'refactor/12345',
272
+ originalBranch: 'main',
273
+ hasStash: false,
274
+ };
275
+
276
+ await manager.merge(checkpoint);
277
+
278
+ expect(execMock).toHaveBeenCalledWith('git checkout main');
279
+ expect(execMock).toHaveBeenCalledWith('git merge refactor/12345');
280
+ });
281
+
282
+ it('cleans up refactor branch after merge', async () => {
283
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
284
+
285
+ const execMock = vi.fn()
286
+ .mockResolvedValue({ stdout: '' });
287
+
288
+ const manager = new CheckpointManager({ exec: execMock });
289
+
290
+ const checkpoint = {
291
+ id: 'test-123',
292
+ branch: 'refactor/12345',
293
+ originalBranch: 'main',
294
+ hasStash: false,
295
+ };
296
+
297
+ await manager.merge(checkpoint, { cleanup: true });
298
+
299
+ expect(execMock).toHaveBeenCalledWith('git branch -d refactor/12345');
300
+ });
301
+ });
302
+
303
+ describe('state tracking', () => {
304
+ it('saves checkpoint state to file', async () => {
305
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
306
+
307
+ const writeFileMock = vi.fn();
308
+ const execMock = vi.fn()
309
+ .mockResolvedValueOnce({ stdout: 'main\n' })
310
+ .mockResolvedValueOnce({ stdout: '' })
311
+ .mockResolvedValueOnce({ stdout: '' })
312
+ .mockResolvedValueOnce({ stdout: 'abc123' });
313
+
314
+ const manager = new CheckpointManager({
315
+ exec: execMock,
316
+ writeFile: writeFileMock,
317
+ stateFile: '.tlc/checkpoint.json',
318
+ });
319
+
320
+ await manager.create();
321
+
322
+ expect(writeFileMock).toHaveBeenCalledWith(
323
+ '.tlc/checkpoint.json',
324
+ expect.any(String)
325
+ );
326
+ });
327
+
328
+ it('loads existing checkpoint state', async () => {
329
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
330
+
331
+ const readFileMock = vi.fn().mockResolvedValue(JSON.stringify({
332
+ id: 'existing-123',
333
+ branch: 'refactor/99999',
334
+ originalBranch: 'develop',
335
+ hasStash: true,
336
+ }));
337
+
338
+ const manager = new CheckpointManager({
339
+ readFile: readFileMock,
340
+ stateFile: '.tlc/checkpoint.json',
341
+ });
342
+
343
+ const checkpoint = await manager.load();
344
+
345
+ expect(checkpoint.id).toBe('existing-123');
346
+ expect(checkpoint.branch).toBe('refactor/99999');
347
+ });
348
+
349
+ it('returns null when no checkpoint exists', async () => {
350
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
351
+
352
+ const readFileMock = vi.fn().mockRejectedValue(new Error('ENOENT'));
353
+
354
+ const manager = new CheckpointManager({
355
+ readFile: readFileMock,
356
+ stateFile: '.tlc/checkpoint.json',
357
+ });
358
+
359
+ const checkpoint = await manager.load();
360
+
361
+ expect(checkpoint).toBeNull();
362
+ });
363
+ });
364
+
365
+ describe('edge cases', () => {
366
+ it('handles conflicts during merge', async () => {
367
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
368
+
369
+ const execMock = vi.fn()
370
+ .mockResolvedValueOnce({ stdout: '' }) // checkout
371
+ .mockRejectedValueOnce(new Error('CONFLICT')); // merge fails
372
+
373
+ const manager = new CheckpointManager({ exec: execMock });
374
+
375
+ const checkpoint = {
376
+ id: 'test-123',
377
+ branch: 'refactor/12345',
378
+ originalBranch: 'main',
379
+ hasStash: false,
380
+ };
381
+
382
+ await expect(manager.merge(checkpoint)).rejects.toThrow('CONFLICT');
383
+ });
384
+
385
+ it('handles detached HEAD state', async () => {
386
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
387
+
388
+ const execMock = vi.fn()
389
+ .mockResolvedValueOnce({ stdout: '' }) // git branch --show-current returns empty
390
+ .mockResolvedValueOnce({ stdout: 'abc123' }) // git rev-parse HEAD
391
+ .mockResolvedValueOnce({ stdout: '' }) // git status
392
+ .mockResolvedValueOnce({ stdout: '' }) // git checkout -b
393
+ .mockResolvedValueOnce({ stdout: 'def456' }); // git rev-parse HEAD
394
+
395
+ const manager = new CheckpointManager({ exec: execMock });
396
+
397
+ const checkpoint = await manager.create();
398
+
399
+ // Should store commit hash as "original" for detached HEAD
400
+ expect(checkpoint.originalBranch).toBe('abc123');
401
+ expect(checkpoint.wasDetached).toBe(true);
402
+ });
403
+
404
+ it('prevents creating checkpoint when one already exists', async () => {
405
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
406
+
407
+ const readFileMock = vi.fn().mockResolvedValue(JSON.stringify({
408
+ id: 'existing-123',
409
+ branch: 'refactor/99999',
410
+ }));
411
+
412
+ const manager = new CheckpointManager({
413
+ readFile: readFileMock,
414
+ stateFile: '.tlc/checkpoint.json',
415
+ });
416
+
417
+ await expect(manager.create()).rejects.toThrow('Checkpoint already exists');
418
+ });
419
+
420
+ it('allows force create to override existing checkpoint', async () => {
421
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
422
+
423
+ const readFileMock = vi.fn().mockResolvedValue(JSON.stringify({
424
+ id: 'existing-123',
425
+ branch: 'refactor/99999',
426
+ }));
427
+
428
+ const execMock = vi.fn()
429
+ .mockResolvedValue({ stdout: '' });
430
+
431
+ const writeFileMock = vi.fn();
432
+
433
+ const manager = new CheckpointManager({
434
+ readFile: readFileMock,
435
+ writeFile: writeFileMock,
436
+ exec: execMock,
437
+ stateFile: '.tlc/checkpoint.json',
438
+ });
439
+
440
+ const checkpoint = await manager.create({ force: true });
441
+
442
+ expect(checkpoint).toBeDefined();
443
+ });
444
+ });
445
+
446
+ describe('status', () => {
447
+ it('returns current checkpoint status', async () => {
448
+ const { CheckpointManager } = await import('./checkpoint-manager.js');
449
+
450
+ const execMock = vi.fn()
451
+ .mockResolvedValueOnce({ stdout: 'refactor/12345\n' }) // current branch
452
+ .mockResolvedValueOnce({ stdout: ' M file.js\n' }); // status
453
+
454
+ const readFileMock = vi.fn().mockResolvedValue(JSON.stringify({
455
+ id: 'test-123',
456
+ branch: 'refactor/12345',
457
+ originalBranch: 'main',
458
+ hasStash: true,
459
+ createdAt: new Date().toISOString(),
460
+ }));
461
+
462
+ const manager = new CheckpointManager({
463
+ exec: execMock,
464
+ readFile: readFileMock,
465
+ });
466
+
467
+ const status = await manager.status();
468
+
469
+ expect(status.active).toBe(true);
470
+ expect(status.branch).toBe('refactor/12345');
471
+ expect(status.hasUncommittedChanges).toBe(true);
472
+ });
473
+ });
474
+ });