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:
soroush.asadi
2026-06-10 13:57:10 +03:30
parent 4a58018837
commit 4416d99360
7 changed files with 362 additions and 2 deletions
+59 -1
View File
@@ -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>
)
}