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,608 @@
1
+ /**
2
+ * Convert Command Tests
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+
7
+ describe('ConvertCommand', () => {
8
+ // Mock dependencies
9
+ const createMockBoundaryDetector = (overrides = {}) => ({
10
+ detect: vi.fn().mockReturnValue({
11
+ services: [
12
+ { name: 'auth', files: ['auth/login.js', 'auth/logout.js'], quality: 85, dependencies: [] },
13
+ { name: 'users', files: ['users/profile.js'], quality: 70, dependencies: ['auth'] },
14
+ { name: 'api', files: ['api/routes.js', 'api/handlers.js'], quality: 60, dependencies: ['auth', 'users'] },
15
+ ],
16
+ shared: ['lib/utils.js', 'lib/config.js'],
17
+ suggestions: [],
18
+ stats: { totalServices: 3, totalShared: 2, averageCoupling: 2 },
19
+ }),
20
+ ...overrides,
21
+ });
22
+
23
+ const createMockConversionPlanner = (overrides = {}) => ({
24
+ planFullConversion: vi.fn().mockReturnValue({
25
+ services: [
26
+ { name: 'auth', files: ['auth/login.js', 'auth/logout.js'], quality: 85, dependencies: [] },
27
+ { name: 'users', files: ['users/profile.js'], quality: 70, dependencies: ['auth'] },
28
+ ],
29
+ shared: ['lib/utils.js'],
30
+ steps: [
31
+ 'Create service directories',
32
+ 'Move auth files to auth-service/',
33
+ 'Update imports',
34
+ ],
35
+ destructiveChanges: [],
36
+ }),
37
+ planServiceExtraction: vi.fn().mockReturnValue({
38
+ service: { name: 'auth', files: ['auth/login.js', 'auth/logout.js'], dependencies: [] },
39
+ shared: ['lib/utils.js'],
40
+ steps: ['Create auth-service directory', 'Move auth files'],
41
+ destructiveChanges: [],
42
+ }),
43
+ ...overrides,
44
+ });
45
+
46
+ const createMockServiceScaffold = (overrides = {}) => ({
47
+ createDirectories: vi.fn().mockResolvedValue({
48
+ created: ['services/auth', 'services/users'],
49
+ skipped: [],
50
+ }),
51
+ ...overrides,
52
+ });
53
+
54
+ describe('microservice generates full plan', () => {
55
+ it('generates conversion plan for all services', async () => {
56
+ const { ConvertCommand } = await import('./convert-command.js');
57
+
58
+ const boundaryDetector = createMockBoundaryDetector();
59
+ const conversionPlanner = createMockConversionPlanner();
60
+ const serviceScaffold = createMockServiceScaffold();
61
+
62
+ const command = new ConvertCommand({
63
+ boundaryDetector,
64
+ conversionPlanner,
65
+ serviceScaffold,
66
+ });
67
+
68
+ const result = await command.run('microservice --dry-run', {
69
+ projectDir: '/test/project',
70
+ graph: { nodes: [], edges: [] },
71
+ });
72
+
73
+ expect(result.success).toBe(true);
74
+ expect(result.plan).toBeDefined();
75
+ expect(boundaryDetector.detect).toHaveBeenCalled();
76
+ expect(conversionPlanner.planFullConversion).toHaveBeenCalled();
77
+ });
78
+
79
+ it('includes services in plan output', async () => {
80
+ const { ConvertCommand } = await import('./convert-command.js');
81
+
82
+ const command = new ConvertCommand({
83
+ boundaryDetector: createMockBoundaryDetector(),
84
+ conversionPlanner: createMockConversionPlanner(),
85
+ serviceScaffold: createMockServiceScaffold(),
86
+ });
87
+
88
+ const result = await command.run('microservice --dry-run', {
89
+ graph: { nodes: [], edges: [] },
90
+ });
91
+
92
+ expect(result.output).toContain('Identified Services');
93
+ expect(result.output).toContain('auth');
94
+ expect(result.output).toContain('users');
95
+ });
96
+ });
97
+
98
+ describe('--service extracts single service', () => {
99
+ it('extracts specific service when --service flag provided', async () => {
100
+ const { ConvertCommand } = await import('./convert-command.js');
101
+
102
+ const conversionPlanner = createMockConversionPlanner();
103
+
104
+ const command = new ConvertCommand({
105
+ boundaryDetector: createMockBoundaryDetector(),
106
+ conversionPlanner,
107
+ serviceScaffold: createMockServiceScaffold(),
108
+ });
109
+
110
+ const result = await command.run('microservice --service auth --dry-run', {
111
+ graph: { nodes: [], edges: [] },
112
+ });
113
+
114
+ expect(result.success).toBe(true);
115
+ expect(conversionPlanner.planServiceExtraction).toHaveBeenCalled();
116
+ expect(conversionPlanner.planFullConversion).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it('returns error for unknown service', async () => {
120
+ const { ConvertCommand } = await import('./convert-command.js');
121
+
122
+ const command = new ConvertCommand({
123
+ boundaryDetector: createMockBoundaryDetector(),
124
+ conversionPlanner: createMockConversionPlanner(),
125
+ serviceScaffold: createMockServiceScaffold(),
126
+ });
127
+
128
+ const result = await command.run('microservice --service unknown', {
129
+ graph: { nodes: [], edges: [] },
130
+ });
131
+
132
+ expect(result.success).toBe(false);
133
+ expect(result.error).toContain('Service not found');
134
+ expect(result.error).toContain('unknown');
135
+ });
136
+
137
+ it('lists available services in error message', async () => {
138
+ const { ConvertCommand } = await import('./convert-command.js');
139
+
140
+ const command = new ConvertCommand({
141
+ boundaryDetector: createMockBoundaryDetector(),
142
+ conversionPlanner: createMockConversionPlanner(),
143
+ serviceScaffold: createMockServiceScaffold(),
144
+ });
145
+
146
+ const result = await command.run('microservice --service nonexistent', {
147
+ graph: { nodes: [], edges: [] },
148
+ });
149
+
150
+ expect(result.error).toContain('auth');
151
+ expect(result.error).toContain('users');
152
+ expect(result.error).toContain('api');
153
+ });
154
+ });
155
+
156
+ describe('--dry-run does not modify files', () => {
157
+ it('does not call scaffold when dry-run is set', async () => {
158
+ const { ConvertCommand } = await import('./convert-command.js');
159
+
160
+ const serviceScaffold = createMockServiceScaffold();
161
+
162
+ const command = new ConvertCommand({
163
+ boundaryDetector: createMockBoundaryDetector(),
164
+ conversionPlanner: createMockConversionPlanner(),
165
+ serviceScaffold,
166
+ });
167
+
168
+ const result = await command.run('microservice --scaffold --dry-run', {
169
+ graph: { nodes: [], edges: [] },
170
+ });
171
+
172
+ expect(result.success).toBe(true);
173
+ expect(result.dryRun).toBe(true);
174
+ expect(serviceScaffold.createDirectories).not.toHaveBeenCalled();
175
+ });
176
+
177
+ it('shows what would happen in dry-run output', async () => {
178
+ const { ConvertCommand } = await import('./convert-command.js');
179
+
180
+ const command = new ConvertCommand({
181
+ boundaryDetector: createMockBoundaryDetector(),
182
+ conversionPlanner: createMockConversionPlanner(),
183
+ serviceScaffold: createMockServiceScaffold(),
184
+ });
185
+
186
+ const result = await command.run('microservice --dry-run', {
187
+ graph: { nodes: [], edges: [] },
188
+ });
189
+
190
+ expect(result.output).toContain('Dry Run');
191
+ expect(result.output).toContain('No files will be modified');
192
+ });
193
+
194
+ it('includes conversion steps in dry-run output', async () => {
195
+ const { ConvertCommand } = await import('./convert-command.js');
196
+
197
+ const command = new ConvertCommand({
198
+ boundaryDetector: createMockBoundaryDetector(),
199
+ conversionPlanner: createMockConversionPlanner(),
200
+ serviceScaffold: createMockServiceScaffold(),
201
+ });
202
+
203
+ const result = await command.run('microservice --dry-run', {
204
+ graph: { nodes: [], edges: [] },
205
+ });
206
+
207
+ expect(result.output).toContain('Conversion Steps');
208
+ expect(result.output).toContain('Create service directories');
209
+ });
210
+ });
211
+
212
+ describe('--scaffold creates directories', () => {
213
+ it('creates service directories when scaffold flag is set', async () => {
214
+ const { ConvertCommand } = await import('./convert-command.js');
215
+
216
+ const serviceScaffold = createMockServiceScaffold();
217
+
218
+ const command = new ConvertCommand({
219
+ boundaryDetector: createMockBoundaryDetector(),
220
+ conversionPlanner: createMockConversionPlanner(),
221
+ serviceScaffold,
222
+ });
223
+
224
+ const result = await command.run('microservice --scaffold', {
225
+ projectDir: '/test/project',
226
+ graph: { nodes: [], edges: [] },
227
+ });
228
+
229
+ expect(result.success).toBe(true);
230
+ expect(serviceScaffold.createDirectories).toHaveBeenCalled();
231
+ expect(result.scaffolded).toContain('services/auth');
232
+ expect(result.scaffolded).toContain('services/users');
233
+ });
234
+
235
+ it('includes created directories in output', async () => {
236
+ const { ConvertCommand } = await import('./convert-command.js');
237
+
238
+ const command = new ConvertCommand({
239
+ boundaryDetector: createMockBoundaryDetector(),
240
+ conversionPlanner: createMockConversionPlanner(),
241
+ serviceScaffold: createMockServiceScaffold(),
242
+ });
243
+
244
+ const result = await command.run('microservice --scaffold', {
245
+ projectDir: '/test/project',
246
+ graph: { nodes: [], edges: [] },
247
+ });
248
+
249
+ expect(result.output).toContain('Created Directories');
250
+ expect(result.output).toContain('services/auth');
251
+ });
252
+ });
253
+
254
+ describe('confirms before destructive changes', () => {
255
+ it('asks for confirmation before destructive changes', async () => {
256
+ const { ConvertCommand } = await import('./convert-command.js');
257
+
258
+ const conversionPlanner = createMockConversionPlanner({
259
+ planFullConversion: vi.fn().mockReturnValue({
260
+ services: [{ name: 'auth', files: [], dependencies: [] }],
261
+ shared: [],
262
+ steps: [],
263
+ destructiveChanges: [
264
+ { type: 'move', file: 'src/auth.js' },
265
+ { type: 'modify', file: 'src/index.js' },
266
+ ],
267
+ }),
268
+ });
269
+
270
+ const onConfirm = vi.fn().mockResolvedValue(true);
271
+
272
+ const command = new ConvertCommand({
273
+ boundaryDetector: createMockBoundaryDetector(),
274
+ conversionPlanner,
275
+ serviceScaffold: createMockServiceScaffold(),
276
+ onConfirm,
277
+ });
278
+
279
+ await command.run('microservice', {
280
+ graph: { nodes: [], edges: [] },
281
+ });
282
+
283
+ expect(onConfirm).toHaveBeenCalled();
284
+ expect(onConfirm).toHaveBeenCalledWith(
285
+ expect.objectContaining({
286
+ type: 'destructive',
287
+ changes: expect.arrayContaining([
288
+ expect.objectContaining({ type: 'move' }),
289
+ ]),
290
+ })
291
+ );
292
+ });
293
+
294
+ it('cancels when user declines confirmation', async () => {
295
+ const { ConvertCommand } = await import('./convert-command.js');
296
+
297
+ const conversionPlanner = createMockConversionPlanner({
298
+ planFullConversion: vi.fn().mockReturnValue({
299
+ services: [],
300
+ shared: [],
301
+ steps: [],
302
+ destructiveChanges: [{ type: 'move', file: 'src/auth.js' }],
303
+ }),
304
+ });
305
+
306
+ const onConfirm = vi.fn().mockResolvedValue(false);
307
+
308
+ const command = new ConvertCommand({
309
+ boundaryDetector: createMockBoundaryDetector(),
310
+ conversionPlanner,
311
+ serviceScaffold: createMockServiceScaffold(),
312
+ onConfirm,
313
+ });
314
+
315
+ const result = await command.run('microservice', {
316
+ graph: { nodes: [], edges: [] },
317
+ });
318
+
319
+ expect(result.cancelled).toBe(true);
320
+ expect(result.output).toContain('cancelled');
321
+ });
322
+
323
+ it('skips confirmation with --force flag', async () => {
324
+ const { ConvertCommand } = await import('./convert-command.js');
325
+
326
+ const conversionPlanner = createMockConversionPlanner({
327
+ planFullConversion: vi.fn().mockReturnValue({
328
+ services: [],
329
+ shared: [],
330
+ steps: [],
331
+ destructiveChanges: [{ type: 'move', file: 'src/auth.js' }],
332
+ }),
333
+ });
334
+
335
+ const onConfirm = vi.fn();
336
+
337
+ const command = new ConvertCommand({
338
+ boundaryDetector: createMockBoundaryDetector(),
339
+ conversionPlanner,
340
+ serviceScaffold: createMockServiceScaffold(),
341
+ onConfirm,
342
+ });
343
+
344
+ const result = await command.run('microservice --force', {
345
+ graph: { nodes: [], edges: [] },
346
+ });
347
+
348
+ expect(onConfirm).not.toHaveBeenCalled();
349
+ expect(result.success).toBe(true);
350
+ });
351
+
352
+ it('does not ask confirmation when no destructive changes', async () => {
353
+ const { ConvertCommand } = await import('./convert-command.js');
354
+
355
+ const onConfirm = vi.fn();
356
+
357
+ const command = new ConvertCommand({
358
+ boundaryDetector: createMockBoundaryDetector(),
359
+ conversionPlanner: createMockConversionPlanner(), // No destructive changes
360
+ serviceScaffold: createMockServiceScaffold(),
361
+ onConfirm,
362
+ });
363
+
364
+ const result = await command.run('microservice', {
365
+ graph: { nodes: [], edges: [] },
366
+ });
367
+
368
+ expect(onConfirm).not.toHaveBeenCalled();
369
+ expect(result.success).toBe(true);
370
+ });
371
+ });
372
+
373
+ describe('parseArgs', () => {
374
+ it('parses microservice action', async () => {
375
+ const { ConvertCommand } = await import('./convert-command.js');
376
+ const command = new ConvertCommand({});
377
+
378
+ const options = command.parseArgs('microservice');
379
+
380
+ expect(options.action).toBe('microservice');
381
+ });
382
+
383
+ it('parses --service flag', async () => {
384
+ const { ConvertCommand } = await import('./convert-command.js');
385
+ const command = new ConvertCommand({});
386
+
387
+ const options = command.parseArgs('microservice --service auth');
388
+
389
+ expect(options.service).toBe('auth');
390
+ });
391
+
392
+ it('parses --dry-run flag', async () => {
393
+ const { ConvertCommand } = await import('./convert-command.js');
394
+ const command = new ConvertCommand({});
395
+
396
+ const options = command.parseArgs('microservice --dry-run');
397
+
398
+ expect(options.dryRun).toBe(true);
399
+ });
400
+
401
+ it('parses --scaffold flag', async () => {
402
+ const { ConvertCommand } = await import('./convert-command.js');
403
+ const command = new ConvertCommand({});
404
+
405
+ const options = command.parseArgs('microservice --scaffold');
406
+
407
+ expect(options.scaffold).toBe(true);
408
+ });
409
+
410
+ it('parses --force flag', async () => {
411
+ const { ConvertCommand } = await import('./convert-command.js');
412
+ const command = new ConvertCommand({});
413
+
414
+ const options = command.parseArgs('microservice --force');
415
+
416
+ expect(options.force).toBe(true);
417
+ });
418
+
419
+ it('parses --output flag', async () => {
420
+ const { ConvertCommand } = await import('./convert-command.js');
421
+ const command = new ConvertCommand({});
422
+
423
+ const options = command.parseArgs('microservice --output /tmp/output');
424
+
425
+ expect(options.output).toBe('/tmp/output');
426
+ });
427
+
428
+ it('parses multiple flags together', async () => {
429
+ const { ConvertCommand } = await import('./convert-command.js');
430
+ const command = new ConvertCommand({});
431
+
432
+ const options = command.parseArgs('microservice --service auth --dry-run --scaffold');
433
+
434
+ expect(options.action).toBe('microservice');
435
+ expect(options.service).toBe('auth');
436
+ expect(options.dryRun).toBe(true);
437
+ expect(options.scaffold).toBe(true);
438
+ });
439
+ });
440
+
441
+ describe('createConvertCommand', () => {
442
+ it('creates command handler with execute method', async () => {
443
+ const { createConvertCommand } = await import('./convert-command.js');
444
+
445
+ const handler = createConvertCommand({
446
+ boundaryDetector: createMockBoundaryDetector(),
447
+ conversionPlanner: createMockConversionPlanner(),
448
+ serviceScaffold: createMockServiceScaffold(),
449
+ });
450
+
451
+ expect(handler.execute).toBeDefined();
452
+ expect(typeof handler.execute).toBe('function');
453
+ });
454
+
455
+ it('creates command handler with parseArgs method', async () => {
456
+ const { createConvertCommand } = await import('./convert-command.js');
457
+
458
+ const handler = createConvertCommand({
459
+ boundaryDetector: createMockBoundaryDetector(),
460
+ conversionPlanner: createMockConversionPlanner(),
461
+ serviceScaffold: createMockServiceScaffold(),
462
+ });
463
+
464
+ expect(handler.parseArgs).toBeDefined();
465
+ const options = handler.parseArgs('microservice --dry-run');
466
+ expect(options.dryRun).toBe(true);
467
+ });
468
+
469
+ it('creates command handler with formatHelp method', async () => {
470
+ const { createConvertCommand } = await import('./convert-command.js');
471
+
472
+ const handler = createConvertCommand({
473
+ boundaryDetector: createMockBoundaryDetector(),
474
+ conversionPlanner: createMockConversionPlanner(),
475
+ serviceScaffold: createMockServiceScaffold(),
476
+ });
477
+
478
+ expect(handler.formatHelp).toBeDefined();
479
+ const help = handler.formatHelp();
480
+ expect(help).toContain('/tlc:convert');
481
+ expect(help).toContain('microservice');
482
+ });
483
+
484
+ it('executes command via handler', async () => {
485
+ const { createConvertCommand } = await import('./convert-command.js');
486
+
487
+ const handler = createConvertCommand({
488
+ boundaryDetector: createMockBoundaryDetector(),
489
+ conversionPlanner: createMockConversionPlanner(),
490
+ serviceScaffold: createMockServiceScaffold(),
491
+ });
492
+
493
+ const result = await handler.execute('microservice --dry-run', {
494
+ graph: { nodes: [], edges: [] },
495
+ });
496
+
497
+ expect(result.success).toBe(true);
498
+ });
499
+ });
500
+
501
+ describe('error handling', () => {
502
+ it('returns error when no action specified', async () => {
503
+ const { ConvertCommand } = await import('./convert-command.js');
504
+
505
+ const command = new ConvertCommand({
506
+ boundaryDetector: createMockBoundaryDetector(),
507
+ conversionPlanner: createMockConversionPlanner(),
508
+ serviceScaffold: createMockServiceScaffold(),
509
+ });
510
+
511
+ const result = await command.run('', {});
512
+
513
+ expect(result.success).toBe(false);
514
+ expect(result.error).toContain('No action specified');
515
+ expect(result.output).toContain('Usage');
516
+ });
517
+
518
+ it('returns error for unknown action', async () => {
519
+ const { ConvertCommand } = await import('./convert-command.js');
520
+
521
+ const command = new ConvertCommand({
522
+ boundaryDetector: createMockBoundaryDetector(),
523
+ conversionPlanner: createMockConversionPlanner(),
524
+ serviceScaffold: createMockServiceScaffold(),
525
+ });
526
+
527
+ const result = await command.run('unknown-action', {});
528
+
529
+ expect(result.success).toBe(false);
530
+ expect(result.error).toContain('Unknown action');
531
+ });
532
+
533
+ it('handles boundary detector errors', async () => {
534
+ const { ConvertCommand } = await import('./convert-command.js');
535
+
536
+ const boundaryDetector = {
537
+ detect: vi.fn().mockImplementation(() => {
538
+ throw new Error('Detection failed');
539
+ }),
540
+ };
541
+
542
+ const command = new ConvertCommand({
543
+ boundaryDetector,
544
+ conversionPlanner: createMockConversionPlanner(),
545
+ serviceScaffold: createMockServiceScaffold(),
546
+ });
547
+
548
+ const result = await command.run('microservice', {
549
+ graph: { nodes: [], edges: [] },
550
+ });
551
+
552
+ expect(result.success).toBe(false);
553
+ expect(result.error).toContain('Detection failed');
554
+ });
555
+ });
556
+
557
+ describe('progress reporting', () => {
558
+ it('reports progress during execution', async () => {
559
+ const { ConvertCommand } = await import('./convert-command.js');
560
+
561
+ const progressUpdates = [];
562
+
563
+ const command = new ConvertCommand({
564
+ boundaryDetector: createMockBoundaryDetector(),
565
+ conversionPlanner: createMockConversionPlanner(),
566
+ serviceScaffold: createMockServiceScaffold(),
567
+ onProgress: (update) => progressUpdates.push(update),
568
+ });
569
+
570
+ await command.run('microservice --scaffold', {
571
+ graph: { nodes: [], edges: [] },
572
+ });
573
+
574
+ expect(progressUpdates.length).toBeGreaterThan(0);
575
+ expect(progressUpdates.some(p => p.phase === 'analyzing')).toBe(true);
576
+ expect(progressUpdates.some(p => p.phase === 'planning')).toBe(true);
577
+ expect(progressUpdates.some(p => p.phase === 'scaffolding')).toBe(true);
578
+ });
579
+ });
580
+
581
+ describe('shared kernel handling', () => {
582
+ it('includes shared files in dry-run output', async () => {
583
+ const { ConvertCommand } = await import('./convert-command.js');
584
+
585
+ const conversionPlanner = createMockConversionPlanner({
586
+ planFullConversion: vi.fn().mockReturnValue({
587
+ services: [{ name: 'auth', files: [], dependencies: [] }],
588
+ shared: ['lib/utils.js', 'lib/config.js', 'lib/logger.js'],
589
+ steps: [],
590
+ destructiveChanges: [],
591
+ }),
592
+ });
593
+
594
+ const command = new ConvertCommand({
595
+ boundaryDetector: createMockBoundaryDetector(),
596
+ conversionPlanner,
597
+ serviceScaffold: createMockServiceScaffold(),
598
+ });
599
+
600
+ const result = await command.run('microservice --dry-run', {
601
+ graph: { nodes: [], edges: [] },
602
+ });
603
+
604
+ expect(result.output).toContain('Shared Kernel');
605
+ expect(result.output).toContain('lib/utils.js');
606
+ });
607
+ });
608
+ });