Skip to content

Commit a24112b

Browse files
authored
Merge pull request #16 from itsflaid/codex/add-model-routing-config
Add command-based AI model routing
2 parents 22a8501 + 53c1f21 commit a24112b

12 files changed

Lines changed: 281 additions & 21 deletions

File tree

PRD.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ MVP success means DevMap can:
251251

252252
## 9. Core Commands — MVP
253253

254-
Four commands. No more, no less.
254+
Four core project commands plus one configuration command.
255255

256256
> Nothing removed. Nothing added until MVP is shipped.
257257
@@ -422,6 +422,21 @@ No issues found.
422422

423423
---
424424

425+
### `devmap config model`
426+
427+
Set a global Groq model override or restore automatic command-based routing.
428+
429+
```bash
430+
devmap config model llama-3.1-8b-instant
431+
devmap config model openai/gpt-oss-120b
432+
devmap config model auto
433+
```
434+
435+
The override applies to AI-powered commands. `auto` restores the defaults in
436+
the model routing table.
437+
438+
---
439+
425440
## 10. Generated Files
426441

427442
DevMap uses generated files to create reusable context for humans and AI agents.
@@ -538,9 +553,10 @@ ai/
538553

539554
| Command | Model | Reason |
540555
|---|---|---|
541-
| `analyze`, `ask` | `openai/gpt-oss-20b` | Production model, fast and cost-efficient |
542-
| `analyze --deep` | `llama-3.3-70b-versatile` | Better reasoning for detailed explanations |
543-
| Fallback | `llama-3.3-70b-versatile` | Production fallback when primary model is unavailable |
556+
| `ask` | `llama-3.1-8b-instant` | Fast model for focused codebase questions |
557+
| `analyze` | `openai/gpt-oss-20b` | Balanced architecture interpretation |
558+
| `analyze --deep` | `openai/gpt-oss-120b` | Heavy cross-module reasoning |
559+
| Fallback | `openai/gpt-oss-20b` | Production fallback when a different primary model is unavailable |
544560

545561
If a model becomes unavailable, DevMap gracefully falls back. No raw provider errors shown to users.
546562

docs/architecture.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,13 +491,16 @@ MVP default model routing:
491491

492492
| Command | Model |
493493
| ---------------- | ------------------------- |
494+
| `ask` | `llama-3.1-8b-instant` |
494495
| `analyze` | `openai/gpt-oss-20b` |
495-
| `ask` | `openai/gpt-oss-20b` |
496-
| `analyze --deep` | `llama-3.3-70b-versatile` |
497-
| Fallback | `llama-3.3-70b-versatile` |
496+
| `analyze --deep` | `openai/gpt-oss-120b` |
497+
| Fallback | `openai/gpt-oss-20b` |
498498

499499
If a model is unavailable, DevMap should fall back gracefully.
500500
Only Groq production models should be used as public defaults.
501+
502+
Users can override automatic routing with `devmap config model <model>`.
503+
Running `devmap config model auto` restores the defaults above.
501504

502505
Raw provider errors should not be shown directly to users.
503506

docs/commands.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
## Overview
88

9-
DevMap MVP provides four core commands:
9+
DevMap MVP provides four core project commands and one configuration command:
1010

1111
* `devmap init`
1212
* `devmap analyze`
1313
* `devmap ask`
14-
* `devmap doctor`
15-
16-
No additional commands should be added until the MVP is shipped.
14+
* `devmap doctor`
15+
* `devmap config model`
16+
17+
No additional product commands should be added until the MVP is shipped.
1718

1819
Future commands are documented in:
1920

@@ -366,7 +367,7 @@ Use existing snapshot or re-analyze first?
366367

367368
---
368369

369-
## `devmap doctor`
370+
## `devmap doctor`
370371

371372
Run diagnostics for DevMap setup.
372373

