tlc-claude-code 1.2.29 → 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 (80) hide show
  1. package/dashboard/dist/components/UsagePane.d.ts +13 -0
  2. package/dashboard/dist/components/UsagePane.js +51 -0
  3. package/dashboard/dist/components/UsagePane.test.d.ts +1 -0
  4. package/dashboard/dist/components/UsagePane.test.js +142 -0
  5. package/dashboard/dist/components/WorkspaceDocsPane.d.ts +19 -0
  6. package/dashboard/dist/components/WorkspaceDocsPane.js +146 -0
  7. package/dashboard/dist/components/WorkspaceDocsPane.test.d.ts +1 -0
  8. package/dashboard/dist/components/WorkspaceDocsPane.test.js +242 -0
  9. package/dashboard/dist/components/WorkspacePane.d.ts +18 -0
  10. package/dashboard/dist/components/WorkspacePane.js +17 -0
  11. package/dashboard/dist/components/WorkspacePane.test.d.ts +1 -0
  12. package/dashboard/dist/components/WorkspacePane.test.js +84 -0
  13. package/package.json +1 -1
  14. package/server/lib/architecture-command.js +450 -0
  15. package/server/lib/architecture-command.test.js +754 -0
  16. package/server/lib/ast-analyzer.js +324 -0
  17. package/server/lib/ast-analyzer.test.js +437 -0
  18. package/server/lib/auth-system.test.js +4 -1
  19. package/server/lib/boundary-detector.js +427 -0
  20. package/server/lib/boundary-detector.test.js +320 -0
  21. package/server/lib/budget-alerts.js +138 -0
  22. package/server/lib/budget-alerts.test.js +235 -0
  23. package/server/lib/candidates-tracker.js +210 -0
  24. package/server/lib/candidates-tracker.test.js +300 -0
  25. package/server/lib/checkpoint-manager.js +251 -0
  26. package/server/lib/checkpoint-manager.test.js +474 -0
  27. package/server/lib/circular-detector.js +337 -0
  28. package/server/lib/circular-detector.test.js +353 -0
  29. package/server/lib/cohesion-analyzer.js +310 -0
  30. package/server/lib/cohesion-analyzer.test.js +447 -0
  31. package/server/lib/contract-testing.js +625 -0
  32. package/server/lib/contract-testing.test.js +342 -0
  33. package/server/lib/conversion-planner.js +469 -0
  34. package/server/lib/conversion-planner.test.js +361 -0
  35. package/server/lib/convert-command.js +351 -0
  36. package/server/lib/convert-command.test.js +608 -0
  37. package/server/lib/coupling-calculator.js +189 -0
  38. package/server/lib/coupling-calculator.test.js +509 -0
  39. package/server/lib/dependency-graph.js +367 -0
  40. package/server/lib/dependency-graph.test.js +516 -0
  41. package/server/lib/duplication-detector.js +349 -0
  42. package/server/lib/duplication-detector.test.js +401 -0
  43. package/server/lib/example-service.js +616 -0
  44. package/server/lib/example-service.test.js +397 -0
  45. package/server/lib/impact-scorer.js +184 -0
  46. package/server/lib/impact-scorer.test.js +211 -0
  47. package/server/lib/mermaid-generator.js +358 -0
  48. package/server/lib/mermaid-generator.test.js +301 -0
  49. package/server/lib/messaging-patterns.js +750 -0
  50. package/server/lib/messaging-patterns.test.js +213 -0
  51. package/server/lib/microservice-template.js +386 -0
  52. package/server/lib/microservice-template.test.js +325 -0
  53. package/server/lib/new-project-microservice.js +450 -0
  54. package/server/lib/new-project-microservice.test.js +600 -0
  55. package/server/lib/refactor-command.js +326 -0
  56. package/server/lib/refactor-command.test.js +528 -0
  57. package/server/lib/refactor-executor.js +254 -0
  58. package/server/lib/refactor-executor.test.js +305 -0
  59. package/server/lib/refactor-observer.js +292 -0
  60. package/server/lib/refactor-observer.test.js +422 -0
  61. package/server/lib/refactor-progress.js +193 -0
  62. package/server/lib/refactor-progress.test.js +251 -0
  63. package/server/lib/refactor-reporter.js +237 -0
  64. package/server/lib/refactor-reporter.test.js +247 -0
  65. package/server/lib/semantic-analyzer.js +198 -0
  66. package/server/lib/semantic-analyzer.test.js +474 -0
  67. package/server/lib/service-scaffold.js +486 -0
  68. package/server/lib/service-scaffold.test.js +373 -0
  69. package/server/lib/shared-kernel.js +578 -0
  70. package/server/lib/shared-kernel.test.js +255 -0
  71. package/server/lib/traefik-config.js +282 -0
  72. package/server/lib/traefik-config.test.js +312 -0
  73. package/server/lib/usage-command.js +218 -0
  74. package/server/lib/usage-command.test.js +391 -0
  75. package/server/lib/usage-formatter.js +192 -0
  76. package/server/lib/usage-formatter.test.js +267 -0
  77. package/server/lib/usage-history.js +122 -0
  78. package/server/lib/usage-history.test.js +206 -0
  79. package/server/package-lock.json +14 -0
  80. package/server/package.json +1 -0
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Traefik Config Generator Tests
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+
7
+ describe('TraefikConfig', () => {
8
+ describe('generateTraefikYml', () => {
9
+ it('generates valid traefik.yml structure', async () => {
10
+ const { TraefikConfig } = await import('./traefik-config.js');
11
+ const traefik = new TraefikConfig();
12
+
13
+ const config = {
14
+ services: ['user', 'order'],
15
+ domain: 'localhost',
16
+ tls: false,
17
+ };
18
+
19
+ const result = traefik.generateTraefikYml(config);
20
+
21
+ expect(result).toContain('entryPoints:');
22
+ expect(result).toContain('api:');
23
+ expect(result).toContain('providers:');
24
+ });
25
+
26
+ it('entrypoint web on port 80', async () => {
27
+ const { TraefikConfig } = await import('./traefik-config.js');
28
+ const traefik = new TraefikConfig();
29
+
30
+ const config = {
31
+ services: ['user'],
32
+ domain: 'localhost',
33
+ tls: false,
34
+ };
35
+
36
+ const result = traefik.generateTraefikYml(config);
37
+
38
+ expect(result).toContain('web:');
39
+ expect(result).toContain(':80');
40
+ });
41
+
42
+ it('entrypoint websecure when tls enabled', async () => {
43
+ const { TraefikConfig } = await import('./traefik-config.js');
44
+ const traefik = new TraefikConfig();
45
+
46
+ const config = {
47
+ services: ['user'],
48
+ domain: 'example.com',
49
+ tls: true,
50
+ };
51
+
52
+ const result = traefik.generateTraefikYml(config);
53
+
54
+ expect(result).toContain('websecure:');
55
+ expect(result).toContain(':443');
56
+ });
57
+
58
+ it('docker provider configured', async () => {
59
+ const { TraefikConfig } = await import('./traefik-config.js');
60
+ const traefik = new TraefikConfig();
61
+
62
+ const config = {
63
+ services: ['user'],
64
+ domain: 'localhost',
65
+ tls: false,
66
+ };
67
+
68
+ const result = traefik.generateTraefikYml(config);
69
+
70
+ expect(result).toContain('docker:');
71
+ expect(result).toContain('exposedByDefault: false');
72
+ });
73
+
74
+ it('API dashboard enabled', async () => {
75
+ const { TraefikConfig } = await import('./traefik-config.js');
76
+ const traefik = new TraefikConfig();
77
+
78
+ const config = {
79
+ services: ['user'],
80
+ domain: 'localhost',
81
+ tls: false,
82
+ };
83
+
84
+ const result = traefik.generateTraefikYml(config);
85
+
86
+ expect(result).toContain('api:');
87
+ expect(result).toContain('dashboard: true');
88
+ });
89
+ });
90
+
91
+ describe('generateDynamicConfig', () => {
92
+ it('routes /api/user to user-service', async () => {
93
+ const { TraefikConfig } = await import('./traefik-config.js');
94
+ const traefik = new TraefikConfig();
95
+
96
+ const config = {
97
+ services: ['user', 'order'],
98
+ domain: 'localhost',
99
+ tls: false,
100
+ };
101
+
102
+ const result = traefik.generateDynamicConfig(config);
103
+
104
+ expect(result).toContain('user-router:');
105
+ expect(result).toContain('PathPrefix(`/api/user`)');
106
+ });
107
+
108
+ it('routes /api/order to order-service', async () => {
109
+ const { TraefikConfig } = await import('./traefik-config.js');
110
+ const traefik = new TraefikConfig();
111
+
112
+ const config = {
113
+ services: ['user', 'order'],
114
+ domain: 'localhost',
115
+ tls: false,
116
+ };
117
+
118
+ const result = traefik.generateDynamicConfig(config);
119
+
120
+ expect(result).toContain('order-router:');
121
+ expect(result).toContain('PathPrefix(`/api/order`)');
122
+ });
123
+
124
+ it('health check middleware included', async () => {
125
+ const { TraefikConfig } = await import('./traefik-config.js');
126
+ const traefik = new TraefikConfig();
127
+
128
+ const config = {
129
+ services: ['user'],
130
+ domain: 'localhost',
131
+ tls: false,
132
+ };
133
+
134
+ const result = traefik.generateDynamicConfig(config);
135
+
136
+ expect(result).toContain('middlewares:');
137
+ });
138
+
139
+ it('rate limiting middleware configured', async () => {
140
+ const { TraefikConfig } = await import('./traefik-config.js');
141
+ const traefik = new TraefikConfig();
142
+
143
+ const config = {
144
+ services: ['user'],
145
+ domain: 'localhost',
146
+ tls: false,
147
+ };
148
+
149
+ const result = traefik.generateDynamicConfig(config);
150
+
151
+ expect(result).toContain('rate-limit:');
152
+ expect(result).toContain('rateLimit:');
153
+ });
154
+ });
155
+
156
+ describe('generateServiceRouter', () => {
157
+ it('generates router with PathPrefix rule', async () => {
158
+ const { TraefikConfig } = await import('./traefik-config.js');
159
+ const traefik = new TraefikConfig();
160
+
161
+ const result = traefik.generateServiceRouter('user', 3000);
162
+
163
+ expect(result).toContain('user-router:');
164
+ expect(result).toContain('PathPrefix(`/api/user`)');
165
+ });
166
+
167
+ it('includes strip prefix middleware', async () => {
168
+ const { TraefikConfig } = await import('./traefik-config.js');
169
+ const traefik = new TraefikConfig();
170
+
171
+ const result = traefik.generateServiceRouter('user', 3000);
172
+
173
+ expect(result).toContain('middlewares:');
174
+ expect(result).toContain('strip-user');
175
+ });
176
+
177
+ it('configures service backend with port', async () => {
178
+ const { TraefikConfig } = await import('./traefik-config.js');
179
+ const traefik = new TraefikConfig();
180
+
181
+ const result = traefik.generateServiceRouter('user', 3001);
182
+
183
+ expect(result).toContain('user-service:');
184
+ expect(result).toContain('http://user-service:3001');
185
+ });
186
+
187
+ it('handles custom service port', async () => {
188
+ const { TraefikConfig } = await import('./traefik-config.js');
189
+ const traefik = new TraefikConfig();
190
+
191
+ const result = traefik.generateServiceRouter('order', 4000);
192
+
193
+ expect(result).toContain('http://order-service:4000');
194
+ });
195
+ });
196
+
197
+ describe('generateMiddlewares', () => {
198
+ it('rate limit average 100', async () => {
199
+ const { TraefikConfig } = await import('./traefik-config.js');
200
+ const traefik = new TraefikConfig();
201
+
202
+ const result = traefik.generateMiddlewares();
203
+
204
+ expect(result).toContain('rate-limit:');
205
+ expect(result).toContain('average: 100');
206
+ });
207
+
208
+ it('rate limit burst 50', async () => {
209
+ const { TraefikConfig } = await import('./traefik-config.js');
210
+ const traefik = new TraefikConfig();
211
+
212
+ const result = traefik.generateMiddlewares();
213
+
214
+ expect(result).toContain('burst: 50');
215
+ });
216
+
217
+ it('strip prefix middleware', async () => {
218
+ const { TraefikConfig } = await import('./traefik-config.js');
219
+ const traefik = new TraefikConfig();
220
+
221
+ const result = traefik.generateMiddlewares();
222
+
223
+ expect(result).toContain('stripPrefix:');
224
+ });
225
+
226
+ it('security headers middleware', async () => {
227
+ const { TraefikConfig } = await import('./traefik-config.js');
228
+ const traefik = new TraefikConfig();
229
+
230
+ const result = traefik.generateMiddlewares();
231
+
232
+ expect(result).toContain('headers:');
233
+ expect(result).toContain('customResponseHeaders:');
234
+ });
235
+ });
236
+
237
+ describe('generateTlsConfig', () => {
238
+ it('TLS config generated when enabled', async () => {
239
+ const { TraefikConfig } = await import('./traefik-config.js');
240
+ const traefik = new TraefikConfig();
241
+
242
+ const result = traefik.generateTlsConfig('example.com');
243
+
244
+ expect(result).toContain('certificatesResolvers:');
245
+ });
246
+
247
+ it('uses Let\'s Encrypt resolver', async () => {
248
+ const { TraefikConfig } = await import('./traefik-config.js');
249
+ const traefik = new TraefikConfig();
250
+
251
+ const result = traefik.generateTlsConfig('example.com');
252
+
253
+ expect(result).toContain('letsencrypt:');
254
+ expect(result).toContain('acme:');
255
+ });
256
+
257
+ it('includes certificate storage', async () => {
258
+ const { TraefikConfig } = await import('./traefik-config.js');
259
+ const traefik = new TraefikConfig();
260
+
261
+ const result = traefik.generateTlsConfig('example.com');
262
+
263
+ expect(result).toContain('storage:');
264
+ expect(result).toContain('acme.json');
265
+ });
266
+
267
+ it('domain configurable', async () => {
268
+ const { TraefikConfig } = await import('./traefik-config.js');
269
+ const traefik = new TraefikConfig();
270
+
271
+ const result = traefik.generateTlsConfig('myapp.example.com');
272
+
273
+ expect(result).toContain('myapp.example.com');
274
+ });
275
+ });
276
+
277
+ describe('edge cases', () => {
278
+ it('handles single service', async () => {
279
+ const { TraefikConfig } = await import('./traefik-config.js');
280
+ const traefik = new TraefikConfig();
281
+
282
+ const config = {
283
+ services: ['api'],
284
+ domain: 'localhost',
285
+ tls: false,
286
+ };
287
+
288
+ const result = traefik.generateDynamicConfig(config);
289
+
290
+ expect(result).toContain('api-router:');
291
+ expect(result).toContain('PathPrefix(`/api/api`)');
292
+ });
293
+
294
+ it('handles multiple services', async () => {
295
+ const { TraefikConfig } = await import('./traefik-config.js');
296
+ const traefik = new TraefikConfig();
297
+
298
+ const config = {
299
+ services: ['user', 'order', 'notification', 'payment'],
300
+ domain: 'localhost',
301
+ tls: false,
302
+ };
303
+
304
+ const result = traefik.generateDynamicConfig(config);
305
+
306
+ expect(result).toContain('user-router:');
307
+ expect(result).toContain('order-router:');
308
+ expect(result).toContain('notification-router:');
309
+ expect(result).toContain('payment-router:');
310
+ });
311
+ });
312
+ });
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Usage Command - /tlc:usage command implementation
3
+ */
4
+
5
+ const { BudgetTracker } = require('./budget-tracker.js');
6
+ const { UsageHistory } = require('./usage-history.js');
7
+ const { formatUsageSummary } = require('./usage-formatter.js');
8
+ const { BudgetAlerts, formatAlertMessage } = require('./budget-alerts.js');
9
+
10
+ /**
11
+ * Parse command line arguments
12
+ * @param {string[]} args - Command line arguments
13
+ * @returns {Object} Parsed options
14
+ */
15
+ function parseArgs(args) {
16
+ const result = {
17
+ reset: false,
18
+ model: null,
19
+ json: false,
20
+ };
21
+
22
+ for (let i = 0; i < args.length; i++) {
23
+ const arg = args[i];
24
+
25
+ if (arg === '--reset') {
26
+ result.reset = true;
27
+ } else if (arg === '--json') {
28
+ result.json = true;
29
+ } else if (arg === '--model') {
30
+ result.model = args[i + 1];
31
+ i++;
32
+ } else if (arg.startsWith('--model=')) {
33
+ result.model = arg.split('=')[1];
34
+ }
35
+ }
36
+
37
+ return result;
38
+ }
39
+
40
+ /**
41
+ * UsageCommand class - handles /tlc:usage command
42
+ */
43
+ class UsageCommand {
44
+ constructor(options = {}) {
45
+ this.budgetTracker = options.budgetTracker || new BudgetTracker();
46
+ this.usageHistory = options.usageHistory || new UsageHistory();
47
+ this.budgetAlerts = options.budgetAlerts || new BudgetAlerts();
48
+ }
49
+
50
+ /**
51
+ * Execute the usage command
52
+ * @param {string[]} args - Command arguments
53
+ * @param {Object} config - Budget config per model { model: { budgetDaily, budgetMonthly } }
54
+ * @returns {Object} Result { success, output, json?, alerts?, error? }
55
+ */
56
+ execute(args, config) {
57
+ const options = parseArgs(args);
58
+
59
+ // Determine which models to process
60
+ let models = Object.keys(config);
61
+
62
+ if (options.model) {
63
+ if (!config[options.model]) {
64
+ return {
65
+ success: false,
66
+ error: `Unknown model: ${options.model}. Available: ${models.join(', ')}`,
67
+ };
68
+ }
69
+ models = [options.model];
70
+ }
71
+
72
+ // Handle reset
73
+ if (options.reset) {
74
+ return this.handleReset(models, options);
75
+ }
76
+
77
+ // Collect data
78
+ const usageData = this.getUsageData(models);
79
+ const historyData = this.getHistoryData(models);
80
+ const alerts = this.checkAlerts(models, usageData, config);
81
+ const alertMessages = alerts.map(a => formatAlertMessage(a));
82
+
83
+ // JSON output
84
+ if (options.json) {
85
+ return {
86
+ success: true,
87
+ json: {
88
+ models: usageData,
89
+ history: historyData,
90
+ alerts: alerts,
91
+ totals: this.calculateTotals(usageData, config),
92
+ },
93
+ alerts: alertMessages,
94
+ };
95
+ }
96
+
97
+ // Text output
98
+ const output = formatUsageSummary(usageData, config, historyData);
99
+
100
+ return {
101
+ success: true,
102
+ output,
103
+ alerts: alertMessages,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Handle --reset flag
109
+ * @param {string[]} models - Models to reset
110
+ * @param {Object} options - Parsed options
111
+ * @returns {Object} Result
112
+ */
113
+ handleReset(models, options) {
114
+ for (const model of models) {
115
+ this.budgetTracker.reset(model);
116
+ this.budgetAlerts.resetAlerts(model);
117
+ }
118
+
119
+ const modelList = models.join(', ');
120
+ return {
121
+ success: true,
122
+ output: `Usage reset for: ${modelList}`,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Collect usage data from all specified models
128
+ * @param {string[]} models - Model names
129
+ * @returns {Object} Usage data per model
130
+ */
131
+ getUsageData(models) {
132
+ const data = {};
133
+
134
+ for (const model of models) {
135
+ data[model] = this.budgetTracker.getUsage(model);
136
+ }
137
+
138
+ return data;
139
+ }
140
+
141
+ /**
142
+ * Collect history data from all specified models
143
+ * @param {string[]} models - Model names
144
+ * @returns {Object} History data per model
145
+ */
146
+ getHistoryData(models) {
147
+ const data = {};
148
+
149
+ for (const model of models) {
150
+ data[model] = this.usageHistory.getHistory(model);
151
+ }
152
+
153
+ return data;
154
+ }
155
+
156
+ /**
157
+ * Check alerts for all models
158
+ * @param {string[]} models - Model names
159
+ * @param {Object} usageData - Usage data per model
160
+ * @param {Object} config - Budget config per model
161
+ * @returns {Object[]} Array of fired alerts
162
+ */
163
+ checkAlerts(models, usageData, config) {
164
+ const allAlerts = [];
165
+
166
+ for (const model of models) {
167
+ const usage = usageData[model];
168
+ const modelConfig = config[model];
169
+
170
+ if (usage && modelConfig) {
171
+ const alerts = this.budgetAlerts.checkThresholds(model, usage, modelConfig);
172
+ allAlerts.push(...alerts);
173
+ }
174
+ }
175
+
176
+ return allAlerts;
177
+ }
178
+
179
+ /**
180
+ * Calculate totals across all models
181
+ * @param {Object} usageData - Usage data per model
182
+ * @param {Object} config - Budget config per model
183
+ * @returns {Object} Totals
184
+ */
185
+ calculateTotals(usageData, config) {
186
+ let totalDaily = 0;
187
+ let totalMonthly = 0;
188
+ let totalRequests = 0;
189
+ let totalBudgetDaily = 0;
190
+ let totalBudgetMonthly = 0;
191
+
192
+ for (const model of Object.keys(usageData)) {
193
+ const usage = usageData[model];
194
+ const modelConfig = config[model] || {};
195
+
196
+ totalDaily += usage.daily || 0;
197
+ totalMonthly += usage.monthly || 0;
198
+ totalRequests += usage.requests || 0;
199
+ totalBudgetDaily += modelConfig.budgetDaily || 0;
200
+ totalBudgetMonthly += modelConfig.budgetMonthly || 0;
201
+ }
202
+
203
+ return {
204
+ daily: totalDaily,
205
+ monthly: totalMonthly,
206
+ requests: totalRequests,
207
+ budgetDaily: totalBudgetDaily,
208
+ budgetMonthly: totalBudgetMonthly,
209
+ remainingDaily: Math.max(0, totalBudgetDaily - totalDaily),
210
+ remainingMonthly: Math.max(0, totalBudgetMonthly - totalMonthly),
211
+ };
212
+ }
213
+ }
214
+
215
+ module.exports = {
216
+ UsageCommand,
217
+ parseArgs,
218
+ };