wogiflow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Model Router
|
|
5
|
+
*
|
|
6
|
+
* Selects optimal model based on task analysis and routing strategy.
|
|
7
|
+
* Supports quality-first, cost-optimized, and learned routing.
|
|
8
|
+
* Includes task-type preferences, language routing, and cascade fallback.
|
|
9
|
+
*
|
|
10
|
+
* Part of Phase 2: Multi-Model Core
|
|
11
|
+
* Enhanced in Phase 3: Intelligent Routing
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* flow model-route "<task>" [--strategy quality-first]
|
|
15
|
+
* flow model-route --analysis <json> --strategy cost-optimized
|
|
16
|
+
* flow route "<task>" --constraints '{"maxCostTier":"standard"}'
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const {
|
|
21
|
+
PROJECT_ROOT,
|
|
22
|
+
parseFlags,
|
|
23
|
+
outputJson,
|
|
24
|
+
color,
|
|
25
|
+
info,
|
|
26
|
+
warn,
|
|
27
|
+
error,
|
|
28
|
+
safeJsonParse,
|
|
29
|
+
getConfig,
|
|
30
|
+
printHeader,
|
|
31
|
+
printSection
|
|
32
|
+
} = require('./flow-utils');
|
|
33
|
+
|
|
34
|
+
const { analyzeTask } = require('./flow-task-analyzer');
|
|
35
|
+
const { loadRegistry, loadStats } = require('./flow-models');
|
|
36
|
+
|
|
37
|
+
// Phase 3: Import cascade fallback (cached singleton)
|
|
38
|
+
let cascadeModule = null;
|
|
39
|
+
try {
|
|
40
|
+
cascadeModule = require('./flow-cascade');
|
|
41
|
+
} catch (err) {
|
|
42
|
+
// Cascade module not available - log only if not a "cannot find module" error
|
|
43
|
+
if (!err.code || err.code !== 'MODULE_NOT_FOUND') {
|
|
44
|
+
console.error('[flow-model-router] Cascade module error:', err.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================================
|
|
49
|
+
// Constants
|
|
50
|
+
// ============================================================
|
|
51
|
+
|
|
52
|
+
const CONFIG_PATH = path.join(PROJECT_ROOT, '.workflow', 'config.json');
|
|
53
|
+
|
|
54
|
+
const ROUTING_STRATEGIES = {
|
|
55
|
+
'quality-first': 'Select highest-capability model matching requirements',
|
|
56
|
+
'cost-optimized': 'Select cheapest model with required capabilities',
|
|
57
|
+
'learned': 'Use historical success rates to optimize selection'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const COST_TIER_ORDER = {
|
|
61
|
+
economy: 1,
|
|
62
|
+
standard: 2,
|
|
63
|
+
premium: 3
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Task-type specific routing preferences (Phase 3).
|
|
68
|
+
* Maps task types to preferred model tiers and required capabilities.
|
|
69
|
+
*/
|
|
70
|
+
const TASK_TYPE_ROUTING = {
|
|
71
|
+
architecture: {
|
|
72
|
+
preferTier: 'premium',
|
|
73
|
+
capabilities: ['reasoning', 'analysis'],
|
|
74
|
+
description: 'Complex architectural decisions benefit from premium models'
|
|
75
|
+
},
|
|
76
|
+
planning: {
|
|
77
|
+
preferTier: 'premium',
|
|
78
|
+
capabilities: ['reasoning', 'analysis'],
|
|
79
|
+
description: 'Planning tasks need strong reasoning capabilities'
|
|
80
|
+
},
|
|
81
|
+
feature: {
|
|
82
|
+
preferTier: 'standard',
|
|
83
|
+
capabilities: ['code-gen', 'reasoning'],
|
|
84
|
+
description: 'Feature development balances quality and cost'
|
|
85
|
+
},
|
|
86
|
+
bugfix: {
|
|
87
|
+
preferTier: 'standard',
|
|
88
|
+
capabilities: ['code-gen', 'analysis'],
|
|
89
|
+
description: 'Bug fixes need good analysis capabilities'
|
|
90
|
+
},
|
|
91
|
+
refactor: {
|
|
92
|
+
preferTier: 'standard',
|
|
93
|
+
capabilities: ['code-gen', 'analysis'],
|
|
94
|
+
description: 'Refactoring needs code understanding'
|
|
95
|
+
},
|
|
96
|
+
boilerplate: {
|
|
97
|
+
preferTier: 'economy',
|
|
98
|
+
capabilities: ['code-gen'],
|
|
99
|
+
description: 'Simple boilerplate can use cheaper models'
|
|
100
|
+
},
|
|
101
|
+
docs: {
|
|
102
|
+
preferTier: 'economy',
|
|
103
|
+
capabilities: ['code-gen'],
|
|
104
|
+
description: 'Documentation tasks are straightforward'
|
|
105
|
+
},
|
|
106
|
+
test: {
|
|
107
|
+
preferTier: 'standard',
|
|
108
|
+
capabilities: ['code-gen'],
|
|
109
|
+
description: 'Test writing needs good code generation'
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Language-specific routing preferences (Phase 3).
|
|
115
|
+
* Maps languages to minimum proficiency requirements.
|
|
116
|
+
*/
|
|
117
|
+
const LANGUAGE_ROUTING = {
|
|
118
|
+
typescript: { minProficiency: 8, description: 'TypeScript needs strong type support' },
|
|
119
|
+
javascript: { minProficiency: 7, description: 'JavaScript is well-supported' },
|
|
120
|
+
python: { minProficiency: 7, description: 'Python is well-supported' },
|
|
121
|
+
rust: { minProficiency: 6, description: 'Rust has specialized syntax' },
|
|
122
|
+
go: { minProficiency: 6, description: 'Go is straightforward' },
|
|
123
|
+
java: { minProficiency: 7, description: 'Java needs good OOP support' },
|
|
124
|
+
csharp: { minProficiency: 7, description: 'C# needs good OOP support' },
|
|
125
|
+
cpp: { minProficiency: 6, description: 'C++ has complex features' },
|
|
126
|
+
ruby: { minProficiency: 6, description: 'Ruby is less common' },
|
|
127
|
+
php: { minProficiency: 6, description: 'PHP is well-documented' }
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const DEFAULT_CONFIG = {
|
|
131
|
+
routingStrategy: 'quality-first',
|
|
132
|
+
fallbackEnabled: true,
|
|
133
|
+
maxEscalations: 2,
|
|
134
|
+
// Phase 3 additions
|
|
135
|
+
constraints: {
|
|
136
|
+
maxCostTier: 'premium',
|
|
137
|
+
requiredCapabilities: []
|
|
138
|
+
},
|
|
139
|
+
taskTypeOverrides: {},
|
|
140
|
+
cascadeEnabled: true
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Load multi-model config
|
|
145
|
+
* @returns {Object} Config with defaults
|
|
146
|
+
*/
|
|
147
|
+
function loadMultiModelConfig() {
|
|
148
|
+
const config = safeJsonParse(CONFIG_PATH);
|
|
149
|
+
return {
|
|
150
|
+
...DEFAULT_CONFIG,
|
|
151
|
+
...(config?.multiModel || {})
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================
|
|
156
|
+
// Model Scoring
|
|
157
|
+
// ============================================================
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Score a model for a given task analysis
|
|
161
|
+
* @param {Object} model - Model data from registry
|
|
162
|
+
* @param {Object} analysis - Task analysis
|
|
163
|
+
* @param {string} strategy - Routing strategy
|
|
164
|
+
* @param {Object} stats - Model stats (optional). Only used for 'learned' strategy
|
|
165
|
+
* to incorporate historical success rates. Passed to all
|
|
166
|
+
* strategies for consistent function signature across routing.
|
|
167
|
+
* @returns {Object} Scoring result
|
|
168
|
+
*/
|
|
169
|
+
function scoreModel(model, analysis, strategy, stats = {}) {
|
|
170
|
+
const scores = {
|
|
171
|
+
capability: 0,
|
|
172
|
+
language: 0,
|
|
173
|
+
cost: 0,
|
|
174
|
+
history: 0,
|
|
175
|
+
total: 0
|
|
176
|
+
};
|
|
177
|
+
const reasons = [];
|
|
178
|
+
|
|
179
|
+
// 1. Capability matching (0-40 points)
|
|
180
|
+
const requiredCaps = new Set(analysis.capabilities);
|
|
181
|
+
const modelCaps = new Set(model.capabilities || []);
|
|
182
|
+
let capMatches = 0;
|
|
183
|
+
let capMisses = [];
|
|
184
|
+
|
|
185
|
+
for (const cap of requiredCaps) {
|
|
186
|
+
if (modelCaps.has(cap)) {
|
|
187
|
+
capMatches++;
|
|
188
|
+
} else {
|
|
189
|
+
capMisses.push(cap);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (requiredCaps.size > 0) {
|
|
194
|
+
scores.capability = (capMatches / requiredCaps.size) * 40;
|
|
195
|
+
if (capMatches === requiredCaps.size) {
|
|
196
|
+
reasons.push('All required capabilities matched');
|
|
197
|
+
} else if (capMisses.length > 0) {
|
|
198
|
+
reasons.push(`Missing capabilities: ${capMisses.join(', ')}`);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
scores.capability = 30; // Default if no specific requirements
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 2. Language proficiency (0-30 points)
|
|
205
|
+
const primaryLang = analysis.languages.primary;
|
|
206
|
+
// Type guard: ensure model.languages is an object before accessing
|
|
207
|
+
const langScore = (model.languages && typeof model.languages === 'object')
|
|
208
|
+
? (model.languages[primaryLang] || 5)
|
|
209
|
+
: 5;
|
|
210
|
+
scores.language = (langScore / 10) * 30;
|
|
211
|
+
|
|
212
|
+
if (langScore >= 9) {
|
|
213
|
+
reasons.push(`Excellent ${primaryLang} support (${langScore}/10)`);
|
|
214
|
+
} else if (langScore >= 7) {
|
|
215
|
+
reasons.push(`Good ${primaryLang} support (${langScore}/10)`);
|
|
216
|
+
} else {
|
|
217
|
+
reasons.push(`Limited ${primaryLang} support (${langScore}/10)`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 3. Cost scoring (0-20 points, depends on strategy)
|
|
221
|
+
const tierScore = {
|
|
222
|
+
economy: 20,
|
|
223
|
+
standard: 10,
|
|
224
|
+
premium: 5
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (strategy === 'cost-optimized') {
|
|
228
|
+
// Higher score for cheaper models
|
|
229
|
+
scores.cost = tierScore[model.costTier] || 10;
|
|
230
|
+
reasons.push(`Cost tier: ${model.costTier}`);
|
|
231
|
+
} else if (strategy === 'quality-first') {
|
|
232
|
+
// Higher score for premium models
|
|
233
|
+
scores.cost = 20 - (tierScore[model.costTier] || 10);
|
|
234
|
+
} else {
|
|
235
|
+
// Balanced
|
|
236
|
+
scores.cost = 10;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 4. Historical performance (0-10 points, for learned routing)
|
|
240
|
+
if (strategy === 'learned' && stats[model.id]) {
|
|
241
|
+
const modelStats = stats[model.id];
|
|
242
|
+
const successRate = modelStats.successRate || 0.5;
|
|
243
|
+
scores.history = successRate * 10;
|
|
244
|
+
|
|
245
|
+
// Check task-type specific stats
|
|
246
|
+
const taskType = analysis.taskType;
|
|
247
|
+
if (modelStats.byTaskType?.[taskType]) {
|
|
248
|
+
const typeStats = modelStats.byTaskType[taskType];
|
|
249
|
+
const total = (typeStats.success || 0) + (typeStats.fail || 0);
|
|
250
|
+
if (total >= 5 && total > 0) {
|
|
251
|
+
const typeRate = (typeStats.success || 0) / total;
|
|
252
|
+
scores.history = typeRate * 10;
|
|
253
|
+
reasons.push(`${(typeRate * 100).toFixed(0)}% success rate on ${taskType} tasks`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Calculate total
|
|
259
|
+
scores.total = scores.capability + scores.language + scores.cost + scores.history;
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
modelId: model.id || model.modelId,
|
|
263
|
+
displayName: model.displayName,
|
|
264
|
+
provider: model.provider,
|
|
265
|
+
costTier: model.costTier,
|
|
266
|
+
scores,
|
|
267
|
+
reasons,
|
|
268
|
+
meetsRequirements: capMisses.length === 0
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================================
|
|
273
|
+
// Routing Strategies
|
|
274
|
+
// ============================================================
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Route using quality-first strategy
|
|
278
|
+
* @param {Object[]} models - Available models
|
|
279
|
+
* @param {Object} analysis - Task analysis
|
|
280
|
+
* @param {Object} stats - Model stats
|
|
281
|
+
* @returns {Object} Routing decision
|
|
282
|
+
*/
|
|
283
|
+
function routeQualityFirst(models, analysis, stats) {
|
|
284
|
+
const scored = models
|
|
285
|
+
.map(m => scoreModel(m, analysis, 'quality-first', stats))
|
|
286
|
+
.filter(s => s.meetsRequirements)
|
|
287
|
+
.sort((a, b) => b.scores.total - a.scores.total);
|
|
288
|
+
|
|
289
|
+
if (scored.length === 0) {
|
|
290
|
+
// Fall back to highest capability model even if not perfect match
|
|
291
|
+
const allScored = models
|
|
292
|
+
.map(m => scoreModel(m, analysis, 'quality-first', stats))
|
|
293
|
+
.sort((a, b) => b.scores.total - a.scores.total);
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
strategy: 'quality-first',
|
|
297
|
+
primary: allScored[0],
|
|
298
|
+
fallback: allScored[1] || null,
|
|
299
|
+
escalation: null,
|
|
300
|
+
warning: 'No model fully meets requirements, using best available'
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
strategy: 'quality-first',
|
|
306
|
+
primary: scored[0],
|
|
307
|
+
fallback: scored[1] || null,
|
|
308
|
+
escalation: null // Already using best
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Route using cost-optimized strategy
|
|
314
|
+
* @param {Object[]} models - Available models
|
|
315
|
+
* @param {Object} analysis - Task analysis
|
|
316
|
+
* @param {Object} stats - Model stats
|
|
317
|
+
* @returns {Object} Routing decision
|
|
318
|
+
*/
|
|
319
|
+
function routeCostOptimized(models, analysis, stats) {
|
|
320
|
+
const scored = models
|
|
321
|
+
.map(m => scoreModel(m, analysis, 'cost-optimized', stats))
|
|
322
|
+
.filter(s => s.meetsRequirements);
|
|
323
|
+
|
|
324
|
+
// Sort by cost tier first, then by capability within tier
|
|
325
|
+
scored.sort((a, b) => {
|
|
326
|
+
const tierDiff = COST_TIER_ORDER[a.costTier] - COST_TIER_ORDER[b.costTier];
|
|
327
|
+
if (tierDiff !== 0) return tierDiff;
|
|
328
|
+
return b.scores.capability - a.scores.capability;
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (scored.length === 0) {
|
|
332
|
+
// Fall back to cheapest model
|
|
333
|
+
const allScored = models
|
|
334
|
+
.map(m => scoreModel(m, analysis, 'cost-optimized', stats))
|
|
335
|
+
.sort((a, b) => COST_TIER_ORDER[a.costTier] - COST_TIER_ORDER[b.costTier]);
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
strategy: 'cost-optimized',
|
|
339
|
+
primary: allScored[0],
|
|
340
|
+
fallback: null,
|
|
341
|
+
escalation: allScored.find(m => COST_TIER_ORDER[m.costTier] > COST_TIER_ORDER[allScored[0].costTier]),
|
|
342
|
+
warning: 'No model fully meets requirements, using cheapest available'
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Find escalation option (higher tier)
|
|
347
|
+
const primaryTier = COST_TIER_ORDER[scored[0].costTier];
|
|
348
|
+
const escalation = scored.find(m => COST_TIER_ORDER[m.costTier] > primaryTier);
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
strategy: 'cost-optimized',
|
|
352
|
+
primary: scored[0],
|
|
353
|
+
fallback: scored.find(m => m.modelId !== scored[0].modelId && COST_TIER_ORDER[m.costTier] === primaryTier),
|
|
354
|
+
escalation
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Route using learned strategy (historical performance)
|
|
360
|
+
* @param {Object[]} models - Available models
|
|
361
|
+
* @param {Object} analysis - Task analysis
|
|
362
|
+
* @param {Object} stats - Model stats
|
|
363
|
+
* @returns {Object} Routing decision
|
|
364
|
+
*/
|
|
365
|
+
function routeLearned(models, analysis, stats) {
|
|
366
|
+
const scored = models
|
|
367
|
+
.map(m => scoreModel(m, analysis, 'learned', stats))
|
|
368
|
+
.filter(s => s.meetsRequirements)
|
|
369
|
+
.sort((a, b) => b.scores.total - a.scores.total);
|
|
370
|
+
|
|
371
|
+
if (scored.length === 0) {
|
|
372
|
+
// Fall back to quality-first if no learned data
|
|
373
|
+
return routeQualityFirst(models, analysis, stats);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Check if we have enough data for learned routing
|
|
377
|
+
const hasEnoughData = Object.values(stats).some(s => (s.totalRuns || 0) >= 10);
|
|
378
|
+
|
|
379
|
+
if (!hasEnoughData) {
|
|
380
|
+
const result = routeQualityFirst(models, analysis, stats);
|
|
381
|
+
result.warning = 'Insufficient historical data, falling back to quality-first';
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
strategy: 'learned',
|
|
387
|
+
primary: scored[0],
|
|
388
|
+
fallback: scored[1] || null,
|
|
389
|
+
escalation: scored.find(m => COST_TIER_ORDER[m.costTier] > COST_TIER_ORDER[scored[0].costTier])
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ============================================================
|
|
394
|
+
// Phase 3: Enhanced Routing
|
|
395
|
+
// ============================================================
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Apply constraints to filter models.
|
|
399
|
+
* @param {Object[]} models - Available models
|
|
400
|
+
* @param {Object} constraints - Constraint configuration
|
|
401
|
+
* @returns {Object[]} Filtered models
|
|
402
|
+
*/
|
|
403
|
+
function applyConstraints(models, constraints = {}) {
|
|
404
|
+
let filtered = [...models];
|
|
405
|
+
|
|
406
|
+
// Filter by max cost tier
|
|
407
|
+
if (constraints.maxCostTier) {
|
|
408
|
+
const maxTier = COST_TIER_ORDER[constraints.maxCostTier] || 3;
|
|
409
|
+
filtered = filtered.filter(m => (COST_TIER_ORDER[m.costTier] || 2) <= maxTier);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Filter by required capabilities
|
|
413
|
+
if (constraints.requiredCapabilities && constraints.requiredCapabilities.length > 0) {
|
|
414
|
+
const required = new Set(constraints.requiredCapabilities);
|
|
415
|
+
filtered = filtered.filter(m => {
|
|
416
|
+
const caps = new Set(m.capabilities || []);
|
|
417
|
+
return [...required].every(c => caps.has(c));
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return filtered;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Apply task-type preferences to scoring.
|
|
426
|
+
* @param {Object} scored - Scored model result
|
|
427
|
+
* @param {string} taskType - Task type
|
|
428
|
+
* @param {Object} overrides - User overrides from config
|
|
429
|
+
* @returns {Object} Adjusted scored model
|
|
430
|
+
*/
|
|
431
|
+
function applyTaskTypePreferences(scored, taskType, overrides = {}) {
|
|
432
|
+
const prefs = overrides[taskType] || TASK_TYPE_ROUTING[taskType];
|
|
433
|
+
if (!prefs) return scored;
|
|
434
|
+
|
|
435
|
+
const adjustedScores = { ...scored.scores };
|
|
436
|
+
const reasons = [...scored.reasons];
|
|
437
|
+
|
|
438
|
+
// Bonus for matching preferred tier
|
|
439
|
+
if (prefs.preferTier === scored.costTier) {
|
|
440
|
+
adjustedScores.taskTypeBonus = 5;
|
|
441
|
+
reasons.push(`Matches preferred tier for ${taskType} tasks`);
|
|
442
|
+
} else {
|
|
443
|
+
adjustedScores.taskTypeBonus = 0;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
adjustedScores.total = adjustedScores.capability + adjustedScores.language +
|
|
447
|
+
adjustedScores.cost + adjustedScores.history + adjustedScores.taskTypeBonus;
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
...scored,
|
|
451
|
+
scores: adjustedScores,
|
|
452
|
+
reasons,
|
|
453
|
+
taskTypeMatch: prefs.preferTier === scored.costTier
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Apply language proficiency requirements.
|
|
459
|
+
* @param {Object} model - Model data
|
|
460
|
+
* @param {string} language - Primary language
|
|
461
|
+
* @returns {Object} Language check result
|
|
462
|
+
*/
|
|
463
|
+
function checkLanguageProficiency(model, language) {
|
|
464
|
+
const langReq = LANGUAGE_ROUTING[language];
|
|
465
|
+
if (!langReq) return { meets: true, reason: 'No specific requirements' };
|
|
466
|
+
|
|
467
|
+
const proficiency = (model.languages && typeof model.languages === 'object')
|
|
468
|
+
? (model.languages[language] || 5)
|
|
469
|
+
: 5;
|
|
470
|
+
|
|
471
|
+
const meets = proficiency >= langReq.minProficiency;
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
meets,
|
|
475
|
+
proficiency,
|
|
476
|
+
required: langReq.minProficiency,
|
|
477
|
+
reason: meets
|
|
478
|
+
? `${language} proficiency ${proficiency}/10 meets requirement`
|
|
479
|
+
: `${language} proficiency ${proficiency}/10 below required ${langReq.minProficiency}`
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Check cascade fallback status for a model.
|
|
485
|
+
* @param {string} modelId - Model identifier
|
|
486
|
+
* @param {string} taskType - Task type
|
|
487
|
+
* @param {Object} routing - Current routing decision
|
|
488
|
+
* @returns {Object|null} Cascade recommendation if applicable
|
|
489
|
+
*/
|
|
490
|
+
function checkCascadeFallback(modelId, taskType, routing) {
|
|
491
|
+
if (!cascadeModule) return null;
|
|
492
|
+
|
|
493
|
+
const escalation = cascadeModule.getEscalationTarget(modelId, routing);
|
|
494
|
+
return escalation;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Enhanced routing with constraints, task-type preferences, and cascade.
|
|
499
|
+
* @param {Object} params - Routing parameters
|
|
500
|
+
* @returns {Object} Enhanced routing decision
|
|
501
|
+
*/
|
|
502
|
+
function routeTaskEnhanced(params) {
|
|
503
|
+
const {
|
|
504
|
+
analysis,
|
|
505
|
+
strategy = 'quality-first',
|
|
506
|
+
constraints = {},
|
|
507
|
+
checkCascade = true
|
|
508
|
+
} = params;
|
|
509
|
+
|
|
510
|
+
// Load registry and stats
|
|
511
|
+
const registry = loadRegistry();
|
|
512
|
+
if (!registry) {
|
|
513
|
+
return { success: false, error: 'Model registry not found' };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const stats = loadStats();
|
|
517
|
+
const config = loadMultiModelConfig();
|
|
518
|
+
|
|
519
|
+
// Merge constraints
|
|
520
|
+
const effectiveConstraints = {
|
|
521
|
+
...config.constraints,
|
|
522
|
+
...constraints
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// Convert registry models to array with IDs
|
|
526
|
+
let models = Object.entries(registry.models || {}).map(([id, data]) => ({
|
|
527
|
+
id,
|
|
528
|
+
...data
|
|
529
|
+
}));
|
|
530
|
+
|
|
531
|
+
if (models.length === 0) {
|
|
532
|
+
return { success: false, error: 'No models in registry' };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Phase 3: Apply constraints
|
|
536
|
+
const constrainedModels = applyConstraints(models, effectiveConstraints);
|
|
537
|
+
const constraintsApplied = constrainedModels.length < models.length;
|
|
538
|
+
|
|
539
|
+
if (constrainedModels.length === 0) {
|
|
540
|
+
return {
|
|
541
|
+
success: false,
|
|
542
|
+
error: 'No models meet constraints',
|
|
543
|
+
constraints: effectiveConstraints,
|
|
544
|
+
originalCount: models.length
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
models = constrainedModels;
|
|
549
|
+
|
|
550
|
+
// Check language proficiency for all models
|
|
551
|
+
const primaryLang = analysis.languages?.primary;
|
|
552
|
+
if (primaryLang) {
|
|
553
|
+
models = models.map(m => ({
|
|
554
|
+
...m,
|
|
555
|
+
languageCheck: checkLanguageProficiency(m, primaryLang)
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Run base routing
|
|
560
|
+
const effectiveStrategy = strategy || config.routingStrategy;
|
|
561
|
+
let decision;
|
|
562
|
+
|
|
563
|
+
switch (effectiveStrategy) {
|
|
564
|
+
case 'quality-first':
|
|
565
|
+
decision = routeQualityFirst(models, analysis, stats);
|
|
566
|
+
break;
|
|
567
|
+
case 'cost-optimized':
|
|
568
|
+
decision = routeCostOptimized(models, analysis, stats);
|
|
569
|
+
break;
|
|
570
|
+
case 'learned':
|
|
571
|
+
decision = routeLearned(models, analysis, stats);
|
|
572
|
+
break;
|
|
573
|
+
default:
|
|
574
|
+
decision = routeQualityFirst(models, analysis, stats);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Phase 3: Apply task-type preferences
|
|
578
|
+
const taskType = analysis.taskType || analysis.type || 'feature';
|
|
579
|
+
if (decision.primary) {
|
|
580
|
+
decision.primary = applyTaskTypePreferences(
|
|
581
|
+
decision.primary,
|
|
582
|
+
taskType,
|
|
583
|
+
config.taskTypeOverrides
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Phase 3: Check cascade fallback
|
|
588
|
+
let cascadeInfo = null;
|
|
589
|
+
if (checkCascade && config.cascadeEnabled && cascadeModule && decision.primary) {
|
|
590
|
+
cascadeInfo = checkCascadeFallback(
|
|
591
|
+
decision.primary.modelId,
|
|
592
|
+
taskType,
|
|
593
|
+
decision
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
if (cascadeInfo?.shouldEscalate) {
|
|
597
|
+
decision.cascadeTriggered = true;
|
|
598
|
+
decision.cascadeInfo = cascadeInfo;
|
|
599
|
+
|
|
600
|
+
// If we have a target model, use it as primary
|
|
601
|
+
if (cascadeInfo.targetModel) {
|
|
602
|
+
const targetModelData = models.find(m => m.id === cascadeInfo.targetModel);
|
|
603
|
+
if (targetModelData) {
|
|
604
|
+
decision.originalPrimary = decision.primary;
|
|
605
|
+
decision.primary = scoreModel(targetModelData, analysis, effectiveStrategy, stats);
|
|
606
|
+
decision.primary.cascadeEscalated = true;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Add enhanced metadata
|
|
613
|
+
decision.success = true;
|
|
614
|
+
decision.config = config;
|
|
615
|
+
decision.routedAt = new Date().toISOString();
|
|
616
|
+
decision.analysis = {
|
|
617
|
+
complexity: analysis.complexity?.level || 'medium',
|
|
618
|
+
domains: analysis.domains?.primary || 'general',
|
|
619
|
+
languages: analysis.languages?.primary || 'javascript',
|
|
620
|
+
taskType,
|
|
621
|
+
capabilities: analysis.capabilities || []
|
|
622
|
+
};
|
|
623
|
+
decision.enhanced = true;
|
|
624
|
+
decision.constraintsApplied = constraintsApplied;
|
|
625
|
+
decision.constraints = effectiveConstraints;
|
|
626
|
+
|
|
627
|
+
// Add task-type routing info
|
|
628
|
+
const taskTypeInfo = TASK_TYPE_ROUTING[taskType];
|
|
629
|
+
if (taskTypeInfo) {
|
|
630
|
+
decision.taskTypeRouting = {
|
|
631
|
+
taskType,
|
|
632
|
+
preferredTier: taskTypeInfo.preferTier,
|
|
633
|
+
requiredCapabilities: taskTypeInfo.capabilities,
|
|
634
|
+
description: taskTypeInfo.description
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return decision;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Get routing configuration (for CLI display).
|
|
643
|
+
* @returns {Object} Routing configuration
|
|
644
|
+
*/
|
|
645
|
+
function getRoutingConfig() {
|
|
646
|
+
const config = loadMultiModelConfig();
|
|
647
|
+
return {
|
|
648
|
+
strategy: config.routingStrategy,
|
|
649
|
+
constraints: config.constraints,
|
|
650
|
+
taskTypeOverrides: config.taskTypeOverrides,
|
|
651
|
+
cascadeEnabled: config.cascadeEnabled,
|
|
652
|
+
taskTypeRouting: TASK_TYPE_ROUTING,
|
|
653
|
+
languageRouting: LANGUAGE_ROUTING
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// ============================================================
|
|
658
|
+
// Main Router
|
|
659
|
+
// ============================================================
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Route task to optimal model
|
|
663
|
+
* @param {Object} params - Routing parameters
|
|
664
|
+
* @returns {Object} Routing decision
|
|
665
|
+
*/
|
|
666
|
+
function routeTask(params) {
|
|
667
|
+
const { analysis, strategy = 'quality-first' } = params;
|
|
668
|
+
|
|
669
|
+
// Load registry and stats
|
|
670
|
+
const registry = loadRegistry();
|
|
671
|
+
if (!registry) {
|
|
672
|
+
return {
|
|
673
|
+
success: false,
|
|
674
|
+
error: 'Model registry not found'
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const stats = loadStats();
|
|
679
|
+
const config = loadMultiModelConfig();
|
|
680
|
+
|
|
681
|
+
// Convert registry models to array with IDs
|
|
682
|
+
const models = Object.entries(registry.models || {}).map(([id, data]) => ({
|
|
683
|
+
id,
|
|
684
|
+
...data
|
|
685
|
+
}));
|
|
686
|
+
|
|
687
|
+
if (models.length === 0) {
|
|
688
|
+
return {
|
|
689
|
+
success: false,
|
|
690
|
+
error: 'No models in registry'
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Select routing strategy
|
|
695
|
+
const effectiveStrategy = strategy || config.routingStrategy;
|
|
696
|
+
let decision;
|
|
697
|
+
|
|
698
|
+
switch (effectiveStrategy) {
|
|
699
|
+
case 'quality-first':
|
|
700
|
+
decision = routeQualityFirst(models, analysis, stats);
|
|
701
|
+
break;
|
|
702
|
+
case 'cost-optimized':
|
|
703
|
+
decision = routeCostOptimized(models, analysis, stats);
|
|
704
|
+
break;
|
|
705
|
+
case 'learned':
|
|
706
|
+
decision = routeLearned(models, analysis, stats);
|
|
707
|
+
break;
|
|
708
|
+
default:
|
|
709
|
+
decision = routeQualityFirst(models, analysis, stats);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Add metadata
|
|
713
|
+
decision.success = true;
|
|
714
|
+
decision.config = config;
|
|
715
|
+
decision.routedAt = new Date().toISOString();
|
|
716
|
+
decision.analysis = {
|
|
717
|
+
complexity: analysis.complexity.level,
|
|
718
|
+
domains: analysis.domains.primary,
|
|
719
|
+
languages: analysis.languages.primary,
|
|
720
|
+
capabilities: analysis.capabilities
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
return decision;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ============================================================
|
|
727
|
+
// CLI Output
|
|
728
|
+
// ============================================================
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Print routing decision
|
|
732
|
+
* @param {Object} decision - Routing decision
|
|
733
|
+
*/
|
|
734
|
+
function printDecision(decision) {
|
|
735
|
+
printHeader('MODEL ROUTING DECISION');
|
|
736
|
+
|
|
737
|
+
if (!decision.success) {
|
|
738
|
+
error(decision.error);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Strategy
|
|
743
|
+
printSection('Strategy');
|
|
744
|
+
console.log(` ${color('cyan', decision.strategy)}`);
|
|
745
|
+
console.log(` ${ROUTING_STRATEGIES[decision.strategy]}`);
|
|
746
|
+
|
|
747
|
+
// Task Analysis Summary
|
|
748
|
+
printSection('Task Analysis');
|
|
749
|
+
console.log(` Complexity: ${decision.analysis.complexity}`);
|
|
750
|
+
console.log(` Domain: ${decision.analysis.domains}`);
|
|
751
|
+
console.log(` Language: ${decision.analysis.languages}`);
|
|
752
|
+
console.log(` Capabilities: ${decision.analysis.capabilities.join(', ')}`);
|
|
753
|
+
|
|
754
|
+
// Primary Model
|
|
755
|
+
printSection('Primary Model');
|
|
756
|
+
const primary = decision.primary;
|
|
757
|
+
console.log(` ${color('green', primary.displayName)} (${primary.provider})`);
|
|
758
|
+
console.log(` Cost tier: ${primary.costTier}`);
|
|
759
|
+
console.log(` Score: ${primary.scores.total.toFixed(1)}/100`);
|
|
760
|
+
for (const reason of primary.reasons.slice(0, 3)) {
|
|
761
|
+
console.log(` - ${reason}`);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Fallback
|
|
765
|
+
if (decision.fallback) {
|
|
766
|
+
printSection('Fallback Model');
|
|
767
|
+
console.log(` ${color('yellow', decision.fallback.displayName)} (${decision.fallback.provider})`);
|
|
768
|
+
console.log(` Score: ${decision.fallback.scores.total.toFixed(1)}/100`);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Escalation
|
|
772
|
+
if (decision.escalation) {
|
|
773
|
+
printSection('Escalation Model');
|
|
774
|
+
console.log(` ${color('cyan', decision.escalation.displayName)} (${decision.escalation.provider})`);
|
|
775
|
+
console.log(` Cost tier: ${decision.escalation.costTier}`);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Warning
|
|
779
|
+
if (decision.warning) {
|
|
780
|
+
console.log('');
|
|
781
|
+
warn(decision.warning);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
console.log('');
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// ============================================================
|
|
788
|
+
// Main
|
|
789
|
+
// ============================================================
|
|
790
|
+
|
|
791
|
+
async function main() {
|
|
792
|
+
const { positional, flags } = parseFlags(process.argv.slice(2));
|
|
793
|
+
|
|
794
|
+
let analysis;
|
|
795
|
+
|
|
796
|
+
// Get analysis from flag or run analyzer
|
|
797
|
+
if (flags.analysis) {
|
|
798
|
+
// flags.analysis is a JSON string from CLI, not a file path
|
|
799
|
+
// Security: Check for prototype pollution attempts
|
|
800
|
+
if (flags.analysis.includes('__proto__') ||
|
|
801
|
+
flags.analysis.includes('constructor') ||
|
|
802
|
+
flags.analysis.includes('prototype')) {
|
|
803
|
+
error('Invalid --analysis JSON: contains restricted keys');
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
analysis = JSON.parse(flags.analysis);
|
|
808
|
+
if (!analysis || typeof analysis !== 'object' || Array.isArray(analysis)) {
|
|
809
|
+
error('Invalid --analysis JSON: must be a non-array object');
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
// Validate expected structure
|
|
813
|
+
const validKeys = ['taskType', 'languages', 'capabilities', 'complexity', 'domains', 'patterns'];
|
|
814
|
+
const analysisKeys = Object.keys(analysis);
|
|
815
|
+
const invalidKeys = analysisKeys.filter(k => !validKeys.includes(k));
|
|
816
|
+
if (invalidKeys.length > 0) {
|
|
817
|
+
error(`Invalid --analysis JSON: unexpected keys: ${invalidKeys.join(', ')}`);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
} catch (err) {
|
|
821
|
+
error(`Invalid --analysis JSON: ${err.message}`);
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
} else if (positional.length > 0) {
|
|
825
|
+
const taskDescription = positional.join(' ');
|
|
826
|
+
analysis = analyzeTask({
|
|
827
|
+
title: taskDescription,
|
|
828
|
+
type: flags.type || 'feature'
|
|
829
|
+
});
|
|
830
|
+
} else {
|
|
831
|
+
error('Usage: flow model-route "<task description>" [--strategy quality-first]');
|
|
832
|
+
error(' flow model-route --analysis <json>');
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Route task
|
|
837
|
+
const strategy = flags.strategy || 'quality-first';
|
|
838
|
+
const decision = routeTask({ analysis, strategy });
|
|
839
|
+
|
|
840
|
+
// Output
|
|
841
|
+
if (flags.json) {
|
|
842
|
+
outputJson(decision);
|
|
843
|
+
} else {
|
|
844
|
+
printDecision(decision);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Export for use by other scripts
|
|
849
|
+
module.exports = {
|
|
850
|
+
// Core routing
|
|
851
|
+
routeTask,
|
|
852
|
+
routeTaskEnhanced,
|
|
853
|
+
scoreModel,
|
|
854
|
+
|
|
855
|
+
// Strategy functions
|
|
856
|
+
routeQualityFirst,
|
|
857
|
+
routeCostOptimized,
|
|
858
|
+
routeLearned,
|
|
859
|
+
|
|
860
|
+
// Phase 3: Enhanced routing helpers
|
|
861
|
+
applyConstraints,
|
|
862
|
+
applyTaskTypePreferences,
|
|
863
|
+
checkLanguageProficiency,
|
|
864
|
+
checkCascadeFallback,
|
|
865
|
+
getRoutingConfig,
|
|
866
|
+
|
|
867
|
+
// Registry/stats access
|
|
868
|
+
loadRegistry,
|
|
869
|
+
loadStats,
|
|
870
|
+
loadMultiModelConfig,
|
|
871
|
+
|
|
872
|
+
// Constants
|
|
873
|
+
ROUTING_STRATEGIES,
|
|
874
|
+
COST_TIER_ORDER,
|
|
875
|
+
TASK_TYPE_ROUTING,
|
|
876
|
+
LANGUAGE_ROUTING
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
if (require.main === module) {
|
|
880
|
+
main().catch(err => {
|
|
881
|
+
error(err.message);
|
|
882
|
+
process.exit(1);
|
|
883
|
+
});
|
|
884
|
+
}
|