tlc-claude-code 2.4.2 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/tlc/build.md +68 -0
- package/.claude/commands/tlc/discuss.md +174 -123
- package/.claude/commands/tlc/e2e-verify.md +1 -1
- package/.claude/commands/tlc/plan.md +77 -2
- package/.claude/commands/tlc/tlc.md +204 -473
- package/CLAUDE.md +6 -5
- package/package.json +4 -1
- package/scripts/dev-link.sh +29 -0
- package/scripts/test-package.sh +54 -0
- package/scripts/version-sync.js +42 -0
- package/scripts/version-sync.test.js +100 -0
- package/server/lib/model-router.js +11 -2
- package/server/lib/model-router.test.js +27 -1
- package/server/lib/orchestration/codex-orchestrator.js +185 -0
- package/server/lib/orchestration/codex-orchestrator.test.js +221 -0
- package/server/lib/orchestration/dep-linker.js +61 -0
- package/server/lib/orchestration/dep-linker.test.js +174 -0
- package/server/lib/router-config.js +18 -3
- package/server/lib/router-config.test.js +57 -1
- package/server/lib/routing/index.js +34 -0
- package/server/lib/routing/index.test.js +33 -0
- package/server/lib/routing-command.js +11 -2
- package/server/lib/routing-command.test.js +39 -1
- package/server/lib/routing-preamble.integration.test.js +319 -0
- package/server/lib/routing-preamble.js +34 -11
- package/server/lib/routing-preamble.test.js +11 -0
- package/server/lib/task-router-config.js +35 -14
- package/server/lib/task-router-config.test.js +77 -13
|
@@ -66,21 +66,22 @@ describe('task-router-config', () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
const result = loadPersonalConfig({ homeDir: '/home/user', fs });
|
|
69
|
-
expect(result).toEqual(personalConfig);
|
|
69
|
+
expect(result).toEqual({ config: personalConfig, warning: null });
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it('returns null when file does not exist', () => {
|
|
73
73
|
const fs = mockFs({});
|
|
74
74
|
const result = loadPersonalConfig({ homeDir: '/home/user', fs });
|
|
75
|
-
expect(result).
|
|
75
|
+
expect(result).toEqual({ config: null, warning: null });
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it('returns
|
|
78
|
+
it('returns warning for malformed JSON', () => {
|
|
79
79
|
const fs = mockFs({
|
|
80
80
|
'/home/user/.tlc/config.json': '{ not valid json',
|
|
81
81
|
});
|
|
82
82
|
const result = loadPersonalConfig({ homeDir: '/home/user', fs });
|
|
83
|
-
expect(result).toBeNull();
|
|
83
|
+
expect(result.config).toBeNull();
|
|
84
|
+
expect(result.warning).toContain('/home/user/.tlc/config.json');
|
|
84
85
|
});
|
|
85
86
|
});
|
|
86
87
|
|
|
@@ -97,7 +98,7 @@ describe('task-router-config', () => {
|
|
|
97
98
|
});
|
|
98
99
|
|
|
99
100
|
const result = loadProjectOverride({ projectDir: '/project', fs });
|
|
100
|
-
expect(result).toEqual(tlcJson.task_routing_override);
|
|
101
|
+
expect(result).toEqual({ override: tlcJson.task_routing_override, warning: null });
|
|
101
102
|
});
|
|
102
103
|
|
|
103
104
|
it('returns null when .tlc.json has no task_routing_override', () => {
|
|
@@ -106,21 +107,22 @@ describe('task-router-config', () => {
|
|
|
106
107
|
});
|
|
107
108
|
|
|
108
109
|
const result = loadProjectOverride({ projectDir: '/project', fs });
|
|
109
|
-
expect(result).
|
|
110
|
+
expect(result).toEqual({ override: null, warning: null });
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
it('returns null when .tlc.json does not exist', () => {
|
|
113
114
|
const fs = mockFs({});
|
|
114
115
|
const result = loadProjectOverride({ projectDir: '/project', fs });
|
|
115
|
-
expect(result).
|
|
116
|
+
expect(result).toEqual({ override: null, warning: null });
|
|
116
117
|
});
|
|
117
118
|
|
|
118
|
-
it('returns
|
|
119
|
+
it('returns warning for malformed JSON', () => {
|
|
119
120
|
const fs = mockFs({
|
|
120
121
|
'/project/.tlc.json': 'broken!!!',
|
|
121
122
|
});
|
|
122
123
|
const result = loadProjectOverride({ projectDir: '/project', fs });
|
|
123
|
-
expect(result).toBeNull();
|
|
124
|
+
expect(result.override).toBeNull();
|
|
125
|
+
expect(result.warning).toContain('/project/.tlc.json');
|
|
124
126
|
});
|
|
125
127
|
});
|
|
126
128
|
|
|
@@ -139,6 +141,7 @@ describe('task-router-config', () => {
|
|
|
139
141
|
models: ['claude'],
|
|
140
142
|
strategy: 'single',
|
|
141
143
|
source: 'shipped-defaults',
|
|
144
|
+
warnings: [],
|
|
142
145
|
});
|
|
143
146
|
});
|
|
144
147
|
|
|
@@ -163,6 +166,7 @@ describe('task-router-config', () => {
|
|
|
163
166
|
models: ['codex'],
|
|
164
167
|
strategy: 'single',
|
|
165
168
|
source: 'personal-config',
|
|
169
|
+
warnings: [],
|
|
166
170
|
});
|
|
167
171
|
});
|
|
168
172
|
|
|
@@ -193,6 +197,7 @@ describe('task-router-config', () => {
|
|
|
193
197
|
models: ['local'],
|
|
194
198
|
strategy: 'single',
|
|
195
199
|
source: 'project-override',
|
|
200
|
+
warnings: [],
|
|
196
201
|
});
|
|
197
202
|
});
|
|
198
203
|
|
|
@@ -224,6 +229,7 @@ describe('task-router-config', () => {
|
|
|
224
229
|
models: ['gemini'],
|
|
225
230
|
strategy: 'single',
|
|
226
231
|
source: 'flag-override',
|
|
232
|
+
warnings: [],
|
|
227
233
|
});
|
|
228
234
|
});
|
|
229
235
|
|
|
@@ -240,6 +246,7 @@ describe('task-router-config', () => {
|
|
|
240
246
|
models: ['claude'],
|
|
241
247
|
strategy: 'single',
|
|
242
248
|
source: 'shipped-defaults',
|
|
249
|
+
warnings: [],
|
|
243
250
|
});
|
|
244
251
|
});
|
|
245
252
|
|
|
@@ -264,6 +271,7 @@ describe('task-router-config', () => {
|
|
|
264
271
|
models: ['claude', 'codex'],
|
|
265
272
|
strategy: 'parallel',
|
|
266
273
|
source: 'personal-config',
|
|
274
|
+
warnings: [],
|
|
267
275
|
});
|
|
268
276
|
|
|
269
277
|
// 'build' should still use defaults
|
|
@@ -277,6 +285,7 @@ describe('task-router-config', () => {
|
|
|
277
285
|
models: ['claude'],
|
|
278
286
|
strategy: 'single',
|
|
279
287
|
source: 'shipped-defaults',
|
|
288
|
+
warnings: [],
|
|
280
289
|
});
|
|
281
290
|
});
|
|
282
291
|
|
|
@@ -301,6 +310,7 @@ describe('task-router-config', () => {
|
|
|
301
310
|
expect(result.strategy).toBe('parallel');
|
|
302
311
|
expect(result.models).toEqual(['claude']);
|
|
303
312
|
expect(result.source).toBe('personal-config');
|
|
313
|
+
expect(result.warnings).toEqual([]);
|
|
304
314
|
});
|
|
305
315
|
|
|
306
316
|
it('partial personal config merges correctly (only models)', () => {
|
|
@@ -323,6 +333,7 @@ describe('task-router-config', () => {
|
|
|
323
333
|
expect(result.models).toEqual(['codex']);
|
|
324
334
|
expect(result.strategy).toBe('single');
|
|
325
335
|
expect(result.source).toBe('personal-config');
|
|
336
|
+
expect(result.warnings).toEqual([]);
|
|
326
337
|
});
|
|
327
338
|
|
|
328
339
|
it('missing personal config file handled gracefully', () => {
|
|
@@ -345,10 +356,31 @@ describe('task-router-config', () => {
|
|
|
345
356
|
models: ['local'],
|
|
346
357
|
strategy: 'single',
|
|
347
358
|
source: 'project-override',
|
|
359
|
+
warnings: [],
|
|
348
360
|
});
|
|
349
361
|
});
|
|
350
362
|
|
|
351
|
-
it('
|
|
363
|
+
it('valid config returns empty warnings array', () => {
|
|
364
|
+
const personalConfig = {
|
|
365
|
+
task_routing: {
|
|
366
|
+
build: { models: ['codex'], strategy: 'single' },
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
const fs = mockFs({
|
|
370
|
+
'/home/user/.tlc/config.json': JSON.stringify(personalConfig),
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const result = resolveRouting({
|
|
374
|
+
command: 'build',
|
|
375
|
+
projectDir: '/project',
|
|
376
|
+
homeDir: '/home/user',
|
|
377
|
+
fs,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(result.warnings).toEqual([]);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('malformed personal JSON handled gracefully with warning', () => {
|
|
352
384
|
const fs = mockFs({
|
|
353
385
|
'/home/user/.tlc/config.json': '{{{{not json!',
|
|
354
386
|
});
|
|
@@ -364,10 +396,11 @@ describe('task-router-config', () => {
|
|
|
364
396
|
models: ['claude'],
|
|
365
397
|
strategy: 'single',
|
|
366
398
|
source: 'shipped-defaults',
|
|
399
|
+
warnings: [expect.stringContaining('/home/user/.tlc/config.json')],
|
|
367
400
|
});
|
|
368
401
|
});
|
|
369
402
|
|
|
370
|
-
it('malformed project JSON handled gracefully', () => {
|
|
403
|
+
it('malformed project JSON handled gracefully with warning', () => {
|
|
371
404
|
const fs = mockFs({
|
|
372
405
|
'/project/.tlc.json': 'not json either!',
|
|
373
406
|
});
|
|
@@ -383,9 +416,31 @@ describe('task-router-config', () => {
|
|
|
383
416
|
models: ['claude'],
|
|
384
417
|
strategy: 'single',
|
|
385
418
|
source: 'shipped-defaults',
|
|
419
|
+
warnings: [expect.stringContaining('/project/.tlc.json')],
|
|
386
420
|
});
|
|
387
421
|
});
|
|
388
422
|
|
|
423
|
+
it('collects both warnings when personal and project JSON are malformed', () => {
|
|
424
|
+
const fs = mockFs({
|
|
425
|
+
'/home/user/.tlc/config.json': '{{{{not json!',
|
|
426
|
+
'/project/.tlc.json': 'not json either!',
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const result = resolveRouting({
|
|
430
|
+
command: 'build',
|
|
431
|
+
projectDir: '/project',
|
|
432
|
+
homeDir: '/home/user',
|
|
433
|
+
fs,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
expect(result.models).toEqual(['claude']);
|
|
437
|
+
expect(result.strategy).toBe('single');
|
|
438
|
+
expect(result.source).toBe('shipped-defaults');
|
|
439
|
+
expect(result.warnings).toHaveLength(2);
|
|
440
|
+
expect(result.warnings[0]).toContain('/home/user/.tlc/config.json');
|
|
441
|
+
expect(result.warnings[1]).toContain('/project/.tlc.json');
|
|
442
|
+
});
|
|
443
|
+
|
|
389
444
|
it('model_providers loaded from personal config', () => {
|
|
390
445
|
const personalConfig = {
|
|
391
446
|
task_routing: {
|
|
@@ -410,9 +465,10 @@ describe('task-router-config', () => {
|
|
|
410
465
|
|
|
411
466
|
expect(result.models).toEqual(['codex']);
|
|
412
467
|
expect(result.providers).toEqual(personalConfig.model_providers);
|
|
468
|
+
expect(result.warnings).toEqual([]);
|
|
413
469
|
});
|
|
414
470
|
|
|
415
|
-
it('personal config with singular model (string) is normalized to models array', () => {
|
|
471
|
+
it('personal config with singular model (string) is normalized to models array with deprecation warning', () => {
|
|
416
472
|
const personalConfig = {
|
|
417
473
|
task_routing: {
|
|
418
474
|
build: { model: 'codex', strategy: 'single' },
|
|
@@ -432,9 +488,12 @@ describe('task-router-config', () => {
|
|
|
432
488
|
expect(result.models).toEqual(['codex']);
|
|
433
489
|
expect(result.strategy).toBe('single');
|
|
434
490
|
expect(result.source).toBe('personal-config');
|
|
491
|
+
expect(result.warnings).toEqual([
|
|
492
|
+
expect.stringMatching(/models/i),
|
|
493
|
+
]);
|
|
435
494
|
});
|
|
436
495
|
|
|
437
|
-
it('project override with singular model (string) is normalized to models array', () => {
|
|
496
|
+
it('project override with singular model (string) is normalized to models array with deprecation warning', () => {
|
|
438
497
|
const tlcJson = {
|
|
439
498
|
task_routing_override: {
|
|
440
499
|
review: { model: 'gemini', strategy: 'single' },
|
|
@@ -454,6 +513,9 @@ describe('task-router-config', () => {
|
|
|
454
513
|
expect(result.models).toEqual(['gemini']);
|
|
455
514
|
expect(result.strategy).toBe('single');
|
|
456
515
|
expect(result.source).toBe('project-override');
|
|
516
|
+
expect(result.warnings).toEqual([
|
|
517
|
+
expect.stringMatching(/models/i),
|
|
518
|
+
]);
|
|
457
519
|
});
|
|
458
520
|
|
|
459
521
|
it('models array takes precedence over model string in same config entry', () => {
|
|
@@ -475,6 +537,7 @@ describe('task-router-config', () => {
|
|
|
475
537
|
|
|
476
538
|
expect(result.models).toEqual(['codex', 'claude']);
|
|
477
539
|
expect(result.strategy).toBe('parallel');
|
|
540
|
+
expect(result.warnings).toEqual([]);
|
|
478
541
|
});
|
|
479
542
|
|
|
480
543
|
it('returns no providers when personal config has none', () => {
|
|
@@ -488,6 +551,7 @@ describe('task-router-config', () => {
|
|
|
488
551
|
});
|
|
489
552
|
|
|
490
553
|
expect(result.providers).toBeUndefined();
|
|
554
|
+
expect(result.warnings).toEqual([]);
|
|
491
555
|
});
|
|
492
556
|
});
|
|
493
557
|
});
|