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.
Files changed (34) hide show
  1. package/.claude/commands/tlc/autofix.md +70 -6
  2. package/.claude/commands/tlc/build.md +138 -6
  3. package/.claude/commands/tlc/coverage.md +70 -6
  4. package/.claude/commands/tlc/discuss.md +244 -129
  5. package/.claude/commands/tlc/docs.md +70 -6
  6. package/.claude/commands/tlc/e2e-verify.md +1 -1
  7. package/.claude/commands/tlc/edge-cases.md +70 -6
  8. package/.claude/commands/tlc/plan.md +147 -8
  9. package/.claude/commands/tlc/quick.md +70 -6
  10. package/.claude/commands/tlc/review.md +70 -6
  11. package/.claude/commands/tlc/tlc.md +204 -473
  12. package/CLAUDE.md +6 -5
  13. package/package.json +4 -1
  14. package/scripts/dev-link.sh +29 -0
  15. package/scripts/test-package.sh +54 -0
  16. package/scripts/version-sync.js +42 -0
  17. package/scripts/version-sync.test.js +100 -0
  18. package/server/lib/model-router.js +11 -2
  19. package/server/lib/model-router.test.js +27 -1
  20. package/server/lib/orchestration/codex-orchestrator.js +185 -0
  21. package/server/lib/orchestration/codex-orchestrator.test.js +221 -0
  22. package/server/lib/orchestration/dep-linker.js +61 -0
  23. package/server/lib/orchestration/dep-linker.test.js +174 -0
  24. package/server/lib/router-config.js +18 -3
  25. package/server/lib/router-config.test.js +57 -1
  26. package/server/lib/routing/index.js +34 -0
  27. package/server/lib/routing/index.test.js +33 -0
  28. package/server/lib/routing-command.js +11 -2
  29. package/server/lib/routing-command.test.js +39 -1
  30. package/server/lib/routing-preamble.integration.test.js +319 -0
  31. package/server/lib/routing-preamble.js +116 -0
  32. package/server/lib/routing-preamble.test.js +266 -0
  33. package/server/lib/task-router-config.js +35 -14
  34. 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
- const { resolveRouting } = require('./server/lib/task-router-config');
15
- const flagModel = (process.argv.find(a => a.startsWith("--model")) || "").replace("--model=", "").replace("--model ", "") || null;
16
- const r = resolveRouting({ command: 'discuss', flagModel, projectDir: process.cwd(), homeDir: process.env.HOME });
17
- console.log(JSON.stringify(r));
18
- " 2>/dev/null
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
- Gathers your preferences for HOW a phase should be built through adaptive questioning. Saves decisions to guide planning and test writing.
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: Load Phase Context
120
+ ### Step 1: Scan Context First
51
121
 
52
- Read from `.planning/ROADMAP.md` to get:
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
- ### Step 2: Adaptive Questioning
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
- Ask about implementation preferences. Adapt questions based on phase type.
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
- **For UI/Frontend phases:**
62
- ```
63
- Layout approach?
64
- 1) Component library (shadcn, MUI, etc.)
65
- 2) Custom components
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
- **For API/Backend phases:**
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
- **For Data/Database phases:**
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
- **For Auth phases:**
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
- ### Step 3: Capture Edge Cases
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
- What edge cases should we handle?
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
- - Empty states?
130
- - Error states?
131
- - Loading states?
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
- ### Step 4: Capture Constraints
167
+ Do not ask "What do you want?" or "How should we build this?" before giving a recommendation.
137
168
 
138
- ```
139
- Any constraints or requirements?
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
- - Performance targets?
142
- - Accessibility requirements?
143
- - Browser support?
144
- - Mobile considerations?
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
- ### Step 5: Save Discussion
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
- Create `.planning/phases/{N}-DISCUSSION.md`:
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
- ## Implementation Preferences
254
+ ## Proposed Approach
155
255
 
156
- | Decision | Choice | Notes |
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
- ## Edge Cases to Handle
258
+ ## Decisions
163
259
 
164
- - [ ] Empty state when no data
165
- - [ ] Error toast on API failure
166
- - [ ] Optimistic updates for better UX
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 work on mobile
171
- - Target 100ms API response time
267
+ - Must preserve existing module boundaries
268
+ - Must work with current local-first workflow
269
+ - Must remain compatible with `/tlc:plan`
172
270
 
173
- ## Notes
271
+ ## Risks / Follow-Ups
174
272
 
175
- [Any additional context from discussion]
176
- ```
273
+ - Migration path needed if persistence changes in later phases
274
+ - Error handling contract should be validated in planning
177
275
 
178
- ### Step 6: Confirm and Continue
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
- Ready to plan this phase?
184
- 1) Yes, continue to /tlc:plan
185
- 2) No, I'll plan later
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
- ## Example
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
- Phase 2: User Dashboard
300
+ ## Interview Rules
194
301
 
195
- Let's discuss how to build this.
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
- State management approach?
198
- 1) React state + context
199
- 2) Zustand
200
- 3) Server state only (React Query)
311
+ ## Example Opening
201
312
 
202
- > 3
313
+ ```text
314
+ > /tlc:discuss 2
315
+
316
+ Phase 2: User Dashboard
203
317
 
204
- Data fetching?
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
- > 2
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
- [...continues until preferences captured...]
322
+ The main decisions I still need to confirm are:
212
323
 
213
- Discussion saved.
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
- Ready to plan? (Y/n)
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
- const { resolveRouting } = require('./server/lib/task-router-config');
15
- const flagModel = (process.argv.find(a => a.startsWith("--model")) || "").replace("--model=", "").replace("--model ", "") || null;
16
- const r = resolveRouting({ command: 'docs', flagModel, projectDir: process.cwd(), homeDir: process.env.HOME });
17
- console.log(JSON.stringify(r));
18
- " 2>/dev/null
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"]', '2026rocks');
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
- const { resolveRouting } = require('./server/lib/task-router-config');
15
- const flagModel = (process.argv.find(a => a.startsWith("--model")) || "").replace("--model=", "").replace("--model ", "") || null;
16
- const r = resolveRouting({ command: 'edge-cases', flagModel, projectDir: process.cwd(), homeDir: process.env.HOME });
17
- console.log(JSON.stringify(r));
18
- " 2>/dev/null
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):