tlc-claude-code 1.6.1 → 1.6.2
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 +99 -56
- package/.claude/commands/tlc/llm.md +4 -4
- package/.claude/commands/tlc/sync.md +1 -1
- package/CLAUDE.md +11 -0
- package/README.md +2 -2
- package/docker-compose.dev.yml +20 -0
- package/package.json +1 -1
- package/server/index.js +212 -0
- package/server/lib/adapters/claude-adapter.js +3 -3
- package/server/lib/adapters/claude-adapter.test.js +2 -2
- package/server/lib/adapters/openai-adapter.js +3 -3
- package/server/lib/adapters/openai-adapter.test.js +2 -2
- package/server/lib/model-pricing.js +2 -0
- package/server/lib/overdrive-command.js +151 -15
- package/server/lib/overdrive-command.test.js +207 -10
- package/sync.md +1 -1
|
@@ -166,13 +166,17 @@ Senior engineers know when rules don't apply:
|
|
|
166
166
|
|
|
167
167
|
This is the core TLC command. Tests before code, one task at a time.
|
|
168
168
|
|
|
169
|
-
**Overdrive Mode:** When tasks are independent, TLC auto-detects and offers parallel execution with multiple agents.
|
|
169
|
+
**Overdrive Mode (Opus 4.6):** When tasks are independent, TLC auto-detects and offers parallel execution with multiple agents. Agents are assigned models based on task complexity (opus for heavy, sonnet for standard, haiku for light). Failed agents can be resumed. No arbitrary cap on agent count.
|
|
170
170
|
|
|
171
171
|
## Usage
|
|
172
172
|
|
|
173
173
|
```
|
|
174
174
|
/tlc:build <phase_number>
|
|
175
|
-
/tlc:build <phase_number> --sequential
|
|
175
|
+
/tlc:build <phase_number> --sequential # Force sequential mode
|
|
176
|
+
/tlc:build <phase_number> --model sonnet # Force all agents to use sonnet
|
|
177
|
+
/tlc:build <phase_number> --model haiku # Force all agents to use haiku (fast/cheap)
|
|
178
|
+
/tlc:build <phase_number> --max-turns 30 # Limit agent execution length
|
|
179
|
+
/tlc:build <phase_number> --agents 5 # Limit parallel agents to 5
|
|
176
180
|
```
|
|
177
181
|
|
|
178
182
|
## Process
|
|
@@ -181,7 +185,7 @@ This is the core TLC command. Tests before code, one task at a time.
|
|
|
181
185
|
|
|
182
186
|
Read all `.planning/phases/{phase}-*-PLAN.md` files for this phase.
|
|
183
187
|
|
|
184
|
-
### Step 1a: Overdrive Detection (Auto-Parallel)
|
|
188
|
+
### Step 1a: Overdrive Detection (Auto-Parallel, Opus 4.6)
|
|
185
189
|
|
|
186
190
|
After loading plans, analyze task dependencies to determine if parallel execution is possible.
|
|
187
191
|
|
|
@@ -200,18 +204,19 @@ After loading plans, analyze task dependencies to determine if parallel executio
|
|
|
200
204
|
2. Look for dependency markers in task descriptions
|
|
201
205
|
3. Check "## Dependencies" section if present
|
|
202
206
|
4. Identify independent tasks (no dependencies)
|
|
207
|
+
5. Estimate task complexity for model assignment (heavy/standard/light)
|
|
203
208
|
|
|
204
209
|
**If 2+ independent tasks found:**
|
|
205
210
|
```
|
|
206
|
-
🚀 Overdrive Mode Available
|
|
211
|
+
🚀 Overdrive Mode Available (Opus 4.6)
|
|
207
212
|
|
|
208
213
|
Phase 3 has 4 independent tasks that can run in parallel:
|
|
209
|
-
- Task 1: Create user schema
|
|
210
|
-
- Task 2: Add validation helpers
|
|
211
|
-
- Task 3: Write migration scripts
|
|
212
|
-
- Task 4: Create seed data
|
|
214
|
+
- Task 1: Create user schema [opus] (heavy)
|
|
215
|
+
- Task 2: Add validation helpers [sonnet] (standard)
|
|
216
|
+
- Task 3: Write migration scripts [sonnet] (standard)
|
|
217
|
+
- Task 4: Create seed data [haiku] (light)
|
|
213
218
|
|
|
214
|
-
Recommended:
|
|
219
|
+
Recommended: 4 agents (one per independent task)
|
|
215
220
|
|
|
216
221
|
Options:
|
|
217
222
|
1) Overdrive mode (parallel agents) [Recommended]
|
|
@@ -219,6 +224,15 @@ Options:
|
|
|
219
224
|
3) Let me pick which tasks to parallelize
|
|
220
225
|
```
|
|
221
226
|
|
|
227
|
+
**Model auto-selection per task complexity:**
|
|
228
|
+
| Complexity | Model | When |
|
|
229
|
+
|-----------|-------|------|
|
|
230
|
+
| Heavy | opus | Architecture, multi-file features, security, auth, database |
|
|
231
|
+
| Standard | sonnet | Normal implementation tasks (default) |
|
|
232
|
+
| Light | haiku | Config, boilerplate, DTOs, enums, constants, seed data |
|
|
233
|
+
|
|
234
|
+
Override with `--model sonnet` to force all agents to the same model.
|
|
235
|
+
|
|
222
236
|
**If tasks have dependencies (waterfall):**
|
|
223
237
|
```
|
|
224
238
|
📋 Sequential Mode
|
|
@@ -230,62 +244,87 @@ Phase 3 tasks have dependencies:
|
|
|
230
244
|
Running in sequential order.
|
|
231
245
|
```
|
|
232
246
|
|
|
233
|
-
### Step 1b: Execute Overdrive (if selected)
|
|
247
|
+
### Step 1b: Execute Overdrive (if selected) — Opus 4.6 Multi-Agent
|
|
234
248
|
|
|
235
|
-
When overdrive mode is selected, spawn parallel agents:
|
|
249
|
+
When overdrive mode is selected, spawn parallel agents with per-task model selection:
|
|
236
250
|
|
|
237
251
|
```
|
|
238
|
-
🚀 Launching Overdrive Mode
|
|
252
|
+
🚀 Launching Overdrive Mode (Opus 4.6)
|
|
239
253
|
|
|
240
|
-
Spawning
|
|
254
|
+
Spawning 4 agents in parallel...
|
|
241
255
|
|
|
242
|
-
Agent 1: Task 1 - Create user schema
|
|
243
|
-
Agent 2: Task 2 - Add validation helpers
|
|
244
|
-
Agent 3: Task 3 - Write migration scripts
|
|
256
|
+
Agent 1: Task 1 - Create user schema [opus]
|
|
257
|
+
Agent 2: Task 2 - Add validation helpers [sonnet]
|
|
258
|
+
Agent 3: Task 3 - Write migration scripts [sonnet]
|
|
259
|
+
Agent 4: Task 4 - Create seed data [haiku]
|
|
245
260
|
|
|
246
261
|
[All agents spawned - working in background]
|
|
247
|
-
[Task 4 queued for next available agent]
|
|
248
262
|
```
|
|
249
263
|
|
|
250
264
|
**Agent execution rules:**
|
|
251
|
-
-
|
|
265
|
+
- One agent per independent task (no arbitrary cap)
|
|
266
|
+
- Model assigned per task complexity (override with `--model`)
|
|
252
267
|
- Agents work autonomously (no confirmation prompts)
|
|
253
268
|
- Each agent commits after completing their task
|
|
254
|
-
- When an agent finishes, it can pick up queued tasks
|
|
255
269
|
- All agents follow test-first methodology
|
|
270
|
+
- `max_turns` limits execution length (default: 50, override with `--max-turns`)
|
|
256
271
|
|
|
257
272
|
**CRITICAL: Spawn all agents in a SINGLE message using multiple Task tool calls.**
|
|
258
273
|
|
|
259
274
|
```
|
|
260
|
-
Task(description="Agent 1: Task 1", prompt="...", subagent_type="general-purpose", run_in_background=true)
|
|
261
|
-
Task(description="Agent 2: Task 2", prompt="...", subagent_type="general-purpose", run_in_background=true)
|
|
262
|
-
Task(description="Agent 3: Task 3", prompt="...", subagent_type="general-purpose", run_in_background=true)
|
|
275
|
+
Task(description="Agent 1: Task 1", prompt="...", subagent_type="general-purpose", model="opus", max_turns=50, run_in_background=true)
|
|
276
|
+
Task(description="Agent 2: Task 2", prompt="...", subagent_type="general-purpose", model="sonnet", max_turns=50, run_in_background=true)
|
|
277
|
+
Task(description="Agent 3: Task 3", prompt="...", subagent_type="general-purpose", model="sonnet", max_turns=50, run_in_background=true)
|
|
278
|
+
Task(description="Agent 4: Task 4", prompt="...", subagent_type="general-purpose", model="haiku", max_turns=50, run_in_background=true)
|
|
263
279
|
```
|
|
264
280
|
|
|
265
|
-
**Live Progress Monitoring:**
|
|
281
|
+
**Live Progress Monitoring (TaskOutput):**
|
|
266
282
|
|
|
267
|
-
|
|
283
|
+
Use `TaskOutput` with `block=false` for non-blocking progress checks:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
TaskOutput(task_id="AGENT_ID_1", block=false, timeout=5000)
|
|
287
|
+
TaskOutput(task_id="AGENT_ID_2", block=false, timeout=5000)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Or use the AgentProgressMonitor for formatted status:
|
|
268
291
|
|
|
269
292
|
```bash
|
|
270
293
|
node -e "
|
|
271
294
|
const { AgentProgressMonitor } = require('./server/lib/agent-progress-monitor.js');
|
|
272
295
|
const monitor = new AgentProgressMonitor('/tmp/claude-1000/-mnt-c-Code-TLC/tasks');
|
|
273
|
-
const agents = ['AGENT_ID_1', 'AGENT_ID_2', 'AGENT_ID_3'];
|
|
296
|
+
const agents = ['AGENT_ID_1', 'AGENT_ID_2', 'AGENT_ID_3', 'AGENT_ID_4'];
|
|
274
297
|
console.log(monitor.formatTable(agents));
|
|
275
298
|
"
|
|
276
299
|
```
|
|
277
300
|
|
|
278
301
|
Display format:
|
|
279
302
|
```
|
|
280
|
-
|
|
281
|
-
🚀 OVERDRIVE STATUS
|
|
282
|
-
|
|
283
|
-
| Agent | Task | Tests | Phase |
|
|
284
|
-
|
|
285
|
-
| a1b2c3 |
|
|
286
|
-
| d4e5f6 |
|
|
287
|
-
| g7h8i9 |
|
|
288
|
-
|
|
303
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
304
|
+
🚀 OVERDRIVE STATUS (Opus 4.6)
|
|
305
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
306
|
+
| Agent | Task | Model | Tests | Phase |
|
|
307
|
+
|-------|------|-------|-------|-------|
|
|
308
|
+
| a1b2c3 | User Schema | opus | 29 ✓ | committed |
|
|
309
|
+
| d4e5f6 | Validation | sonnet | 18 ✓ | implementing |
|
|
310
|
+
| g7h8i9 | Migrations | sonnet | - | writing-tests |
|
|
311
|
+
| j0k1l2 | Seed Data | haiku | 5 ✓ | committed |
|
|
312
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Handling Agent Failures (Opus 4.6 Resumption):**
|
|
316
|
+
|
|
317
|
+
If an agent fails or gets stuck:
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
# Check if agent is stuck (non-blocking)
|
|
321
|
+
TaskOutput(task_id="AGENT_ID", block=false, timeout=5000)
|
|
322
|
+
|
|
323
|
+
# Cancel a stuck agent
|
|
324
|
+
TaskStop(task_id="AGENT_ID")
|
|
325
|
+
|
|
326
|
+
# Resume a failed agent from where it left off (full context preserved)
|
|
327
|
+
Task(resume="AGENT_ID", prompt="Continue from where you left off. Fix any errors and complete the task.")
|
|
289
328
|
```
|
|
290
329
|
|
|
291
330
|
**Show status automatically:**
|
|
@@ -799,7 +838,7 @@ Writing tests for Task 2: Validation...
|
|
|
799
838
|
Phase 1 complete. Ready for /tlc:verify 1
|
|
800
839
|
```
|
|
801
840
|
|
|
802
|
-
### Overdrive Mode (Independent Tasks)
|
|
841
|
+
### Overdrive Mode (Independent Tasks, Opus 4.6)
|
|
803
842
|
|
|
804
843
|
```
|
|
805
844
|
User: /tlc:build 2
|
|
@@ -807,15 +846,15 @@ User: /tlc:build 2
|
|
|
807
846
|
Claude: Loading phase 2 plans...
|
|
808
847
|
Found: 2-PLAN.md (4 tasks)
|
|
809
848
|
|
|
810
|
-
🚀 Overdrive Mode Available
|
|
849
|
+
🚀 Overdrive Mode Available (Opus 4.6)
|
|
811
850
|
|
|
812
851
|
Phase 2 has 4 independent tasks:
|
|
813
|
-
- Task 1: Create API routes
|
|
814
|
-
- Task 2: Add input validation
|
|
815
|
-
- Task 3: Write error handlers
|
|
816
|
-
- Task 4: Add rate limiting
|
|
852
|
+
- Task 1: Create API routes [sonnet] (standard)
|
|
853
|
+
- Task 2: Add input validation [sonnet] (standard)
|
|
854
|
+
- Task 3: Write error handlers [sonnet] (standard)
|
|
855
|
+
- Task 4: Add rate limiting config [haiku] (light)
|
|
817
856
|
|
|
818
|
-
Recommended:
|
|
857
|
+
Recommended: 4 agents (one per task)
|
|
819
858
|
|
|
820
859
|
Options:
|
|
821
860
|
1) Overdrive mode (parallel) [Recommended]
|
|
@@ -824,21 +863,20 @@ Options:
|
|
|
824
863
|
|
|
825
864
|
User: 1
|
|
826
865
|
|
|
827
|
-
Claude: 🚀 Launching Overdrive Mode
|
|
866
|
+
Claude: 🚀 Launching Overdrive Mode (Opus 4.6)
|
|
828
867
|
|
|
829
|
-
Spawning
|
|
830
|
-
[Agent 1] Task 1: Create API routes - STARTED
|
|
831
|
-
[Agent 2] Task 2: Add input validation - STARTED
|
|
832
|
-
[Agent 3] Task 3: Write error handlers - STARTED
|
|
833
|
-
[
|
|
868
|
+
Spawning 4 agents...
|
|
869
|
+
[Agent 1] Task 1: Create API routes [sonnet] - STARTED
|
|
870
|
+
[Agent 2] Task 2: Add input validation [sonnet] - STARTED
|
|
871
|
+
[Agent 3] Task 3: Write error handlers [sonnet] - STARTED
|
|
872
|
+
[Agent 4] Task 4: Add rate limiting config [haiku] - STARTED
|
|
834
873
|
|
|
835
874
|
... agents working in background ...
|
|
836
875
|
|
|
837
|
-
[Agent
|
|
838
|
-
[Agent 2] Task
|
|
839
|
-
[Agent 1] ✅ Task 1 complete (4 commits)
|
|
840
|
-
[Agent 3] ✅ Task 3 complete (2 commits)
|
|
841
|
-
[Agent 2] ✅ Task 4 complete (2 commits)
|
|
876
|
+
[Agent 4] ✅ Task 4 complete (1 commit) [haiku - fast]
|
|
877
|
+
[Agent 2] ✅ Task 2 complete (3 commits) [sonnet]
|
|
878
|
+
[Agent 1] ✅ Task 1 complete (4 commits) [sonnet]
|
|
879
|
+
[Agent 3] ✅ Task 3 complete (2 commits) [sonnet]
|
|
842
880
|
|
|
843
881
|
All agents complete. Running full test suite...
|
|
844
882
|
✅ 24 tests passing
|
|
@@ -863,18 +901,23 @@ Phase 2 complete. Ready for /tlc:verify 2
|
|
|
863
901
|
- Suggest running `/tlc:build {phase}` again to retry
|
|
864
902
|
- Or manually fix and run `/tlc:status` to verify
|
|
865
903
|
|
|
866
|
-
**Overdrive mode issues:**
|
|
867
|
-
- Agent stuck → Check with `TaskOutput`
|
|
904
|
+
**Overdrive mode issues (Opus 4.6):**
|
|
905
|
+
- Agent stuck → Check with `TaskOutput(task_id, block=false)`, cancel with `TaskStop(task_id)`
|
|
906
|
+
- Agent failed → Resume with `Task(resume=agent_id)` to continue from where it left off
|
|
868
907
|
- Merge conflicts → Agents working on same files (rare if tasks are truly independent)
|
|
869
|
-
- One agent failed → Other agents continue;
|
|
908
|
+
- One agent failed → Other agents continue; resume failed agent or fix manually
|
|
870
909
|
- Want sequential instead → Use `--sequential` flag: `/tlc:build 2 --sequential`
|
|
910
|
+
- Cost too high → Use `--model haiku` to force all agents to haiku
|
|
911
|
+
- Agent running too long → Use `--max-turns 30` to limit execution
|
|
871
912
|
|
|
872
913
|
## Flags
|
|
873
914
|
|
|
874
915
|
| Flag | Description |
|
|
875
916
|
|------|-------------|
|
|
876
917
|
| `--sequential` | Force sequential execution even if tasks are independent |
|
|
877
|
-
| `--agents N` |
|
|
918
|
+
| `--agents N` | Limit parallel agents to N (default: one per independent task) |
|
|
919
|
+
| `--model MODEL` | Force all agents to use a specific model (opus, sonnet, haiku) |
|
|
920
|
+
| `--max-turns N` | Limit each agent's execution to N turns (default: 50) |
|
|
878
921
|
|
|
879
922
|
## When Overdrive is NOT Used
|
|
880
923
|
|
|
@@ -61,13 +61,13 @@ Add to `.tlc.json`:
|
|
|
61
61
|
"claude": {
|
|
62
62
|
"type": "cli",
|
|
63
63
|
"command": "claude",
|
|
64
|
-
"model": "claude-4-
|
|
64
|
+
"model": "claude-opus-4-6",
|
|
65
65
|
"capabilities": ["review", "code-gen", "refactor"]
|
|
66
66
|
},
|
|
67
67
|
"codex": {
|
|
68
68
|
"type": "cli",
|
|
69
69
|
"command": "codex",
|
|
70
|
-
"model": "gpt-5.
|
|
70
|
+
"model": "gpt-5.3-codex",
|
|
71
71
|
"capabilities": ["review", "code-gen"]
|
|
72
72
|
},
|
|
73
73
|
"gemini": {
|
|
@@ -89,9 +89,9 @@ Add to `.tlc.json`:
|
|
|
89
89
|
|
|
90
90
|
## Supported Models
|
|
91
91
|
|
|
92
|
-
**Claude:** claude-4-opus, claude-4-sonnet, claude-3.5-sonnet, claude-3-opus, claude-3-sonnet, claude-3-haiku
|
|
92
|
+
**Claude:** claude-opus-4-6, claude-sonnet-4-5, claude-4-opus, claude-4-sonnet, claude-3.5-sonnet, claude-3-opus, claude-3-sonnet, claude-3-haiku
|
|
93
93
|
|
|
94
|
-
**OpenAI/Codex:** gpt-5.2, gpt-5, gpt-4o, gpt-4o-mini, gpt-4-turbo, o3, o3-mini, o1, o1-mini, o1-pro
|
|
94
|
+
**OpenAI/Codex:** gpt-5.3-codex, gpt-5.2, gpt-5, gpt-4o, gpt-4o-mini, gpt-4-turbo, o3, o3-mini, o1, o1-mini, o1-pro
|
|
95
95
|
|
|
96
96
|
**Gemini:** gemini-3-preview, gemini-3, gemini-2.5-pro, gemini-2.0-flash, gemini-2.0-flash-thinking, gemini-1.5-pro
|
|
97
97
|
|
package/CLAUDE.md
CHANGED
|
@@ -89,6 +89,17 @@ All implementation follows **Red → Green → Refactor**:
|
|
|
89
89
|
|
|
90
90
|
Tests are written BEFORE implementation, not after.
|
|
91
91
|
|
|
92
|
+
## Context Management
|
|
93
|
+
|
|
94
|
+
**Use multiple sessions/agents for large tasks.** When working in overdrive mode or across workspaces:
|
|
95
|
+
- Use the `Task` tool to spawn sub-agents for independent work (research, testing, building)
|
|
96
|
+
- Keep the main conversation focused on orchestration and decisions
|
|
97
|
+
- Delegate file-heavy operations (reading many files, running test suites) to sub-agents
|
|
98
|
+
- This prevents context window overflow, which causes crashes and lost work
|
|
99
|
+
- Especially critical in workspace mode where multiple repos are involved
|
|
100
|
+
|
|
101
|
+
**Signs you need to delegate:** If you've read 15+ files, run 10+ commands, or the conversation is getting long — spawn a sub-agent for the next chunk of work.
|
|
102
|
+
|
|
92
103
|
## After TLC Updates
|
|
93
104
|
|
|
94
105
|
If TLC command files are updated, re-read them before executing. Check version in `package.json`.
|
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Or use `/tlc` for the full dashboard.
|
|
|
72
72
|
### For Solo Developers
|
|
73
73
|
|
|
74
74
|
- **Test-first by default** — Claude writes tests before code
|
|
75
|
-
- **Auto-parallelization** —
|
|
75
|
+
- **Auto-parallelization** — Agents run independent tasks simultaneously with per-task model selection (Opus 4.6)
|
|
76
76
|
- **`/tlc:next`** — One command to progress. No decisions needed.
|
|
77
77
|
- **Smart dashboard** — See progress, run actions
|
|
78
78
|
- **Coverage gaps** — Find and fix untested code
|
|
@@ -103,7 +103,7 @@ Or use `/tlc` for the full dashboard.
|
|
|
103
103
|
| `/tlc` | Smart dashboard — full status view |
|
|
104
104
|
| `/tlc:new-project` | Start new project with roadmap |
|
|
105
105
|
| `/tlc:init` | Add TLC to existing codebase |
|
|
106
|
-
| `/tlc:build` | Write tests → implement (auto-parallelizes
|
|
106
|
+
| `/tlc:build` | Write tests → implement (auto-parallelizes with model selection) |
|
|
107
107
|
| `/tlc:coverage` | Find and fix untested code |
|
|
108
108
|
| `/tlc:quality` | Test quality scoring |
|
|
109
109
|
| `/tlc:autofix` | Auto-repair failing tests |
|
package/docker-compose.dev.yml
CHANGED
|
@@ -121,6 +121,26 @@ services:
|
|
|
121
121
|
- app
|
|
122
122
|
restart: on-failure
|
|
123
123
|
|
|
124
|
+
# TLC Dashboard Web (built from dashboard-web, serves static files via nginx)
|
|
125
|
+
dashboard-web:
|
|
126
|
+
build:
|
|
127
|
+
context: ./dashboard-web
|
|
128
|
+
dockerfile: Dockerfile
|
|
129
|
+
container_name: tlc-${COMPOSE_PROJECT_NAME:-dev}-dashboard-web
|
|
130
|
+
ports:
|
|
131
|
+
- "${DASHBOARD_WEB_PORT:-3002}:80"
|
|
132
|
+
environment:
|
|
133
|
+
- API_URL=http://dashboard:3147
|
|
134
|
+
depends_on:
|
|
135
|
+
- dashboard
|
|
136
|
+
restart: on-failure
|
|
137
|
+
healthcheck:
|
|
138
|
+
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/health"]
|
|
139
|
+
interval: 30s
|
|
140
|
+
timeout: 3s
|
|
141
|
+
start_period: 5s
|
|
142
|
+
retries: 3
|
|
143
|
+
|
|
124
144
|
# Playwright E2E Tests (optional - starts on demand)
|
|
125
145
|
playwright:
|
|
126
146
|
image: mcr.microsoft.com/playwright:v1.40.0-jammy
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Suppress punycode deprecation from upstream deps (http-proxy-middleware, ws)
|
|
4
|
+
const originalEmit = process.emit;
|
|
5
|
+
process.emit = function (event, error) {
|
|
6
|
+
if (event === 'warning' && error?.name === 'DeprecationWarning' && error?.message?.includes('punycode')) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return originalEmit.apply(process, arguments);
|
|
10
|
+
};
|
|
11
|
+
|
|
3
12
|
const express = require('express');
|
|
4
13
|
const { createServer } = require('http');
|
|
5
14
|
const { WebSocketServer } = require('ws');
|
|
@@ -50,6 +59,7 @@ let appProcess = null;
|
|
|
50
59
|
let appPort = 3000;
|
|
51
60
|
let wsClients = new Set();
|
|
52
61
|
const logs = { app: [], test: [], git: [] };
|
|
62
|
+
const commandHistory = [];
|
|
53
63
|
|
|
54
64
|
// Create Express app
|
|
55
65
|
const app = express();
|
|
@@ -944,6 +954,177 @@ app.post('/api/test', (req, res) => {
|
|
|
944
954
|
res.json({ success: true });
|
|
945
955
|
});
|
|
946
956
|
|
|
957
|
+
// ============================================
|
|
958
|
+
// Command Execution API
|
|
959
|
+
// ============================================
|
|
960
|
+
|
|
961
|
+
// GET /api/commands/history - Return command execution history
|
|
962
|
+
app.get('/api/commands/history', (req, res) => {
|
|
963
|
+
res.json({ success: true, history: commandHistory });
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
// POST /api/commands/:command - Execute a TLC command
|
|
967
|
+
app.post('/api/commands/:command', (req, res) => {
|
|
968
|
+
const command = req.params.command;
|
|
969
|
+
const { args } = req.body || {};
|
|
970
|
+
|
|
971
|
+
// Whitelist of allowed TLC commands
|
|
972
|
+
const allowedCommands = ['plan', 'build', 'verify', 'bug', 'claim', 'release', 'who', 'progress'];
|
|
973
|
+
if (!allowedCommands.includes(command)) {
|
|
974
|
+
return res.status(400).json({ success: false, error: `Unknown command: ${command}. Allowed: ${allowedCommands.join(', ')}` });
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const entry = {
|
|
978
|
+
id: `cmd-${Date.now()}`,
|
|
979
|
+
command,
|
|
980
|
+
args: args || null,
|
|
981
|
+
status: 'running',
|
|
982
|
+
startedAt: new Date().toISOString(),
|
|
983
|
+
output: '',
|
|
984
|
+
};
|
|
985
|
+
commandHistory.push(entry);
|
|
986
|
+
if (commandHistory.length > 100) commandHistory.shift();
|
|
987
|
+
|
|
988
|
+
addLog('app', `Executing command: tlc:${command}${args ? ' ' + args : ''}`, 'info');
|
|
989
|
+
broadcast('command-started', { id: entry.id, command });
|
|
990
|
+
|
|
991
|
+
// Build the CLI command
|
|
992
|
+
const cliArgs = ['tlc', command];
|
|
993
|
+
if (args) cliArgs.push(args);
|
|
994
|
+
const fullCmd = `npx ${cliArgs.join(' ')}`;
|
|
995
|
+
|
|
996
|
+
const cmdProcess = spawn('npx', cliArgs.slice(1), {
|
|
997
|
+
cwd: PROJECT_DIR,
|
|
998
|
+
env: { ...process.env },
|
|
999
|
+
shell: true,
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
let output = '';
|
|
1003
|
+
|
|
1004
|
+
cmdProcess.stdout.on('data', (data) => {
|
|
1005
|
+
const text = data.toString();
|
|
1006
|
+
output += text;
|
|
1007
|
+
broadcast('command-output', { id: entry.id, data: text, stream: 'stdout' });
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
cmdProcess.stderr.on('data', (data) => {
|
|
1011
|
+
const text = data.toString();
|
|
1012
|
+
output += text;
|
|
1013
|
+
broadcast('command-output', { id: entry.id, data: text, stream: 'stderr' });
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
cmdProcess.on('exit', (code) => {
|
|
1017
|
+
entry.status = code === 0 ? 'completed' : 'failed';
|
|
1018
|
+
entry.exitCode = code;
|
|
1019
|
+
entry.output = output;
|
|
1020
|
+
entry.completedAt = new Date().toISOString();
|
|
1021
|
+
broadcast('command-completed', { id: entry.id, exitCode: code });
|
|
1022
|
+
addLog('app', `Command tlc:${command} ${code === 0 ? 'completed' : 'failed'} (exit ${code})`, code === 0 ? 'info' : 'error');
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
cmdProcess.on('error', (err) => {
|
|
1026
|
+
entry.status = 'failed';
|
|
1027
|
+
entry.output = err.message;
|
|
1028
|
+
entry.completedAt = new Date().toISOString();
|
|
1029
|
+
broadcast('command-completed', { id: entry.id, error: err.message });
|
|
1030
|
+
addLog('app', `Command tlc:${command} error: ${err.message}`, 'error');
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
res.json({ success: true, id: entry.id, command, message: `Command tlc:${command} started` });
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
// ============================================
|
|
1037
|
+
// Dashboard Completion API (Phase 62)
|
|
1038
|
+
// ============================================
|
|
1039
|
+
|
|
1040
|
+
// GET /api/config - Read .tlc.json configuration
|
|
1041
|
+
app.get('/api/config', (req, res) => {
|
|
1042
|
+
try {
|
|
1043
|
+
const tlcPath = path.join(PROJECT_DIR, '.tlc.json');
|
|
1044
|
+
if (!fs.existsSync(tlcPath)) {
|
|
1045
|
+
return res.json({});
|
|
1046
|
+
}
|
|
1047
|
+
const config = JSON.parse(fs.readFileSync(tlcPath, 'utf-8'));
|
|
1048
|
+
res.json(config);
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
res.status(500).json({ error: err.message });
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
// PUT /api/config - Write .tlc.json configuration
|
|
1055
|
+
app.put('/api/config', (req, res) => {
|
|
1056
|
+
try {
|
|
1057
|
+
const tlcPath = path.join(PROJECT_DIR, '.tlc.json');
|
|
1058
|
+
const config = req.body;
|
|
1059
|
+
if (!config || typeof config !== 'object') {
|
|
1060
|
+
return res.status(400).json({ error: 'Invalid config object' });
|
|
1061
|
+
}
|
|
1062
|
+
fs.writeFileSync(tlcPath, JSON.stringify(config, null, 2));
|
|
1063
|
+
res.json({ success: true, config });
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
res.status(500).json({ error: err.message });
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
// GET /api/health - System health status
|
|
1070
|
+
app.get('/api/health', (req, res) => {
|
|
1071
|
+
const memUsage = process.memoryUsage();
|
|
1072
|
+
res.json({
|
|
1073
|
+
status: 'ok',
|
|
1074
|
+
timestamp: new Date().toISOString(),
|
|
1075
|
+
uptime: process.uptime(),
|
|
1076
|
+
memory: {
|
|
1077
|
+
rss: memUsage.rss,
|
|
1078
|
+
heapUsed: memUsage.heapUsed,
|
|
1079
|
+
heapTotal: memUsage.heapTotal,
|
|
1080
|
+
},
|
|
1081
|
+
appRunning: appProcess !== null,
|
|
1082
|
+
appPort,
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
// GET /api/tasks/:id - Get single task by ID
|
|
1087
|
+
app.get('/api/tasks/:id', (req, res) => {
|
|
1088
|
+
const plan = parsePlan(PROJECT_DIR);
|
|
1089
|
+
const task = (plan.tasks || []).find(t => t.id === req.params.id);
|
|
1090
|
+
if (!task) {
|
|
1091
|
+
return res.status(404).json({ error: 'Task not found' });
|
|
1092
|
+
}
|
|
1093
|
+
res.json(task);
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
// PATCH /api/tasks/:id - Update task status/owner
|
|
1097
|
+
app.patch('/api/tasks/:id', (req, res) => {
|
|
1098
|
+
const plan = parsePlan(PROJECT_DIR);
|
|
1099
|
+
const task = (plan.tasks || []).find(t => t.id === req.params.id);
|
|
1100
|
+
if (!task) {
|
|
1101
|
+
return res.status(404).json({ error: 'Task not found' });
|
|
1102
|
+
}
|
|
1103
|
+
const updates = req.body;
|
|
1104
|
+
const updated = { ...task, ...updates };
|
|
1105
|
+
broadcast('task-update', updated);
|
|
1106
|
+
addLog('app', `Task ${req.params.id} updated`, 'info');
|
|
1107
|
+
res.json(updated);
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
// DELETE /api/tasks/:id - Delete task
|
|
1111
|
+
app.delete('/api/tasks/:id', (req, res) => {
|
|
1112
|
+
broadcast('task-update', { id: req.params.id, deleted: true });
|
|
1113
|
+
addLog('app', `Task ${req.params.id} deleted`, 'info');
|
|
1114
|
+
res.status(204).send();
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// DELETE /api/logs/:type - Clear logs by type
|
|
1118
|
+
app.delete('/api/logs/:type', (req, res) => {
|
|
1119
|
+
const type = req.params.type;
|
|
1120
|
+
if (logs[type]) {
|
|
1121
|
+
logs[type] = [];
|
|
1122
|
+
res.json({ success: true });
|
|
1123
|
+
} else {
|
|
1124
|
+
res.status(404).json({ error: 'Unknown log type' });
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
|
|
947
1128
|
// ============================================
|
|
948
1129
|
// Agent Registry API (Phase 32)
|
|
949
1130
|
// ============================================
|
|
@@ -1014,6 +1195,37 @@ app.get('/api/agents/:id', (req, res) => {
|
|
|
1014
1195
|
}
|
|
1015
1196
|
});
|
|
1016
1197
|
|
|
1198
|
+
// Stop a running agent
|
|
1199
|
+
app.post('/api/agents/:id/stop', (req, res) => {
|
|
1200
|
+
try {
|
|
1201
|
+
const registry = getAgentRegistry();
|
|
1202
|
+
const agent = registry.getAgent(req.params.id);
|
|
1203
|
+
if (!agent) {
|
|
1204
|
+
return res.status(404).json({ success: false, error: 'Agent not found' });
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Transition state to stopped
|
|
1208
|
+
if (agent.stateMachine) {
|
|
1209
|
+
const currentState = agent.stateMachine.getState();
|
|
1210
|
+
if (currentState === 'stopped' || currentState === 'completed') {
|
|
1211
|
+
return res.status(400).json({ success: false, error: `Agent is already ${currentState}` });
|
|
1212
|
+
}
|
|
1213
|
+
const result = agent.stateMachine.transition('stopped', { reason: req.body.reason || 'Stopped via API' });
|
|
1214
|
+
if (!result.success) {
|
|
1215
|
+
// If formal transition fails, force the state
|
|
1216
|
+
agent.stateMachine.forceState('stopped', { reason: req.body.reason || 'Force stopped via API' });
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
broadcast('agent-updated', formatAgent(agent));
|
|
1221
|
+
addLog('app', `Agent ${agent.name || req.params.id} stopped`, 'info');
|
|
1222
|
+
|
|
1223
|
+
res.json({ success: true, agent: formatAgent(agent) });
|
|
1224
|
+
} catch (err) {
|
|
1225
|
+
res.status(500).json({ success: false, error: err.message });
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1017
1229
|
// Register new agent
|
|
1018
1230
|
app.post('/api/agents', (req, res) => {
|
|
1019
1231
|
try {
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
const { BaseAdapter } = require('./base-adapter.js');
|
|
7
7
|
|
|
8
|
-
// Latest model: claude-opus-4-
|
|
9
|
-
const CLAUDE_MODEL = 'claude-opus-4-
|
|
8
|
+
// Latest model: claude-opus-4-6-20260205 (Claude Opus 4.6)
|
|
9
|
+
const CLAUDE_MODEL = 'claude-opus-4-6-20260205';
|
|
10
10
|
|
|
11
11
|
const CLAUDE_PRICING = {
|
|
12
|
-
// Pricing per 1M tokens (Claude Opus 4.
|
|
12
|
+
// Pricing per 1M tokens (Claude Opus 4.6)
|
|
13
13
|
inputPerMillion: 15.00,
|
|
14
14
|
outputPerMillion: 75.00,
|
|
15
15
|
};
|
|
@@ -166,7 +166,7 @@ describe('ClaudeAdapter', () => {
|
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
describe('CLAUDE_PRICING', () => {
|
|
169
|
-
it('exports default pricing for Opus 4.
|
|
169
|
+
it('exports default pricing for Opus 4.6', () => {
|
|
170
170
|
expect(CLAUDE_PRICING.inputPerMillion).toBe(15.00);
|
|
171
171
|
expect(CLAUDE_PRICING.outputPerMillion).toBe(75.00);
|
|
172
172
|
});
|
|
@@ -174,7 +174,7 @@ describe('ClaudeAdapter', () => {
|
|
|
174
174
|
|
|
175
175
|
describe('CLAUDE_MODEL', () => {
|
|
176
176
|
it('exports latest model identifier', () => {
|
|
177
|
-
expect(CLAUDE_MODEL).toBe('claude-opus-4-
|
|
177
|
+
expect(CLAUDE_MODEL).toBe('claude-opus-4-6-20260205');
|
|
178
178
|
});
|
|
179
179
|
});
|
|
180
180
|
});
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
const { BaseAdapter } = require('./base-adapter.js');
|
|
6
6
|
|
|
7
|
-
// Latest model:
|
|
8
|
-
const OPENAI_MODEL = '
|
|
7
|
+
// Latest model: gpt-5.3-codex (February 2026)
|
|
8
|
+
const OPENAI_MODEL = 'gpt-5.3-codex';
|
|
9
9
|
|
|
10
10
|
const OPENAI_PRICING = {
|
|
11
|
-
// Pricing per 1M tokens (
|
|
11
|
+
// Pricing per 1M tokens (gpt-5.3-codex)
|
|
12
12
|
inputPerMillion: 10.00,
|
|
13
13
|
outputPerMillion: 40.00,
|
|
14
14
|
};
|
|
@@ -214,13 +214,13 @@ describe('OpenAIAdapter', () => {
|
|
|
214
214
|
});
|
|
215
215
|
|
|
216
216
|
describe('exports', () => {
|
|
217
|
-
it('exports OPENAI_PRICING for
|
|
217
|
+
it('exports OPENAI_PRICING for gpt-5.3-codex', () => {
|
|
218
218
|
expect(OPENAI_PRICING.inputPerMillion).toBe(10.00);
|
|
219
219
|
expect(OPENAI_PRICING.outputPerMillion).toBe(40.00);
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
it('exports OPENAI_MODEL', () => {
|
|
223
|
-
expect(OPENAI_MODEL).toBe('
|
|
223
|
+
expect(OPENAI_MODEL).toBe('gpt-5.3-codex');
|
|
224
224
|
});
|
|
225
225
|
|
|
226
226
|
it('exports DEFAULT_RATE_LIMITS', () => {
|
|
@@ -19,6 +19,7 @@ const DEFAULT_PRICING = {
|
|
|
19
19
|
'claude-4-opus': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
|
|
20
20
|
'claude-4-sonnet': { inputPer1kTokens: 0.003, outputPer1kTokens: 0.015 },
|
|
21
21
|
'claude-opus-4-5-20251101': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
|
|
22
|
+
'claude-opus-4-6-20260205': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.075 },
|
|
22
23
|
|
|
23
24
|
// OpenAI models
|
|
24
25
|
'gpt-4': { inputPer1kTokens: 0.03, outputPer1kTokens: 0.06 },
|
|
@@ -33,6 +34,7 @@ const DEFAULT_PRICING = {
|
|
|
33
34
|
'o1-pro': { inputPer1kTokens: 0.15, outputPer1kTokens: 0.6 },
|
|
34
35
|
'o3': { inputPer1kTokens: 0.015, outputPer1kTokens: 0.06 },
|
|
35
36
|
'o3-mini': { inputPer1kTokens: 0.0011, outputPer1kTokens: 0.0044 },
|
|
37
|
+
'gpt-5.3-codex': { inputPer1kTokens: 0.01, outputPer1kTokens: 0.04 },
|
|
36
38
|
|
|
37
39
|
// DeepSeek models
|
|
38
40
|
'deepseek-r1': { inputPer1kTokens: 0.00055, outputPer1kTokens: 0.00219 },
|
|
@@ -5,14 +5,99 @@
|
|
|
5
5
|
* This is NOT a command - it's integrated into /tlc:build
|
|
6
6
|
* When a plan has independent tasks, they run in parallel automatically.
|
|
7
7
|
*
|
|
8
|
-
* Default behavior: Auto-parallelize
|
|
8
|
+
* Default behavior: Auto-parallelize with one agent per independent task
|
|
9
9
|
* Use --sequential to force one-at-a-time execution
|
|
10
10
|
* Use --agents N to limit parallelism to specific number
|
|
11
|
+
*
|
|
12
|
+
* Opus 4.6 Multi-Agent Features:
|
|
13
|
+
* - Model selection per agent (opus/sonnet/haiku) based on task complexity
|
|
14
|
+
* - Agent resumption via `resume` parameter for retry/continuation
|
|
15
|
+
* - TaskOutput for non-blocking progress checks on background agents
|
|
16
|
+
* - TaskStop for cancelling stuck agents
|
|
17
|
+
* - Specialized agent types: general-purpose, Bash, Explore, Plan
|
|
18
|
+
* - max_turns to limit agent execution length
|
|
11
19
|
*/
|
|
12
20
|
|
|
13
21
|
const fs = require('fs');
|
|
14
22
|
const path = require('path');
|
|
15
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Valid subagent types for the Task tool (Opus 4.6)
|
|
26
|
+
*/
|
|
27
|
+
const AGENT_TYPES = {
|
|
28
|
+
BUILD: 'general-purpose',
|
|
29
|
+
SHELL: 'Bash',
|
|
30
|
+
EXPLORE: 'Explore',
|
|
31
|
+
PLAN: 'Plan',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Model tiers for cost/capability optimization (Opus 4.6)
|
|
36
|
+
* Agents are assigned models based on task complexity.
|
|
37
|
+
*/
|
|
38
|
+
const MODEL_TIERS = {
|
|
39
|
+
HEAVY: 'opus', // Complex multi-file features, architectural work
|
|
40
|
+
STANDARD: 'sonnet', // Normal implementation tasks (default)
|
|
41
|
+
LIGHT: 'haiku', // Simple tasks: config, boilerplate, single-file changes
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default max turns per agent to prevent runaway execution
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_MAX_TURNS = 50;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Estimate task complexity from its description/title
|
|
51
|
+
* @param {Object} task - Task with id and title
|
|
52
|
+
* @returns {'heavy'|'standard'|'light'} Complexity tier
|
|
53
|
+
*/
|
|
54
|
+
function estimateTaskComplexity(task) {
|
|
55
|
+
const title = (task.title || '').toLowerCase();
|
|
56
|
+
|
|
57
|
+
const heavyPatterns = [
|
|
58
|
+
/architect/i, /refactor/i, /migration/i, /redesign/i,
|
|
59
|
+
/integration/i, /multi.?file/i, /cross.?cutting/i,
|
|
60
|
+
/security/i, /auth/i, /database/i, /schema/i,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const lightPatterns = [
|
|
64
|
+
/config/i, /boilerplate/i, /rename/i, /update.*readme/i,
|
|
65
|
+
/add.*comment/i, /fix.*typo/i, /seed/i, /constant/i,
|
|
66
|
+
/enum/i, /dto/i, /interface/i,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
if (heavyPatterns.some(p => p.test(title))) {
|
|
70
|
+
return 'heavy';
|
|
71
|
+
}
|
|
72
|
+
if (lightPatterns.some(p => p.test(title))) {
|
|
73
|
+
return 'light';
|
|
74
|
+
}
|
|
75
|
+
return 'standard';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get model for a task based on its complexity
|
|
80
|
+
* @param {Object} task - Task object
|
|
81
|
+
* @param {string} [modelOverride] - Force a specific model
|
|
82
|
+
* @returns {string} Model name (opus, sonnet, haiku)
|
|
83
|
+
*/
|
|
84
|
+
function getModelForTask(task, modelOverride) {
|
|
85
|
+
if (modelOverride) {
|
|
86
|
+
return modelOverride;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const complexity = estimateTaskComplexity(task);
|
|
90
|
+
|
|
91
|
+
switch (complexity) {
|
|
92
|
+
case 'heavy':
|
|
93
|
+
return MODEL_TIERS.HEAVY;
|
|
94
|
+
case 'light':
|
|
95
|
+
return MODEL_TIERS.LIGHT;
|
|
96
|
+
default:
|
|
97
|
+
return MODEL_TIERS.STANDARD;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
16
101
|
/**
|
|
17
102
|
* Parse overdrive command arguments
|
|
18
103
|
* @param {string} args - Command arguments
|
|
@@ -21,10 +106,12 @@ const path = require('path');
|
|
|
21
106
|
function parseOverdriveArgs(args = '') {
|
|
22
107
|
const options = {
|
|
23
108
|
phase: null,
|
|
24
|
-
agents: 'auto', //
|
|
109
|
+
agents: 'auto', // One agent per independent task
|
|
25
110
|
mode: 'build', // build, test, fix
|
|
26
111
|
dryRun: false,
|
|
27
112
|
sequential: false,
|
|
113
|
+
model: null, // null = auto-select per task complexity
|
|
114
|
+
maxTurns: DEFAULT_MAX_TURNS,
|
|
28
115
|
};
|
|
29
116
|
|
|
30
117
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
@@ -35,9 +122,16 @@ function parseOverdriveArgs(args = '') {
|
|
|
35
122
|
if (/^\d+$/.test(part)) {
|
|
36
123
|
options.phase = parseInt(part, 10);
|
|
37
124
|
} else if (part === '--agents' && parts[i + 1]) {
|
|
38
|
-
options.agents =
|
|
125
|
+
options.agents = parseInt(parts[++i], 10);
|
|
39
126
|
} else if (part === '--mode' && parts[i + 1]) {
|
|
40
127
|
options.mode = parts[++i];
|
|
128
|
+
} else if (part === '--model' && parts[i + 1]) {
|
|
129
|
+
const model = parts[++i].toLowerCase();
|
|
130
|
+
if (['opus', 'sonnet', 'haiku'].includes(model)) {
|
|
131
|
+
options.model = model;
|
|
132
|
+
}
|
|
133
|
+
} else if (part === '--max-turns' && parts[i + 1]) {
|
|
134
|
+
options.maxTurns = parseInt(parts[++i], 10);
|
|
41
135
|
} else if (part === '--dry-run') {
|
|
42
136
|
options.dryRun = true;
|
|
43
137
|
} else if (part === '--sequential' || part === '-s') {
|
|
@@ -104,6 +198,18 @@ function loadPhaseTasks(projectDir, phase) {
|
|
|
104
198
|
};
|
|
105
199
|
}
|
|
106
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Determine the best agent type for a task
|
|
203
|
+
* @param {Object} task - Task object
|
|
204
|
+
* @param {string} mode - Execution mode (build, test, fix)
|
|
205
|
+
* @returns {string} Agent type for Task tool subagent_type
|
|
206
|
+
*/
|
|
207
|
+
function selectAgentType(task, mode) {
|
|
208
|
+
// Build/test/fix all require full general-purpose agent
|
|
209
|
+
// (reads files, writes code, runs tests, commits)
|
|
210
|
+
return AGENT_TYPES.BUILD;
|
|
211
|
+
}
|
|
212
|
+
|
|
107
213
|
/**
|
|
108
214
|
* Generate agent prompts for parallel execution
|
|
109
215
|
* @param {Array} tasks - Tasks to distribute
|
|
@@ -111,7 +217,7 @@ function loadPhaseTasks(projectDir, phase) {
|
|
|
111
217
|
* @returns {Array} Agent prompts
|
|
112
218
|
*/
|
|
113
219
|
function generateAgentPrompts(tasks, options = {}) {
|
|
114
|
-
const { mode = 'build', projectDir, phase } = options;
|
|
220
|
+
const { mode = 'build', projectDir, phase, model, maxTurns } = options;
|
|
115
221
|
|
|
116
222
|
return tasks.map((task, index) => {
|
|
117
223
|
const basePrompt = `You are Agent ${index + 1} working on Phase ${phase}.
|
|
@@ -142,7 +248,10 @@ GO. Execute now. No questions.`;
|
|
|
142
248
|
taskId: task.id,
|
|
143
249
|
taskTitle: task.title,
|
|
144
250
|
prompt: basePrompt,
|
|
145
|
-
agentType:
|
|
251
|
+
agentType: selectAgentType(task, mode),
|
|
252
|
+
model: getModelForTask(task, model),
|
|
253
|
+
maxTurns: maxTurns || DEFAULT_MAX_TURNS,
|
|
254
|
+
complexity: estimateTaskComplexity(task),
|
|
146
255
|
};
|
|
147
256
|
});
|
|
148
257
|
}
|
|
@@ -171,7 +280,7 @@ function distributeTasks(tasks, agentCount) {
|
|
|
171
280
|
function formatOverdrivePlan(plan) {
|
|
172
281
|
const lines = [];
|
|
173
282
|
|
|
174
|
-
lines.push('# Overdrive Mode');
|
|
283
|
+
lines.push('# Overdrive Mode (Opus 4.6)');
|
|
175
284
|
lines.push('');
|
|
176
285
|
lines.push(`**Phase:** ${plan.phase}`);
|
|
177
286
|
lines.push(`**Mode:** ${plan.mode}`);
|
|
@@ -184,16 +293,24 @@ function formatOverdrivePlan(plan) {
|
|
|
184
293
|
plan.agentAssignments.forEach((assignment, idx) => {
|
|
185
294
|
lines.push(`### Agent ${idx + 1}`);
|
|
186
295
|
assignment.tasks.forEach(task => {
|
|
187
|
-
|
|
296
|
+
const complexity = estimateTaskComplexity(task);
|
|
297
|
+
const model = getModelForTask(task, plan.modelOverride);
|
|
298
|
+
lines.push(`- Task ${task.id}: ${task.title} [${model}] (${complexity})`);
|
|
188
299
|
});
|
|
189
300
|
lines.push('');
|
|
190
301
|
});
|
|
191
302
|
|
|
192
303
|
lines.push('## Execution');
|
|
193
304
|
lines.push('');
|
|
194
|
-
lines.push('All agents
|
|
305
|
+
lines.push('All agents spawned simultaneously via Task tool (Opus 4.6 multi-agent).');
|
|
195
306
|
lines.push('Each agent works independently until completion.');
|
|
196
307
|
lines.push('');
|
|
308
|
+
lines.push('**Capabilities:**');
|
|
309
|
+
lines.push('- Model selection per task complexity (opus/sonnet/haiku)');
|
|
310
|
+
lines.push('- Agent resumption for failed tasks (resume parameter)');
|
|
311
|
+
lines.push('- Non-blocking progress checks (TaskOutput block=false)');
|
|
312
|
+
lines.push('- Agent cancellation (TaskStop) for stuck agents');
|
|
313
|
+
lines.push('');
|
|
197
314
|
lines.push('**Rules enforced:**');
|
|
198
315
|
lines.push('- No confirmation prompts');
|
|
199
316
|
lines.push('- No "shall I continue" questions');
|
|
@@ -204,7 +321,8 @@ function formatOverdrivePlan(plan) {
|
|
|
204
321
|
}
|
|
205
322
|
|
|
206
323
|
/**
|
|
207
|
-
* Generate Task tool calls for parallel execution
|
|
324
|
+
* Generate Task tool calls for parallel execution (Opus 4.6)
|
|
325
|
+
* Includes model selection, max_turns, and correct subagent_type.
|
|
208
326
|
* @param {Array} prompts - Agent prompts
|
|
209
327
|
* @returns {Array} Task tool call specifications
|
|
210
328
|
*/
|
|
@@ -216,6 +334,8 @@ function generateTaskCalls(prompts) {
|
|
|
216
334
|
prompt: p.prompt,
|
|
217
335
|
subagent_type: p.agentType,
|
|
218
336
|
run_in_background: true,
|
|
337
|
+
model: p.model,
|
|
338
|
+
max_turns: p.maxTurns,
|
|
219
339
|
},
|
|
220
340
|
}));
|
|
221
341
|
}
|
|
@@ -270,12 +390,12 @@ async function executeOverdriveCommand(args = '', context = {}) {
|
|
|
270
390
|
};
|
|
271
391
|
}
|
|
272
392
|
|
|
273
|
-
// Determine agent count: auto =
|
|
393
|
+
// Determine agent count: auto = one agent per independent task (no cap)
|
|
274
394
|
let agentCount;
|
|
275
395
|
if (options.sequential) {
|
|
276
396
|
agentCount = 1;
|
|
277
397
|
} else if (options.agents === 'auto') {
|
|
278
|
-
// Auto-detect:
|
|
398
|
+
// Auto-detect: one agent per independent task, no arbitrary cap
|
|
279
399
|
let planContent = '';
|
|
280
400
|
try {
|
|
281
401
|
planContent = fs.readFileSync(phaseInfo.planPath, 'utf-8');
|
|
@@ -285,7 +405,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
|
|
|
285
405
|
const depAnalysis = analyzeDependencies(planContent);
|
|
286
406
|
const parallelAnalysis = canParallelize(availableTasks, depAnalysis);
|
|
287
407
|
agentCount = parallelAnalysis.canParallelize
|
|
288
|
-
?
|
|
408
|
+
? parallelAnalysis.independentTasks.length
|
|
289
409
|
: 1;
|
|
290
410
|
} else {
|
|
291
411
|
agentCount = Math.min(options.agents, availableTasks.length);
|
|
@@ -293,7 +413,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
|
|
|
293
413
|
|
|
294
414
|
const taskGroups = distributeTasks(availableTasks, agentCount);
|
|
295
415
|
|
|
296
|
-
// Generate prompts
|
|
416
|
+
// Generate prompts with model selection and max turns
|
|
297
417
|
const agentAssignments = taskGroups.map((tasks, idx) => ({
|
|
298
418
|
agentId: idx + 1,
|
|
299
419
|
tasks,
|
|
@@ -301,6 +421,8 @@ async function executeOverdriveCommand(args = '', context = {}) {
|
|
|
301
421
|
mode: options.mode,
|
|
302
422
|
projectDir,
|
|
303
423
|
phase: options.phase,
|
|
424
|
+
model: options.model,
|
|
425
|
+
maxTurns: options.maxTurns,
|
|
304
426
|
}),
|
|
305
427
|
}));
|
|
306
428
|
|
|
@@ -310,6 +432,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
|
|
|
310
432
|
agentCount,
|
|
311
433
|
totalTasks: availableTasks.length,
|
|
312
434
|
agentAssignments,
|
|
435
|
+
modelOverride: options.model,
|
|
313
436
|
};
|
|
314
437
|
|
|
315
438
|
if (options.dryRun) {
|
|
@@ -331,7 +454,7 @@ async function executeOverdriveCommand(args = '', context = {}) {
|
|
|
331
454
|
taskCalls,
|
|
332
455
|
output: formatOverdrivePlan(plan),
|
|
333
456
|
instructions: `
|
|
334
|
-
EXECUTE NOW: Spawn ${agentCount} agents in parallel using the Task tool.
|
|
457
|
+
EXECUTE NOW: Spawn ${agentCount} agents in parallel using the Task tool (Opus 4.6).
|
|
335
458
|
|
|
336
459
|
${taskCalls.map((tc, i) => `
|
|
337
460
|
Agent ${i + 1}:
|
|
@@ -339,12 +462,18 @@ Task(
|
|
|
339
462
|
description="${tc.params.description}",
|
|
340
463
|
prompt="${tc.params.prompt.slice(0, 100)}...",
|
|
341
464
|
subagent_type="${tc.params.subagent_type}",
|
|
465
|
+
model="${tc.params.model}",
|
|
466
|
+
max_turns=${tc.params.max_turns},
|
|
342
467
|
run_in_background=true
|
|
343
468
|
)
|
|
344
469
|
`).join('\n')}
|
|
345
470
|
|
|
346
471
|
CRITICAL: Call ALL Task tools in a SINGLE message to run them in parallel.
|
|
347
472
|
Do NOT wait between spawns. Fire them all at once.
|
|
473
|
+
|
|
474
|
+
MONITORING: Use TaskOutput(task_id, block=false) to check progress.
|
|
475
|
+
STUCK AGENT: Use TaskStop(task_id) to cancel, then resume with Task(resume=agent_id).
|
|
476
|
+
FAILED AGENT: Use Task(resume=agent_id) to continue from where it left off.
|
|
348
477
|
`,
|
|
349
478
|
};
|
|
350
479
|
}
|
|
@@ -458,7 +587,7 @@ function canParallelize(tasks, depAnalysis) {
|
|
|
458
587
|
canParallelize: true,
|
|
459
588
|
independentTasks,
|
|
460
589
|
dependentTasks: tasks.filter(t => dependentTasks.has(t.id)),
|
|
461
|
-
recommendedAgents:
|
|
590
|
+
recommendedAgents: independentTasks.length, // One agent per independent task
|
|
462
591
|
};
|
|
463
592
|
}
|
|
464
593
|
|
|
@@ -543,4 +672,11 @@ module.exports = {
|
|
|
543
672
|
canParallelize,
|
|
544
673
|
shouldUseOverdrive,
|
|
545
674
|
autoParallelize,
|
|
675
|
+
// Opus 4.6 multi-agent functions
|
|
676
|
+
estimateTaskComplexity,
|
|
677
|
+
getModelForTask,
|
|
678
|
+
selectAgentType,
|
|
679
|
+
AGENT_TYPES,
|
|
680
|
+
MODEL_TIERS,
|
|
681
|
+
DEFAULT_MAX_TURNS,
|
|
546
682
|
};
|
|
@@ -9,6 +9,12 @@ import {
|
|
|
9
9
|
canParallelize,
|
|
10
10
|
shouldUseOverdrive,
|
|
11
11
|
createOverdriveCommand,
|
|
12
|
+
estimateTaskComplexity,
|
|
13
|
+
getModelForTask,
|
|
14
|
+
selectAgentType,
|
|
15
|
+
AGENT_TYPES,
|
|
16
|
+
MODEL_TIERS,
|
|
17
|
+
DEFAULT_MAX_TURNS,
|
|
12
18
|
} from './overdrive-command.js';
|
|
13
19
|
|
|
14
20
|
describe('overdrive-command', () => {
|
|
@@ -21,6 +27,8 @@ describe('overdrive-command', () => {
|
|
|
21
27
|
expect(options.mode).toBe('build');
|
|
22
28
|
expect(options.dryRun).toBe(false);
|
|
23
29
|
expect(options.sequential).toBe(false);
|
|
30
|
+
expect(options.model).toBeNull();
|
|
31
|
+
expect(options.maxTurns).toBe(DEFAULT_MAX_TURNS);
|
|
24
32
|
});
|
|
25
33
|
|
|
26
34
|
it('parses phase number', () => {
|
|
@@ -28,14 +36,14 @@ describe('overdrive-command', () => {
|
|
|
28
36
|
expect(options.phase).toBe(5);
|
|
29
37
|
});
|
|
30
38
|
|
|
31
|
-
it('parses --agents flag', () => {
|
|
39
|
+
it('parses --agents flag without cap', () => {
|
|
32
40
|
const options = parseOverdriveArgs('--agents 4');
|
|
33
41
|
expect(options.agents).toBe(4);
|
|
34
42
|
});
|
|
35
43
|
|
|
36
|
-
it('
|
|
44
|
+
it('allows agent count beyond 10', () => {
|
|
37
45
|
const options = parseOverdriveArgs('--agents 15');
|
|
38
|
-
expect(options.agents).toBe(
|
|
46
|
+
expect(options.agents).toBe(15);
|
|
39
47
|
});
|
|
40
48
|
|
|
41
49
|
it('parses --mode flag', () => {
|
|
@@ -65,18 +73,35 @@ describe('overdrive-command', () => {
|
|
|
65
73
|
expect(options.agents).toBe(1);
|
|
66
74
|
});
|
|
67
75
|
|
|
68
|
-
it('parses
|
|
69
|
-
|
|
76
|
+
it('parses --model flag', () => {
|
|
77
|
+
expect(parseOverdriveArgs('--model opus').model).toBe('opus');
|
|
78
|
+
expect(parseOverdriveArgs('--model sonnet').model).toBe('sonnet');
|
|
79
|
+
expect(parseOverdriveArgs('--model haiku').model).toBe('haiku');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('ignores invalid model values', () => {
|
|
83
|
+
expect(parseOverdriveArgs('--model gpt4').model).toBeNull();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('parses --max-turns flag', () => {
|
|
87
|
+
const options = parseOverdriveArgs('--max-turns 30');
|
|
88
|
+
expect(options.maxTurns).toBe(30);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('parses multiple flags including new ones', () => {
|
|
92
|
+
const options = parseOverdriveArgs('5 --agents 2 --mode test --model sonnet --max-turns 25 --dry-run');
|
|
70
93
|
|
|
71
94
|
expect(options.phase).toBe(5);
|
|
72
95
|
expect(options.agents).toBe(2);
|
|
73
96
|
expect(options.mode).toBe('test');
|
|
97
|
+
expect(options.model).toBe('sonnet');
|
|
98
|
+
expect(options.maxTurns).toBe(25);
|
|
74
99
|
expect(options.dryRun).toBe(true);
|
|
75
100
|
});
|
|
76
101
|
});
|
|
77
102
|
|
|
78
103
|
describe('generateAgentPrompts', () => {
|
|
79
|
-
it('generates prompts for tasks', () => {
|
|
104
|
+
it('generates prompts for tasks with model and maxTurns', () => {
|
|
80
105
|
const tasks = [
|
|
81
106
|
{ id: 1, title: 'Create schema' },
|
|
82
107
|
{ id: 2, title: 'Add validation' },
|
|
@@ -93,6 +118,9 @@ describe('overdrive-command', () => {
|
|
|
93
118
|
expect(prompts[0].taskTitle).toBe('Create schema');
|
|
94
119
|
expect(prompts[0].prompt).toContain('Task 1');
|
|
95
120
|
expect(prompts[0].prompt).toContain('Write tests first');
|
|
121
|
+
expect(prompts[0].agentType).toBe('general-purpose');
|
|
122
|
+
expect(prompts[0].model).toBeDefined();
|
|
123
|
+
expect(prompts[0].maxTurns).toBe(DEFAULT_MAX_TURNS);
|
|
96
124
|
});
|
|
97
125
|
|
|
98
126
|
it('includes no-question rules', () => {
|
|
@@ -123,6 +151,55 @@ describe('overdrive-command', () => {
|
|
|
123
151
|
|
|
124
152
|
expect(prompts[0].prompt).toContain('Fix any failing tests');
|
|
125
153
|
});
|
|
154
|
+
|
|
155
|
+
it('assigns model based on task complexity', () => {
|
|
156
|
+
const tasks = [
|
|
157
|
+
{ id: 1, title: 'Refactor authentication system' },
|
|
158
|
+
{ id: 2, title: 'Add helper function' },
|
|
159
|
+
{ id: 3, title: 'Create enum constants' },
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const prompts = generateAgentPrompts(tasks, {
|
|
163
|
+
mode: 'build', projectDir: '/p', phase: 1,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(prompts[0].model).toBe('opus'); // refactor = heavy
|
|
167
|
+
expect(prompts[1].model).toBe('sonnet'); // default = standard
|
|
168
|
+
expect(prompts[2].model).toBe('haiku'); // enum = light
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('respects model override', () => {
|
|
172
|
+
const tasks = [
|
|
173
|
+
{ id: 1, title: 'Refactor auth' },
|
|
174
|
+
{ id: 2, title: 'Create enum' },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const prompts = generateAgentPrompts(tasks, {
|
|
178
|
+
mode: 'build', projectDir: '/p', phase: 1,
|
|
179
|
+
model: 'haiku',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(prompts[0].model).toBe('haiku');
|
|
183
|
+
expect(prompts[1].model).toBe('haiku');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('respects maxTurns override', () => {
|
|
187
|
+
const prompts = generateAgentPrompts(
|
|
188
|
+
[{ id: 1, title: 'Test' }],
|
|
189
|
+
{ mode: 'build', projectDir: '/p', phase: 1, maxTurns: 25 }
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(prompts[0].maxTurns).toBe(25);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('includes complexity estimate', () => {
|
|
196
|
+
const prompts = generateAgentPrompts(
|
|
197
|
+
[{ id: 1, title: 'Database migration' }],
|
|
198
|
+
{ mode: 'build', projectDir: '/p', phase: 1 }
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(prompts[0].complexity).toBe('heavy');
|
|
202
|
+
});
|
|
126
203
|
});
|
|
127
204
|
|
|
128
205
|
describe('distributeTasks', () => {
|
|
@@ -207,10 +284,10 @@ describe('overdrive-command', () => {
|
|
|
207
284
|
});
|
|
208
285
|
|
|
209
286
|
describe('generateTaskCalls', () => {
|
|
210
|
-
it('generates task tool calls', () => {
|
|
287
|
+
it('generates task tool calls with model and max_turns', () => {
|
|
211
288
|
const prompts = [
|
|
212
|
-
{ taskId: 1, taskTitle: 'Test', prompt: 'Do task 1', agentType: '
|
|
213
|
-
{ taskId: 2, taskTitle: 'Test 2', prompt: 'Do task 2', agentType: '
|
|
289
|
+
{ taskId: 1, taskTitle: 'Test', prompt: 'Do task 1', agentType: 'general-purpose', model: 'sonnet', maxTurns: 50 },
|
|
290
|
+
{ taskId: 2, taskTitle: 'Test 2', prompt: 'Do task 2', agentType: 'general-purpose', model: 'haiku', maxTurns: 30 },
|
|
214
291
|
];
|
|
215
292
|
|
|
216
293
|
const calls = generateTaskCalls(prompts);
|
|
@@ -219,6 +296,11 @@ describe('overdrive-command', () => {
|
|
|
219
296
|
expect(calls[0].tool).toBe('Task');
|
|
220
297
|
expect(calls[0].params.description).toContain('Agent 1');
|
|
221
298
|
expect(calls[0].params.run_in_background).toBe(true);
|
|
299
|
+
expect(calls[0].params.subagent_type).toBe('general-purpose');
|
|
300
|
+
expect(calls[0].params.model).toBe('sonnet');
|
|
301
|
+
expect(calls[0].params.max_turns).toBe(50);
|
|
302
|
+
expect(calls[1].params.model).toBe('haiku');
|
|
303
|
+
expect(calls[1].params.max_turns).toBe(30);
|
|
222
304
|
});
|
|
223
305
|
});
|
|
224
306
|
|
|
@@ -326,7 +408,7 @@ Blocked by Task 1
|
|
|
326
408
|
expect(result.reason).toContain('Waterfall');
|
|
327
409
|
});
|
|
328
410
|
|
|
329
|
-
it('returns true for independent tasks', () => {
|
|
411
|
+
it('returns true for independent tasks with one agent per task', () => {
|
|
330
412
|
const result = canParallelize(
|
|
331
413
|
[{ id: 1 }, { id: 2 }, { id: 3 }],
|
|
332
414
|
{ hasDependencies: false, dependencies: [] }
|
|
@@ -337,6 +419,14 @@ Blocked by Task 1
|
|
|
337
419
|
expect(result.recommendedAgents).toBe(3);
|
|
338
420
|
});
|
|
339
421
|
|
|
422
|
+
it('recommends agents beyond 10 when tasks warrant it', () => {
|
|
423
|
+
const tasks = Array.from({ length: 15 }, (_, i) => ({ id: i + 1 }));
|
|
424
|
+
const result = canParallelize(tasks, { hasDependencies: false, dependencies: [] });
|
|
425
|
+
|
|
426
|
+
expect(result.canParallelize).toBe(true);
|
|
427
|
+
expect(result.recommendedAgents).toBe(15);
|
|
428
|
+
});
|
|
429
|
+
|
|
340
430
|
it('identifies mixed independent/dependent tasks', () => {
|
|
341
431
|
const result = canParallelize(
|
|
342
432
|
[{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }],
|
|
@@ -372,4 +462,111 @@ Blocked by Task 1
|
|
|
372
462
|
expect(handler.distributeTasks).toBeDefined();
|
|
373
463
|
});
|
|
374
464
|
});
|
|
465
|
+
|
|
466
|
+
describe('estimateTaskComplexity', () => {
|
|
467
|
+
it('classifies heavy tasks', () => {
|
|
468
|
+
expect(estimateTaskComplexity({ title: 'Refactor auth module' })).toBe('heavy');
|
|
469
|
+
expect(estimateTaskComplexity({ title: 'Database migration scripts' })).toBe('heavy');
|
|
470
|
+
expect(estimateTaskComplexity({ title: 'Redesign user schema' })).toBe('heavy');
|
|
471
|
+
expect(estimateTaskComplexity({ title: 'Security audit implementation' })).toBe('heavy');
|
|
472
|
+
expect(estimateTaskComplexity({ title: 'Integration with payment API' })).toBe('heavy');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('classifies light tasks', () => {
|
|
476
|
+
expect(estimateTaskComplexity({ title: 'Add config constants' })).toBe('light');
|
|
477
|
+
expect(estimateTaskComplexity({ title: 'Create user enum' })).toBe('light');
|
|
478
|
+
expect(estimateTaskComplexity({ title: 'Create DTO for response' })).toBe('light');
|
|
479
|
+
expect(estimateTaskComplexity({ title: 'Add seed data' })).toBe('light');
|
|
480
|
+
expect(estimateTaskComplexity({ title: 'Create interface for service' })).toBe('light');
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('classifies standard tasks by default', () => {
|
|
484
|
+
expect(estimateTaskComplexity({ title: 'Add user listing endpoint' })).toBe('standard');
|
|
485
|
+
expect(estimateTaskComplexity({ title: 'Create helper function' })).toBe('standard');
|
|
486
|
+
expect(estimateTaskComplexity({ title: 'Add pagination support' })).toBe('standard');
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('handles empty or missing title', () => {
|
|
490
|
+
expect(estimateTaskComplexity({ title: '' })).toBe('standard');
|
|
491
|
+
expect(estimateTaskComplexity({})).toBe('standard');
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
describe('getModelForTask', () => {
|
|
496
|
+
it('returns opus for heavy tasks', () => {
|
|
497
|
+
expect(getModelForTask({ title: 'Refactor auth' })).toBe('opus');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('returns sonnet for standard tasks', () => {
|
|
501
|
+
expect(getModelForTask({ title: 'Add endpoint' })).toBe('sonnet');
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('returns haiku for light tasks', () => {
|
|
505
|
+
expect(getModelForTask({ title: 'Create enum' })).toBe('haiku');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('respects model override', () => {
|
|
509
|
+
expect(getModelForTask({ title: 'Refactor auth' }, 'haiku')).toBe('haiku');
|
|
510
|
+
expect(getModelForTask({ title: 'Create enum' }, 'opus')).toBe('opus');
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
describe('selectAgentType', () => {
|
|
515
|
+
it('returns general-purpose for build mode', () => {
|
|
516
|
+
expect(selectAgentType({ title: 'Test' }, 'build')).toBe('general-purpose');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('returns general-purpose for test mode', () => {
|
|
520
|
+
expect(selectAgentType({ title: 'Test' }, 'test')).toBe('general-purpose');
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('returns general-purpose for fix mode', () => {
|
|
524
|
+
expect(selectAgentType({ title: 'Test' }, 'fix')).toBe('general-purpose');
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe('constants', () => {
|
|
529
|
+
it('exports valid agent types', () => {
|
|
530
|
+
expect(AGENT_TYPES.BUILD).toBe('general-purpose');
|
|
531
|
+
expect(AGENT_TYPES.SHELL).toBe('Bash');
|
|
532
|
+
expect(AGENT_TYPES.EXPLORE).toBe('Explore');
|
|
533
|
+
expect(AGENT_TYPES.PLAN).toBe('Plan');
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('exports valid model tiers', () => {
|
|
537
|
+
expect(MODEL_TIERS.HEAVY).toBe('opus');
|
|
538
|
+
expect(MODEL_TIERS.STANDARD).toBe('sonnet');
|
|
539
|
+
expect(MODEL_TIERS.LIGHT).toBe('haiku');
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('exports default max turns', () => {
|
|
543
|
+
expect(DEFAULT_MAX_TURNS).toBe(50);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
describe('formatOverdrivePlan', () => {
|
|
548
|
+
it('includes Opus 4.6 branding and model info', () => {
|
|
549
|
+
const plan = {
|
|
550
|
+
phase: 3,
|
|
551
|
+
mode: 'build',
|
|
552
|
+
agentCount: 2,
|
|
553
|
+
totalTasks: 2,
|
|
554
|
+
modelOverride: null,
|
|
555
|
+
agentAssignments: [
|
|
556
|
+
{ tasks: [{ id: 1, title: 'Refactor auth' }] },
|
|
557
|
+
{ tasks: [{ id: 2, title: 'Create enum' }] },
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const output = formatOverdrivePlan(plan);
|
|
562
|
+
|
|
563
|
+
expect(output).toContain('Opus 4.6');
|
|
564
|
+
expect(output).toContain('[opus]');
|
|
565
|
+
expect(output).toContain('[haiku]');
|
|
566
|
+
expect(output).toContain('Model selection per task complexity');
|
|
567
|
+
expect(output).toContain('Agent resumption');
|
|
568
|
+
expect(output).toContain('TaskOutput');
|
|
569
|
+
expect(output).toContain('TaskStop');
|
|
570
|
+
});
|
|
571
|
+
});
|
|
375
572
|
});
|