---
phase: 23-brainstormer-flow
verified: 2026-04-01T22:15:00Z
status: human_needed
score: 13/15 must-haves verified
re_verification: false
human_verification:
  - test: "Open a new conversation and verify the Brainstormer (general agent) greets the user with a structured questioning flow"
    expected: "The Brainstormer agent introduces itself and begins asking clarifying questions (What are you building? Why? Constraints?). The agent must respond using a real LLM adapter, not the streamEcho stub."
    why_human: "The streamEcho stub (pre-existing from Phase 22) only echoes back user input word-by-word — it does not greet the user or produce structured questions. The Brainstormer persona requires a real LLM integration. AGENT-02 (structured questioning) and Success Criterion 1 (greets user, begins questioning) cannot be verified without a live LLM connection."
  - test: "After a simulated Brainstormer conversation, POST a spec_card message to a conversation via the API, then verify ChatSpecCard renders in the chat"
    expected: "A message with messageType=spec_card renders as a card with What, Why, Constraints, Success sections plus Send to PM, Edit, and Save as Draft buttons."
    why_human: "The ChatSpecCard component exists and is wired correctly in ChatMessage dispatch. However, there is no server-side mechanism in Phase 23 that creates spec_card messages — the Brainstormer LLM must produce them when it concludes its questioning flow. Rendering can only be tested by manually POSTing a spec_card message via the API or having a real LLM produce it."
  - test: "Verify the general agent SOUL.md persona reflects Brainstormer / structured-questioning behavior for AGENT-01 and AGENT-02"
    expected: "The general role agent's system prompt includes Brainstormer-style instructions: greet users, ask clarifying questions, produce a spec in the spec_card format."
    why_human: "server/src/onboarding-assets/general/SOUL.md currently contains a generic Generalist persona with no mention of Brainstormer behavior, clarifying questions, or spec card generation. AGENT-01 states the default agent should behave as a Brainstormer. This persona gap cannot be verified programmatically — it requires a human to confirm whether the persona configuration is intentionally deferred or a gap."
---

# Phase 23: Brainstormer Flow — Verification Report

**Phase Goal:** Users can open Nexus, start a conversation with the Brainstormer, receive structured clarifying questions, approve a spec, and watch it become real Nexus tasks — without ever touching the dashboard
**Verified:** 2026-04-01T22:15:00Z
**Status:** human_needed
**Re-verification:** No — initial verification

---

## Goal Achievement

### Observable Truths (from Success Criteria)

| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Brainstormer is default agent for new conversations; greets user and begins structured questioning | ? UNCERTAIN | `useBrainstormerDefault` auto-selects general agent — wired. But `streamEcho` stub (pre-existing from Phase 22) echoes text, does not produce greetings or questions. No Brainstormer persona in `general/SOUL.md`. |
| 2 | Brainstormer produces formatted spec card (What/Why/Constraints/Success + action buttons) | ? UNCERTAIN | `ChatSpecCard` component is fully implemented with all UI. `messageType=spec_card` dispatch is wired in `ChatMessage`. No server-side mechanism in Phase 23 creates `spec_card` messages — requires real LLM output or manual API call. |
| 3 | "Send to PM" button triggers handoff indicator in chat showing "Brainstormer → PM" | ✓ VERIFIED | `handleHandoff` in `ChatPanel` calls `chatApi.handoffSpec` → POST `/conversations/:id/handoff` → inserts `messageType=handoff` system message → `ChatHandoffIndicator` renders it. Full chain wired end-to-end. |
| 4 | PM agent creates Nexus issues from spec; user sees task IDs in chat | ✓ VERIFIED | Handoff route calls `issueSvc.create()`, inserts `messageType=task_created` message with `{taskId, taskTitle, taskUrl}` JSON. `ChatTaskCreatedBadge` renders task ID with View task link. |
| 5 | Engineer/Generalist completion status update appears in chat | ✓ VERIFIED | `POST /conversations/:id/status-update` inserts `messageType=status_update` system message. `ChatStatusUpdateBadge` renders CheckCircle2 + agent + task reference. `chatApi.postStatusUpdate()` exists for callers. |

**Automated score:** 3/5 truths verified, 2/5 need human confirmation

---

