20 KiB
| phase | verified | status | score | re_verification | human_verification | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 23-brainstormer-flow | 2026-04-01T22:15:00Z | human_needed | 13/15 must-haves verified | false |
|
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)