@@ -435,11 +436,34 @@ Issues found:
435436
* Output must be readable
436437
* Errors must be actionable
437438
* Do not expose raw stack traces
438-
* Mention what command the user should run next
439-
440-
---
441-
442-
## Global Flags
439+
* Mention what command the user should run next
440+
441+
---
442+
443+
## `devmap config model`
444+
445+
Set a global model override for AI-powered commands.
446+
447+
### Usage
448+
449+
```bash
450+
devmap config model llama-3.1-8b-instant
451+
devmap config model openai/gpt-oss-120b
452+
devmap config model auto
453+
```
454+
455+
`auto` restores command-based routing:
456+
457+
* `ask` uses `llama-3.1-8b-instant`
458+
* `analyze` uses `openai/gpt-oss-20b`
459+
* `analyze --deep` uses `openai/gpt-oss-120b`
460+
461+
The command preserves the configured provider and API key. DevMap must be
462+
initialized before changing the model.
463+
464+
---
465+
466+
## Global Flags
443467

444468
Available globally:
445469

docs/for-me-personal/PROGRESS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ Terakhir diperbarui: 2026-06-14
44

55
## Update 2026-06-14
66

7+
### Model Routing And Config
8+
9+
- Default `devmap ask` memakai `llama-3.1-8b-instant`.
10+
- Standard `devmap analyze` tetap memakai `openai/gpt-oss-20b`.
11+
- `devmap analyze --deep` memakai `openai/gpt-oss-120b`.
12+
- Fallback model memakai `openai/gpt-oss-20b`.
13+
- Command `devmap config model <model>` dapat menetapkan override global tanpa
14+
mengubah provider atau API key.
15+
- `devmap config model auto` mengembalikan routing default per command.
16+
717
### Context Builder Token Optimization
818

919
- Pertanyaan navigasi English seperti `where` dan `find` sekarang memakai

docs/for-me-personal/TEST.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,32 @@ Periksa `Relevant Files` dan prompt token usage. Query pertama seharusnya
4444
memprioritaskan production CLI source dan memakai context jauh lebih kecil
4545
daripada default lama lima file dengan maksimal 200 baris per file.
4646

47+
## Model Routing And Override
48+
49+
Focused automated test:
50+
51+
```powershell
52+
pnpm --filter devmap exec tsx --test test/config-command.test.ts test/analyze-ai.test.ts test/ask-command.test.ts
53+
```
54+
55+
Expected automatic routing:
56+
57+
- `ask`: `llama-3.1-8b-instant`
58+
- `analyze`: `openai/gpt-oss-20b`
59+
- `analyze --deep`: `openai/gpt-oss-120b`
60+
- fallback: `openai/gpt-oss-20b`
61+
62+
Manual override:
63+
64+
```powershell
65+
pnpm dev:cli config model openai/gpt-oss-120b
66+
pnpm dev:cli doctor
67+
pnpm dev:cli config model auto
68+
```
69+
70+
The first command should preserve the existing API key and provider. The last
71+
command should restore automatic command-based routing.
72+
4773
## Urutan Testing Yang Direkomendasikan
4874

4975
Untuk development harian:

packages/cli/src/ai/groq.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ const MAX_RATE_LIMIT_DELAY_MS = 10_000;
1313
const MAX_RATE_LIMIT_RETRIES = 3;
1414

1515
export const DEFAULT_AI_MODELS = {
16-
ask: "openai/gpt-oss-20b",
16+
ask: "llama-3.1-8b-instant",
1717
analyze: "openai/gpt-oss-20b",
18-
deepAnalyze: "llama-3.3-70b-versatile",
19-
fallback: "llama-3.3-70b-versatile"
18+
deepAnalyze: "openai/gpt-oss-120b",
19+
fallback: "openai/gpt-oss-20b"
2020
} as const;
2121

2222
export type GroqClientDependencies = {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
readConfig,
3+
writeConfig,
4+
type DevmapConfig
5+
} from "../utils/config.js";
6+
import { output } from "../utils/output.js";
7+
8+
export type ConfigDependencies = {
9+
loadConfig?: () => Promise<DevmapConfig | null>;
10+
persistConfig?: (config: DevmapConfig) => Promise<void>;
11+
};
12+
13+
export async function configModelCommand(
14+
model: string,
15+
dependencies: ConfigDependencies = {}
16+
): Promise<void> {
17+
const selectedModel = model.trim();
18+
if (!selectedModel) {
19+
output.error("Model name cannot be empty.");
20+
return;
21+
}
22+
23+
const loadConfig = dependencies.loadConfig ?? readConfig;
24+
const persistConfig = dependencies.persistConfig ?? writeConfig;
25+
const config = await loadConfig();
26+
27+
if (!config) {
28+
output.error("DevMap is not configured yet.");
29+
output.note("Run devmap init before changing the model.");
30+
return;
31+
}
32+
33+
await persistConfig({
34+
...config,
35+
model: selectedModel
36+
});
37+
38+
output.success(
39+
selectedModel === "auto"
40+
? "Restored automatic command-based model routing."
41+
: `Default model override set to ${selectedModel}.`
42+
);
43+
}

