- Top Candidates
+ Top Models
@@ -835,7 +858,7 @@ export function CandidatesContent({
{budgetHire && (
)}
- {/* ── All Candidates Table ────────────────────────────────────── */}
+ {/* ── All Models Table ────────────────────────────────────────── */}
{/* Subtle background */}
- All Candidates
+ All Models
📊 Compare all candidates
@@ -983,7 +1006,7 @@ export function CandidatesContent({
variants={containerVariants}>
Back to all roles
@@ -991,7 +1014,7 @@ export function CandidatesContent({
📊 Compare candidates
diff --git a/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/comparison-chart.tsx b/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/comparison-chart.tsx
index 998e1af261..d03f83fe67 100644
--- a/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/comparison-chart.tsx
+++ b/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/comparison-chart.tsx
@@ -2,6 +2,7 @@
import { useState, useMemo, useCallback } from "react"
import Link from "next/link"
+import { useSearchParams } from "next/navigation"
import { motion } from "framer-motion"
import {
ArrowLeft,
@@ -409,12 +410,12 @@ function ScatterTooltip({
- Daily Salary:
+ Daily Spend:
${data.dailyCost}/day
- Interview Score:
+ Eval Score:
{data.score}
@@ -433,11 +434,29 @@ interface ComparisonChartProps {
recommendation: RoleRecommendation
role: EngineerRole
roleId: string
+ workersRootPath?: string
}
-export function ComparisonChart({ recommendation, role, roleId }: ComparisonChartProps) {
+export function ComparisonChart({
+ recommendation,
+ role,
+ roleId,
+ workersRootPath = "/evals/workers",
+}: ComparisonChartProps) {
+ const searchParams = useSearchParams()
const { allCandidates } = recommendation
const theme = ROLE_THEMES[roleId] ?? DEFAULT_THEME
+ const alternateWorkersRootPath = workersRootPath === "/evals/workers-v2" ? "/evals/workers" : "/evals/workers-v2"
+ const alternateVersionLabel = workersRootPath === "/evals/workers-v2" ? "View baseline" : "View V2 preview"
+ const setupQuery = (() => {
+ const outcome = searchParams.get("outcome")
+ if (!outcome) return ""
+ const params = new URLSearchParams()
+ params.set("outcome", outcome)
+ const mode = searchParams.get("mode")
+ if (mode) params.set("mode", mode)
+ return `?${params.toString()}`
+ })()
// ── State ───────────────────────────────────────────────────────────────
const [selectedLanguage, setSelectedLanguage] = useState("all")
@@ -561,15 +580,25 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
Evals
/
-
- Hire an AI Engineer
+
+ Build with Roo Code Cloud
/
-
+
{role.name}
/
- Compare Candidates
+ Compare Models
+ /
+
+ {alternateVersionLabel}
+
{/* Title row */}
@@ -579,7 +608,7 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
-
Compare Candidates
+
Compare Models
{role.name}
@@ -599,7 +628,7 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
{filteredCandidates.length}
- of {allCandidates.length} candidates shown
+ of {allCandidates.length} models shown
@@ -731,10 +760,10 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
className="rounded-2xl border border-border/50 bg-card/50 p-6 backdrop-blur-sm"
variants={fadeUpVariants}>
-
Value Map: Salary vs Interview Score
+ Value Map: Spend vs Eval Score
- Upper-left = best value. Each dot is a candidate model. Size reflects success rate.
+ Upper-left = higher score at lower spend. Each dot is a model. Size reflects success rate.
{/* Tier legend */}
@@ -753,7 +782,7 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
{scatterData.length === 0 ? (
-
No candidates match the current filters.
+
No models match the current filters.
Try adjusting the provider or success rate filters.
@@ -765,7 +794,7 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
`$${v}`}
stroke="hsl(var(--muted-foreground))"
@@ -773,7 +802,7 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
axisLine={false}
label={{
- value: "Daily Salary ($)",
+ value: "Daily Spend ($)",
position: "insideBottom",
offset: -10,
style: { fontSize: 11, fill: "hsl(var(--muted-foreground))" },
@@ -782,14 +811,14 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
-
+
{scatterData.map((entry, index) => (
- No candidates match the current filters.
+ No models match the current filters.
Try adjusting the provider or success rate filters.
@@ -993,15 +1022,15 @@ export function ComparisonChart({ recommendation, role, roleId }: ComparisonChar
- Back to {role.name} candidates
+ Back to {role.name} models
All roles
diff --git a/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/page.tsx b/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/page.tsx
index d79b22eedd..9d03aa9cca 100644
--- a/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/page.tsx
+++ b/apps/web-roo-code/src/app/evals/workers/[roleId]/compare/page.tsx
@@ -22,9 +22,9 @@ export async function generateMetadata({ params }: PageProps): Promise
}
}
- const title = `Compare Candidates — ${role.name} | Roo Code Evals`
- const description = `Interactive comparison of AI model candidates for the ${role.name} role. Compare composite score, success rate, cost efficiency, and speed.`
- const ogDescription = `Compare Candidates — ${role.name}`
+ const title = `Compare Models — ${role.name} | Roo Code Evals`
+ const description = `Interactive comparison of AI models for the ${role.name} setup. Compare composite score, success rate, cost efficiency, and speed.`
+ const ogDescription = `Compare Models — ${role.name}`
const path = `/evals/workers/${roleId}/compare`
return {
@@ -57,19 +57,19 @@ export async function generateMetadata({ params }: PageProps): Promise
},
keywords: [
...SEO.keywords,
- "AI engineer",
+ "AI coding",
"model comparison",
"coding evals",
role.name.toLowerCase(),
"bar chart",
- "candidate comparison",
+ "model comparison",
],
}
}
// ── Page Component ──────────────────────────────────────────────────────────
-export default async function CompareCandidatesPage({ params }: PageProps) {
+export default async function CompareModelsPage({ params }: PageProps) {
const { roleId } = await params
const recommendation = getRoleRecommendation(roleId)
@@ -77,5 +77,12 @@ export default async function CompareCandidatesPage({ params }: PageProps) {
notFound()
}
- return
+ return (
+
+ )
}
diff --git a/apps/web-roo-code/src/app/evals/workers/[roleId]/page.tsx b/apps/web-roo-code/src/app/evals/workers/[roleId]/page.tsx
index 8daa554cd2..54ca16d0d2 100644
--- a/apps/web-roo-code/src/app/evals/workers/[roleId]/page.tsx
+++ b/apps/web-roo-code/src/app/evals/workers/[roleId]/page.tsx
@@ -23,9 +23,9 @@ export async function generateMetadata({ params }: PageProps): Promise
}
const { role } = recommendation
- const title = `${role.name} — AI Engineer Candidates | Roo Code Evals`
- const description = `Interview results for ${role.name} AI candidates. Compare models by success rate, cost, and speed across 5 languages.`
- const ogDescription = `${role.name} — AI Engineer Candidates`
+ const title = `${role.name} — Recommended Models | Roo Code Evals`
+ const description = `Eval-backed recommendations for ${role.name}. Compare models by success rate, cost, and speed across 5 languages.`
+ const ogDescription = `${role.name} — Recommended Models`
const path = `/evals/workers/${roleId}`
return {
@@ -58,7 +58,8 @@ export async function generateMetadata({ params }: PageProps): Promise
},
keywords: [
...SEO.keywords,
- "AI engineer",
+ "AI coding",
+ "coding agents",
"model recommendations",
"coding evals",
role.name.toLowerCase(),
@@ -99,6 +100,7 @@ export default async function RoleCandidatesPage({ params }: PageProps) {
totalExercises={totalExercises}
lastUpdated={lastUpdated}
cloudUrls={cloudUrls}
+ workersRootPath="/evals/workers"
/>
)
}
diff --git a/apps/web-roo-code/src/app/evals/workers/page.tsx b/apps/web-roo-code/src/app/evals/workers/page.tsx
index d7a8f5cc17..a2b95a1bd8 100644
--- a/apps/web-roo-code/src/app/evals/workers/page.tsx
+++ b/apps/web-roo-code/src/app/evals/workers/page.tsx
@@ -8,10 +8,10 @@ import { WorkersContent } from "./workers-content"
// ── SEO Metadata ────────────────────────────────────────────────────────────
-const TITLE = "Hire an AI Engineer | Roo Code Evals"
+const TITLE = "Build with Roo Code Cloud | Roo Code Evals"
const DESCRIPTION =
- "Find the right AI coding model for your team. Compare interview results across Junior, Senior, and Staff Engineer roles."
-const OG_DESCRIPTION = "Find the right AI coding model for your team"
+ "Eval-backed model recommendations for shipping production code. Pick a setup based on the work you're doing: single-file fixes, multi-file changes, review, and autonomous runs."
+const OG_DESCRIPTION = "Eval-backed model recommendations for shipping production code"
const PATH = "/evals/workers"
export const metadata: Metadata = {
@@ -44,18 +44,20 @@ export const metadata: Metadata = {
},
keywords: [
...SEO.keywords,
- "AI engineer",
+ "AI coding",
+ "coding agents",
+ "roo code cloud",
"model recommendations",
"coding evals",
"model comparison",
- "hire AI",
- "talent marketplace",
+ "shipping code",
+ "prototype",
],
}
// ── Page Component ──────────────────────────────────────────────────────────
-export default function HireAnAIEngineerPage() {
+export default function WorkersPage() {
const roles = getEngineerRoles()
const recommendations = getAllRecommendations()
@@ -80,6 +82,10 @@ export default function HireAnAIEngineerPage() {
totalExercises={totalExercises}
totalModels={totalModels}
lastUpdated={lastUpdated}
+ workersRootPath="/evals/workers"
+ enableOutcomeLayer={false}
+ alternateVersionHref="/evals/workers-v2"
+ alternateVersionLabel="View V2 preview"
/>
)
}
diff --git a/apps/web-roo-code/src/app/evals/workers/workers-content.tsx b/apps/web-roo-code/src/app/evals/workers/workers-content.tsx
index 755cc4e3b7..e5c770ea6d 100644
--- a/apps/web-roo-code/src/app/evals/workers/workers-content.tsx
+++ b/apps/web-roo-code/src/app/evals/workers/workers-content.tsx
@@ -1,6 +1,6 @@
"use client"
-import { useMemo } from "react"
+import { useCallback, useMemo } from "react"
import { motion } from "framer-motion"
import {
Code,
@@ -20,10 +20,12 @@ import {
} from "lucide-react"
import type { LucideIcon } from "lucide-react"
import Link from "next/link"
+import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { ScatterChart, Scatter, XAxis, YAxis, ZAxis, Tooltip, ResponsiveContainer, Cell, ReferenceLine } from "recharts"
import type { EngineerRole, RoleRecommendation } from "@/lib/mock-recommendations"
import { TASKS_PER_DAY, MODEL_TIMELINE } from "@/lib/mock-recommendations"
+import { EVAL_OUTCOMES, isEvalOutcomeId, type EvalOutcomeId } from "@/lib/eval-outcomes"
// ── Icon Mapping ────────────────────────────────────────────────────────────
@@ -139,6 +141,37 @@ const ROLE_THEMES: Record = {
const DEFAULT_THEME = ROLE_THEMES.senior!
+// ── Outcome Layer: Optimization Modes ──────────────────────────────────────
+
+type EvalOptimizationMode = "best" | "fastest" | "cost"
+
+const OPTIMIZATION_MODES: Array<{
+ id: EvalOptimizationMode
+ label: string
+ description: string
+}> = [
+ { id: "best", label: "Best", description: "Best overall quality across our eval suite." },
+ { id: "fastest", label: "Fastest", description: "Lower latency per task when speed matters." },
+ { id: "cost", label: "Most cost-effective", description: "Lower cost per task for high-volume work." },
+]
+
+function isEvalOptimizationMode(value: string): value is EvalOptimizationMode {
+ return value === "best" || value === "fastest" || value === "cost"
+}
+
+function getModeCandidate(rec: RoleRecommendation | undefined, mode: EvalOptimizationMode) {
+ if (!rec) return null
+ if (mode === "fastest") return rec.speedHire ?? rec.best[0] ?? null
+ if (mode === "cost") return rec.budgetHire ?? rec.best[0] ?? null
+ return rec.best[0] ?? null
+}
+
+function getModeLabel(mode: EvalOptimizationMode) {
+ if (mode === "fastest") return "Fastest"
+ if (mode === "cost") return "Most cost-effective"
+ return "Best"
+}
+
// ── Framer Motion Variants ──────────────────────────────────────────────────
const containerVariants = {
@@ -284,6 +317,10 @@ type WorkersContentProps = {
totalExercises: number
totalModels: number
lastUpdated: string | undefined
+ workersRootPath?: string
+ enableOutcomeLayer?: boolean
+ alternateVersionHref?: string
+ alternateVersionLabel?: string
}
export function WorkersContent({
@@ -293,8 +330,91 @@ export function WorkersContent({
totalExercises,
totalModels,
lastUpdated,
+ workersRootPath = "/evals/workers",
+ enableOutcomeLayer = false,
+ alternateVersionHref,
+ alternateVersionLabel,
}: WorkersContentProps) {
+ const router = useRouter()
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+
+ const selectedOutcomeId = useMemo(() => {
+ const outcome = searchParams.get("outcome")
+ if (!outcome) return null
+ return isEvalOutcomeId(outcome) ? outcome : null
+ }, [searchParams])
+
+ const selectedMode = useMemo((): EvalOptimizationMode => {
+ const mode = searchParams.get("mode")
+ if (!mode) return "best"
+ return isEvalOptimizationMode(mode) ? mode : "best"
+ }, [searchParams])
+
+ const setOutcome = useCallback(
+ (nextOutcomeId: EvalOutcomeId | null) => {
+ const params = new URLSearchParams(searchParams.toString())
+ if (nextOutcomeId) params.set("outcome", nextOutcomeId)
+ else params.delete("outcome")
+
+ const query = params.toString()
+ router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false })
+ },
+ [pathname, router, searchParams],
+ )
+
+ const setMode = useCallback(
+ (nextMode: EvalOptimizationMode) => {
+ const params = new URLSearchParams(searchParams.toString())
+ params.set("mode", nextMode)
+
+ const query = params.toString()
+ router.replace(query ? `${pathname}?${query}` : pathname, { scroll: false })
+ },
+ [pathname, router, searchParams],
+ )
+
const recByRole = new Map(recommendations.map((r) => [r.roleId, r]))
+ const roleById = useMemo(() => new Map(roles.map((r) => [r.id, r])), [roles])
+
+ const selectedOutcome = useMemo(() => {
+ if (!selectedOutcomeId) return null
+ return EVAL_OUTCOMES.find((o) => o.id === selectedOutcomeId) ?? null
+ }, [selectedOutcomeId])
+
+ const setupQuery = useMemo(() => {
+ if (!enableOutcomeLayer || !selectedOutcomeId) return ""
+ const params = new URLSearchParams()
+ params.set("outcome", selectedOutcomeId)
+ params.set("mode", selectedMode)
+ const query = params.toString()
+ return query ? `?${query}` : ""
+ }, [enableOutcomeLayer, selectedOutcomeId, selectedMode])
+
+ const profileTitle = selectedOutcome?.builderProfile?.title ?? "Your Builder Profile"
+ const profileDescription =
+ selectedOutcome?.builderProfile?.description ??
+ "A default setup built from our eval signals. It’s a baseline, not a guarantee."
+ const profileHowItWorks = selectedOutcome?.builderProfile?.howItWorks ?? selectedOutcome?.whyItWorks ?? []
+
+ const profileCapabilities = useMemo(() => {
+ if (!selectedOutcome) return []
+ const fromProfile = selectedOutcome.builderProfile?.capabilities
+ if (fromProfile && fromProfile.length > 0) return fromProfile
+ return selectedOutcome.recommendedRoleIds.map((roleId) => {
+ const role = roleById.get(roleId)
+ return {
+ id: roleId,
+ name: role?.name ?? roleId,
+ description: role?.salaryRange ?? "",
+ roleId,
+ }
+ })
+ }, [selectedOutcome, roleById])
+
+ const agentCapabilities = useMemo(() => profileCapabilities.filter((c) => Boolean(c.roleId)), [profileCapabilities])
+
+ const builtInCapabilities = useMemo(() => profileCapabilities.filter((c) => !c.roleId), [profileCapabilities])
// ── Timeline scatter data ──────────────────────────────────────────────
const timelineData = useMemo(() => {
@@ -340,6 +460,21 @@ export function WorkersContent({
+ {/* Blueprint grid overlay (V2) */}
+ {enableOutcomeLayer ? (
+
+ ) : null}
+
{/* Gradient fade from hero atmosphere to cards */}
@@ -351,33 +486,91 @@ export function WorkersContent({
variants={containerVariants}>
{/* Badge */}
-
-
- How we interview AI models
-
-
+
+
+
+ How we run evals
+
+
+ {alternateVersionHref && alternateVersionLabel ? (
+
+ {alternateVersionLabel}
+
+ ) : null}
+
+ {enableOutcomeLayer ? (
+
+ Outcomes over artifacts
+
+ ) : null}
+
{/* Heading */}
- Hire an{" "}
-
- AI Engineer
-
+ {enableOutcomeLayer ? (
+ <>
+ Build from outcomes.
+ Ship{" "}
+
+ real code
+
+ .
+ >
+ ) : (
+ <>
+ Build with{" "}
+
+ Roo Code Cloud
+
+ >
+ )}
{/* Subheading */}
- Every model runs the same coding tasks, same tools, same time limit. Pick the right
- candidate for your team and budget.
+ {enableOutcomeLayer ? (
+ <>
+ Pick what you're trying to ship. We assemble a Builder Profile: the
+ capabilities you need, plus a default model recommendation backed by eval data.
+ >
+ ) : (
+ <>
+ Outcomes over artifacts: start from the production codebase and ship as a reviewable
+ PR. Every model runs the same tasks, same tools, and the same time limit. Your repo
+ will differ—treat this as a baseline.
+ >
+ )}
+ {enableOutcomeLayer ? (
+
+
+ Start with Prototype → PR
+
+
+
+ Browse outcomes
+
+
+ ) : null}
+
{/* Stats bar */}
- {/* ── Role Cards Grid ────────────────────────────────────────── */}
-
- {/* Subtle section background */}
-
-
-
+ {/* ── Outcomes Overlay ───────────────────────────────────────── */}
+ {enableOutcomeLayer ? (
+
+
+
+
+
+ Start with an outcome
+
+
+ Pick what you're trying to ship. We assemble a Builder Profile: capabilities
+ plus a default model recommendation. It's a baseline, not a guarantee.
+
+
-
- {/* Section connector */}
-
-
- Choose your agentic team members
-
-
-
+
+ {EVAL_OUTCOMES.map((outcome) => {
+ const Icon = outcome.icon
+ const isSelected = outcome.id === selectedOutcomeId
+ const isFeatured = outcome.id === "prototype_to_pr"
-
- {roles.map((role) => {
- const rec = recByRole.get(role.id)
- const IconComponent = ICON_MAP[role.icon] ?? Code
- const candidateCount = rec?.allCandidates.length ?? 0
- const exerciseCount = rec?.totalExercises ?? 0
- const theme = ROLE_THEMES[role.id] ?? DEFAULT_THEME
- const topModel = rec?.best[0]
-
- return (
-
-
- {/* Subtle glow on hover */}
-
-
-
- {/* Header: Icon + role badge */}
-
-
-
+ return (
+ setOutcome(isSelected ? null : outcome.id)}
+ className={[
+ "group rounded-2xl border bg-card/40 p-5 text-left backdrop-blur-sm transition-all duration-200 hover:bg-card/60",
+ isSelected
+ ? "border-foreground/20 ring-1 ring-foreground/15"
+ : "border-border/50 hover:border-border",
+ isFeatured ? "lg:col-span-2" : "",
+ ].join(" ")}>
+
+
+
- {topModel && (
-
- Top: {topModel.displayName}
-
- )}
-
-
- {/* Role name + salary */}
- {role.name}
-
- {role.salaryRange}
-
-
- {/* Description */}
-
- {role.description}
-
-
- {/* Best for */}
-
-
- Best for
-
-
- {role.bestFor.map((item) => (
-
- {item}
+
+ {isFeatured ? (
+
+ Recommended starting point
- ))}
+ ) : null}
+
+ {outcome.name}
+
+
+ {outcome.description}
+
+
+ )
+ })}
+
- {/* Strengths & Weaknesses side by side */}
-
- {/* Strengths */}
-
-
- Strengths
-
-
- {role.strengths.map((item) => (
-
-
- {item}
+ {selectedOutcome ? (
+
+
+
+
+ {profileTitle}
+
+
+ {selectedOutcome.name}
+
+
+ {profileDescription}
+
+
+ {profileHowItWorks.length > 0 ? (
+
+
+ {selectedOutcome.builderProfile
+ ? "How it works"
+ : "Why it works"}
+
+
+ {profileHowItWorks.map((line) => (
+
+
+ {line}
))}
+ ) : null}
- {/* Weaknesses */}
-
-
- Trade-offs
-
-
- {role.weaknesses.map((item) => (
-
-
- {item}
+ {selectedOutcome.builderProfile?.howItWorks ? (
+
+
+ Why it works
+
+
+ {selectedOutcome.whyItWorks.map((line) => (
+
+
+ {line}
))}
-
+ ) : null}
+
- {/* Bottom stats + CTA */}
-
-
-
-
- {candidateCount} candidates
-
-
-
- {exerciseCount.toLocaleString()} exercises
+
+
+
+
+ Optimize for
+ {OPTIMIZATION_MODES.map((mode) => {
+ const isSelected = mode.id === selectedMode
+ return (
+ setMode(mode.id)}
+ className={[
+ "inline-flex items-center rounded-full border px-3 py-1.5 text-xs font-semibold transition-colors",
+ isSelected
+ ? "border-foreground/20 bg-foreground/5 text-foreground"
+ : "border-border/50 bg-background/20 text-foreground/75 hover:border-border hover:text-foreground",
+ ].join(" ")}>
+ {mode.label}
+
+ )
+ })}
-
-
- View Candidates
-
-
+
+ Capability set
+
+
+
+
+
+ Agents
+
+
+ Click for candidates & settings
+
+
+
+ {agentCapabilities.map((capability) => {
+ const roleId = capability.roleId!
+ const rec = recByRole.get(roleId)
+ const candidate = getModeCandidate(rec, selectedMode)
+
+ return (
+
+
+
+
+ {capability.name}
+
+ {capability.description ? (
+
+ {capability.description}
+
+ ) : null}
+
+ {candidate ? (
+ <>
+
+ {getModeLabel(selectedMode)}:
+ {" "}
+ {candidate.displayName}
+ >
+ ) : (
+
+ View models
+
+ )}
+
+
+
+
+
+ )
+ })}
+
+
+
+ {builtInCapabilities.length > 0 ? (
+
+
+ Built-ins
+
+
+ {builtInCapabilities.map((capability) => (
+
+
+
+
+
+
+ {capability.name}
+
+
+ Built-in
+
+
+
+ {capability.description}
+
+
+
+
+ ))}
+
+
+ ) : null}
- )
- })}
+ ) : null}
+
+
+
+ ) : null}
+
+ {/* ── Role Cards Grid (baseline only) ────────────────────────── */}
+ {!enableOutcomeLayer ? (
+
+ {/* Subtle section background */}
+
+
-
-
+
+
+ {/* Section connector */}
+
+
+ Choose a setup for the work
+
+
+
+
+
+ {roles.map((role) => {
+ const rec = recByRole.get(role.id)
+ const IconComponent = ICON_MAP[role.icon] ?? Code
+ const candidateCount = rec?.allCandidates.length ?? 0
+ const exerciseCount = rec?.totalExercises ?? 0
+ const theme = ROLE_THEMES[role.id] ?? DEFAULT_THEME
+ const topModel = rec?.best[0] ?? null
+
+ return (
+
+
+ {/* Subtle glow on hover */}
+
+
+
+ {/* Header: Icon + role badge */}
+
+
+
+
+ {topModel && (
+
+ Top: {topModel.displayName}
+
+ )}
+
+
+ {/* Profile name + descriptor */}
+ {role.name}
+
+ {role.salaryRange}
+
+
+ {/* Description */}
+
+ {role.description}
+
+
+ {/* Best for */}
+
+
+ Best for
+
+
+ {role.bestFor.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+ {/* Strengths & Weaknesses side by side */}
+
+ {/* Strengths */}
+
+
+ Strengths
+
+
+ {role.strengths.map((item) => (
+
+
+ {item}
+
+ ))}
+
+
+
+ {/* Weaknesses */}
+
+
+ Trade-offs
+
+
+ {role.weaknesses.map((item) => (
+
+
+ {item}
+
+ ))}
+
+
+
+
+ {/* Bottom stats + CTA */}
+
+
+
+
+ {candidateCount} models
+
+
+
+ {exerciseCount.toLocaleString()} exercises
+
+
+
+
+ View models
+
+
+
+
+
+
+ )
+ })}
+
+
+
+ ) : null}
{/* ── AI Coding Capability Over Time ─────────────────────────── */}
@@ -582,7 +1021,7 @@ export function WorkersContent({
variants={containerVariants}>
{/* Section header */}
-
+
AI Coding Capability{" "}
Over Time
diff --git a/apps/web-roo-code/src/lib/eval-outcomes.ts b/apps/web-roo-code/src/lib/eval-outcomes.ts
new file mode 100644
index 0000000000..85ced98347
--- /dev/null
+++ b/apps/web-roo-code/src/lib/eval-outcomes.ts
@@ -0,0 +1,171 @@
+import type { LucideIcon } from "lucide-react"
+import { Bug, CheckCircle2, GitPullRequest, Sparkles, Workflow } from "lucide-react"
+
+export type EvalOutcomeId =
+ | "prototype_to_pr"
+ | "paper_cuts"
+ | "sentry_triage"
+ | "repro_to_fix"
+ | "review_guardrails"
+ | "issue_to_pr"
+
+export type EvalOutcomeCapability = {
+ id: string
+ name: string
+ description: string
+ /**
+ * Optional roleId for capabilities that map directly to a role page.
+ * Non-role capabilities represent Roo Code Cloud behaviors (validation, PR packaging, etc.).
+ */
+ roleId?: string
+}
+
+export type EvalOutcomeProfile = {
+ title: string
+ description: string
+ capabilities: EvalOutcomeCapability[]
+ howItWorks: string[]
+}
+
+export type EvalOutcome = {
+ id: EvalOutcomeId
+ name: string
+ description: string
+ icon: LucideIcon
+ /**
+ * Ordered list of roleIds to suggest as a "setup".
+ * Keep roleIds stable even if display names evolve.
+ */
+ recommendedRoleIds: string[]
+ whyItWorks: string[]
+ /**
+ * Optional profile details used to render a more comprehensive “exoskeleton”
+ * for an outcome. Start with the most important outcomes and expand over time.
+ */
+ builderProfile?: EvalOutcomeProfile
+}
+
+export const EVAL_OUTCOMES: EvalOutcome[] = [
+ {
+ id: "prototype_to_pr",
+ name: "Prototype → PR",
+ description: "Build a working prototype on the production codebase, then turn it into a reviewable diff.",
+ icon: Sparkles,
+ recommendedRoleIds: ["senior", "reviewer"],
+ whyItWorks: [
+ "Multi-file changes with a reviewer pass for coherence and edge cases.",
+ "Optimizes for shipping, not slides.",
+ ],
+ builderProfile: {
+ title: "Your Builder Profile",
+ description:
+ "A default set of capabilities for turning a working prototype into a reviewable PR—on the production codebase.",
+ capabilities: [
+ {
+ id: "multi_file_builder",
+ name: "Multi-file Builder",
+ description: "Builds the prototype directly in your repo across the files it touches.",
+ roleId: "senior",
+ },
+ {
+ id: "reviewer_guardrails",
+ name: "Reviewer & Guardrails",
+ description: "Reviews the diff for correctness, edge cases, and coherence before you merge.",
+ roleId: "reviewer",
+ },
+ {
+ id: "environment_setup",
+ name: "Environment setup",
+ description:
+ "Bootstraps a working dev environment and runs the workflow without you fighting Git, installs, or tests.",
+ },
+ {
+ id: "validation_loop",
+ name: "Validation loop",
+ description: "Runs tests/lint/typechecks and iterates until it’s clean (or flags what’s blocked).",
+ },
+ {
+ id: "pr_ready_output",
+ name: "PR-ready output",
+ description: "Produces a focused diff plus a plain-English summary and review notes.",
+ },
+ {
+ id: "straight_line_merge",
+ name: "Straight-line to merge",
+ description:
+ "No export/import step: the work is already on the production codebase, so merge is a straight line.",
+ },
+ {
+ id: "scope_control",
+ name: "Scope control",
+ description: "Keeps diffs tight: smaller review surface, fewer surprises, and easier merges.",
+ },
+ ],
+ howItWorks: [
+ "Build a working prototype directly in the production codebase.",
+ "Convert the prototype into a tight diff (tests, cleanup, and safeguards).",
+ "Run a reviewer pass to catch edge cases and improve merge confidence.",
+ "Deliver a PR-ready result with context and next steps.",
+ ],
+ },
+ },
+ {
+ id: "paper_cuts",
+ name: "Paper cuts & small fixes",
+ description: "Fix the small stuff without dragging engineers off big projects.",
+ icon: CheckCircle2,
+ recommendedRoleIds: ["junior", "reviewer"],
+ whyItWorks: [
+ "Small diffs are high-leverage when the work is well-scoped.",
+ "Reviewer keeps the quality bar and reduces surprise.",
+ ],
+ },
+ {
+ id: "sentry_triage",
+ name: "Sentry triage",
+ description: "Turn recurring errors into concrete fixes with proof before review.",
+ icon: Bug,
+ recommendedRoleIds: ["autonomous", "reviewer"],
+ whyItWorks: [
+ "Autonomous runs handle multi-step investigation and iteration.",
+ "Reviewer focuses on safety, correctness, and “does this hold up?”.",
+ ],
+ },
+ {
+ id: "repro_to_fix",
+ name: "Bug repro → fix",
+ description: "Make the handoff less lossy: reproduce, patch, and validate in one loop.",
+ icon: Workflow,
+ recommendedRoleIds: ["senior", "reviewer"],
+ whyItWorks: [
+ "Good default for ambiguous bugs that touch a few files.",
+ "Reviewer helps catch cross-team assumptions early.",
+ ],
+ },
+ {
+ id: "review_guardrails",
+ name: "Guardrails & review",
+ description: "Raise the quality bar without becoming the blocker.",
+ icon: GitPullRequest,
+ recommendedRoleIds: ["reviewer"],
+ whyItWorks: [
+ "Works alongside CI, linters, and team review.",
+ "Scales judgement through fast, consistent feedback.",
+ ],
+ },
+ {
+ id: "issue_to_pr",
+ name: "Issue → PR",
+ description: "Run end-to-end work in the background and come back to a reviewable result.",
+ icon: GitPullRequest,
+ recommendedRoleIds: ["autonomous", "reviewer"],
+ whyItWorks: [
+ "Handles out-of-band work while humans stay on the roadmap.",
+ "Pairs autonomy with guardrails for merge safety.",
+ ],
+ },
+]
+
+export function isEvalOutcomeId(value: string): value is EvalOutcomeId {
+ return EVAL_OUTCOMES.some((o) => o.id === value)
+}
diff --git a/apps/web-roo-code/src/lib/mock-recommendations.ts b/apps/web-roo-code/src/lib/mock-recommendations.ts
index 99cc7f9b80..9a08487ca7 100644
--- a/apps/web-roo-code/src/lib/mock-recommendations.ts
+++ b/apps/web-roo-code/src/lib/mock-recommendations.ts
@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------------
// Eval Recommendations: Types + Mock Data (S1.1a)
// ---------------------------------------------------------------------------
-// This file defines the API contract for the AI Engineer Talent Marketplace.
+// This file defines the API contract for the /evals/workers recommendation pages.
// The backend (Sprint 3-4) will produce data matching these exact types.
// ---------------------------------------------------------------------------
@@ -16,11 +16,11 @@ export const TASKS_PER_DAY = 80
// ── Types ──────────────────────────────────────────────────────────────────
-/** Engineer role definition: maps task complexity to a hiring tier. */
+/** Engineer role definition: maps task complexity to a recommendation tier. */
export type EngineerRole = {
id: string
name: string
- /** Daily salary range string, e.g. "$3–38/day" */
+ /** Short descriptor shown under the profile name (scope, mode, etc.). */
salaryRange: string
description: string
bestFor: string[]
@@ -80,51 +80,51 @@ export type RoleRecommendation = {
const ENGINEER_ROLES: EngineerRole[] = [
{
id: "junior",
- name: "Junior Engineer",
- salaryRange: "$2–10/day",
+ name: "Single-file Builder",
+ salaryRange: "Scope: single-file",
description:
- "Handles well-scoped, single-file tasks: boilerplate, simple bug fixes, and test generation at the lowest cost per task.",
- bestFor: ["Single-file fixes", "Boilerplate generation", "Test generation", "Simple implementations"],
- strengths: ["Cheap", "High throughput", "Best cost-to-quality ratio on simple tasks"],
- weaknesses: ["Struggles with multi-file changes", "Limited reasoning depth", "May miss edge cases"],
+ "Best for tight diffs: boilerplate, small fixes, and test updates. Great when the work is clear and bounded.",
+ bestFor: ["Small fixes", "Boilerplate", "Test updates", "Simple implementations"],
+ strengths: ["Fast iteration", "Stays close to the requested change", "Great for well-scoped diffs"],
+ weaknesses: ["Not ideal for cross-cutting work", "Can miss edge cases in complex systems"],
icon: "Code",
},
{
id: "senior",
- name: "Senior Engineer",
- salaryRange: "$10–26/day",
+ name: "Multi-file Builder",
+ salaryRange: "Scope: multi-file",
description:
- "The sweet spot for most engineering work. Senior-tier models balance cost and quality across multi-file refactors, feature development, and debugging.",
- bestFor: ["Multi-file refactors", "Feature development", "Debugging", "Code review"],
+ "For most day-to-day shipping: feature work across a few files, refactors, and debugging with solid consistency.",
+ bestFor: ["Feature work", "Multi-file refactors", "Debugging", "Integrations"],
strengths: [
- "Balanced cost/quality",
- "Handles multi-file changes and cross-cutting refactors",
- "Consistent pass rates across all five languages",
+ "Reliable for common product work",
+ "Handles multi-file changes and dependencies",
+ "Consistent across all five languages",
],
- weaknesses: ["More expensive than junior", "Overkill for trivial tasks"],
+ weaknesses: ["Overkill for trivial diffs", "May need help on cross-cutting architecture"],
icon: "GitBranch",
},
{
id: "staff",
- name: "Staff Engineer",
- salaryRange: "$8–34/day",
+ name: "Architecture & Refactor",
+ salaryRange: "Scope: cross-cutting",
description:
- "For architecture decisions, system design, and complex refactors. Staff-tier models handle ambiguous requirements and cross-cutting changes where other tiers fail.",
- bestFor: ["Architecture decisions", "Complex features", "System design", "Ambiguous requirements"],
+ "For ambiguity and cross-cutting changes: architecture decisions, complex refactors, and work where correctness matters more than speed.",
+ bestFor: ["Complex refactors", "Architecture changes", "Ambiguous requirements", "System design"],
strengths: [
- "Handles multi-step reasoning and ambiguous specs",
- "Passes existing test suites consistently",
- "Resolves underspecified requirements",
+ "Strong multi-step reasoning",
+ "Good at navigating bigger codebases",
+ "Better at making safe, coherent changes",
],
- weaknesses: ["Most expensive", "Overkill for simple tasks", "Diminishing returns on easy work"],
+ weaknesses: ["Overkill for simple diffs", "Still needs human review before merge"],
icon: "Building2",
},
{
id: "reviewer",
- name: "Architecture Reviewer",
- salaryRange: "$15–40/day",
+ name: "Reviewer & Guardrails",
+ salaryRange: "Mode: review",
description:
- "For code review, PR feedback, security analysis, and design critique. Reviewer-tier models catch issues other models miss and provide actionable, context-aware suggestions.",
+ "For PR feedback, security review, and design critique. Use this to improve quality and reduce surprises before merge.",
bestFor: ["Code review", "PR feedback", "Security analysis", "Design critique", "Refactor guidance"],
strengths: [
"Catches subtle bugs and logic errors",
@@ -132,18 +132,18 @@ const ENGINEER_ROLES: EngineerRole[] = [
"Understands cross-file impact of changes",
],
weaknesses: [
- "Not for writing code from scratch",
- "More expensive than running linters",
+ "Not for writing features end-to-end",
+ "Not a replacement for CI and linters",
"Review quality varies by codebase size",
],
icon: "Search",
},
{
id: "autonomous",
- name: "Autonomous Agent",
- salaryRange: "$5–30/day",
+ name: "Autonomous Delivery",
+ salaryRange: "Mode: end-to-end",
description:
- "For issue-to-PR workflows, long-running tasks, and multi-step debugging with minimal supervision. Autonomous-tier models complete tasks end-to-end and recover from errors without human intervention.",
+ "For issue-to-PR workflows and long-running tasks. Best when you want an agent to run, iterate, and bring back a reviewable result.",
bestFor: [
"Issue-to-PR workflows",
"Multi-step debugging",
|