Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ As usage grows, the platform needs stronger derived data pipelines, performance
- [ ] [P1] Cursor-specific workflow fingerprinting and session archetype mapping
- [x] [P1] Session archetype detection: debugging, feature delivery, refactor, migration, docs, investigation
- [ ] [P1] Delegation graph capture for multi-agent and subagent workflows
- [ ] [P2] Exemplar session library for high-value workflows and onboarding examples
- [x] [P2] Exemplar session library for high-value workflows and onboarding examples
- [ ] [P2] Prompt, skill, and template reuse analytics by workflow and outcome

## Friction & Bottleneck Analysis
Expand Down Expand Up @@ -144,7 +144,7 @@ As usage grows, the platform needs stronger derived data pipelines, performance
- [x] Onboarding velocity scoring
- [x] Onboarding recommendations
- [x] Shared behavior pattern discovery with approach comparison
- [ ] [P1] Bright spot detection: explicitly surface high performers and cross-pollinate their patterns
- [x] [P1] Bright spot detection: explicitly surface high performers and cross-pollinate their patterns
- [ ] [P1] Exemplar-session-to-learning-path pipeline
- [ ] [P1] Team skill gap mapping by workflow, tool category, and project context
- [ ] [P2] Coaching program measurement: which onboarding or training changes improved outcomes
Expand Down Expand Up @@ -206,7 +206,8 @@ As usage grows, the platform needs stronger derived data pipelines, performance
- [x] Budget tracking with burn-rate alerts and projected overrun warnings
- [x] Cost per successful outcome metric
- [ ] [P1] Break-even analysis for API vs seat-based pricing with per-engineer recommendations
- [ ] [P1] Cost per workflow archetype and cost per engineering outcome
- [x] [P1] Cost per workflow archetype and cost per engineering outcome
- [x] [P1] Workflow compare mode for archetype and fingerprint performance
- [ ] [P1] Model-choice opportunity scoring for overspend reduction
- [ ] [P2] Budget policy simulation by team, project, and billing model

Expand All @@ -222,6 +223,11 @@ As usage grows, the platform needs stronger derived data pipelines, performance
- [ ] [P2] Weekly manager review packs that combine quality, friction, growth, and cost
- [ ] [P2] Recommendation narratives that explain why a workflow is likely to help

## Website & Positioning

- [ ] [P1] Reposition the website around workflow intelligence for agentic engineering
- [ ] [P1] Showcase workflow cost, quality, compare mode, and exemplar sessions as the core proof points

## Interventions & Experimentation

- [x] [P0] Recommendation-to-intervention workflow with owner, status, due date, and linked evidence
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { render, screen } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"

import { ExemplarSessionLibrary } from "@/components/growth/exemplar-session-library"

describe("ExemplarSessionLibrary", () => {
it("renders an empty state", () => {
render(
<MemoryRouter>
<ExemplarSessionLibrary exemplars={[]} />
</MemoryRouter>,
)

expect(screen.getByText("No exemplar sessions identified yet.")).toBeInTheDocument()
})

it("renders workflow-aware exemplar cards", () => {
render(
<MemoryRouter>
<ExemplarSessionLibrary
exemplars={[
{
exemplar_id: "exemplar:sess-1",
title: "debugging: read -> edit -> execute -> fix",
summary: "Backed by 3 engineers across 4 sessions.",
why_selected: "Chosen as the fastest successful example for debugging on primer.",
session_id: "sess-1",
engineer_id: "eng-1",
engineer_name: "Alice Example",
project_name: "primer",
outcome: "success",
helpfulness: "very_helpful",
session_summary: "Resolved the failing auth flow after reproducing locally.",
duration_seconds: 180,
estimated_cost: 1.24,
tools_used: ["Read", "Edit", "Bash"],
workflow_archetype: "debugging",
workflow_fingerprint: "debugging: read -> edit -> execute -> fix",
workflow_steps: ["read", "edit", "execute", "fix"],
supporting_session_count: 4,
supporting_engineer_count: 3,
supporting_pattern_count: 2,
success_rate: 1,
linked_patterns: [
{
cluster_id: "cluster-1",
cluster_type: "session_type",
cluster_label: "debugging on primer",
session_count: 4,
engineer_count: 3,
success_rate: 1,
},
],
},
]}
/>
</MemoryRouter>,
)

expect(screen.getAllByText("debugging: read -> edit -> execute -> fix")).toHaveLength(2)
expect(screen.getByText("Why This Exemplar")).toBeInTheDocument()
expect(screen.getByText("Workflow Evidence")).toBeInTheDocument()
expect(screen.getByText("$1.24")).toBeInTheDocument()
expect(screen.getByText("debugging on primer")).toBeInTheDocument()
expect(screen.getByText("Open exemplar")).toHaveAttribute("href", "/sessions/sess-1")
})
})
152 changes: 152 additions & 0 deletions frontend/src/components/growth/exemplar-session-library.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Link } from "react-router-dom"