packages/cli/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { Command } from "commander";
33
import { analyzeCommand } from "./commands/analyze.js";
44
import { askCommand } from "./commands/ask.js";
5+
import { configModelCommand } from "./commands/config.js";
56
import { doctorCommand } from "./commands/doctor.js";
67
import { initCommand } from "./commands/init.js";
78
import { printHelp } from "./utils/help.js";
@@ -34,6 +35,14 @@ program
3435
.argument("<question...>", "question to ask")
3536
.action(askCommand);
3637

38+
program
39+
.command("config")
40+
.description("Update DevMap configuration")
41+
.command("model")
42+
.description("Set a model override or restore automatic routing")
43+
.argument("<model>", "Groq model ID or auto")
44+
.action(configModelCommand);
45+
3746
program
3847
.command("doctor")
3948
.description("Diagnose DevMap setup")

packages/cli/src/utils/help.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const commands = [
44
["init", "Initialize DevMap configuration"],
55
["analyze", "Analyze project structure"],
66
["ask <question>", "Ask about your codebase"],
7+
["config model", "Set model override or automatic routing"],
78
["doctor", "Diagnose DevMap setup"]
89
] as const;
910

packages/cli/test/analyze-ai.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
33
import { tmpdir } from "node:os";
44
import { join } from "node:path";
55
import test from "node:test";
6+
import { DEFAULT_AI_MODELS } from "../src/ai/groq.js";
67
import type {
78
AiClient,
89
AiCompletionRequest,
@@ -124,6 +125,56 @@ test("analyze warns and continues when package.json is malformed", async () => {
124125
}
125126
});
126127

128+
test("analyze auto routing uses 20B normally and 120B for deep analysis", async () => {
129+
const projectRoot = await mkdtemp(join(tmpdir(), "devmap-model-routing-"));
130+
const requests: AiCompletionRequest[] = [];
131+
const client: AiClient = {
132+
async complete(request): Promise<AiCompletionResult> {
133+
requests.push(request);
134+
return {
135+
content: "Architecture result.",
136+
model: request.model
137+
};
138+
}
139+
};
140+
141+
try {
142+
await writeFile(
143+
join(projectRoot, "package.json"),
144+
JSON.stringify({ name: "model-routing-fixture" }),
145+
"utf8"
146+
);
147+
await writeFile(join(projectRoot, "index.ts"), "export const ready = true;\n", "utf8");
148+
149+
const dependencies = {
150+
loadConfig: async () => ({
151+
provider: "groq" as const,
152+
apiKey: "gsk_fixture",
153+
model: "auto"
154+
}),
155+
createAiClient: () => client
156+
};
157+
158+
await captureOutput(() => analyzeCommand(
159+
projectRoot,
160+
{ fresh: true },
161+
dependencies
162+
));
163+
await captureOutput(() => analyzeCommand(
164+
projectRoot,
165+
{ deep: true, fresh: true },
166+
dependencies
167+
));
168+
169+
assert.equal(requests[0]?.model, DEFAULT_AI_MODELS.analyze);
170+
assert.equal(requests[1]?.model, DEFAULT_AI_MODELS.deepAnalyze);
171+
assert.equal(requests[0]?.fallbackModel, DEFAULT_AI_MODELS.fallback);
172+
assert.equal(requests[1]?.fallbackModel, DEFAULT_AI_MODELS.fallback);
173+
} finally {
174+
await rm(projectRoot, { recursive: true, force: true });
175+
}
176+
});
177+
127178
function stripAnsi(value: string): string {
128179
return value.replace(/\u001B\[[0-9;]*m/g, "");
129180
}

0 commit comments

Comments
 (0)