tlc-claude-code 2.4.1 → 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/autofix.md +70 -6
- package/.claude/commands/tlc/build.md +138 -6
- package/.claude/commands/tlc/coverage.md +70 -6
- package/.claude/commands/tlc/discuss.md +244 -129
- package/.claude/commands/tlc/docs.md +70 -6
- package/.claude/commands/tlc/e2e-verify.md +1 -1
- package/.claude/commands/tlc/edge-cases.md +70 -6
- package/.claude/commands/tlc/plan.md +147 -8
- package/.claude/commands/tlc/quick.md +70 -6
- package/.claude/commands/tlc/review.md +70 -6
- 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 +116 -0
- package/server/lib/routing-preamble.test.js +266 -0
- package/server/lib/task-router-config.js +35 -14
- package/server/lib/task-router-config.test.js +77 -13
|
@@ -10,12 +10,76 @@ This command supports multi-model routing via `~/.tlc/config.json`.
|
|
|
10
10
|
|
|
11
11
|
1. Read routing config:
|
|
12
12
|
```bash
|
|
13
|
-
node -e "
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
node -e "const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
function readJson(filePath, fileSystem) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fileSystem.readFileSync(filePath, 'utf8'));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function loadPersonalConfig(options) {
|
|
24
|
+
const configPath = path.join(options.homeDir, '.tlc', 'config.json');
|
|
25
|
+
return readJson(configPath, options.fs);
|
|
26
|
+
}
|
|
27
|
+
function loadProjectOverride(options) {
|
|
28
|
+
const configPath = path.join(options.projectDir, '.tlc.json');
|
|
29
|
+
const data = readJson(configPath, options.fs);
|
|
30
|
+
return data && data.task_routing_override ? data.task_routing_override : null;
|
|
31
|
+
}
|
|
32
|
+
function resolveRouting(options) {
|
|
33
|
+
let models = ['claude'];
|
|
34
|
+
let strategy = 'single';
|
|
35
|
+
let source = 'shipped-defaults';
|
|
36
|
+
let providers;
|
|
37
|
+
const personal = loadPersonalConfig({ homeDir: options.homeDir, fs: options.fs });
|
|
38
|
+
if (personal) {
|
|
39
|
+
if (personal.model_providers) {
|
|
40
|
+
providers = personal.model_providers;
|
|
41
|
+
}
|
|
42
|
+
const personalRouting = personal.task_routing && personal.task_routing[options.command];
|
|
43
|
+
if (personalRouting) {
|
|
44
|
+
if (Array.isArray(personalRouting.models)) {
|
|
45
|
+
models = personalRouting.models.slice();
|
|
46
|
+
} else if (typeof personalRouting.model === 'string') {
|
|
47
|
+
models = [personalRouting.model];
|
|
48
|
+
}
|
|
49
|
+
if (personalRouting.strategy) {
|
|
50
|
+
strategy = personalRouting.strategy;
|
|
51
|
+
}
|
|
52
|
+
source = 'personal-config';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const projectOverride = loadProjectOverride({ projectDir: options.projectDir, fs: options.fs });
|
|
56
|
+
if (projectOverride) {
|
|
57
|
+
const overrideEntry = projectOverride[options.command];
|
|
58
|
+
if (overrideEntry) {
|
|
59
|
+
if (Array.isArray(overrideEntry.models)) {
|
|
60
|
+
models = overrideEntry.models.slice();
|
|
61
|
+
} else if (typeof overrideEntry.model === 'string') {
|
|
62
|
+
models = [overrideEntry.model];
|
|
63
|
+
}
|
|
64
|
+
if (overrideEntry.strategy) {
|
|
65
|
+
strategy = overrideEntry.strategy;
|
|
66
|
+
}
|
|
67
|
+
source = 'project-override';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (options.flagModel) {
|
|
71
|
+
models = [options.flagModel];
|
|
72
|
+
strategy = 'single';
|
|
73
|
+
source = 'flag-override';
|
|
74
|
+
}
|
|
75
|
+
const result = { models, strategy, source };
|
|
76
|
+
if (providers) {
|
|
77
|
+
result.providers = providers;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
const result = resolveRouting({ command: \"discuss\", flagModel: process.argv[1], projectDir: process.cwd(), homeDir: process.env.HOME || os.homedir(), fs });
|
|
82
|
+
process.stdout.write(JSON.stringify(result));" 2>/dev/null
|
|
19
83
|
```
|
|
20
84
|
|
|
21
85
|
2. If `models[0]` is NOT `claude` (i.e., routed to external model):
|
|
@@ -35,7 +99,13 @@ This command supports multi-model routing via `~/.tlc/config.json`.
|
|
|
35
99
|
|
|
36
100
|
## What This Does
|
|
37
101
|
|
|
38
|
-
|
|
102
|
+
Runs a short implementation interview for the current phase using a silent expansion workflow:
|
|
103
|
+
- First scan the phase goal, codebase patterns, stack, and recent changes
|
|
104
|
+
- Then propose a concrete implementation approach before asking questions
|
|
105
|
+
- Ask only questions that materially improve the plan
|
|
106
|
+
- Save decisions, trade-offs, and constraints to `.planning/phases/{N}-DISCUSSION.md`
|
|
107
|
+
|
|
108
|
+
This command is not a blank-page brainstorming prompt. The agent should do the initial thinking first, then ask the user to confirm or correct the proposal.
|
|
39
109
|
|
|
40
110
|
## Usage
|
|
41
111
|
|
|
@@ -43,174 +113,219 @@ Gathers your preferences for HOW a phase should be built through adaptive questi
|
|
|
43
113
|
/tlc:discuss [phase_number]
|
|
44
114
|
```
|
|
45
115
|
|
|
46
|
-
If no phase number, auto-detect current phase from ROADMAP.md
|
|
116
|
+
If no phase number, auto-detect current phase from `.planning/ROADMAP.md`.
|
|
47
117
|
|
|
48
118
|
## Process
|
|
49
119
|
|
|
50
|
-
### Step 1:
|
|
120
|
+
### Step 1: Scan Context First
|
|
51
121
|
|
|
52
|
-
|
|
53
|
-
- Phase name and goal
|
|
54
|
-
- What comes before (context)
|
|
55
|
-
- What comes after (constraints)
|
|
122
|
+
Before asking anything, silently inspect the implementation context for the phase.
|
|
56
123
|
|
|
57
|
-
|
|
124
|
+
Read:
|
|
125
|
+
- `.planning/ROADMAP.md` - current phase name, goal, and surrounding phase context
|
|
126
|
+
- `PROJECT.md` - product constraints, stack, architecture, conventions
|
|
127
|
+
- `.planning/phases/{N}-DISCUSSION.md` if it exists - previously made decisions and unresolved gaps
|
|
128
|
+
- Relevant code for the phase area - existing modules, patterns, folder structure, interfaces, tests
|
|
58
129
|
|
|
59
|
-
|
|
130
|
+
Also inspect:
|
|
131
|
+
- Tech stack already in use
|
|
132
|
+
- Existing implementation patterns that should be preserved
|
|
133
|
+
- Recent git changes that affect the phase direction, integration surface, or constraints
|
|
60
134
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
3) Minimal styling (Tailwind only)
|
|
67
|
-
|
|
68
|
-
State management?
|
|
69
|
-
1) React state + context
|
|
70
|
-
2) Zustand / Jotai
|
|
71
|
-
3) Redux
|
|
72
|
-
4) Server state only (React Query)
|
|
73
|
-
|
|
74
|
-
Form handling?
|
|
75
|
-
1) React Hook Form
|
|
76
|
-
2) Formik
|
|
77
|
-
3) Native forms
|
|
78
|
-
```
|
|
135
|
+
The scan should answer:
|
|
136
|
+
- What is this phase trying to accomplish?
|
|
137
|
+
- What implementation shape is most consistent with the current codebase?
|
|
138
|
+
- What decisions are already implied by the stack or recent work?
|
|
139
|
+
- What is still genuinely ambiguous?
|
|
79
140
|
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
API style?
|
|
83
|
-
1) REST
|
|
84
|
-
2) tRPC
|
|
85
|
-
3) GraphQL
|
|
86
|
-
|
|
87
|
-
Validation approach?
|
|
88
|
-
1) Zod schemas
|
|
89
|
-
2) Yup
|
|
90
|
-
3) Manual validation
|
|
91
|
-
|
|
92
|
-
Error handling?
|
|
93
|
-
1) Return error objects
|
|
94
|
-
2) Throw exceptions
|
|
95
|
-
3) Result types (Ok/Err)
|
|
96
|
-
```
|
|
141
|
+
Do not ask the user to restate information that can be learned from the repo.
|
|
97
142
|
|
|
98
|
-
|
|
99
|
-
```
|
|
100
|
-
Query approach?
|
|
101
|
-
1) Raw SQL
|
|
102
|
-
2) Query builder (Kysely, Knex)
|
|
103
|
-
3) ORM (Prisma, Drizzle)
|
|
104
|
-
|
|
105
|
-
Migration strategy?
|
|
106
|
-
1) Schema-first (Prisma migrate)
|
|
107
|
-
2) Code-first
|
|
108
|
-
3) Manual SQL migrations
|
|
109
|
-
```
|
|
143
|
+
### Step 2: Expand Before Asking
|
|
110
144
|
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
Auth provider?
|
|
114
|
-
1) Custom (JWT + bcrypt)
|
|
115
|
-
2) NextAuth / Auth.js
|
|
116
|
-
3) Clerk / Auth0 / Supabase Auth
|
|
117
|
-
|
|
118
|
-
Session storage?
|
|
119
|
-
1) JWT in httpOnly cookie
|
|
120
|
-
2) Database sessions
|
|
121
|
-
3) Redis sessions
|
|
122
|
-
```
|
|
145
|
+
After scanning, propose a full default implementation approach before any questions.
|
|
123
146
|
|
|
124
|
-
|
|
147
|
+
The proposal should be concrete enough that the user can react to it:
|
|
148
|
+
- Architecture and boundaries
|
|
149
|
+
- Main components/modules/files involved
|
|
150
|
+
- Data flow or request flow
|
|
151
|
+
- Key trade-offs
|
|
152
|
+
- Constraints or risks that seem likely
|
|
153
|
+
- Assumptions inferred from the stack and recent code
|
|
125
154
|
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
The proposal should sound like:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
Based on the roadmap goal, current patterns, and recent changes, I would implement this as:
|
|
159
|
+
- ...
|
|
160
|
+
- ...
|
|
128
161
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
- Offline behavior?
|
|
133
|
-
- Rate limiting?
|
|
162
|
+
The main open decisions I still need to confirm are:
|
|
163
|
+
1. ...
|
|
164
|
+
2. ...
|
|
134
165
|
```
|
|
135
166
|
|
|
136
|
-
|
|
167
|
+
Do not ask "What do you want?" or "How should we build this?" before giving a recommendation.
|
|
137
168
|
|
|
138
|
-
|
|
139
|
-
|
|
169
|
+
### Step 3: Filter Questions Aggressively
|
|
170
|
+
|
|
171
|
+
Only ask a question if removing it would degrade the resulting `DISCUSSION.md` or weaken the next `/tlc:plan`.
|
|
172
|
+
|
|
173
|
+
Each question must extract substance such as:
|
|
174
|
+
- Architectural decisions
|
|
175
|
+
- Integration boundaries
|
|
176
|
+
- Operational constraints
|
|
177
|
+
- Failure-mode handling
|
|
178
|
+
- Trade-offs that materially change implementation
|
|
179
|
+
|
|
180
|
+
Do not ask about preferences that do not affect the outcome, such as:
|
|
181
|
+
- Naming choices
|
|
182
|
+
- Formatting
|
|
183
|
+
- Minor stylistic conventions
|
|
184
|
+
- Options already dictated by the existing stack unless there is a real conflict
|
|
185
|
+
|
|
186
|
+
Bad:
|
|
187
|
+
- "What database should we use?"
|
|
188
|
+
- "How do you want this named?"
|
|
189
|
+
|
|
190
|
+
Good:
|
|
191
|
+
- "I would keep this on SQLite with WAL mode because the project already uses local-first storage and recent work assumes file-backed state. Any reason to use a different persistence strategy?"
|
|
192
|
+
- "I would model this as a service plus thin CLI adapter to match the existing command architecture. Good, or do you need a different boundary for reuse/testing?"
|
|
140
193
|
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
-
|
|
194
|
+
### Step 4: Ask Propose-Then-Confirm Questions
|
|
195
|
+
|
|
196
|
+
Every question must include:
|
|
197
|
+
- The recommended answer
|
|
198
|
+
- The evidence behind that recommendation
|
|
199
|
+
- The decision to confirm or correct
|
|
200
|
+
|
|
201
|
+
Question style:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
1. Persistence
|
|
205
|
+
I would use SQLite with WAL mode because the current stack is local-first and this phase needs durable state with low operational overhead.
|
|
206
|
+
Good to keep that, or do you need a different storage model for sync/concurrency reasons?
|
|
145
207
|
```
|
|
146
208
|
|
|
147
|
-
|
|
209
|
+
Prefer questions that let the user confirm, reject, or constrain the recommendation.
|
|
210
|
+
|
|
211
|
+
### Step 5: Calibrate Depth
|
|
212
|
+
|
|
213
|
+
Choose question count based on phase complexity.
|
|
148
214
|
|
|
149
|
-
|
|
215
|
+
For simple phases:
|
|
216
|
+
- Ask 2-3 questions maximum
|
|
217
|
+
- Focus on the few decisions that materially change scope or architecture
|
|
218
|
+
|
|
219
|
+
For complex phases:
|
|
220
|
+
- Ask 5-8 questions
|
|
221
|
+
- Cover the major decision surfaces only
|
|
222
|
+
|
|
223
|
+
Complexity signals include:
|
|
224
|
+
- Multiple subsystems or services
|
|
225
|
+
- New infrastructure or persistence
|
|
226
|
+
- Cross-cutting auth/security concerns
|
|
227
|
+
- Large UX flows with state transitions
|
|
228
|
+
- Significant ambiguity in roadmap wording
|
|
229
|
+
|
|
230
|
+
Do not turn straightforward phases into long interviews.
|
|
231
|
+
|
|
232
|
+
### Step 6: Iterate When Answers Reveal New Gaps
|
|
233
|
+
|
|
234
|
+
If the user’s answers expose a new implementation gap, contradiction, or constraint, ask targeted follow-up questions.
|
|
235
|
+
|
|
236
|
+
Follow-up questions must also use the same pattern:
|
|
237
|
+
- explain the inferred implication
|
|
238
|
+
- recommend the likely answer
|
|
239
|
+
- ask for confirmation/correction
|
|
240
|
+
|
|
241
|
+
Stop when the remaining uncertainty no longer meaningfully affects planning.
|
|
242
|
+
|
|
243
|
+
### Step 7: Write Concrete Decisions
|
|
244
|
+
|
|
245
|
+
Create or update `.planning/phases/{N}-DISCUSSION.md`.
|
|
246
|
+
|
|
247
|
+
The file must contain decisions and rationale, not a transcript of the conversation.
|
|
248
|
+
|
|
249
|
+
Use this structure:
|
|
150
250
|
|
|
151
251
|
```markdown
|
|
152
252
|
# Phase {N}: {Name} - Discussion
|
|
153
253
|
|
|
154
|
-
##
|
|
254
|
+
## Proposed Approach
|
|
155
255
|
|
|
156
|
-
|
|
157
|
-
|----------|--------|-------|
|
|
158
|
-
| State management | Zustand | Simple, minimal boilerplate |
|
|
159
|
-
| Form handling | React Hook Form | Good validation support |
|
|
160
|
-
| API style | tRPC | Type-safe, good DX |
|
|
256
|
+
[Short summary of the implementation direction that emerged from the scan and discussion]
|
|
161
257
|
|
|
162
|
-
##
|
|
258
|
+
## Decisions
|
|
163
259
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
260
|
+
| Decision | Recommendation | Confirmed / Adjusted | Why It Matters |
|
|
261
|
+
|----------|----------------|----------------------|----------------|
|
|
262
|
+
| Service boundary | Core service with thin CLI adapter | Confirmed | Matches existing command architecture and keeps logic testable |
|
|
263
|
+
| Persistence | SQLite with WAL mode | Adjusted: in-memory for this phase | Avoids premature persistence until sync arrives |
|
|
167
264
|
|
|
168
265
|
## Constraints
|
|
169
266
|
|
|
170
|
-
- Must
|
|
171
|
-
-
|
|
267
|
+
- Must preserve existing module boundaries
|
|
268
|
+
- Must work with current local-first workflow
|
|
269
|
+
- Must remain compatible with `/tlc:plan`
|
|
172
270
|
|
|
173
|
-
##
|
|
271
|
+
## Risks / Follow-Ups
|
|
174
272
|
|
|
175
|
-
|
|
176
|
-
|
|
273
|
+
- Migration path needed if persistence changes in later phases
|
|
274
|
+
- Error handling contract should be validated in planning
|
|
177
275
|
|
|
178
|
-
|
|
276
|
+
## Planning Notes
|
|
179
277
|
|
|
278
|
+
- Call out concrete file/module areas likely to change
|
|
279
|
+
- Record unresolved items only if they materially affect planning
|
|
180
280
|
```
|
|
181
|
-
Discussion saved to .planning/phases/{N}-DISCUSSION.md
|
|
182
281
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
282
|
+
Guidelines for the output:
|
|
283
|
+
- Prefer concrete decisions over raw notes
|
|
284
|
+
- Capture trade-offs and rationale briefly
|
|
285
|
+
- Record unresolved questions only if they still matter for `/tlc:plan`
|
|
286
|
+
- Do not dump the full interview transcript
|
|
187
287
|
|
|
188
|
-
|
|
288
|
+
### Step 8: Confirm Next Step
|
|
289
|
+
|
|
290
|
+
After writing the file, report:
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
Discussion saved to .planning/phases/{N}-DISCUSSION.md
|
|
189
294
|
|
|
295
|
+
Ready to continue with /tlc:plan?
|
|
296
|
+
1) Yes
|
|
297
|
+
2) No
|
|
190
298
|
```
|
|
191
|
-
> /tlc:discuss 2
|
|
192
299
|
|
|
193
|
-
|
|
300
|
+
## Interview Rules
|
|
194
301
|
|
|
195
|
-
|
|
302
|
+
- Scan before asking
|
|
303
|
+
- Propose before questioning
|
|
304
|
+
- Ask only high-leverage questions
|
|
305
|
+
- Recommend an answer in every question
|
|
306
|
+
- Prefer substance over preference
|
|
307
|
+
- Adapt question count to phase complexity
|
|
308
|
+
- Add follow-ups only when answers reveal real new gaps
|
|
309
|
+
- Produce a decision document, not a transcript
|
|
196
310
|
|
|
197
|
-
|
|
198
|
-
1) React state + context
|
|
199
|
-
2) Zustand
|
|
200
|
-
3) Server state only (React Query)
|
|
311
|
+
## Example Opening
|
|
201
312
|
|
|
202
|
-
|
|
313
|
+
```text
|
|
314
|
+
> /tlc:discuss 2
|
|
315
|
+
|
|
316
|
+
Phase 2: User Dashboard
|
|
203
317
|
|
|
204
|
-
|
|
205
|
-
1) REST + fetch
|
|
206
|
-
2) tRPC
|
|
207
|
-
3) React Query + REST
|
|
318
|
+
Based on the roadmap goal, the existing React + tRPC patterns, and recent changes in the dashboard data layer, I would implement this as a server-driven dashboard shell with a thin client state layer for filters and optimistic updates.
|
|
208
319
|
|
|
209
|
-
|
|
320
|
+
I would keep data fetching in the existing query pattern, add one aggregation endpoint for summary cards, and keep widget rendering as local components rather than introducing a new layout system.
|
|
210
321
|
|
|
211
|
-
|
|
322
|
+
The main decisions I still need to confirm are:
|
|
212
323
|
|
|
213
|
-
|
|
324
|
+
1. Dashboard state boundary
|
|
325
|
+
I would keep filter/sort state in URL params plus local component state so it stays shareable without introducing a global store.
|
|
326
|
+
Good, or do you need cross-page state that justifies Zustand?
|
|
214
327
|
|
|
215
|
-
|
|
328
|
+
2. Empty/loading/error behavior
|
|
329
|
+
I would standardize on skeleton loading plus inline empty states because that matches current UI behavior and keeps failure modes visible per widget.
|
|
330
|
+
Good, or do you need a page-level loading/error shell?
|
|
216
331
|
```
|
|
@@ -10,12 +10,76 @@ This command supports multi-model routing via `~/.tlc/config.json`.
|
|
|
10
10
|
|
|
11
11
|
1. Read routing config:
|
|
12
12
|
```bash
|
|
13
|
-
node -e "
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
node -e "const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
function readJson(filePath, fileSystem) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fileSystem.readFileSync(filePath, 'utf8'));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function loadPersonalConfig(options) {
|
|
24
|
+
const configPath = path.join(options.homeDir, '.tlc', 'config.json');
|
|
25
|
+
return readJson(configPath, options.fs);
|
|
26
|
+
}
|
|
27
|
+
function loadProjectOverride(options) {
|
|
28
|
+
const configPath = path.join(options.projectDir, '.tlc.json');
|
|
29
|
+
const data = readJson(configPath, options.fs);
|
|
30
|
+
return data && data.task_routing_override ? data.task_routing_override : null;
|
|
31
|
+
}
|
|
32
|
+
function resolveRouting(options) {
|
|
33
|
+
let models = ['claude'];
|
|
34
|
+
let strategy = 'single';
|
|
35
|
+
let source = 'shipped-defaults';
|
|
36
|
+
let providers;
|
|
37
|
+
const personal = loadPersonalConfig({ homeDir: options.homeDir, fs: options.fs });
|
|
38
|
+
if (personal) {
|
|
39
|
+
if (personal.model_providers) {
|
|
40
|
+
providers = personal.model_providers;
|
|
41
|
+
}
|
|
42
|
+
const personalRouting = personal.task_routing && personal.task_routing[options.command];
|
|
43
|
+
if (personalRouting) {
|
|
44
|
+
if (Array.isArray(personalRouting.models)) {
|
|
45
|
+
models = personalRouting.models.slice();
|
|
46
|
+
} else if (typeof personalRouting.model === 'string') {
|
|
47
|
+
models = [personalRouting.model];
|
|
48
|
+
}
|
|
49
|
+
if (personalRouting.strategy) {
|
|
50
|
+
strategy = personalRouting.strategy;
|
|
51
|
+
}
|
|
52
|
+
source = 'personal-config';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const projectOverride = loadProjectOverride({ projectDir: options.projectDir, fs: options.fs });
|
|
56
|
+
if (projectOverride) {
|
|
57
|
+
const overrideEntry = projectOverride[options.command];
|
|
58
|
+
if (overrideEntry) {
|
|
59
|
+
if (Array.isArray(overrideEntry.models)) {
|
|
60
|
+
models = overrideEntry.models.slice();
|
|
61
|
+
} else if (typeof overrideEntry.model === 'string') {
|
|
62
|
+
models = [overrideEntry.model];
|
|
63
|
+
}
|
|
64
|
+
if (overrideEntry.strategy) {
|
|
65
|
+
strategy = overrideEntry.strategy;
|
|
66
|
+
}
|
|
67
|
+
source = 'project-override';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (options.flagModel) {
|
|
71
|
+
models = [options.flagModel];
|
|
72
|
+
strategy = 'single';
|
|
73
|
+
source = 'flag-override';
|
|
74
|
+
}
|
|
75
|
+
const result = { models, strategy, source };
|
|
76
|
+
if (providers) {
|
|
77
|
+
result.providers = providers;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
const result = resolveRouting({ command: \"docs\", flagModel: process.argv[1], projectDir: process.cwd(), homeDir: process.env.HOME || os.homedir(), fs });
|
|
82
|
+
process.stdout.write(JSON.stringify(result));" 2>/dev/null
|
|
19
83
|
```
|
|
20
84
|
|
|
21
85
|
2. If `models[0]` is NOT `claude` (i.e., routed to external model):
|
|
@@ -78,7 +78,7 @@ await page.screenshot({ path: '/tmp/tlc-screenshots/login.png', fullPage: true }
|
|
|
78
78
|
|
|
79
79
|
// After login
|
|
80
80
|
await page.fill('[data-testid="email"]', 'user@local.com');
|
|
81
|
-
await page.fill('[data-testid="password"]', '
|
|
81
|
+
await page.fill('[data-testid="password"]', 'REDACTED');
|
|
82
82
|
await page.click('[data-testid="login-btn"]');
|
|
83
83
|
await page.waitForURL('**/dashboard**');
|
|
84
84
|
await page.screenshot({ path: '/tmp/tlc-screenshots/after-login.png', fullPage: true });
|
|
@@ -10,12 +10,76 @@ This command supports multi-model routing via `~/.tlc/config.json`.
|
|
|
10
10
|
|
|
11
11
|
1. Read routing config:
|
|
12
12
|
```bash
|
|
13
|
-
node -e "
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
node -e "const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
function readJson(filePath, fileSystem) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fileSystem.readFileSync(filePath, 'utf8'));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function loadPersonalConfig(options) {
|
|
24
|
+
const configPath = path.join(options.homeDir, '.tlc', 'config.json');
|
|
25
|
+
return readJson(configPath, options.fs);
|
|
26
|
+
}
|
|
27
|
+
function loadProjectOverride(options) {
|
|
28
|
+
const configPath = path.join(options.projectDir, '.tlc.json');
|
|
29
|
+
const data = readJson(configPath, options.fs);
|
|
30
|
+
return data && data.task_routing_override ? data.task_routing_override : null;
|
|
31
|
+
}
|
|
32
|
+
function resolveRouting(options) {
|
|
33
|
+
let models = ['claude'];
|
|
34
|
+
let strategy = 'single';
|
|
35
|
+
let source = 'shipped-defaults';
|
|
36
|
+
let providers;
|
|
37
|
+
const personal = loadPersonalConfig({ homeDir: options.homeDir, fs: options.fs });
|
|
38
|
+
if (personal) {
|
|
39
|
+
if (personal.model_providers) {
|
|
40
|
+
providers = personal.model_providers;
|
|
41
|
+
}
|
|
42
|
+
const personalRouting = personal.task_routing && personal.task_routing[options.command];
|
|
43
|
+
if (personalRouting) {
|
|
44
|
+
if (Array.isArray(personalRouting.models)) {
|
|
45
|
+
models = personalRouting.models.slice();
|
|
46
|
+
} else if (typeof personalRouting.model === 'string') {
|
|
47
|
+
models = [personalRouting.model];
|
|
48
|
+
}
|
|
49
|
+
if (personalRouting.strategy) {
|
|
50
|
+
strategy = personalRouting.strategy;
|
|
51
|
+
}
|
|
52
|
+
source = 'personal-config';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const projectOverride = loadProjectOverride({ projectDir: options.projectDir, fs: options.fs });
|
|
56
|
+
if (projectOverride) {
|
|
57
|
+
const overrideEntry = projectOverride[options.command];
|
|
58
|
+
if (overrideEntry) {
|
|
59
|
+
if (Array.isArray(overrideEntry.models)) {
|
|
60
|
+
models = overrideEntry.models.slice();
|
|
61
|
+
} else if (typeof overrideEntry.model === 'string') {
|
|
62
|
+
models = [overrideEntry.model];
|
|
63
|
+
}
|
|
64
|
+
if (overrideEntry.strategy) {
|
|
65
|
+
strategy = overrideEntry.strategy;
|
|
66
|
+
}
|
|
67
|
+
source = 'project-override';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (options.flagModel) {
|
|
71
|
+
models = [options.flagModel];
|
|
72
|
+
strategy = 'single';
|
|
73
|
+
source = 'flag-override';
|
|
74
|
+
}
|
|
75
|
+
const result = { models, strategy, source };
|
|
76
|
+
if (providers) {
|
|
77
|
+
result.providers = providers;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
const result = resolveRouting({ command: \"edge-cases\", flagModel: process.argv[1], projectDir: process.cwd(), homeDir: process.env.HOME || os.homedir(), fs });
|
|
82
|
+
process.stdout.write(JSON.stringify(result));" 2>/dev/null
|
|
19
83
|
```
|
|
20
84
|
|
|
21
85
|
2. If `models[0]` is NOT `claude` (i.e., routed to external model):
|