import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { formatCost, formatDuration, formatLabel, formatPercent, titleize } from "@/lib/utils"
import type { ExemplarSession } from "@/types/api"

interface ExemplarSessionLibraryProps {
exemplars: ExemplarSession[]
}

export function ExemplarSessionLibrary({ exemplars }: ExemplarSessionLibraryProps) {
if (exemplars.length === 0) {
return (
<div className="py-8 text-center text-sm text-muted-foreground">
No exemplar sessions identified yet.
</div>
)
}

return (
<div className="grid gap-4 xl:grid-cols-2">
{exemplars.map((exemplar) => (
<Card key={exemplar.exemplar_id}>
<CardHeader className="pb-3">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="space-y-1">
<CardTitle className="text-base">{exemplar.title}</CardTitle>
<p className="text-sm text-muted-foreground">{exemplar.summary}</p>
</div>
<div className="flex flex-wrap gap-2">
{exemplar.workflow_archetype && (
<Badge variant="success">{formatLabel(exemplar.workflow_archetype)}</Badge>
)}
{exemplar.project_name && (
<Badge variant="outline">{exemplar.project_name}</Badge>
)}
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-3 sm:grid-cols-4">
<div className="rounded-xl border border-border/60 bg-muted/30 p-3">
<p className="text-xs uppercase tracking-wide text-muted-foreground">Support</p>
<p className="mt-1 font-display text-2xl">{exemplar.supporting_engineer_count}</p>
<p className="text-xs text-muted-foreground">
{exemplar.supporting_session_count} sessions
</p>
</div>
<div className="rounded-xl border border-border/60 bg-muted/30 p-3">
<p className="text-xs uppercase tracking-wide text-muted-foreground">Success</p>
<p className="mt-1 font-display text-2xl">{formatPercent(exemplar.success_rate)}</p>
</div>
<div className="rounded-xl border border-border/60 bg-muted/30 p-3">
<p className="text-xs uppercase tracking-wide text-muted-foreground">Duration</p>
<p className="mt-1 font-display text-2xl">{formatDuration(exemplar.duration_seconds)}</p>
</div>
<div className="rounded-xl border border-border/60 bg-muted/30 p-3">
<p className="text-xs uppercase tracking-wide text-muted-foreground">Cost</p>
<p className="mt-1 font-display text-2xl">
{exemplar.estimated_cost != null ? formatCost(exemplar.estimated_cost) : "-"}
</p>
</div>
</div>

<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Why This Exemplar
</p>
<p className="text-sm text-muted-foreground">{exemplar.why_selected}</p>
</div>

{exemplar.session_summary && (
<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Session Summary
</p>
<p className="text-sm text-muted-foreground">{exemplar.session_summary}</p>
</div>
)}

<div className="grid gap-4 lg:grid-cols-2">
<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Workflow Evidence
</p>
<div className="flex flex-wrap gap-2">
{exemplar.workflow_fingerprint ? (
<Badge variant="secondary">{exemplar.workflow_fingerprint}</Badge>
) : (
<span className="text-sm text-muted-foreground">No workflow fingerprint yet</span>
)}
{exemplar.workflow_steps.map((step) => (
<Badge key={step} variant="outline">
{titleize(step)}
</Badge>
))}
</div>
</div>

<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Exemplar Tools
</p>
<div className="flex flex-wrap gap-2">
{exemplar.tools_used.length === 0 ? (
<span className="text-sm text-muted-foreground">No tool telemetry</span>
) : (
exemplar.tools_used.map((tool) => (
<Badge key={tool} variant="secondary">
{tool}
</Badge>
))
)}
</div>
</div>
</div>

<div className="space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Linked Patterns
</p>
<div className="flex flex-wrap gap-2">
{exemplar.linked_patterns.map((pattern) => (
<Badge key={pattern.cluster_id} variant="outline">
{pattern.cluster_label}
</Badge>
))}
</div>
</div>

<div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border/60 bg-background p-4">
<div className="space-y-1">
<p className="font-medium">{exemplar.engineer_name}</p>
<p className="text-sm text-muted-foreground">
{exemplar.outcome ? formatLabel(exemplar.outcome) : "Outcome unknown"}
{exemplar.helpfulness ? ` • ${formatLabel(exemplar.helpfulness)}` : ""}
</p>
</div>
<Link
to={`/sessions/${exemplar.session_id}`}
className="text-sm font-medium text-primary hover:underline"
>
Open exemplar
</Link>
</div>
</CardContent>
</Card>
))}
</div>
)
}
26 changes: 26 additions & 0 deletions frontend/src/pages/__tests__/growth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ vi.mock("@/components/growth/pattern-summary", () => ({
vi.mock("@/components/growth/bright-spot-cards", () => ({
BrightSpotCards: () => <div>bright spots</div>,
}))
vi.mock("@/components/growth/exemplar-session-library", () => ({
ExemplarSessionLibrary: () => <div>exemplar session library</div>,
}))
vi.mock("@/components/growth/shared-pattern-card", () => ({
SharedPatternCards: () => <div>shared patterns</div>,
}))
Expand Down Expand Up @@ -166,6 +169,29 @@ describe("GrowthPage", () => {
).toBeInTheDocument()
})

it("shows exemplar library content on the patterns tab", () => {
mockUsePatternSharing.mockReturnValue({
data: {
patterns: [],
bright_spots: [],
exemplar_sessions: [],
total_clusters_found: 0,
sessions_analyzed: 0,
},
isLoading: false,
} as unknown as ReturnType<typeof usePatternSharing>)
mockUseEngineerProfile.mockReturnValue({
data: null,
isLoading: false,
} as unknown as ReturnType<typeof useEngineerProfile>)

renderPage()
fireEvent.click(screen.getByRole("button", { name: "Patterns" }))

expect(screen.getByText("Exemplar Session Library")).toBeInTheDocument()
expect(screen.getByText("exemplar session library")).toBeInTheDocument()
})

it("shows an engineer chooser for API-key admins without a selected context", () => {
mockGetApiKey.mockReturnValue("test-key")
mockUseAuth.mockReturnValue({ user: null } as ReturnType<typeof useAuth>)
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/pages/growth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { VelocityChart } from "@/components/growth/velocity-chart"
import { OnboardingRecommendations } from "@/components/growth/onboarding-recommendations"
import { PatternSummary } from "@/components/growth/pattern-summary"
import { BrightSpotCards } from "@/components/growth/bright-spot-cards"
import { ExemplarSessionLibrary } from "@/components/growth/exemplar-session-library"
import { SharedPatternCards } from "@/components/growth/shared-pattern-card"
import { SkillInventorySummary } from "@/components/insights/skill-inventory-summary"
import { CoverageSummary } from "@/components/growth/coverage-summary"
Expand Down Expand Up @@ -69,6 +70,15 @@ function PatternsTab({ teamId, startDate, endDate }: TabProps) {
<div className="space-y-6">
<PatternSummary data={data} />
<BrightSpotCards spots={data.bright_spots} />
<div className="space-y-3">
<div className="space-y-1">
<h3 className="text-sm font-medium">Exemplar Session Library</h3>
<p className="text-sm text-muted-foreground">
Browse concrete sessions worth copying, with workflow, support, and cost context.
</p>
</div>
<ExemplarSessionLibrary exemplars={data.exemplar_sessions ?? []} />
</div>
<SharedPatternCards patterns={data.patterns} />
</div>
)
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -828,9 +828,44 @@ export interface BrightSpot {
exemplar_tools: string[]
}

export interface ExemplarPatternReference {
cluster_id: string
cluster_type: string
cluster_label: string
session_count: number
engineer_count: number
success_rate: number | null
}

export interface ExemplarSession {
exemplar_id: string
title: string
summary: string
why_selected: string
session_id: string
engineer_id: string
engineer_name: string
project_name: string | null
outcome: string | null
helpfulness: string | null
session_summary: string | null
duration_seconds: number | null
estimated_cost: number | null
tools_used: string[]
workflow_archetype: string | null
workflow_fingerprint: string | null
workflow_steps: string[]
supporting_session_count: number
supporting_engineer_count: number
supporting_pattern_count: number
success_rate: number | null
linked_patterns: ExemplarPatternReference[]
}

export interface PatternSharingResponse {
patterns: SharedPattern[]
bright_spots: BrightSpot[]
exemplar_sessions: ExemplarSession[]
total_clusters_found: number
sessions_analyzed: number
}
Expand Down
Loading
Loading