tlc-claude-code 2.4.10 → 2.6.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.
- package/.claude/commands/tlc/autofix.md +34 -1
- package/.claude/commands/tlc/build.md +203 -27
- package/.claude/commands/tlc/ci.md +178 -414
- package/.claude/commands/tlc/coverage.md +34 -0
- package/.claude/commands/tlc/deploy.md +19 -6
- package/.claude/commands/tlc/discuss.md +34 -0
- package/.claude/commands/tlc/docs.md +35 -1
- package/.claude/commands/tlc/e2e.md +300 -0
- package/.claude/commands/tlc/edge-cases.md +35 -1
- package/.claude/commands/tlc/init.md +38 -8
- package/.claude/commands/tlc/issues.md +46 -0
- package/.claude/commands/tlc/new-project.md +46 -4
- package/.claude/commands/tlc/plan.md +76 -0
- package/.claude/commands/tlc/quick.md +33 -0
- package/.claude/commands/tlc/release.md +85 -135
- package/.claude/commands/tlc/restore.md +14 -0
- package/.claude/commands/tlc/review.md +80 -1
- package/.claude/commands/tlc/tlc.md +134 -0
- package/.claude/commands/tlc/verify.md +64 -65
- package/.claude/commands/tlc/watchci.md +10 -0
- package/.claude/hooks/tlc-block-tools.sh +13 -0
- package/.claude/hooks/tlc-session-init.sh +9 -0
- package/CODING-STANDARDS.md +35 -10
- package/package.json +1 -1
- package/server/lib/block-tools-hook.js +23 -0
- package/server/lib/e2e/acceptance-parser.js +132 -0
- package/server/lib/e2e/acceptance-parser.test.js +110 -0
- package/server/lib/e2e/framework-detector.js +47 -0
- package/server/lib/e2e/framework-detector.test.js +94 -0
- package/server/lib/e2e/log-assertions.js +107 -0
- package/server/lib/e2e/log-assertions.test.js +68 -0
- package/server/lib/e2e/test-generator.js +159 -0
- package/server/lib/e2e/test-generator.test.js +121 -0
- package/server/lib/e2e/verify-runner.js +191 -0
- package/server/lib/e2e/verify-runner.test.js +167 -0
- package/server/lib/github/config.js +458 -0
- package/server/lib/github/config.test.js +385 -0
- package/server/lib/github/gh-client.js +303 -0
- package/server/lib/github/gh-client.test.js +499 -0
- package/server/lib/github/gh-projects.js +594 -0
- package/server/lib/github/gh-projects.test.js +583 -0
- package/server/lib/github/index.js +19 -0
- package/server/lib/github/plan-sync.js +456 -0
- package/server/lib/github/plan-sync.test.js +805 -0
- package/server/lib/hooks/block-tools-hook.test.js +54 -0
- package/server/lib/orchestration/cli-dispatch.js +16 -1
- package/server/lib/orchestration/cli-dispatch.test.js +94 -8
- package/server/lib/orchestration/completion-checker.js +101 -0
- package/server/lib/orchestration/completion-checker.test.js +177 -0
- package/server/lib/orchestration/result-verifier.js +143 -0
- package/server/lib/orchestration/result-verifier.test.js +291 -0
- package/server/lib/orchestration/session-dispatcher.js +99 -0
- package/server/lib/orchestration/session-dispatcher.test.js +215 -0
- package/server/lib/orchestration/session-status.js +147 -0
- package/server/lib/orchestration/session-status.test.js +130 -0
- package/server/lib/release/agent-runner-updates.js +24 -0
- package/server/lib/release/agent-runner-updates.test.js +22 -0
- package/server/lib/release/changelog-generator.js +142 -0
- package/server/lib/release/changelog-generator.test.js +113 -0
- package/server/lib/release/ci-watcher.js +83 -0
- package/server/lib/release/ci-watcher.test.js +81 -0
- package/server/lib/release/health-checker.js +111 -0
- package/server/lib/release/health-checker.test.js +121 -0
- package/server/lib/release/release-pipeline.js +187 -0
- package/server/lib/release/release-pipeline.test.js +262 -0
- package/server/lib/release/version-bumper.js +183 -0
- package/server/lib/release/version-bumper.test.js +142 -0
- package/server/lib/routing-preamble.integration.test.js +12 -0
- package/server/lib/routing-preamble.js +13 -2
- package/server/lib/routing-preamble.test.js +49 -0
- package/server/lib/scaffolding/ci-detector.js +139 -0
- package/server/lib/scaffolding/ci-detector.test.js +198 -0
- package/server/lib/scaffolding/ci-scaffolder.js +347 -0
- package/server/lib/scaffolding/ci-scaffolder.test.js +157 -0
- package/server/lib/scaffolding/deploy-detector.js +135 -0
- package/server/lib/scaffolding/deploy-detector.test.js +106 -0
- package/server/lib/scaffolding/health-scaffold.js +374 -0
- package/server/lib/scaffolding/health-scaffold.test.js +99 -0
- package/server/lib/scaffolding/logger-scaffold.js +196 -0
- package/server/lib/scaffolding/logger-scaffold.test.js +146 -0
- package/server/lib/scaffolding/migration-detector.js +78 -0
- package/server/lib/scaffolding/migration-detector.test.js +127 -0
- package/server/lib/scaffolding/snapshot-manager.js +142 -0
- package/server/lib/scaffolding/snapshot-manager.test.js +225 -0
- package/server/lib/task-router-config.js +50 -20
- package/server/lib/task-router-config.test.js +29 -15
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const fixedDate = new Date('2026-03-30T08:15:00.000Z');
|
|
5
|
+
const mockedFs = vi.hoisted(() => ({
|
|
6
|
+
copyFileSync: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('fs', () => mockedFs);
|
|
10
|
+
|
|
11
|
+
describe('snapshot-manager', () => {
|
|
12
|
+
let exec;
|
|
13
|
+
let fsMock;
|
|
14
|
+
let takeSnapshot;
|
|
15
|
+
let restoreSnapshot;
|
|
16
|
+
let listSnapshots;
|
|
17
|
+
let enforceRetention;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
vi.resetModules();
|
|
21
|
+
vi.useFakeTimers();
|
|
22
|
+
vi.setSystemTime(fixedDate);
|
|
23
|
+
|
|
24
|
+
exec = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });
|
|
25
|
+
mockedFs.copyFileSync.mockReset();
|
|
26
|
+
fsMock = {
|
|
27
|
+
...mockedFs,
|
|
28
|
+
readdirSync: vi.fn(),
|
|
29
|
+
statSync: vi.fn(),
|
|
30
|
+
unlinkSync: vi.fn(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
({
|
|
34
|
+
takeSnapshot,
|
|
35
|
+
restoreSnapshot,
|
|
36
|
+
listSnapshots,
|
|
37
|
+
enforceRetention,
|
|
38
|
+
} = await import('./snapshot-manager.js'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.useRealTimers();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('PostgreSQL snapshot runs pg_dump with correct args', async () => {
|
|
46
|
+
const result = await takeSnapshot({
|
|
47
|
+
dbType: 'postgresql',
|
|
48
|
+
connectionString: 'postgres://user:secret@db.example.com:5432/app_db',
|
|
49
|
+
snapshotDir: '/tmp/snaps',
|
|
50
|
+
gitRef: 'abc1234',
|
|
51
|
+
exec,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(exec).toHaveBeenCalledWith(
|
|
55
|
+
'pg_dump --format=custom -f "/tmp/snaps/2026-03-30-abc1234-app_db.dump" "postgres://user:secret@db.example.com:5432/app_db"'
|
|
56
|
+
);
|
|
57
|
+
expect(result).toEqual({
|
|
58
|
+
success: true,
|
|
59
|
+
snapshotPath: '/tmp/snaps/2026-03-30-abc1234-app_db.dump',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('MySQL snapshot runs mysqldump with correct args', async () => {
|
|
64
|
+
const result = await takeSnapshot({
|
|
65
|
+
dbType: 'mysql',
|
|
66
|
+
connectionString: 'mysql://root:secret@mysql.example.com:3307/shop',
|
|
67
|
+
snapshotDir: '/tmp/snaps',
|
|
68
|
+
gitRef: 'feature-x',
|
|
69
|
+
exec,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(exec).toHaveBeenCalledWith(
|
|
73
|
+
'mysqldump --host=mysql.example.com --port=3307 --user=root --password=secret shop > "/tmp/snaps/2026-03-30-feature-x-shop.dump"'
|
|
74
|
+
);
|
|
75
|
+
expect(result.snapshotPath).toBe('/tmp/snaps/2026-03-30-feature-x-shop.dump');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('SQLite snapshot copies file', async () => {
|
|
79
|
+
const result = await takeSnapshot({
|
|
80
|
+
dbType: 'sqlite',
|
|
81
|
+
connectionString: '/data/app.sqlite',
|
|
82
|
+
snapshotDir: '/tmp/snaps',
|
|
83
|
+
gitRef: 'main',
|
|
84
|
+
exec,
|
|
85
|
+
fs: fsMock,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(fsMock.copyFileSync).toHaveBeenCalledWith(
|
|
89
|
+
'/data/app.sqlite',
|
|
90
|
+
'/tmp/snaps/2026-03-30-main-app.dump'
|
|
91
|
+
);
|
|
92
|
+
expect(exec).not.toHaveBeenCalled();
|
|
93
|
+
expect(result.snapshotPath).toBe('/tmp/snaps/2026-03-30-main-app.dump');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('Snapshot filename has correct format', async () => {
|
|
97
|
+
const result = await takeSnapshot({
|
|
98
|
+
dbType: 'postgresql',
|
|
99
|
+
connectionString: 'postgres://user:secret@localhost:5432/reporting',
|
|
100
|
+
snapshotDir: '/tmp/snaps',
|
|
101
|
+
gitRef: 'release-1',
|
|
102
|
+
exec,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(path.basename(result.snapshotPath)).toBe('2026-03-30-release-1-reporting.dump');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('Restore runs correct command per dbType', async () => {
|
|
109
|
+
await restoreSnapshot({
|
|
110
|
+
snapshotPath: '/tmp/snaps/sample.dump',
|
|
111
|
+
dbType: 'postgresql',
|
|
112
|
+
connectionString: 'postgres://user:secret@db.example.com:5432/app_db',
|
|
113
|
+
exec,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(exec).toHaveBeenNthCalledWith(
|
|
117
|
+
1,
|
|
118
|
+
'pg_restore --clean --if-exists --dbname="postgres://user:secret@db.example.com:5432/app_db" "/tmp/snaps/sample.dump"'
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
await restoreSnapshot({
|
|
122
|
+
snapshotPath: '/tmp/snaps/sample.dump',
|
|
123
|
+
dbType: 'mysql',
|
|
124
|
+
connectionString: 'mysql://root:secret@mysql.example.com:3307/shop',
|
|
125
|
+
exec,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(exec).toHaveBeenNthCalledWith(
|
|
129
|
+
2,
|
|
130
|
+
'mysql --host=mysql.example.com --port=3307 --user=root --password=secret shop < "/tmp/snaps/sample.dump"'
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
await restoreSnapshot({
|
|
134
|
+
snapshotPath: '/tmp/snaps/sample.dump',
|
|
135
|
+
dbType: 'sqlite',
|
|
136
|
+
connectionString: '/data/app.sqlite',
|
|
137
|
+
exec,
|
|
138
|
+
fs: fsMock,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(fsMock.copyFileSync).toHaveBeenCalledWith('/tmp/snaps/sample.dump', '/data/app.sqlite');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('List returns snapshots sorted newest first', () => {
|
|
145
|
+
fsMock.readdirSync.mockReturnValue([
|
|
146
|
+
'2026-03-28-main-app.dump',
|
|
147
|
+
'2026-03-30-feature-app.dump',
|
|
148
|
+
'2026-03-29-fix-auth.dump',
|
|
149
|
+
]);
|
|
150
|
+
fsMock.statSync.mockImplementation((filePath) => ({
|
|
151
|
+
size: filePath.includes('feature') ? 300 : filePath.includes('fix') ? 200 : 100,
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
const result = listSnapshots({
|
|
155
|
+
snapshotDir: '/tmp/snaps',
|
|
156
|
+
fs: fsMock,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result).toEqual([
|
|
160
|
+
{
|
|
161
|
+
path: '/tmp/snaps/2026-03-30-feature-app.dump',
|
|
162
|
+
date: '2026-03-30',
|
|
163
|
+
gitRef: 'feature',
|
|
164
|
+
dbName: 'app',
|
|
165
|
+
size: 300,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
path: '/tmp/snaps/2026-03-29-fix-auth.dump',
|
|
169
|
+
date: '2026-03-29',
|
|
170
|
+
gitRef: 'fix',
|
|
171
|
+
dbName: 'auth',
|
|
172
|
+
size: 200,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
path: '/tmp/snaps/2026-03-28-main-app.dump',
|
|
176
|
+
date: '2026-03-28',
|
|
177
|
+
gitRef: 'main',
|
|
178
|
+
dbName: 'app',
|
|
179
|
+
size: 100,
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('Retention deletes oldest when over limit', () => {
|
|
185
|
+
fsMock.readdirSync.mockReturnValue([
|
|
186
|
+
'2026-03-27-main-app.dump',
|
|
187
|
+
'2026-03-28-main-app.dump',
|
|
188
|
+
'2026-03-29-main-app.dump',
|
|
189
|
+
'2026-03-30-main-app.dump',
|
|
190
|
+
]);
|
|
191
|
+
fsMock.statSync.mockImplementation(() => ({ size: 100 }));
|
|
192
|
+
|
|
193
|
+
const deleted = enforceRetention({
|
|
194
|
+
snapshotDir: '/tmp/snaps',
|
|
195
|
+
maxSnapshots: 2,
|
|
196
|
+
fs: fsMock,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(fsMock.unlinkSync).toHaveBeenCalledTimes(2);
|
|
200
|
+
expect(fsMock.unlinkSync).toHaveBeenNthCalledWith(1, '/tmp/snaps/2026-03-27-main-app.dump');
|
|
201
|
+
expect(fsMock.unlinkSync).toHaveBeenNthCalledWith(2, '/tmp/snaps/2026-03-28-main-app.dump');
|
|
202
|
+
expect(deleted).toEqual([
|
|
203
|
+
'/tmp/snaps/2026-03-27-main-app.dump',
|
|
204
|
+
'/tmp/snaps/2026-03-28-main-app.dump',
|
|
205
|
+
]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('DB unreachable returns failure with reason', async () => {
|
|
209
|
+
exec.mockRejectedValueOnce(new Error('connect ECONNREFUSED'));
|
|
210
|
+
|
|
211
|
+
const result = await takeSnapshot({
|
|
212
|
+
dbType: 'postgresql',
|
|
213
|
+
connectionString: 'postgres://user:secret@localhost:5432/app_db',
|
|
214
|
+
snapshotDir: '/tmp/snaps',
|
|
215
|
+
gitRef: 'abc1234',
|
|
216
|
+
exec,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(result).toEqual({
|
|
220
|
+
success: false,
|
|
221
|
+
reason: 'connect ECONNREFUSED',
|
|
222
|
+
error: expect.any(Error),
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -23,28 +23,50 @@ const ROUTABLE_COMMANDS = [
|
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Dispatch modes for provider execution.
|
|
27
27
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* - 'interactive': Provider needs write access (build, fix, implement).
|
|
29
|
+
* Dispatched via tmux session with --dangerously-bypass-approvals-and-sandbox.
|
|
30
|
+
* Codex exec sandbox blocks disk writes — interactive mode is required for coding.
|
|
30
31
|
*
|
|
31
|
-
*
|
|
32
|
+
* - 'exec': Provider only reads (review, analysis, coverage).
|
|
33
|
+
* Dispatched via `codex exec --full-auto` or `gemini -p`. Fast, no tmux needed.
|
|
34
|
+
*
|
|
35
|
+
* - 'inline': Runs in the current Claude session (discuss, plan, docs).
|
|
36
|
+
* No external dispatch.
|
|
37
|
+
*/
|
|
38
|
+
const DISPATCH_MODES = {
|
|
39
|
+
interactive: 'interactive',
|
|
40
|
+
exec: 'exec',
|
|
41
|
+
inline: 'inline',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default routing: coding commands → codex interactive, review → codex exec,
|
|
46
|
+
* thinking commands → claude inline.
|
|
47
|
+
*
|
|
48
|
+
* Coding (write): build, quick, autofix → codex interactive (tmux)
|
|
49
|
+
* Analysis (read): review, edge-cases → codex exec
|
|
50
|
+
* Thinking: plan, discuss, docs, coverage → claude inline
|
|
51
|
+
*
|
|
52
|
+
* @type {Record<string, {models: string[], strategy: string, mode: string}>}
|
|
32
53
|
*/
|
|
33
54
|
const SHIPPED_DEFAULTS = {
|
|
34
|
-
// Coding — codex
|
|
35
|
-
'build': { models: ['codex'], strategy: 'single' },
|
|
36
|
-
'quick': { models: ['codex'], strategy: 'single' },
|
|
37
|
-
'autofix': { models: ['codex'], strategy: 'single' },
|
|
38
|
-
|
|
39
|
-
'
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
55
|
+
// Coding — codex interactive (needs disk writes, tmux session)
|
|
56
|
+
'build': { models: ['codex'], strategy: 'single', mode: 'interactive' },
|
|
57
|
+
'quick': { models: ['codex'], strategy: 'single', mode: 'interactive' },
|
|
58
|
+
'autofix': { models: ['codex'], strategy: 'single', mode: 'interactive' },
|
|
59
|
+
// Analysis — codex exec (read-only, fast)
|
|
60
|
+
'edge-cases': { models: ['codex'], strategy: 'parallel', mode: 'exec' },
|
|
61
|
+
'review': { models: ['codex', 'claude'], strategy: 'parallel', mode: 'exec' },
|
|
62
|
+
// Thinking — claude inline
|
|
63
|
+
'plan': { models: ['codex', 'claude'], strategy: 'parallel', mode: 'inline' },
|
|
64
|
+
'discuss': { models: ['claude'], strategy: 'single', mode: 'inline' },
|
|
65
|
+
'docs': { models: ['claude'], strategy: 'single', mode: 'inline' },
|
|
66
|
+
'coverage': { models: ['claude'], strategy: 'single', mode: 'exec' },
|
|
67
|
+
'test': { models: ['claude'], strategy: 'single', mode: 'inline' },
|
|
68
|
+
'design': { models: ['claude'], strategy: 'single', mode: 'inline' },
|
|
69
|
+
'vision': { models: ['claude'], strategy: 'single', mode: 'inline' },
|
|
48
70
|
};
|
|
49
71
|
|
|
50
72
|
/**
|
|
@@ -107,9 +129,10 @@ function loadProjectOverride({ projectDir, fs }) {
|
|
|
107
129
|
*/
|
|
108
130
|
function resolveRouting({ command, flagModel, projectDir = process.cwd(), homeDir = require('os').homedir(), fs = require('fs') }) {
|
|
109
131
|
// Start with shipped defaults
|
|
110
|
-
const defaultEntry = SHIPPED_DEFAULTS[command] || { models: ['claude'], strategy: 'single' };
|
|
132
|
+
const defaultEntry = SHIPPED_DEFAULTS[command] || { models: ['claude'], strategy: 'single', mode: 'inline' };
|
|
111
133
|
let models = [...defaultEntry.models];
|
|
112
134
|
let strategy = defaultEntry.strategy;
|
|
135
|
+
let mode = defaultEntry.mode || 'inline';
|
|
113
136
|
let source = 'shipped-defaults';
|
|
114
137
|
let providers;
|
|
115
138
|
const warnings = [];
|
|
@@ -136,6 +159,9 @@ function resolveRouting({ command, flagModel, projectDir = process.cwd(), homeDi
|
|
|
136
159
|
if (personalRouting.strategy) {
|
|
137
160
|
strategy = personalRouting.strategy;
|
|
138
161
|
}
|
|
162
|
+
if (personalRouting.mode) {
|
|
163
|
+
mode = personalRouting.mode;
|
|
164
|
+
}
|
|
139
165
|
source = 'personal-config';
|
|
140
166
|
}
|
|
141
167
|
}
|
|
@@ -157,6 +183,9 @@ function resolveRouting({ command, flagModel, projectDir = process.cwd(), homeDi
|
|
|
157
183
|
if (overrideEntry.strategy) {
|
|
158
184
|
strategy = overrideEntry.strategy;
|
|
159
185
|
}
|
|
186
|
+
if (overrideEntry.mode) {
|
|
187
|
+
mode = overrideEntry.mode;
|
|
188
|
+
}
|
|
160
189
|
source = 'project-override';
|
|
161
190
|
}
|
|
162
191
|
}
|
|
@@ -168,7 +197,7 @@ function resolveRouting({ command, flagModel, projectDir = process.cwd(), homeDi
|
|
|
168
197
|
source = 'flag-override';
|
|
169
198
|
}
|
|
170
199
|
|
|
171
|
-
const result = { models, strategy, source, warnings };
|
|
200
|
+
const result = { models, strategy, mode, source, warnings };
|
|
172
201
|
if (providers) {
|
|
173
202
|
result.providers = providers;
|
|
174
203
|
}
|
|
@@ -181,4 +210,5 @@ module.exports = {
|
|
|
181
210
|
loadProjectOverride,
|
|
182
211
|
SHIPPED_DEFAULTS,
|
|
183
212
|
ROUTABLE_COMMANDS,
|
|
213
|
+
DISPATCH_MODES,
|
|
184
214
|
};
|
|
@@ -27,22 +27,25 @@ function mockFs(fileMap = {}) {
|
|
|
27
27
|
describe('task-router-config', () => {
|
|
28
28
|
// ── SHIPPED_DEFAULTS ──────────────────────────────────────────────
|
|
29
29
|
describe('SHIPPED_DEFAULTS', () => {
|
|
30
|
-
it('routes coding commands to codex', () => {
|
|
31
|
-
expect(SHIPPED_DEFAULTS['build']).toEqual({ models: ['codex'], strategy: 'single' });
|
|
32
|
-
expect(SHIPPED_DEFAULTS['quick']).toEqual({ models: ['codex'], strategy: 'single' });
|
|
33
|
-
expect(SHIPPED_DEFAULTS['autofix']).toEqual({ models: ['codex'], strategy: 'single' });
|
|
34
|
-
expect(SHIPPED_DEFAULTS['edge-cases']).toEqual({ models: ['codex'], strategy: 'parallel' });
|
|
35
|
-
expect(SHIPPED_DEFAULTS['review']).toEqual({ models: ['codex', 'claude'], strategy: 'parallel' });
|
|
30
|
+
it('routes coding commands to codex interactive', () => {
|
|
31
|
+
expect(SHIPPED_DEFAULTS['build']).toEqual({ models: ['codex'], strategy: 'single', mode: 'interactive' });
|
|
32
|
+
expect(SHIPPED_DEFAULTS['quick']).toEqual({ models: ['codex'], strategy: 'single', mode: 'interactive' });
|
|
33
|
+
expect(SHIPPED_DEFAULTS['autofix']).toEqual({ models: ['codex'], strategy: 'single', mode: 'interactive' });
|
|
36
34
|
});
|
|
37
35
|
|
|
38
|
-
it('routes
|
|
39
|
-
expect(SHIPPED_DEFAULTS['
|
|
40
|
-
expect(SHIPPED_DEFAULTS['
|
|
41
|
-
expect(SHIPPED_DEFAULTS['
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
expect(SHIPPED_DEFAULTS['
|
|
36
|
+
it('routes analysis commands to exec mode', () => {
|
|
37
|
+
expect(SHIPPED_DEFAULTS['edge-cases']).toEqual({ models: ['codex'], strategy: 'parallel', mode: 'exec' });
|
|
38
|
+
expect(SHIPPED_DEFAULTS['review']).toEqual({ models: ['codex', 'claude'], strategy: 'parallel', mode: 'exec' });
|
|
39
|
+
expect(SHIPPED_DEFAULTS['coverage']).toEqual({ models: ['claude'], strategy: 'single', mode: 'exec' });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('routes thinking commands to claude inline', () => {
|
|
43
|
+
expect(SHIPPED_DEFAULTS['plan']).toEqual({ models: ['codex', 'claude'], strategy: 'parallel', mode: 'inline' });
|
|
44
|
+
expect(SHIPPED_DEFAULTS['discuss']).toEqual({ models: ['claude'], strategy: 'single', mode: 'inline' });
|
|
45
|
+
expect(SHIPPED_DEFAULTS['docs']).toEqual({ models: ['claude'], strategy: 'single', mode: 'inline' });
|
|
46
|
+
expect(SHIPPED_DEFAULTS['test']).toEqual({ models: ['claude'], strategy: 'single', mode: 'inline' });
|
|
47
|
+
expect(SHIPPED_DEFAULTS['design']).toEqual({ models: ['claude'], strategy: 'single', mode: 'inline' });
|
|
48
|
+
expect(SHIPPED_DEFAULTS['vision']).toEqual({ models: ['claude'], strategy: 'single', mode: 'inline' });
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
it('has an entry for every routable command', () => {
|
|
@@ -156,6 +159,7 @@ describe('task-router-config', () => {
|
|
|
156
159
|
expect(result).toEqual({
|
|
157
160
|
models: ['codex'],
|
|
158
161
|
strategy: 'single',
|
|
162
|
+
mode: 'interactive',
|
|
159
163
|
source: 'shipped-defaults',
|
|
160
164
|
warnings: [],
|
|
161
165
|
});
|
|
@@ -173,6 +177,7 @@ describe('task-router-config', () => {
|
|
|
173
177
|
expect(result).toEqual({
|
|
174
178
|
models: ['claude'],
|
|
175
179
|
strategy: 'single',
|
|
180
|
+
mode: 'inline',
|
|
176
181
|
source: 'shipped-defaults',
|
|
177
182
|
warnings: [],
|
|
178
183
|
});
|
|
@@ -198,6 +203,7 @@ describe('task-router-config', () => {
|
|
|
198
203
|
expect(result).toEqual({
|
|
199
204
|
models: ['codex'],
|
|
200
205
|
strategy: 'single',
|
|
206
|
+
mode: 'interactive',
|
|
201
207
|
source: 'personal-config',
|
|
202
208
|
warnings: [],
|
|
203
209
|
});
|
|
@@ -229,6 +235,7 @@ describe('task-router-config', () => {
|
|
|
229
235
|
expect(result).toEqual({
|
|
230
236
|
models: ['local'],
|
|
231
237
|
strategy: 'single',
|
|
238
|
+
mode: 'interactive',
|
|
232
239
|
source: 'project-override',
|
|
233
240
|
warnings: [],
|
|
234
241
|
});
|
|
@@ -261,6 +268,7 @@ describe('task-router-config', () => {
|
|
|
261
268
|
expect(result).toEqual({
|
|
262
269
|
models: ['gemini'],
|
|
263
270
|
strategy: 'single',
|
|
271
|
+
mode: 'interactive',
|
|
264
272
|
source: 'flag-override',
|
|
265
273
|
warnings: [],
|
|
266
274
|
});
|
|
@@ -278,6 +286,7 @@ describe('task-router-config', () => {
|
|
|
278
286
|
expect(result).toEqual({
|
|
279
287
|
models: ['claude'],
|
|
280
288
|
strategy: 'single',
|
|
289
|
+
mode: 'inline',
|
|
281
290
|
source: 'shipped-defaults',
|
|
282
291
|
warnings: [],
|
|
283
292
|
});
|
|
@@ -303,11 +312,12 @@ describe('task-router-config', () => {
|
|
|
303
312
|
expect(reviewResult).toEqual({
|
|
304
313
|
models: ['claude', 'codex'],
|
|
305
314
|
strategy: 'parallel',
|
|
315
|
+
mode: 'exec',
|
|
306
316
|
source: 'personal-config',
|
|
307
317
|
warnings: [],
|
|
308
318
|
});
|
|
309
319
|
|
|
310
|
-
// 'build' should still use its shipped default (codex/single)
|
|
320
|
+
// 'build' should still use its shipped default (codex/single/interactive)
|
|
311
321
|
const buildResult = resolveRouting({
|
|
312
322
|
command: 'build',
|
|
313
323
|
projectDir: '/project',
|
|
@@ -317,6 +327,7 @@ describe('task-router-config', () => {
|
|
|
317
327
|
expect(buildResult).toEqual({
|
|
318
328
|
models: ['codex'],
|
|
319
329
|
strategy: 'single',
|
|
330
|
+
mode: 'interactive',
|
|
320
331
|
source: 'shipped-defaults',
|
|
321
332
|
warnings: [],
|
|
322
333
|
});
|
|
@@ -388,6 +399,7 @@ describe('task-router-config', () => {
|
|
|
388
399
|
expect(result).toEqual({
|
|
389
400
|
models: ['local'],
|
|
390
401
|
strategy: 'single',
|
|
402
|
+
mode: 'interactive',
|
|
391
403
|
source: 'project-override',
|
|
392
404
|
warnings: [],
|
|
393
405
|
});
|
|
@@ -428,6 +440,7 @@ describe('task-router-config', () => {
|
|
|
428
440
|
expect(result).toEqual({
|
|
429
441
|
models: ['codex'],
|
|
430
442
|
strategy: 'single',
|
|
443
|
+
mode: 'interactive',
|
|
431
444
|
source: 'shipped-defaults',
|
|
432
445
|
warnings: [expect.stringContaining('/home/user/.tlc/config.json')],
|
|
433
446
|
});
|
|
@@ -448,6 +461,7 @@ describe('task-router-config', () => {
|
|
|
448
461
|
expect(result).toEqual({
|
|
449
462
|
models: ['codex'],
|
|
450
463
|
strategy: 'single',
|
|
464
|
+
mode: 'interactive',
|
|
451
465
|
source: 'shipped-defaults',
|
|
452
466
|
warnings: [expect.stringContaining('/project/.tlc.json')],
|
|
453
467
|
});
|