tlc-claude-code 1.2.28 → 1.3.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 (87) hide show
  1. package/README.md +9 -4
  2. package/dashboard/dist/components/UsagePane.d.ts +13 -0
  3. package/dashboard/dist/components/UsagePane.js +51 -0
  4. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  5. package/dashboard/dist/components/UsagePane.test.js +142 -0
  6. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  7. package/dashboard/dist/components/WorkspaceDocsPane.js +146 -0
  8. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  9. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  10. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  11. package/dashboard/dist/components/WorkspacePane.js +17 -0
  12. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  13. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  14. package/package.json +15 -4
  15. package/scripts/capture-screenshots.js +170 -0
  16. package/scripts/docs-update.js +253 -0
  17. package/scripts/generate-screenshots.js +321 -0
  18. package/scripts/project-docs.js +377 -0
  19. package/scripts/vps-setup.sh +477 -0
  20. package/server/lib/architecture-command.js +450 -0
  21. package/server/lib/architecture-command.test.js +754 -0
  22. package/server/lib/ast-analyzer.js +324 -0
  23. package/server/lib/ast-analyzer.test.js +437 -0
  24. package/server/lib/auth-system.test.js +4 -1
  25. package/server/lib/boundary-detector.js +427 -0
  26. package/server/lib/boundary-detector.test.js +320 -0
  27. package/server/lib/budget-alerts.js +138 -0
  28. package/server/lib/budget-alerts.test.js +235 -0
  29. package/server/lib/candidates-tracker.js +210 -0
  30. package/server/lib/candidates-tracker.test.js +300 -0
  31. package/server/lib/checkpoint-manager.js +251 -0
  32. package/server/lib/checkpoint-manager.test.js +474 -0
  33. package/server/lib/circular-detector.js +337 -0
  34. package/server/lib/circular-detector.test.js +353 -0
  35. package/server/lib/cohesion-analyzer.js +310 -0
  36. package/server/lib/cohesion-analyzer.test.js +447 -0
  37. package/server/lib/contract-testing.js +625 -0
  38. package/server/lib/contract-testing.test.js +342 -0
  39. package/server/lib/conversion-planner.js +469 -0
  40. package/server/lib/conversion-planner.test.js +361 -0
  41. package/server/lib/convert-command.js +351 -0
  42. package/server/lib/convert-command.test.js +608 -0
  43. package/server/lib/coupling-calculator.js +189 -0
  44. package/server/lib/coupling-calculator.test.js +509 -0
  45. package/server/lib/dependency-graph.js +367 -0
  46. package/server/lib/dependency-graph.test.js +516 -0
  47. package/server/lib/duplication-detector.js +349 -0
  48. package/server/lib/duplication-detector.test.js +401 -0
  49. package/server/lib/example-service.js +616 -0
  50. package/server/lib/example-service.test.js +397 -0
  51. package/server/lib/impact-scorer.js +184 -0
  52. package/server/lib/impact-scorer.test.js +211 -0
  53. package/server/lib/mermaid-generator.js +358 -0
  54. package/server/lib/mermaid-generator.test.js +301 -0
  55. package/server/lib/messaging-patterns.js +750 -0
  56. package/server/lib/messaging-patterns.test.js +213 -0
  57. package/server/lib/microservice-template.js +386 -0
  58. package/server/lib/microservice-template.test.js +325 -0
  59. package/server/lib/new-project-microservice.js +450 -0
  60. package/server/lib/new-project-microservice.test.js +600 -0
  61. package/server/lib/refactor-command.js +326 -0
  62. package/server/lib/refactor-command.test.js +528 -0
  63. package/server/lib/refactor-executor.js +254 -0
  64. package/server/lib/refactor-executor.test.js +305 -0
  65. package/server/lib/refactor-observer.js +292 -0
  66. package/server/lib/refactor-observer.test.js +422 -0
  67. package/server/lib/refactor-progress.js +193 -0
  68. package/server/lib/refactor-progress.test.js +251 -0
  69. package/server/lib/refactor-reporter.js +237 -0
  70. package/server/lib/refactor-reporter.test.js +247 -0
  71. package/server/lib/semantic-analyzer.js +198 -0
  72. package/server/lib/semantic-analyzer.test.js +474 -0
  73. package/server/lib/service-scaffold.js +486 -0
  74. package/server/lib/service-scaffold.test.js +373 -0
  75. package/server/lib/shared-kernel.js +578 -0
  76. package/server/lib/shared-kernel.test.js +255 -0
  77. package/server/lib/traefik-config.js +282 -0
  78. package/server/lib/traefik-config.test.js +312 -0
  79. package/server/lib/usage-command.js +218 -0
  80. package/server/lib/usage-command.test.js +391 -0
  81. package/server/lib/usage-formatter.js +192 -0
  82. package/server/lib/usage-formatter.test.js +267 -0
  83. package/server/lib/usage-history.js +122 -0
  84. package/server/lib/usage-history.test.js +206 -0
  85. package/server/package-lock.json +14 -0
  86. package/server/package.json +1 -0
  87. package/templates/docs-sync.yml +91 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Service Scaffold Generator Tests
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+
7
+ describe('ServiceScaffold', () => {
8
+ describe('generate', () => {
9
+ it('creates service directory structure', async () => {
10
+ const { ServiceScaffold } = await import('./service-scaffold.js');
11
+ const scaffold = new ServiceScaffold();
12
+
13
+ const service = {
14
+ name: 'user-service',
15
+ description: 'User management service',
16
+ };
17
+
18
+ const result = scaffold.generate(service);
19
+
20
+ expect(result.directory).toBe('user-service');
21
+ expect(result.files.length).toBeGreaterThan(0);
22
+ });
23
+
24
+ it('generates all required files', async () => {
25
+ const { ServiceScaffold } = await import('./service-scaffold.js');
26
+ const scaffold = new ServiceScaffold();
27
+
28
+ const service = {
29
+ name: 'auth-service',
30
+ endpoints: [{ method: 'POST', path: '/login' }],
31
+ };
32
+
33
+ const result = scaffold.generate(service);
34
+ const paths = result.files.map(f => f.path);
35
+
36
+ expect(paths).toContain('auth-service/package.json');
37
+ expect(paths).toContain('auth-service/Dockerfile');
38
+ expect(paths).toContain('auth-service/docker-compose.yml');
39
+ expect(paths).toContain('auth-service/client/index.js');
40
+ expect(paths).toContain('auth-service/src/index.js');
41
+ expect(paths).toContain('auth-service/src/health.js');
42
+ expect(paths).toContain('auth-service/README.md');
43
+ });
44
+ });
45
+
46
+ describe('generatePackageJson', () => {
47
+ it('has correct deps', async () => {
48
+ const { ServiceScaffold } = await import('./service-scaffold.js');
49
+ const scaffold = new ServiceScaffold();
50
+
51
+ const service = {
52
+ name: 'my-service',
53
+ endpoints: [{ method: 'GET', path: '/users' }],
54
+ };
55
+
56
+ const result = scaffold.generate(service);
57
+ const pkgFile = result.files.find(f => f.path.endsWith('package.json'));
58
+ const pkg = JSON.parse(pkgFile.content);
59
+
60
+ expect(pkg.name).toBe('my-service');
61
+ expect(pkg.dependencies.express).toBeDefined();
62
+ expect(pkg.devDependencies.vitest).toBeDefined();
63
+ });
64
+
65
+ it('adds database dep for models', async () => {
66
+ const { ServiceScaffold } = await import('./service-scaffold.js');
67
+ const scaffold = new ServiceScaffold();
68
+
69
+ const service = {
70
+ name: 'data-service',
71
+ models: ['User', 'Product'],
72
+ };
73
+
74
+ const result = scaffold.generate(service);
75
+ const pkgFile = result.files.find(f => f.path.endsWith('package.json'));
76
+ const pkg = JSON.parse(pkgFile.content);
77
+
78
+ expect(pkg.dependencies.pg).toBeDefined();
79
+ });
80
+
81
+ it('includes service scripts', async () => {
82
+ const { ServiceScaffold } = await import('./service-scaffold.js');
83
+ const scaffold = new ServiceScaffold();
84
+
85
+ const service = { name: 'test-service' };
86
+
87
+ const result = scaffold.generate(service);
88
+ const pkgFile = result.files.find(f => f.path.endsWith('package.json'));
89
+ const pkg = JSON.parse(pkgFile.content);
90
+
91
+ expect(pkg.scripts.start).toBeDefined();
92
+ expect(pkg.scripts.dev).toBeDefined();
93
+ expect(pkg.scripts.test).toBeDefined();
94
+ });
95
+ });
96
+
97
+ describe('generateDockerfile', () => {
98
+ it('follows best practices', async () => {
99
+ const { ServiceScaffold } = await import('./service-scaffold.js');
100
+ const scaffold = new ServiceScaffold();
101
+
102
+ const service = {
103
+ name: 'docker-service',
104
+ port: 8080,
105
+ };
106
+
107
+ const result = scaffold.generate(service);
108
+ const dockerFile = result.files.find(f => f.path.endsWith('Dockerfile'));
109
+ const content = dockerFile.content;
110
+
111
+ expect(content).toContain('FROM node:');
112
+ expect(content).toContain('WORKDIR /app');
113
+ expect(content).toContain('COPY package');
114
+ expect(content).toContain('npm ci');
115
+ expect(content).toContain('EXPOSE 8080');
116
+ expect(content).toContain('HEALTHCHECK');
117
+ });
118
+
119
+ it('uses alpine image', async () => {
120
+ const { ServiceScaffold } = await import('./service-scaffold.js');
121
+ const scaffold = new ServiceScaffold();
122
+
123
+ const service = { name: 'alpine-service' };
124
+
125
+ const result = scaffold.generate(service);
126
+ const dockerFile = result.files.find(f => f.path.endsWith('Dockerfile'));
127
+
128
+ expect(dockerFile.content).toContain('-alpine');
129
+ });
130
+ });
131
+
132
+ describe('generateDockerCompose', () => {
133
+ it('has correct config', async () => {
134
+ const { ServiceScaffold } = await import('./service-scaffold.js');
135
+ const scaffold = new ServiceScaffold();
136
+
137
+ const service = {
138
+ name: 'compose-service',
139
+ port: 4000,
140
+ };
141
+
142
+ const result = scaffold.generate(service);
143
+ const composeFile = result.files.find(f => f.path.endsWith('docker-compose.yml'));
144
+ const content = composeFile.content;
145
+
146
+ expect(content).toContain('version:');
147
+ expect(content).toContain('services:');
148
+ expect(content).toContain('compose-service');
149
+ expect(content).toContain('4000:4000');
150
+ });
151
+
152
+ it('adds database when models exist', async () => {
153
+ const { ServiceScaffold } = await import('./service-scaffold.js');
154
+ const scaffold = new ServiceScaffold();
155
+
156
+ const service = {
157
+ name: 'db-service',
158
+ models: ['Order'],
159
+ };
160
+
161
+ const result = scaffold.generate(service);
162
+ const composeFile = result.files.find(f => f.path.endsWith('docker-compose.yml'));
163
+ const content = composeFile.content;
164
+
165
+ expect(content).toContain('postgres');
166
+ expect(content).toContain('DATABASE_URL');
167
+ });
168
+ });
169
+
170
+ describe('generateApiClient', () => {
171
+ it('matches contract', async () => {
172
+ const { ServiceScaffold } = await import('./service-scaffold.js');
173
+ const scaffold = new ServiceScaffold();
174
+
175
+ const service = {
176
+ name: 'api-service',
177
+ endpoints: [
178
+ { method: 'GET', path: '/items', name: 'getItems' },
179
+ { method: 'POST', path: '/items', name: 'createItem' },
180
+ ],
181
+ };
182
+
183
+ const result = scaffold.generate(service);
184
+ const clientFile = result.files.find(f => f.path.includes('client/index.js'));
185
+ const content = clientFile.content;
186
+
187
+ expect(content).toContain('ApiServiceClient');
188
+ expect(content).toContain('getItems');
189
+ expect(content).toContain('createItem');
190
+ });
191
+
192
+ it('generates method for each endpoint', async () => {
193
+ const { ServiceScaffold } = await import('./service-scaffold.js');
194
+ const scaffold = new ServiceScaffold();
195
+
196
+ const service = {
197
+ name: 'user-api',
198
+ endpoints: [
199
+ { method: 'GET', path: '/users' },
200
+ { method: 'GET', path: '/users/:id' },
201
+ { method: 'POST', path: '/users' },
202
+ { method: 'PUT', path: '/users/:id' },
203
+ { method: 'DELETE', path: '/users/:id' },
204
+ ],
205
+ };
206
+
207
+ const result = scaffold.generate(service);
208
+ const clientFile = result.files.find(f => f.path.includes('client/index.js'));
209
+ const content = clientFile.content;
210
+
211
+ expect(content).toContain("request('GET'");
212
+ expect(content).toContain("request('POST'");
213
+ expect(content).toContain("request('PUT'");
214
+ expect(content).toContain("request('DELETE'");
215
+ });
216
+ });
217
+
218
+ describe('generateEntryPoint', () => {
219
+ it('uses express for services with endpoints', async () => {
220
+ const { ServiceScaffold } = await import('./service-scaffold.js');
221
+ const scaffold = new ServiceScaffold();
222
+
223
+ const service = {
224
+ name: 'express-service',
225
+ port: 3000,
226
+ endpoints: [{ method: 'GET', path: '/api' }],
227
+ };
228
+
229
+ const result = scaffold.generate(service);
230
+ const indexFile = result.files.find(f => f.path.endsWith('src/index.js'));
231
+ const content = indexFile.content;
232
+
233
+ expect(content).toContain("require('express')");
234
+ expect(content).toContain('app.listen');
235
+ expect(content).toContain('/health');
236
+ });
237
+
238
+ it('is simple for services without endpoints', async () => {
239
+ const { ServiceScaffold } = await import('./service-scaffold.js');
240
+ const scaffold = new ServiceScaffold();
241
+
242
+ const service = {
243
+ name: 'worker-service',
244
+ };
245
+
246
+ const result = scaffold.generate(service);
247
+ const indexFile = result.files.find(f => f.path.endsWith('src/index.js'));
248
+ const content = indexFile.content;
249
+
250
+ expect(content).not.toContain("require('express')");
251
+ expect(content).toContain('start()');
252
+ });
253
+ });
254
+
255
+ describe('generateHealthCheck', () => {
256
+ it('returns healthy status', async () => {
257
+ const { ServiceScaffold } = await import('./service-scaffold.js');
258
+ const scaffold = new ServiceScaffold();
259
+
260
+ const service = { name: 'health-service' };
261
+
262
+ const result = scaffold.generate(service);
263
+ const healthFile = result.files.find(f => f.path.endsWith('src/health.js'));
264
+ const content = healthFile.content;
265
+
266
+ expect(content).toContain("status: 'healthy'");
267
+ expect(content).toContain('health-service');
268
+ });
269
+ });
270
+
271
+ describe('generateConfig', () => {
272
+ it('includes port and env', async () => {
273
+ const { ServiceScaffold } = await import('./service-scaffold.js');
274
+ const scaffold = new ServiceScaffold();
275
+
276
+ const service = {
277
+ name: 'config-service',
278
+ port: 5000,
279
+ };
280
+
281
+ const result = scaffold.generate(service);
282
+ const configFile = result.files.find(f => f.path.endsWith('src/config.js'));
283
+ const content = configFile.content;
284
+
285
+ expect(content).toContain('port:');
286
+ expect(content).toContain('5000');
287
+ expect(content).toContain('NODE_ENV');
288
+ });
289
+
290
+ it('includes database URL for services with models', async () => {
291
+ const { ServiceScaffold } = await import('./service-scaffold.js');
292
+ const scaffold = new ServiceScaffold();
293
+
294
+ const service = {
295
+ name: 'db-config-service',
296
+ models: ['Entity'],
297
+ };
298
+
299
+ const result = scaffold.generate(service);
300
+ const configFile = result.files.find(f => f.path.endsWith('src/config.js'));
301
+ const content = configFile.content;
302
+
303
+ expect(content).toContain('databaseUrl');
304
+ expect(content).toContain('DATABASE_URL');
305
+ });
306
+ });
307
+
308
+ describe('generateReadme', () => {
309
+ it('includes quick start instructions', async () => {
310
+ const { ServiceScaffold } = await import('./service-scaffold.js');
311
+ const scaffold = new ServiceScaffold();
312
+
313
+ const service = { name: 'readme-service' };
314
+
315
+ const result = scaffold.generate(service);
316
+ const readmeFile = result.files.find(f => f.path.endsWith('README.md'));
317
+ const content = readmeFile.content;
318
+
319
+ expect(content).toContain('# readme-service');
320
+ expect(content).toContain('Quick Start');
321
+ expect(content).toContain('npm install');
322
+ expect(content).toContain('docker-compose');
323
+ });
324
+
325
+ it('lists endpoints', async () => {
326
+ const { ServiceScaffold } = await import('./service-scaffold.js');
327
+ const scaffold = new ServiceScaffold();
328
+
329
+ const service = {
330
+ name: 'docs-service',
331
+ endpoints: [
332
+ { method: 'GET', path: '/docs', description: 'Get documentation' },
333
+ ],
334
+ };
335
+
336
+ const result = scaffold.generate(service);
337
+ const readmeFile = result.files.find(f => f.path.endsWith('README.md'));
338
+ const content = readmeFile.content;
339
+
340
+ expect(content).toContain('Endpoints');
341
+ expect(content).toContain('/docs');
342
+ expect(content).toContain('Get documentation');
343
+ });
344
+ });
345
+
346
+ describe('edge cases', () => {
347
+ it('handles service with no name', async () => {
348
+ const { ServiceScaffold } = await import('./service-scaffold.js');
349
+ const scaffold = new ServiceScaffold();
350
+
351
+ const service = {};
352
+
353
+ const result = scaffold.generate(service);
354
+
355
+ expect(result.directory).toBe('service');
356
+ });
357
+
358
+ it('handles empty endpoints array', async () => {
359
+ const { ServiceScaffold } = await import('./service-scaffold.js');
360
+ const scaffold = new ServiceScaffold();
361
+
362
+ const service = {
363
+ name: 'empty-endpoints',
364
+ endpoints: [],
365
+ };
366
+
367
+ const result = scaffold.generate(service);
368
+ const clientFile = result.files.find(f => f.path.includes('client/index.js'));
369
+
370
+ expect(clientFile.content).toContain('EmptyEndpointsClient');
371
+ });
372
+ });
373
+ });