298 lines
13 KiB
Markdown
298 lines
13 KiB
Markdown
---
|
|
phase: 24-search-history-branching
|
|
plan: 00
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- packages/db/src/migrations/0050_add_branch_columns.sql
|
|
- packages/db/src/migrations/0051_add_message_search_vector.sql
|
|
- packages/db/src/migrations/0052_create_chat_message_bookmarks.sql
|
|
- packages/db/src/migrations/meta/_journal.json
|
|
- packages/db/src/schema/chat_conversations.ts
|
|
- packages/db/src/schema/chat_messages.ts
|
|
- packages/db/src/schema/chat_message_bookmarks.ts
|
|
- packages/db/src/schema/index.ts
|
|
- packages/shared/src/types/chat.ts
|
|
- packages/shared/src/validators/chat.ts
|
|
- packages/shared/src/index.ts
|
|
- server/src/__tests__/chat-service.test.ts
|
|
- server/src/__tests__/chat-routes.test.ts
|
|
autonomous: true
|
|
requirements:
|
|
- CHAT-07
|
|
- CHAT-13
|
|
- CHAT-14
|
|
- HIST-04
|
|
- HIST-07
|
|
- HIST-08
|
|
- PERF-04
|
|
|
|
must_haves:
|
|
truths:
|
|
- "DB has parentConversationId and branchFromMessageId columns on chat_conversations"
|
|
- "DB has content_search tsvector generated column with GIN index on chat_messages"
|
|
- "DB has chat_message_bookmarks table with companyId + messageId columns"
|
|
- "Shared types include ChatMessageSearchResult, ChatBookmark, ChatConversationWithBranch"
|
|
- "Test stubs exist for searchMessages, toggleBookmark, branchConversation, exportConversation"
|
|
artifacts:
|
|
- path: "packages/db/src/migrations/0050_add_branch_columns.sql"
|
|
provides: "Branch columns migration"
|
|
contains: "parent_conversation_id"
|
|
- path: "packages/db/src/migrations/0051_add_message_search_vector.sql"
|
|
provides: "tsvector + GIN index migration"
|
|
contains: "content_search"
|
|
- path: "packages/db/src/migrations/0052_create_chat_message_bookmarks.sql"
|
|
provides: "Bookmarks table migration"
|
|
contains: "chat_message_bookmarks"
|
|
- path: "packages/db/src/schema/chat_message_bookmarks.ts"
|
|
provides: "Drizzle schema for bookmarks table"
|
|
exports: ["chatMessageBookmarks"]
|
|
- path: "packages/shared/src/types/chat.ts"
|
|
provides: "Search result, bookmark, and branch types"
|
|
contains: "ChatMessageSearchResult"
|
|
key_links:
|
|
- from: "packages/db/src/schema/chat_message_bookmarks.ts"
|
|
to: "packages/db/src/schema/index.ts"
|
|
via: "re-export"
|
|
pattern: "chatMessageBookmarks"
|
|
- from: "packages/shared/src/types/chat.ts"
|
|
to: "packages/shared/src/index.ts"
|
|
via: "re-export"
|
|
pattern: "ChatMessageSearchResult"
|
|
---
|
|
|
|
<objective>
|
|
Create the DB migrations, Drizzle schema updates, shared types/validators, and Wave 0 test stubs for Phase 24.
|
|
|
|
Purpose: Foundation layer — all subsequent plans depend on these schema changes and type definitions.
|
|
Output: Three migrations applied, updated Drizzle schemas, shared types, and test scaffolding.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/24-search-history-branching/24-RESEARCH.md
|
|
|
|
@packages/db/src/schema/chat_conversations.ts
|
|
@packages/db/src/schema/chat_messages.ts
|
|
@packages/db/src/schema/index.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
|
|
@server/src/__tests__/chat-service.test.ts
|
|
@server/src/__tests__/chat-routes.test.ts
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: DB migrations and Drizzle schema updates</name>
|
|
<files>
|
|
packages/db/src/migrations/0050_add_branch_columns.sql,
|
|
packages/db/src/migrations/0051_add_message_search_vector.sql,
|
|
packages/db/src/migrations/0052_create_chat_message_bookmarks.sql,
|
|
packages/db/src/migrations/meta/_journal.json,
|
|
packages/db/src/schema/chat_conversations.ts,
|
|
packages/db/src/schema/chat_messages.ts,
|
|
packages/db/src/schema/chat_message_bookmarks.ts,
|
|
packages/db/src/schema/index.ts
|
|
</files>
|
|
<read_first>
|
|
packages/db/src/schema/chat_conversations.ts,
|
|
packages/db/src/schema/chat_messages.ts,
|
|
packages/db/src/schema/index.ts,
|
|
packages/db/src/migrations/meta/_journal.json,
|
|
packages/db/src/schema/companies.ts
|
|
</read_first>
|
|
<action>
|
|
**Migration 0050_add_branch_columns.sql:**
|
|
```sql
|
|
ALTER TABLE "chat_conversations"
|
|
ADD COLUMN "parent_conversation_id" uuid REFERENCES "chat_conversations"("id") ON DELETE SET NULL,
|
|
ADD COLUMN "branch_from_message_id" uuid;
|
|
CREATE INDEX "chat_conversations_parent_idx" ON "chat_conversations" ("parent_conversation_id");
|
|
```
|
|
|
|
**Migration 0051_add_message_search_vector.sql:**
|
|
```sql
|
|
ALTER TABLE "chat_messages"
|
|
ADD COLUMN "content_search" tsvector
|
|
GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED;
|
|
CREATE INDEX "chat_messages_content_search_idx"
|
|
ON "chat_messages" USING GIN ("content_search");
|
|
```
|
|
|
|
**Migration 0052_create_chat_message_bookmarks.sql:**
|
|
```sql
|
|
CREATE TABLE "chat_message_bookmarks" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
"company_id" uuid NOT NULL REFERENCES "companies"("id"),
|
|
"message_id" uuid NOT NULL REFERENCES "chat_messages"("id") ON DELETE CASCADE,
|
|
"conversation_id" uuid NOT NULL REFERENCES "chat_conversations"("id") ON DELETE CASCADE,
|
|
"created_at" timestamp with time zone NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX "chat_bookmarks_company_message_idx" ON "chat_message_bookmarks" ("company_id", "message_id");
|
|
CREATE INDEX "chat_bookmarks_company_conv_idx" ON "chat_message_bookmarks" ("company_id", "conversation_id");
|
|
```
|
|
|
|
**Update _journal.json:** Add entries for idx 50, 51, 52 following existing format (version "7", breakpoints true). Use tags: `0050_add_branch_columns`, `0051_add_message_search_vector`, `0052_create_chat_message_bookmarks`. Use timestamp `1775200000000` for 0050, `+1000` for each subsequent.
|
|
|
|
**Update chat_conversations.ts:** Add two columns after `updatedAt`:
|
|
- `parentConversationId: uuid("parent_conversation_id").references(() => chatConversations.id, { onDelete: "set null" })`
|
|
- `branchFromMessageId: uuid("branch_from_message_id")`
|
|
Add index: `parentIdx: index("chat_conversations_parent_idx").on(table.parentConversationId)`
|
|
|
|
**Update chat_messages.ts:** Do NOT add contentSearch to the Drizzle schema — it is a Postgres generated column referenced only via raw `sql` in queries. Add a comment: `// content_search tsvector column exists in Postgres (generated stored) — queried via sql\`\` only`
|
|
|
|
**Create chat_message_bookmarks.ts:** New schema file following Pattern 3 from RESEARCH.md. Use object-syntax `(table) => ({})` for index callbacks (codebase convention).
|
|
|
|
**Update schema/index.ts:** Add `export { chatMessageBookmarks } from "./chat_message_bookmarks.js";` at the end.
|
|
|
|
**Run migrations:** `pnpm --filter @paperclipai/db db:push` or the project's migration command to apply.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm --filter @paperclipai/db build 2>&1 | tail -5</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "parent_conversation_id" packages/db/src/migrations/0050_add_branch_columns.sql
|
|
- grep -q "content_search" packages/db/src/migrations/0051_add_message_search_vector.sql
|
|
- grep -q "chat_message_bookmarks" packages/db/src/migrations/0052_create_chat_message_bookmarks.sql
|
|
- grep -q "parentConversationId" packages/db/src/schema/chat_conversations.ts
|
|
- grep -q "chatMessageBookmarks" packages/db/src/schema/chat_message_bookmarks.ts
|
|
- grep -q "chatMessageBookmarks" packages/db/src/schema/index.ts
|
|
- grep -q "0050" packages/db/src/migrations/meta/_journal.json
|
|
</acceptance_criteria>
|
|
<done>Three migration SQL files exist, Drizzle schemas updated with branch columns and bookmark table, schema index exports chatMessageBookmarks, db package builds cleanly.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Shared types, validators, and Wave 0 test stubs</name>
|
|
<files>
|
|
packages/shared/src/types/chat.ts,
|
|
packages/shared/src/validators/chat.ts,
|
|
packages/shared/src/index.ts,
|
|
server/src/__tests__/chat-service.test.ts,
|
|
server/src/__tests__/chat-routes.test.ts
|
|
</files>
|
|
<read_first>
|
|
packages/shared/src/types/chat.ts,
|
|
packages/shared/src/validators/chat.ts,
|
|
packages/shared/src/index.ts,
|
|
server/src/__tests__/chat-service.test.ts,
|
|
server/src/__tests__/chat-routes.test.ts
|
|
</read_first>
|
|
<action>
|
|
**Add to packages/shared/src/types/chat.ts:**
|
|
|
|
```typescript
|
|
export interface ChatMessageSearchResult {
|
|
messageId: string;
|
|
conversationId: string;
|
|
conversationTitle: string | null;
|
|
content: string;
|
|
role: "user" | "assistant" | "system";
|
|
agentId: string | null;
|
|
createdAt: string;
|
|
rank: number;
|
|
}
|
|
|
|
export interface ChatMessageSearchResponse {
|
|
items: ChatMessageSearchResult[];
|
|
}
|
|
|
|
export interface ChatBookmark {
|
|
id: string;
|
|
companyId: string;
|
|
messageId: string;
|
|
conversationId: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface ChatBookmarkWithMessage extends ChatBookmark {
|
|
message: ChatMessage;
|
|
conversationTitle: string | null;
|
|
}
|
|
|
|
export interface ChatBookmarkListResponse {
|
|
items: ChatBookmarkWithMessage[];
|
|
}
|
|
|
|
export interface ChatBookmarkToggleResponse {
|
|
bookmarked: boolean;
|
|
}
|
|
```
|
|
|
|
Add `parentConversationId: string | null` and `branchFromMessageId: string | null` to `ChatConversation` interface. Also add them to `ChatConversationListItem`.
|
|
|
|
**Add to packages/shared/src/validators/chat.ts:**
|
|
|
|
```typescript
|
|
export const searchMessagesSchema = z.object({
|
|
q: z.string().min(2).max(200),
|
|
limit: z.coerce.number().int().min(1).max(50).optional(),
|
|
});
|
|
|
|
export const branchConversationSchema = z.object({
|
|
branchFromMessageId: z.string().uuid(),
|
|
});
|
|
|
|
export type SearchMessages = z.infer<typeof searchMessagesSchema>;
|
|
export type BranchConversation = z.infer<typeof branchConversationSchema>;
|
|
```
|
|
|
|
**Update packages/shared/src/index.ts:** Re-export the new types (`ChatMessageSearchResult`, `ChatMessageSearchResponse`, `ChatBookmark`, `ChatBookmarkWithMessage`, `ChatBookmarkListResponse`, `ChatBookmarkToggleResponse`) and validators (`searchMessagesSchema`, `branchConversationSchema`, `SearchMessages`, `BranchConversation`).
|
|
|
|
**Add Wave 0 test stubs to chat-service.test.ts:** Add four new `describe` blocks at the end of the file:
|
|
- `describe("searchMessages", () => { it.todo("returns ranked results for matching term"); it.todo("returns empty for no match"); it.todo("respects companyId scope"); })`
|
|
- `describe("toggleBookmark", () => { it.todo("creates bookmark when not exists"); it.todo("removes bookmark when exists"); })`
|
|
- `describe("branchConversation", () => { it.todo("creates child conversation with copied messages"); it.todo("throws not found for invalid message id"); })`
|
|
- `describe("exportConversation", () => { it.todo("exports as markdown with agent names"); it.todo("exports as JSON with all messages"); })`
|
|
|
|
**Add Wave 0 test stubs to chat-routes.test.ts:** Add four new `describe` blocks:
|
|
- `describe("GET /companies/:id/messages/search", () => { it.todo("returns 200 with search results"); it.todo("returns 400 for short query"); })`
|
|
- `describe("POST /conversations/:id/bookmarks", () => { it.todo("toggles bookmark on/off"); })`
|
|
- `describe("POST /conversations/:id/branch", () => { it.todo("returns 201 with branched conversation"); })`
|
|
- `describe("GET /conversations/:id/export", () => { it.todo("returns markdown file download"); it.todo("returns JSON file download"); })`
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm --filter @paperclipai/shared build 2>&1 | tail -5</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "ChatMessageSearchResult" packages/shared/src/types/chat.ts
|
|
- grep -q "ChatBookmark" packages/shared/src/types/chat.ts
|
|
- grep -q "parentConversationId" packages/shared/src/types/chat.ts
|
|
- grep -q "searchMessagesSchema" packages/shared/src/validators/chat.ts
|
|
- grep -q "branchConversationSchema" packages/shared/src/validators/chat.ts
|
|
- grep -q "ChatMessageSearchResult" packages/shared/src/index.ts
|
|
- grep -q "searchMessages" server/src/__tests__/chat-service.test.ts
|
|
- grep -q "toggleBookmark" server/src/__tests__/chat-service.test.ts
|
|
- grep -q "branchConversation" server/src/__tests__/chat-service.test.ts
|
|
- grep -q "exportConversation" server/src/__tests__/chat-service.test.ts
|
|
</acceptance_criteria>
|
|
<done>Shared types include search result, bookmark, and branch interfaces. Validators include searchMessagesSchema and branchConversationSchema. ChatConversation has parentConversationId + branchFromMessageId. Test stubs exist for all four service methods and four route groups.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `pnpm --filter @paperclipai/db build` passes
|
|
- `pnpm --filter @paperclipai/shared build` passes
|
|
- Migration SQL files contain correct DDL
|
|
- Test stubs are `it.todo()` (not `it.skip()`)
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
Three migration files exist with correct SQL. Drizzle schemas updated. Shared types exported. Wave 0 test stubs in place. Both packages build cleanly.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/24-search-history-branching/24-00-SUMMARY.md`
|
|
</output>
|