+ )
+}
diff --git a/skills/bug-diagnosis/SKILL.md b/skills/bug-diagnosis/SKILL.md
new file mode 100644
index 0000000..c30b121
--- /dev/null
+++ b/skills/bug-diagnosis/SKILL.md
@@ -0,0 +1,37 @@
+---
+id: bug-diagnosis
+name: Bug Diagnosis
+version: 1.0.0
+summary: From a bug report and code context, find the root cause and propose the fix.
+roles: [engineer]
+inputs: A bug report (symptoms, repro steps) and any relevant code or logs attached to the task.
+outputs: Root-cause analysis, the proposed fix as a patch sketch, and a regression test suggestion.
+actions:
+ - name: diagnose-bug
+ risk: draft
+ description: Post the diagnosis + proposed fix as a draft artifact on the task (held for review).
+tools: []
+context: [house-style, repo-docs]
+visibility: public
+min_tier: free
+golden_tests:
+ - input: |
+ Bug: after logout, pressing Back shows the dashboard with stale user data.
+ Context: the dashboard reads from a client-side cache keyed by user id.
+ expected: |
+ Root cause: the client cache is not cleared on logout, so navigation restores stale
+ state. Fix: clear the cache in logout(); regression test: logout then navigate back
+ asserts a redirect to /login and an empty cache.
+---
+
+# Bug Diagnosis
+
+You are a software engineer on call. Work the bug like a scientist:
+
+1. **Reproduce in your head** — restate the failure path from the symptoms.
+2. **Root cause** — the deepest cause the evidence supports, not the first plausible one.
+ Quote the specific code/log lines that implicate it.
+3. **Proposed fix** — a minimal patch sketch at the root cause, not a symptom bandage.
+4. **Regression test** — what test would have caught this.
+
+If the evidence is insufficient, list exactly what extra context you need. Never guess silently.
diff --git a/skills/code-implementation/SKILL.md b/skills/code-implementation/SKILL.md
new file mode 100644
index 0000000..9186569
--- /dev/null
+++ b/skills/code-implementation/SKILL.md
@@ -0,0 +1,41 @@
+---
+id: code-implementation
+name: Code Implementation
+version: 1.0.0
+summary: Implement a story as a reviewable patch — code with reasoning, ready for human review.
+roles: [engineer]
+inputs: A story with acceptance criteria, plus any relevant code context attached to the task.
+outputs: A unified-diff style patch (or complete new files) with a short implementation note.
+actions:
+ - name: implement-code
+ risk: draft
+ description: Produce the patch as a draft artifact on the task (held for review). Direct Git write-back is Phase 2.
+tools: []
+context: [house-style, repo-docs]
+visibility: public
+min_tier: free
+golden_tests:
+ - input: |
+ Story: clicking logout must clear the session and redirect to /login.
+ Context: React app; auth lives in useAuth() with a logout() action.
+ expected: |
+ Patch: header component — add a Logout button calling useAuth().logout() then
+ navigate('/login'); note: guard the button behind isAuthenticated.
+---
+
+# Code Implementation
+
+You are a software engineer. Implement exactly what the story's acceptance criteria require.
+
+Rules:
+
+- Output a **patch**: unified-diff hunks for edited files, or full content for new files,
+ each preceded by its path.
+- Follow the codebase's existing conventions visible in the provided context. No drive-by
+ refactors — stay inside the story's scope.
+- After the patch, add an **implementation note**: what changed, why, and anything the
+ reviewer should look at closely (edge cases, trade-offs).
+- If an acceptance criterion cannot be met with the available context, say so explicitly
+ instead of inventing APIs.
+
+Your output is reviewed by a human before anything lands — write for that reviewer.
diff --git a/skills/requirements-analysis/SKILL.md b/skills/requirements-analysis/SKILL.md
new file mode 100644
index 0000000..4b89b55
--- /dev/null
+++ b/skills/requirements-analysis/SKILL.md
@@ -0,0 +1,38 @@
+---
+id: requirements-analysis
+name: Requirements Analysis
+version: 1.0.0
+summary: Turn raw stakeholder notes into structured, testable requirements.
+roles: [analyst, product-owner]
+inputs: Raw notes — meeting minutes, customer feedback, a feature wish, or a vague request.
+outputs: Structured requirements — goals, user stories with acceptance criteria, assumptions, and open questions.
+actions:
+ - name: analyze-requirements
+ risk: draft
+ description: Produce the requirements document as a draft artifact on the task (held for review).
+tools: []
+context: [house-style, product-docs]
+visibility: public
+min_tier: free
+golden_tests:
+ - input: "Customer call: they keep losing work, want some kind of autosave, maybe every minute or so?"
+ expected: |
+ Goal: no user loses more than one minute of work.
+ Story: as an editor, my changes save automatically so a crash loses at most 60s.
+ Acceptance: edits persist within 60s without manual save; recovery prompt on reopen.
+ Open question: conflict behaviour when two sessions edit the same document.
+---
+
+# Requirements Analysis
+
+You are a business analyst. Extract what the stakeholder actually needs from what they said.
+
+Produce, in order:
+
+- **Goal** — the outcome in one sentence, measurable where possible.
+- **User stories** — "as a …, I … so that …", each with verifiable acceptance criteria.
+- **Assumptions** — what you inferred that a stakeholder should confirm.
+- **Open questions** — ambiguities that block implementation, phrased so a yes/no or short
+ answer resolves them.
+
+Do not invent scope. Anything not grounded in the input belongs under assumptions or questions.
diff --git a/skills/ui-design-spec/SKILL.md b/skills/ui-design-spec/SKILL.md
new file mode 100644
index 0000000..13a0dd2
--- /dev/null
+++ b/skills/ui-design-spec/SKILL.md
@@ -0,0 +1,38 @@
+---
+id: ui-design-spec
+name: UI Design Spec
+version: 1.0.0
+summary: Turn a feature into a concrete screen spec — layout, components, states, and flows.
+roles: [designer]
+inputs: A feature or story, plus the product's design language notes if attached.
+outputs: A screen-by-screen spec — layout, components, interaction states, and the user flow.
+actions:
+ - name: write-design-spec
+ risk: draft
+ description: Produce the design spec as a draft artifact on the task (held for review).
+tools: []
+context: [house-style, design-system]
+visibility: public
+min_tier: free
+golden_tests:
+ - input: "Feature: users need a way to log out from anywhere in the app."
+ expected: |
+ Placement: avatar menu, top-right header, last item "Log out" with icon.
+ States: confirm none (instant), loading spinner on click, redirect to /login.
+ Flow: any page → avatar menu → Log out → /login with a "signed out" toast.
+---
+
+# UI Design Spec
+
+You are a product designer. Specify the screen(s) so a developer can build them without
+guessing.
+
+For each screen or surface:
+
+- **Layout** — regions and hierarchy (what's where, and why).
+- **Components** — name them in the product's design system terms where possible.
+- **States** — empty, loading, error, success, and permission-restricted variants.
+- **Flow** — entry points, the happy path, and exits.
+- **Copy** — exact labels for buttons, titles, and empty states.
+
+Stay inside the existing design language; flag any new pattern you introduce and justify it.
diff --git a/tests/TeamUp.IntegrationTests/AnyRoleSeatTests.cs b/tests/TeamUp.IntegrationTests/AnyRoleSeatTests.cs
new file mode 100644
index 0000000..380ece0
--- /dev/null
+++ b/tests/TeamUp.IntegrationTests/AnyRoleSeatTests.cs
@@ -0,0 +1,148 @@
+using System.Net;
+using System.Net.Http.Headers;
+using System.Net.Http.Json;
+using Microsoft.Extensions.DependencyInjection;
+using TeamUp.Modules.Assembler.Queue;
+using TeamUp.Modules.Assembler.Runtime;
+using Xunit;
+
+namespace TeamUp.IntegrationTests;
+
+///
+/// The core product thesis: ANY seat can be AI-staffed — a role is just a name + skill atoms.
+/// An "Engineer" seat (not PO, not QA) runs the same pipeline: skills assemble, the model is
+/// called, and the implement-code proposal is held for review like any other governed action.
+///
+public sealed class AnyRoleSeatTests(PostgresFixture postgres) : IClassFixture
+{
+ private sealed record BootstrapResponse(string Token, Guid MemberId, Guid OrganizationId);
+
+ private sealed record IdResponse(Guid Id);
+
+ private sealed record TeamResponse(Guid Id, Guid OrganizationId, string Name);
+
+ private sealed record SeatResponse(Guid Id, Guid TeamId, string RoleName, string State, Guid? MemberId, Guid? AgentId);
+
+ private sealed record SyncResult(int Indexed);
+
+ private sealed record RunResponse(
+ Guid Id, Guid SeatId, Guid WorkItemId, Guid? AgentId, string Status,
+ string? ActionType, string? ActionRisk, string? Prompt, string? Output, string? Error);
+
+ private sealed record ReviewItemResponse(
+ Guid Id, Guid OrganizationId, Guid TeamId, Guid AgentRunId, Guid AgentId, Guid WorkItemId,
+ string ActionKind, string Risk, string Title, string Content, List ChildTitles,
+ string? Trace, string Status, string? Decision, double? EditDistance, DateTimeOffset CreatedAtUtc);
+
+ [Fact]
+ public async Task An_engineer_seat_runs_the_same_governed_pipeline()
+ {
+ var settings = new Dictionary
+ {
+ ["GitSource:Provider"] = "filesystem",
+ ["GitSource:Root"] = LocateSkillsDirectory(),
+ };
+
+ await using var factory = new TeamUpWebFactory(postgres.ConnectionString, settings);
+ using var anon = factory.CreateClient();
+
+ var owner = await PostOk(anon, "/api/identity/bootstrap", new
+ {
+ organizationName = "AliaSaaS",
+ ownerEmail = "owner@alia.test",
+ ownerDisplayName = "Owner",
+ ownerPassword = "Passw0rd!",
+ });
+ using var client = Authed(factory, owner.Token);
+
+ await client.PostAsJsonAsync("/api/orgboard/organizations", new { organizationId = owner.OrganizationId, name = "AliaSaaS" });
+ var team = await PostOk(client, "/api/orgboard/teams", new { organizationId = owner.OrganizationId, name = "IPNOPS" });
+ var config = await PostOk(client, "/api/integrations/api-configs", new
+ {
+ organizationId = owner.OrganizationId,
+ name = "Vertex-Pro",
+ provider = "stub",
+ model = "gemini-pro",
+ apiKey = "sk-demo-key",
+ });
+
+ // The catalogue now carries atoms for engineer/designer/analyst roles too.
+ var sync = await PostOk(client, "/api/skills/sync", new { });
+ Assert.True(sync.Indexed >= 8);
+
+ // Staff an ENGINEER seat with AI — same configurator, different atoms.
+ var seat = await PostOk(client, "/api/orgboard/seats", new { teamId = team.Id, roleName = "Backend Engineer" });
+ await client.PostAsJsonAsync($"/api/orgboard/seats/{seat.Id}/agent", new
+ {
+ name = "Edison",
+ monogram = "ED",
+ autonomy = "Gated",
+ apiConfigId = config.Id,
+ skillKeys = new[] { "code-implementation", "bug-diagnosis" },
+ docs = Array.Empty(),
+ });
+
+ var task = await PostOk(client, "/api/orgboard/tasks", new
+ {
+ teamId = team.Id,
+ title = "Implement the logout endpoint",
+ description = "POST /logout clears the session.",
+ type = "Story",
+ });
+
+ var run = await PostOk(client, "/api/assembler/runs", new { seatId = seat.Id, workItemId = task.Id });
+ await DrainOneJob(factory);
+
+ var done = await client.GetFromJsonAsync($"/api/assembler/runs/{run.Id}");
+ Assert.Equal("Completed", done!.Status);
+ Assert.Equal("implement-code", done.ActionType); // the engineer atom's primary action
+ Assert.Equal("Draft", done.ActionRisk);
+ Assert.Contains("Code Implementation", done.Prompt); // the skill body assembled in
+
+ // Gated engineer output is governed exactly like PO/QA output: held for human review.
+ var pending = await client.GetFromJsonAsync>(
+ $"/api/governance/reviews?organizationId={owner.OrganizationId}");
+ var held = Assert.Single(pending!);
+ Assert.Equal("implement-code", held.ActionKind);
+ Assert.Equal(task.Id, held.WorkItemId);
+ }
+
+ private sealed record RunTask(Guid Id);
+
+ private static async Task DrainOneJob(TeamUpWebFactory factory)
+ {
+ await using var scope = factory.Services.CreateAsyncScope();
+ var queue = scope.ServiceProvider.GetRequiredService();
+ var job = await queue.ClaimNextAsync("test-worker");
+ Assert.NotNull(job);
+ await scope.ServiceProvider.GetRequiredService().ProcessAsync(job!);
+ }
+
+ private static HttpClient Authed(TeamUpWebFactory factory, string token)
+ {
+ var client = factory.CreateClient();
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
+ return client;
+ }
+
+ private static async Task PostOk(HttpClient client, string url, object body)
+ {
+ var response = await client.PostAsJsonAsync(url, body);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var value = await response.Content.ReadFromJsonAsync();
+ Assert.NotNull(value);
+ return value!;
+ }
+
+ private static string LocateSkillsDirectory()
+ {
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
+ while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "TeamUp.slnx")))
+ {
+ dir = dir.Parent;
+ }
+
+ Assert.NotNull(dir);
+ return Path.Combine(dir!.FullName, "skills");
+ }
+}
diff --git a/tests/TeamUp.IntegrationTests/SkillSyncTests.cs b/tests/TeamUp.IntegrationTests/SkillSyncTests.cs
index fb15580..e7310cc 100644
--- a/tests/TeamUp.IntegrationTests/SkillSyncTests.cs
+++ b/tests/TeamUp.IntegrationTests/SkillSyncTests.cs
@@ -49,7 +49,7 @@ public sealed class SkillSyncTests(PostgresFixture postgres) : IClassFixture();
- Assert.Equal(4, result!.Indexed);
+ Assert.True(result!.Indexed >= 8, $"expected all atoms indexed, got {result.Indexed}");
var productOwner = await client.GetFromJsonAsync>("/api/skills/?role=product-owner");
Assert.Contains(productOwner!, s => s.SkillKey == "spec-writing");