### Required Artifacts

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `packages/db/src/schema/chat_messages.ts` | messageType column definition | ✓ VERIFIED | `messageType: text("message_type")` on line 13, nullable |
| `packages/db/src/migrations/0049_add_message_type.sql` | SQL migration for message_type | ✓ VERIFIED | `ALTER TABLE "chat_messages" ADD COLUMN "message_type" text;` |
| `packages/shared/src/types/chat.ts` | ChatMessage.messageType field | ✓ VERIFIED | `messageType: string \| null;` in `ChatMessage` interface |
| `packages/shared/src/validators/chat.ts` | handoffSchema and messageType in createMessageSchema | ✓ VERIFIED | Both `handoffSchema` (with spec/targetRole) and `messageType: z.string().optional()` in `createMessageSchema` |
| `packages/shared/src/index.ts` | handoffSchema and Handoff re-exported | ✓ VERIFIED | Lines 564 and 568 export `handoffSchema` and `type Handoff` |
| `server/src/services/chat.ts` | addSystemMessage helper and messageType in addMessage | ✓ VERIFIED | `addSystemMessage` at line 196, `addMessage` accepts `messageType?: string` at line 140 |
| `server/src/routes/chat.ts` | handoff and status-update routes | ✓ VERIFIED | `POST /conversations/:id/handoff` at line 151, `POST /conversations/:id/status-update` at line 194 |
| `ui/src/components/ChatSpecCard.tsx` | Spec card with 4 fields and action buttons | ✓ VERIFIED | 233 lines; renders What/Why/Constraints/Success; edit mode; Send to PM / Edit / Save as Draft buttons |
| `ui/src/components/ChatHandoffIndicator.tsx` | Separator-style handoff indicator | ✓ VERIFIED | Flanking `<hr aria-hidden="true">`, `aria-label="Agent handoff from Brainstormer to PM"` |
| `ui/src/components/ChatTaskCreatedBadge.tsx` | Task created badge | ✓ VERIFIED | Loading state ("Creating task...") and resolved state with taskId, taskTitle, View task link |
| `ui/src/components/ChatStatusUpdateBadge.tsx` | Status update badge | ✓ VERIFIED | CheckCircle2 icon, agentName, taskId, View task link, `role="status"` |
| `ui/src/hooks/useBrainstormerDefault.ts` | General agent auto-selector | ✓ VERIFIED | Queries `["agents", selectedCompanyId]`, filters `role === "general"`, sorts by `createdAt`, returns first |
| `ui/src/components/ChatMessage.tsx` | messageType dispatch to specialized components | ✓ VERIFIED | Dispatch block at line 51 routes to ChatSpecCard / ChatHandoffIndicator / ChatTaskCreatedBadge / ChatStatusUpdateBadge |
| `ui/src/components/ChatMessageList.tsx` | messageType prop propagation | ✓ VERIFIED | `messageType={msg.messageType}`, `conversationId={conversationId}`, `onHandoff={onHandoff}` passed to `<ChatMessage>` |
| `ui/src/components/ChatPanel.tsx` | useBrainstormerDefault wiring | ✓ VERIFIED | Imported, called, auto-select useEffect wired, `handleHandoff` callback wired, `onHandoff={handleHandoff}` on ChatMessageList |
| `ui/src/api/chat.ts` | handoffSpec and postStatusUpdate API methods | ✓ VERIFIED | `handoffSpec()` at line 152, `postStatusUpdate()` at line 163 |
| Wave 0 test stubs (5 files) | it.todo() stubs for all Phase 23 components/hooks | ✓ VERIFIED | All 5 files exist: ChatSpecCard.test.tsx (9 todos), ChatHandoffIndicator.test.tsx, ChatTaskCreatedBadge.test.tsx, ChatStatusUpdateBadge.test.tsx, useBrainstormerDefault.test.ts (4 todos) |

---

### Key Link Verification

| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `packages/db/src/schema/chat_messages.ts` | `packages/shared/src/types/chat.ts` | `messageType` field match | ✓ WIRED | Both define `messageType` as nullable; schema uses `text("message_type")`, type uses `string \| null` |
| `server/src/routes/chat.ts` | `server/src/services/chat.ts` | `svc.addSystemMessage` call | ✓ WIRED | Grep confirms `svc.addSystemMessage` called 3 times in chat.ts routes |
| `server/src/routes/chat.ts` | `server/src/services/issues.ts` | `issueSvc.create` for task creation | ✓ WIRED | `issueService` imported from `../services/issues.js`, `issueSvc.create()` called in handoff route |
| `ui/src/components/ChatMessageList.tsx` | `ui/src/components/ChatMessage.tsx` | `messageType={msg.messageType}` prop | ✓ WIRED | Line 148: `messageType={msg.messageType}` and `onHandoff={onHandoff}` passed |
| `ui/src/components/ChatMessage.tsx` | `ui/src/components/ChatSpecCard.tsx` | dispatch when `messageType === "spec_card"` | ✓ WIRED | Line 52: `if (messageType === "spec_card") return <ChatSpecCard .../>` |
| `ui/src/components/ChatPanel.tsx` | `ui/src/hooks/useBrainstormerDefault.ts` | hook call for default agent selection | ✓ WIRED | Imported at line 17, called at line 32, used in useEffect at line 36 |
| `ui/src/components/ChatSpecCard.tsx` | `ui/src/api/chat.ts` | `handoffSpec` call on Send to PM | ? PARTIAL | `ChatSpecCard` calls `onHandoff?.(spec)` prop callback — it does NOT call `chatApi.handoffSpec` directly. `ChatPanel.handleHandoff` calls `chatApi.handoffSpec`. The chain: ChatSpecCard → onHandoff prop → ChatPanel.handleHandoff → chatApi.handoffSpec. This is correct and intentional — ChatSpecCard correctly delegates to parent. |
| `ui/src/hooks/useBrainstormerDefault.ts` | `ui/src/api/agents.ts` | `useQuery` with agents queryKey | ✓ WIRED | `queryKey: ["agents", selectedCompanyId]`, `queryFn: () => agentsApi.list(selectedCompanyId!)` |

**Note on ChatSpecCard → chatApi link:** The PLAN specified `ChatSpecCard → chatApi.handoffSpec` directly, but the actual implementation correctly uses prop callback delegation (`onHandoff` prop → `ChatPanel.handleHandoff` → `chatApi.handoffSpec`). This is an intentional architecture improvement (component stays data-free) and does not represent a gap — the chain is fully wired.

---

### Data-Flow Trace (Level 4)

| Artifact | Data Variable | Source | Produces Real Data | Status |
|----------|--------------|--------|--------------------|--------|
| `useBrainstormerDefault.ts` | `agents` (Agent[]) | `agentsApi.list()` → GET `/companies/:id/agents` | Yes — real DB query in agents route | ✓ FLOWING |
| `ChatSpecCard.tsx` | `spec` (SpecContent) | `JSON.parse(content)` from `ChatMessage` content prop | Content comes from chat_messages DB row via `useChatMessages` | ✓ FLOWING |
| `ChatTaskCreatedBadge.tsx` | `taskId`, `taskTitle`, `taskUrl` | `JSON.parse(content)` from message; content set by handoff route from `issueSvc.create()` return | Real DB insert via issueService | ✓ FLOWING |
| `ChatStatusUpdateBadge.tsx` | `agentName`, `taskId` | `JSON.parse(content)` from message; content set by caller via POST `/status-update` | Caller-provided data; no hardcoded stubs | ✓ FLOWING |
| `ChatPanel.tsx` `handleHandoff` | `activeConversationId` | `useChatPanel()` context, set when user opens a conversation | Real context value | ✓ FLOWING |
| `ChatMessageList.tsx` messages | `messages` (ChatMessageType[]) | `useChatMessages(conversationId)` → GET `/conversations/:id/messages` | Real DB query via `chatService.listMessages()` | ✓ FLOWING |

---

### Behavioral Spot-Checks

