Any seat can be AI-staffed: engineer/designer/analyst atoms + role-aware seat suggestions
The core product thesis made tangible beyond PO/QA:
- Four new golden-tested skill atoms in skills/: code-implementation + bug-diagnosis
(engineer — output is a reviewable patch/diagnosis artifact; Git write-back stays Phase 2),
ui-design-spec (designer), requirements-analysis (analyst, also tagged product-owner).
The catalogue now spans five roles with eight atoms.
- Seat configurator: SuggestedSkills — maps the seat's free-text role name to skill role
tags and offers the matching set one click ("Use set"). Any role name → staffed with AI.
- AnyRoleSeatTests: an "Backend Engineer" seat (Edison, gated) runs the same pipeline —
skills assemble, implement-code/Draft parsed, proposal held in the review inbox like any
governed action. SkillSyncTests updated for the larger catalogue.
Verified: IntegrationTests 44/44, client build green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { KeyRound, Plus, Bot, Wand2 } from 'lucide-react'
|
||||
import { KeyRound, Plus, Bot, Sparkles, Wand2 } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { AppShell } from '@/components/AppShell'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
@@ -337,6 +337,14 @@ export function SeatsPage() {
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Skills</Label>
|
||||
{selected && (
|
||||
<SuggestedSkills
|
||||
roleName={selected.roleName}
|
||||
skills={skills}
|
||||
current={agent.skillKeys}
|
||||
onApply={(keys) => setAgent({ ...agent, skillKeys: keys })}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{skills.map((skill) => (
|
||||
<button key={skill.skillKey} onClick={() => toggleSkill(skill.skillKey)}>
|
||||
@@ -375,3 +383,53 @@ function Field({ label, children }: { label: string; children: React.ReactNode }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Maps a free-text seat role name to skill role tags — any role can be AI-staffed. */
|
||||
function roleTagsFor(roleName: string): string[] {
|
||||
const n = roleName.toLowerCase()
|
||||
const tags: string[] = []
|
||||
if (n.includes('product') || n.includes('owner') || n.includes('pm')) tags.push('product-owner')
|
||||
if (n.includes('qa') || n.includes('test') || n.includes('quality')) tags.push('qa')
|
||||
if (n.includes('engineer') || n.includes('dev') || n.includes('programmer') || n.includes('backend') || n.includes('frontend')) tags.push('engineer')
|
||||
if (n.includes('design') || n.includes('ux') || n.includes('ui')) tags.push('designer')
|
||||
if (n.includes('analyst') || n.includes('analysis') || n.includes('business')) tags.push('analyst')
|
||||
return tags
|
||||
}
|
||||
|
||||
/** Suggests the skill set matching the seat's role — one click staffs any role with AI. */
|
||||
function SuggestedSkills({
|
||||
roleName,
|
||||
skills,
|
||||
current,
|
||||
onApply,
|
||||
}: {
|
||||
roleName: string
|
||||
skills: { skillKey: string; name: string; roles: string[] }[]
|
||||
current: string[]
|
||||
onApply: (keys: string[]) => void
|
||||
}) {
|
||||
const tags = roleTagsFor(roleName)
|
||||
const suggested = skills.filter((s) => s.roles.some((r) => tags.includes(r)))
|
||||
if (suggested.length === 0) return null
|
||||
|
||||
const keys = suggested.map((s) => s.skillKey)
|
||||
const applied = keys.every((k) => current.includes(k))
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-md border border-dashed px-3 py-2 text-xs text-muted-foreground">
|
||||
<Sparkles className="size-3.5 shrink-0 text-primary" />
|
||||
<span className="min-w-0 truncate">
|
||||
Suggested for “{roleName}”: {suggested.map((s) => s.name).join(', ')}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-auto shrink-0"
|
||||
disabled={applied}
|
||||
onClick={() => onApply([...new Set([...current, ...keys])])}
|
||||
>
|
||||
{applied ? 'Applied' : 'Use set'}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user