Plan 00 (Wave 0): DB migration for message_type, shared types/validators, test stubs Plan 01 (Wave 1): Server — addSystemMessage, handoff route, status-update route Plan 02 (Wave 1): UI — ChatSpecCard, ChatHandoffIndicator, ChatTaskCreatedBadge, ChatStatusUpdateBadge, useBrainstormerDefault Plan 03 (Wave 2): Wiring — ChatMessage dispatch, ChatMessageList propagation, ChatPanel brainstormer default, chatApi handoff Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
313 lines
12 KiB
Markdown
313 lines
12 KiB
Markdown
---
|
|
phase: 23-brainstormer-flow
|
|
plan: 00
|
|
type: execute
|
|
wave: 0
|
|
depends_on: []
|
|
files_modified:
|
|
- packages/db/src/schema/chat_messages.ts
|
|
- packages/db/src/migrations/0049_add_message_type.sql
|
|
- packages/db/src/migrations/meta/_journal.json
|
|
- packages/shared/src/types/chat.ts
|
|
- packages/shared/src/validators/chat.ts
|
|
- packages/shared/src/index.ts
|
|
- ui/src/components/ChatSpecCard.test.tsx
|
|
- ui/src/components/ChatHandoffIndicator.test.tsx
|
|
- ui/src/components/ChatTaskCreatedBadge.test.tsx
|
|
- ui/src/components/ChatStatusUpdateBadge.test.tsx
|
|
- ui/src/hooks/useBrainstormerDefault.test.ts
|
|
autonomous: true
|
|
requirements:
|
|
- AGENT-01
|
|
- AGENT-02
|
|
- AGENT-03
|
|
- AGENT-05
|
|
- AGENT-06
|
|
- AGENT-07
|
|
- CHAT-09
|
|
|
|
must_haves:
|
|
truths:
|
|
- "chat_messages table has a message_type text column"
|
|
- "ChatMessage shared type includes messageType field"
|
|
- "createMessageSchema accepts optional messageType"
|
|
- "handoffSchema validates spec content and targetRole"
|
|
- "Test stubs exist for all new Phase 23 components and hooks"
|
|
artifacts:
|
|
- path: "packages/db/src/schema/chat_messages.ts"
|
|
provides: "messageType column definition"
|
|
contains: "messageType"
|
|
- path: "packages/db/src/migrations/0049_add_message_type.sql"
|
|
provides: "SQL migration for message_type column"
|
|
contains: "ADD COLUMN"
|
|
- path: "packages/shared/src/types/chat.ts"
|
|
provides: "ChatMessage.messageType field"
|
|
contains: "messageType"
|
|
- path: "packages/shared/src/validators/chat.ts"
|
|
provides: "handoffSchema and messageType in createMessageSchema"
|
|
contains: "handoffSchema"
|
|
key_links:
|
|
- from: "packages/db/src/schema/chat_messages.ts"
|
|
to: "packages/shared/src/types/chat.ts"
|
|
via: "messageType field must match"
|
|
pattern: "messageType"
|
|
---
|
|
|
|
<objective>
|
|
Foundation: DB migration for message_type column, shared types/validators extension, and Wave 0 test stubs for all Phase 23 components.
|
|
|
|
Purpose: Every subsequent plan depends on the message_type column existing in the DB schema, the ChatMessage type having a messageType field, and test stubs being in place for TDD-style development.
|
|
Output: Migration file, updated schema/types/validators, test stub files.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@.claude/get-shit-done/workflows/execute-plan.md
|
|
@.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/23-brainstormer-flow/23-RESEARCH.md
|
|
@.planning/phases/23-brainstormer-flow/23-UI-SPEC.md
|
|
|
|
<interfaces>
|
|
<!-- Existing types and schemas the executor needs -->
|
|
|
|
From packages/shared/src/types/chat.ts:
|
|
```typescript
|
|
export interface ChatMessage {
|
|
id: string;
|
|
conversationId: string;
|
|
role: "user" | "assistant" | "system";
|
|
content: string;
|
|
agentId: string | null;
|
|
createdAt: string;
|
|
updatedAt: string | null;
|
|
}
|
|
```
|
|
|
|
From packages/shared/src/validators/chat.ts:
|
|
```typescript
|
|
export const createMessageSchema = z.object({
|
|
role: z.enum(["user", "assistant", "system"]),
|
|
content: z.string().min(1).max(100_000),
|
|
agentId: z.string().uuid().optional(),
|
|
});
|
|
```
|
|
|
|
From packages/db/src/schema/chat_messages.ts:
|
|
```typescript
|
|
export const chatMessages = pgTable(
|
|
"chat_messages",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
conversationId: uuid("conversation_id").notNull()
|
|
.references(() => chatConversations.id, { onDelete: "cascade" }),
|
|
role: text("role").notNull(),
|
|
content: text("content").notNull(),
|
|
agentId: uuid("agent_id"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(),
|
|
},
|
|
(table) => ({
|
|
conversationCreatedIdx: index("chat_messages_conversation_created_idx").on(table.conversationId, table.createdAt),
|
|
}),
|
|
);
|
|
```
|
|
|
|
Migration journal: last entry is idx 46, tag "0046_smooth_sentinels". Files on disk go up to 0048. Next migration must be 0049.
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: DB migration and shared types for message_type</name>
|
|
<read_first>
|
|
- packages/db/src/schema/chat_messages.ts
|
|
- packages/db/src/migrations/meta/_journal.json
|
|
- packages/shared/src/types/chat.ts
|
|
- packages/shared/src/validators/chat.ts
|
|
- packages/shared/src/index.ts
|
|
</read_first>
|
|
<files>
|
|
packages/db/src/schema/chat_messages.ts,
|
|
packages/db/src/migrations/0049_add_message_type.sql,
|
|
packages/db/src/migrations/meta/_journal.json,
|
|
packages/shared/src/types/chat.ts,
|
|
packages/shared/src/validators/chat.ts,
|
|
packages/shared/src/index.ts
|
|
</files>
|
|
<action>
|
|
1. Create migration file `packages/db/src/migrations/0049_add_message_type.sql`:
|
|
```sql
|
|
ALTER TABLE "chat_messages" ADD COLUMN "message_type" text;
|
|
```
|
|
|
|
2. Update `packages/db/src/migrations/meta/_journal.json`: append a new entry after idx 46 with idx 49, version "7", tag "0049_add_message_type", breakpoints true. Use `when: Date.now()` (current epoch ms).
|
|
|
|
IMPORTANT: The journal has entries up to idx 46 but disk has files 0047 and 0048 (added manually in Phases 21/22 without journal entries). Add ONLY the idx 49 entry. Do NOT add entries for 0047 or 0048 — those were applied outside the journal.
|
|
|
|
Wait — re-read the journal. If _journal.json only goes to idx 46, but files go to 0048, we must add entries for 47, 48, AND 49 to keep the journal consistent. Read the actual SQL files 0047 and 0048 to determine their tags.
|
|
|
|
Actually, looking at the research notes: "The migration numbering... The next migration must be named 0049_*.sql with idx 49 added to _journal.json." Follow this guidance — add idx 47 (tag "0047_nebulous_klaw"), idx 48 (tag "0048_add_chat_messages_updated_at"), and idx 49 (tag "0049_add_message_type") entries.
|
|
|
|
3. Update `packages/db/src/schema/chat_messages.ts`: add `messageType: text("message_type"),` after the `agentId` field. This is a nullable text column. Values: null (normal), "handoff", "spec_card", "task_created", "status_update".
|
|
|
|
4. Update `packages/shared/src/types/chat.ts`:
|
|
- Add `messageType: string | null;` to the `ChatMessage` interface (after `agentId`).
|
|
|
|
5. Update `packages/shared/src/validators/chat.ts`:
|
|
- Add `messageType: z.string().optional(),` to `createMessageSchema`.
|
|
- Add new `handoffSchema`:
|
|
```typescript
|
|
export const handoffSchema = z.object({
|
|
spec: z.object({
|
|
what: z.string().min(1),
|
|
why: z.string().min(1),
|
|
constraints: z.string().optional().default(""),
|
|
success: z.string().optional().default(""),
|
|
}),
|
|
targetRole: z.enum(["pm", "engineer", "general"]),
|
|
});
|
|
export type Handoff = z.infer<typeof handoffSchema>;
|
|
```
|
|
- Add `handoffSchema` and `Handoff` to the file's exports.
|
|
|
|
6. Update `packages/shared/src/index.ts`: add re-exports for `handoffSchema` and `Handoff` type from validators/chat.ts. Follow the existing pattern of re-exporting from `./validators/chat.js`.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm exec tsc --noEmit -p packages/shared/tsconfig.json 2>&1 | head -20 && pnpm exec tsc --noEmit -p packages/db/tsconfig.json 2>&1 | head -20</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "messageType" packages/db/src/schema/chat_messages.ts
|
|
- grep -q "message_type" packages/db/src/migrations/0049_add_message_type.sql
|
|
- grep -q "messageType" packages/shared/src/types/chat.ts
|
|
- grep -q "handoffSchema" packages/shared/src/validators/chat.ts
|
|
- grep -q "handoffSchema" packages/shared/src/index.ts
|
|
- grep -q "0049_add_message_type" packages/db/src/migrations/meta/_journal.json
|
|
</acceptance_criteria>
|
|
<done>message_type column defined in Drizzle schema and SQL migration; ChatMessage type and createMessageSchema include messageType; handoffSchema exported from shared package</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Wave 0 test stubs for Phase 23 components and hooks</name>
|
|
<read_first>
|
|
- ui/src/components/ChatMessage.test.tsx
|
|
- ui/src/components/ChatMessageList.test.tsx
|
|
</read_first>
|
|
<files>
|
|
ui/src/components/ChatSpecCard.test.tsx,
|
|
ui/src/components/ChatHandoffIndicator.test.tsx,
|
|
ui/src/components/ChatTaskCreatedBadge.test.tsx,
|
|
ui/src/components/ChatStatusUpdateBadge.test.tsx,
|
|
ui/src/hooks/useBrainstormerDefault.test.ts
|
|
</files>
|
|
<action>
|
|
Create test stub files using it.todo() pattern (matching Phase 21 convention — NOT it.skip()):
|
|
|
|
1. `ui/src/components/ChatSpecCard.test.tsx`:
|
|
```tsx
|
|
// @vitest-environment jsdom
|
|
import { describe, it } from "vitest";
|
|
|
|
describe("ChatSpecCard", () => {
|
|
it.todo("renders four spec sections: What, Why, Constraints, Success");
|
|
it.todo("parses JSON content and displays field values");
|
|
it.todo("shows error fallback on JSON parse failure");
|
|
it.todo("Send to PM button calls onHandoff with spec content");
|
|
it.todo("Edit button switches to textarea edit mode");
|
|
it.todo("Save changes button disabled when all fields empty");
|
|
it.todo("Discard button reverts to read-only mode");
|
|
it.todo("Save as Draft button adds [Draft] badge");
|
|
it.todo("renders with role=region and aria-label=Specification");
|
|
});
|
|
```
|
|
|
|
2. `ui/src/components/ChatHandoffIndicator.test.tsx`:
|
|
```tsx
|
|
// @vitest-environment jsdom
|
|
import { describe, it } from "vitest";
|
|
|
|
describe("ChatHandoffIndicator", () => {
|
|
it.todo("renders content text between two hr elements");
|
|
it.todo("has aria-label for agent handoff");
|
|
it.todo("hr elements have aria-hidden=true");
|
|
});
|
|
```
|
|
|
|
3. `ui/src/components/ChatTaskCreatedBadge.test.tsx`:
|
|
```tsx
|
|
// @vitest-environment jsdom
|
|
import { describe, it } from "vitest";
|
|
|
|
describe("ChatTaskCreatedBadge", () => {
|
|
it.todo("shows Creating task... when taskId is not provided");
|
|
it.todo("renders taskId, taskTitle, and View task link when resolved");
|
|
it.todo("View task link has correct aria-label");
|
|
it.todo("has role=status on container");
|
|
});
|
|
```
|
|
|
|
4. `ui/src/components/ChatStatusUpdateBadge.test.tsx`:
|
|
```tsx
|
|
// @vitest-environment jsdom
|
|
import { describe, it } from "vitest";
|
|
|
|
describe("ChatStatusUpdateBadge", () => {
|
|
it.todo("renders CheckCircle2 icon, agent name, and task reference");
|
|
it.todo("View task link navigates to issue detail");
|
|
it.todo("has role=status on container");
|
|
});
|
|
```
|
|
|
|
5. `ui/src/hooks/useBrainstormerDefault.test.ts`:
|
|
```ts
|
|
// @vitest-environment jsdom
|
|
import { describe, it } from "vitest";
|
|
|
|
describe("useBrainstormerDefault", () => {
|
|
it.todo("returns general role agent ID when available");
|
|
it.todo("returns first by createdAt when multiple general agents exist");
|
|
it.todo("returns null when no agents loaded");
|
|
it.todo("returns null when no general agent exists");
|
|
});
|
|
```
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm vitest run --project=ui -- ChatSpecCard ChatHandoffIndicator ChatTaskCreatedBadge ChatStatusUpdateBadge useBrainstormerDefault 2>&1 | tail -10</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- test -f ui/src/components/ChatSpecCard.test.tsx
|
|
- test -f ui/src/components/ChatHandoffIndicator.test.tsx
|
|
- test -f ui/src/components/ChatTaskCreatedBadge.test.tsx
|
|
- test -f ui/src/components/ChatStatusUpdateBadge.test.tsx
|
|
- test -f ui/src/hooks/useBrainstormerDefault.test.ts
|
|
- grep -q "it.todo" ui/src/components/ChatSpecCard.test.tsx
|
|
- grep -q "it.todo" ui/src/hooks/useBrainstormerDefault.test.ts
|
|
</acceptance_criteria>
|
|
<done>All 5 test stub files exist with it.todo() entries covering every Phase 23 behavior; vitest run finds them without errors</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `pnpm exec tsc --noEmit` passes for shared and db packages
|
|
- Migration file 0049 exists with correct SQL
|
|
- Journal updated with idx 49 entry
|
|
- All 5 test stub files parseable by vitest
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- messageType column in Drizzle schema and SQL migration
|
|
- ChatMessage type has messageType field
|
|
- handoffSchema exported from shared
|
|
- createMessageSchema accepts optional messageType
|
|
- 5 test stub files with it.todo() entries
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/23-brainstormer-flow/23-00-SUMMARY.md`
|
|
</output>
|