| Behavior | Check | Result | Status |
|----------|-------|--------|--------|
| Handoff route exists and is callable | `grep "router.post.*handoff"` in chat.ts | Found at line 151 | ✓ PASS |
| status-update route exists | `grep "router.post.*status-update"` in chat.ts | Found at line 194 | ✓ PASS |
| `issueSvc.create` called in handoff | Static analysis of chat.ts | `issueSvc.create(companyId, {...})` at line 173 | ✓ PASS |
| `addSystemMessage` inserts 3 typed messages in handoff flow | Static analysis of chat.ts routes | handoff (line 160), task_created (line 181); status-update in separate route (line 202) | ✓ PASS |
| chatApi.handoffSpec posts to correct endpoint | `grep "handoffSpec"` in chat.ts | `api.post("/conversations/${conversationId}/handoff", ...)` at line 157 | ✓ PASS |
| ChatMessage messageType dispatch complete | `grep "spec_card\|handoff\|task_created\|status_update"` in ChatMessage.tsx | All 4 branches present at lines 52, 62, 65, 73 | ✓ PASS |
| Streaming synthetic message has `messageType: null` | ChatMessageList.tsx line 49 | `messageType: null` present — no false dispatch | ✓ PASS |
| All Phase 23 commits exist in git | `git show --stat` on all 5 documented commit SHAs | 6e436950, 0a1b3dc0, 588bbdd5, 1489e499, 651864ba, c7a48bc5, 3f575453 — all verified | ✓ PASS |
| Brainstormer default agent auto-select useEffect | `grep "brainstormerDefaultId"` in ChatPanel.tsx | `useEffect` at line 35 sets `activeAgentId` when `=== null && !activeConversationId` | ✓ PASS |
| Structured questioning / spec_card creation by LLM | Requires running LLM | streamEcho stub only echoes text | ? SKIP — needs human |

---

### Requirements Coverage

| Requirement | Source Plan | Description | Status | Evidence |
|-------------|------------|-------------|--------|----------|
| AGENT-01 | 23-00, 23-02, 23-03 | Default agent is the Brainstormer (general role) | ? PARTIAL | Wiring: `useBrainstormerDefault` selects first `role=general` agent and `ChatPanel` auto-selects it for new conversations. Gap: `general/SOUL.md` contains Generalist persona, not Brainstormer persona — no structured questioning instructions. Behavioral verification requires human. |
| AGENT-02 | 23-00, 23-02, 23-03 | Brainstormer follows structured questioning, produces spec template | ? PARTIAL | UI infra: `ChatSpecCard` renders spec card when `messageType=spec_card`. Wiring: dispatch in `ChatMessage` is in place. Gap: no mechanism in Phase 23 creates `spec_card` messages from the server; the LLM must produce them and the LLM integration (streamEcho stub) does not exist yet. |
| AGENT-03 | 23-01 | PM agent receives specs from chat and creates Nexus tasks | ✓ SATISFIED | `POST /conversations/:id/handoff` calls `issueSvc.create()`, returns issue with `id`, `identifier`, `title`. `ChatTaskCreatedBadge` renders task reference in chat. |
| AGENT-05 | 23-01, 23-02, 23-03 | Handoff indicators visible in chat | ✓ SATISFIED | `ChatHandoffIndicator` renders when `messageType=handoff`. Handoff route inserts `messageType=handoff` system message. Full chain verified. |
| AGENT-06 | 23-01, 23-03 | Task creation from chat becomes Nexus issue | ✓ SATISFIED | Handoff route creates issue via `issueService.create()`. `ChatTaskCreatedBadge` displays resulting task ID. `chatApi.postStatusUpdate()` available for agents. |
| AGENT-07 | 23-01, 23-02, 23-03 | Status updates from agents appear in chat | ✓ SATISFIED | `POST /conversations/:id/status-update` inserts `messageType=status_update` message. `ChatStatusUpdateBadge` renders CheckCircle2 + agent + task reference. |
| CHAT-09 | 23-01, 23-02, 23-03 | System message indicator for handoff visible in chat | ✓ SATISFIED | `messageType=handoff` in DB identifies handoff messages. `ChatHandoffIndicator` renders separator-style indicator with content text between two hr elements. |

**Orphaned requirements check:** All 7 requirements mapped to Phase 23 in REQUIREMENTS.md (AGENT-01, AGENT-02, AGENT-03, AGENT-05, AGENT-06, AGENT-07, CHAT-09) are covered in at least one plan's frontmatter. No orphaned requirements.

---

### Anti-Patterns Found

| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `server/src/services/chat.ts` | 220-227 | `streamEcho` stub yields words with 50ms delay instead of real LLM | ⚠️ Warning | Pre-existing from Phase 22. The Brainstormer cannot produce greetings, questions, or spec_card messages — it echoes user input. Does NOT block handoff/task-creation flow (those are explicit API calls, not LLM-generated), but blocks Success Criteria 1 and 2. |
| `server/src/onboarding-assets/general/SOUL.md` | 1-end | Generalist persona with no Brainstormer behavior | ⚠️ Warning | The `general` role agent is selected as default Brainstormer, but its persona contains no structured questioning instructions. AGENT-01 specifies "Brainstormer (Generalist with a Superpowers-style system prompt)". The system prompt gap means AGENT-02 behavior cannot activate even when LLM is connected. |
| `ui/src/components/ChatSpecCard.tsx` | 91-137 | `placeholder` attributes on textareas | ℹ️ Info | These are legitimate textarea placeholders ("What should be built?", etc.) — not stub patterns. Not a blocker. |

**Stub classification note:** The `streamEcho` method is a pre-existing stub from Phase 22, documented in 23-01-SUMMARY.md under "Known Stubs". It does not block handoff or task creation (those are explicit POST routes). It blocks the LLM conversational experience and is expected to be replaced in a future LLM integration phase.

---

### Human Verification Required

#### 1. Brainstormer Greeting and Structured Questioning (Success Criterion 1 + AGENT-01/02)

**Test:** Open Nexus, click "New Conversation." Verify the general role agent is auto-selected. Send a message. Verify the agent responds with a structured greeting and begins asking clarifying questions.
**Expected:** Agent introduces itself as Brainstormer, asks "What are you looking to build?" and proceeds with structured questions.
**Why human:** `streamEcho` stub only echoes input back. Even with a real LLM, `general/SOUL.md` contains no Brainstormer persona — no structured questioning instructions exist. This requires either (a) confirming the streamEcho replacement is scoped to a future phase and AGENT-02 is intentionally deferred, or (b) identifying this as a gap requiring SOUL.md updates.

#### 2. Spec Card Visual Rendering and Interaction (Success Criterion 2 + AGENT-02)

**Test:** Manually POST a message to a conversation with `{ role: "system", messageType: "spec_card", content: "{\"what\":\"Build a login page\",\"why\":\"Users need auth\",\"constraints\":\"Must use OAuth\",\"success\":\"Users can log in\"}" }`. Then open that conversation in the UI.
**Expected:** A formatted card renders with four labeled sections (What, Why, Constraints, Success), plus Send to PM, Edit, and Save as Draft buttons. Edit button opens textarea mode. Escape key discards edits. Save as Draft shows [Draft] badge.
**Why human:** Requires running the UI. Cannot verify visual layout or interactive behavior via static analysis. Also confirms the messageType dispatch chain works end-to-end.

#### 3. Full Handoff Flow (Success Criteria 3 + 4 + AGENT-03/05/06)

**Test:** In the UI, click "Send to PM" on a spec card. Verify: (a) handoff separator appears with "Brainstormer → PM: spec handed off", (b) task created badge appears with a real task identifier (e.g. NXS-42), (c) clicking "View task" navigates to the issue.
**Expected:** Three new messages appear in chat: handoff indicator, task created badge with identifier and title, and the View task link navigates correctly.
**Why human:** Requires live server with database. The link in `taskUrl` uses `/issues/${issue.id}` (UUID) — must verify the router resolves this path correctly.

---

### Gaps Summary

No structural gaps found in Phase 23 implementation. All planned artifacts exist, are substantive (not stubs), and are wired. All key links verified.

Two items flagged for human verification represent **behavior that requires a real LLM** (Success Criteria 1 and 2), which is outside Phase 23's stated implementation scope per RESEARCH.md: "Phase 23 does not require a real LLM to function — the spec card, handoff, and task creation flows are all triggered by explicit API calls from the UI, not by the LLM output type changing."

One additional item requires human judgment on scope: whether the Brainstormer system prompt (AGENT-01: "Generalist with a Superpowers-style system prompt") is intentionally deferred to a future LLM integration phase, or represents a gap that should be addressed in Phase 23. The `general/SOUL.md` Generalist persona has no Brainstormer-specific behavior.

**Assessment:** Phase 23 successfully delivered the complete infrastructure for the Brainstormer flow — DB schema, server routes, UI components, API wiring, and integration plumbing. The success criteria items involving live LLM behavior are gated on the LLM integration (replacing streamEcho), which is correctly identified as a dependency outside Phase 23's scope.

---

_Verified: 2026-04-01T22:15:00Z_
_Verifier: Claude (gsd-verifier)_
