# Story 2.1: Ghostwriter Agent & Markdown Generation

Status: done

<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->

## Story

As a user,
I want the system to draft a polished post based on my chat,
So that I can see my raw thoughts transformed into value.

## Acceptance Criteria

1. **Ghostwriter Agent Trigger**
   - Given the user has completed the interview or used "Fast Track"
   - When the "Ghostwriter" agent is triggered
   - Then it consumes the entire chat history and the "Lesson" context
   - And generates a structured Markdown artifact (Title, Body, Tags)

2. **Drafting Animation**
   - Given the generation is processing
   - When the user waits
   - Then they see a distinct "Drafting" animation (different from "Typing")
   - And the tone of the output matches the "Professional/LinkedIn" persona

## Tasks / Subtasks

- [x] Implement Ghostwriter Prompt Engine
  - [x] Create `generateGhostwriterPrompt()` function in `src/lib/llm/prompt-engine.ts`
  - [x] Build prompt structure: Chat history + Intent context + Persona instructions
  - [x] Define output format: Markdown with Title, Body, Tags sections
  - [x] Add constraints: Professional tone, no hallucinations, grounded in user input

- [x] Implement Ghostwriter LLM Service
  - [x] Create `getGhostwriterResponse()` in `src/services/llm-service.ts`
  - [x] Handle streaming response for draft generation
  - [x] Add retry logic for failed generations
  - [x] Return structured Markdown object

- [x] Create Draft State Management
  - [x] Add draft state to ChatStore: `currentDraft`, `isDrafting`
  - [x] Add `generateDraft()` action to trigger Ghostwriter
  - [x] Add `clearDraft()` action for state reset
  - [x] Persist draft to `drafts` table in IndexedDB

- [x] Implement Drafting Indicator
  - [x] Create `DraftingIndicator.tsx` component
  - [x] Use distinct animation (shimmer/skeleton) different from typing indicator
  - [x] Show "Drafting your post..." message with professional tone

- [x] Create Draft Storage Schema
  - [x] Add `drafts` table to Dexie schema in `src/lib/db/index.ts`
  - [x] Define Draft interface: id, sessionId, title, content, tags, createdAt, status
  - [x] Add indexes for querying drafts by session and date

- [x] Integrate Ghostwriter with Chat Service
  - [x] Modify `ChatService` to route to Ghostwriter after interview completion
  - [x] Implement trigger logic: user taps "Draft It" or Fast Track sends input
  - [x] Store generated draft in IndexedDB
  - [x] Update ChatStore with draft result

- [x] Test Ghostwriter End-to-End
  - [x] Unit test: Prompt generation with various chat histories
  - [x] Unit test: Ghostwriter LLM service with mocked responses
  - [x] Integration test: Full flow from chat to draft generation
  - [x] Integration test: Fast Track triggers Ghostwriter directly
  - [x] Edge case: Empty chat history
  - [x] Edge case: Very long chat history (token limits)

## Dev Notes

### Architecture Compliance (CRITICAL)

**Logic Sandwich Pattern - DO NOT VIOLATE:**
- **UI Components** MUST NOT import `src/lib/llm` or `src/services/llm-service.ts` directly
- All Ghostwriter logic MUST go through `ChatService` layer
- ChatService then calls `LLMService` as needed
- Components use Zustand store via atomic selectors only
- Services return plain objects, not Dexie observables

**State Management - Atomic Selectors Required:**
```typescript
// BAD - Causes unnecessary re-renders
const { currentDraft, isDrafting } = useChatStore();

// GOOD - Atomic selectors
const currentDraft = useChatStore(s => s.currentDraft);
const isDrafting = useChatStore(s => s.isDrafting);
const generateDraft = useChatStore(s => s.generateDraft);
```

**Local-First Data Boundary:**
- Generated drafts MUST be stored in IndexedDB (`drafts` table)
- Drafts are the primary artifacts - chat history is the source context
- Drafts persist offline and can be accessed from history view
- No draft content sent to server for storage

**Edge Runtime Constraint:**
- All API routes under `app/api/` must use the Edge Runtime
- The Ghostwriter LLM call goes through `/api/llm` route (same as Teacher)
- Code: `export const runtime = 'edge';`

### Architecture Implementation Details

**Story Purpose:**
This is the FIRST story of Epic 2 ("The Magic Mirror"). It implements the core value proposition: transforming raw chat input into a polished artifact. The Ghostwriter Agent is the "magic" that turns venting into content.

**State Management:**
```typescript
// Add to ChatStore (src/lib/store/chat-store.ts)
interface ChatStore {
  // Draft state
  currentDraft: Draft | null;
  isDrafting: boolean;
  generateDraft: (sessionId: string) => Promise<void>;
  clearDraft: () => void;
}

interface Draft {
  id: string;
  sessionId: string;
  title: string;
  content: string;  // Markdown formatted
  tags: string[];
  createdAt: number;
  status: 'draft' | 'completed' | 'regenerated';
}
```

**Dexie Schema Extensions:**
```typescript
// Add to src/lib/db/schema.ts
db.version(1).stores({
  chatLogs: 'id, sessionId, timestamp, role',
  sessions: 'id, createdAt, updatedAt',
  drafts: 'id, sessionId, createdAt, status'  // NEW table
});

interface DraftRecord {
  id: string;
  sessionId: string;
  title: string;
  content: string;
  tags: string[];
  createdAt: number;
  status: 'draft' | 'completed' | 'regenerated';
}
```

**Logic Flow:**
1. User completes interview OR uses Fast Track
2. ChatService detects "ready to draft" state
3. ChatService calls `LLMService.getGhostwriterResponse(chatHistory, intent)`
4. LLMService streams response through `/api/llm` edge function
5. ChatStore updates `isDrafting` state (shows drafting indicator)
6. On completion, draft stored in IndexedDB and ChatStore updated
7. Draft view UI displays the result (Story 2.2)

**Files to Create:**
- `src/components/features/chat/DraftingIndicator.tsx` - Drafting animation component
- `src/lib/db/draft-service.ts` - Draft CRUD operations (follows Service pattern)

**Files to Modify:**
- `src/lib/db/schema.ts` - Add drafts table
- `src/lib/llm/prompt-engine.ts` - Add `generateGhostwriterPrompt()` function
- `src/services/llm-service.ts` - Add `getGhostwriterResponse()` function
- `src/services/chat-service.ts` - Add draft generation orchestration
- `src/lib/store/chat-store.ts` - Add draft state and actions
- `src/app/api/llm/route.ts` - Handle Ghostwriter requests (extend existing)

### UX Design Specifications

**From UX Design Document:**

**Visual Feedback - Drafting State:**
- Use "Skeleton card loader" (shimmering lines) to show work is happening
- Different from "Teacher is typing..." dots
- Text: "Drafting your post..." or "Polishing your insight..."

**Output Format - The "Magic Moment":**
- The draft should appear as a "Card" or "Article" view (Story 2.2 will implement)
- Use `Merriweather` font (serif) to signal "published work"
- Distinct visual shift from Chat (casual) to Draft (professional)

**Tone and Persona:**
- Ghostwriter should use "Professional/LinkedIn" persona
- Output should be polished but authentic
- Avoid corporate jargon; maintain the user's voice

**Transition Pattern:**
- When drafting completes, the Draft View slides up (Sheet pattern)
- Chat remains visible underneath for context
- This "Split-Personality" UI reinforces the transformation value

### Testing Requirements

**Unit Tests:**
- `PromptEngine`: `generateGhostwriterPrompt()` produces correct structure
- `PromptEngine`: Includes chat history context in prompt
- `PromptEngine`: Handles empty/short chat history gracefully
- `LLMService`: `getGhostwriterResponse()` calls Edge API correctly
- `LLMService`: Handles streaming response with callbacks
- `ChatStore`: `generateDraft()` action updates state correctly
- `ChatStore`: Draft persisted to IndexedDB
- `DraftService`: CRUD operations work correctly

**Integration Tests:**
- Full flow: Chat history -> Draft generation -> Draft stored
- Fast Track flow: Single input -> Draft generation
- Draft state: Draft appears in UI after generation
- Offline scenario: Draft queued if offline (basic handling for now, full sync in Epic 3)

**Edge Cases:**
- Empty chat history: Should return helpful error message
- Very long chat history: Should truncate/summarize within token limits
- LLM API failure: Should show retry option
- Malformed LLM response: Should handle gracefully

**Performance Tests:**
- Draft generation time: < 5 seconds (NFR requirement)
- Drafting indicator appears within 1 second of trigger
- Large chat history (100+ messages): Should handle efficiently

### Previous Story Intelligence (from Epic 1)

**Patterns Established (must follow):**
- **Logic Sandwich Pattern:** UI -> Zustand -> Service -> LLM (strictly enforced)
- **Atomic Selectors:** All state access uses `useChatStore(s => s.field)`
- **Streaming Pattern:** LLM responses use streaming with callbacks (onToken, onComplete)
- **Edge Runtime:** All API routes use `export const runtime = 'edge'`
- **Typing Indicator:** Pattern for showing processing state
- **Intent Detection:** Teacher agent classifies user input (context for Ghostwriter)

**Key Files from Epic 1 (Reference):**
- `src/lib/llm/prompt-engine.ts` - Has `generateTeacherPrompt()`, add Ghostwriter version
- `src/services/llm-service.ts` - Has `getTeacherResponseStream()`, add Ghostwriter version
- `src/app/api/llm/route.ts` - Handles Teacher requests, extend for Ghostwriter
- `src/lib/store/chat-store.ts` - Has chat state, add draft state
- `src/services/chat-service.ts` - Orchestrates chat flow, add draft generation
- `src/lib/db/schema.ts` - Has chatLogs and sessions, add drafts table

**Learnings to Apply:**
- Story 1.4 established Fast Track mode that directly triggers Ghostwriter
- Use `isProcessing` pattern for `isDrafting` state
- Follow streaming callback pattern: `onToken` for building draft incrementally
- Ghostwriter prompt should include intent context from Teacher agent
- Draft generation should use same Edge API proxy as Teacher agent

**Testing Patterns:**
- Epic 1 established 101 passing tests
- Follow same test structure: unit tests for each service, integration tests for full flow
- Use mocked LLM responses for deterministic testing
- Test streaming behavior with callback mocks

### Ghostwriter Prompt Specifications

**Prompt Structure:**
```typescript
function generateGhostwriterPrompt(
  chatHistory: ChatMessage[],
  intent?: 'venting' | 'insight'
): string {
  return `
You are the Ghostwriter Agent. Your role is to transform a user's chat session into a polished, professional post.

CONTEXT:
- User Intent: ${intent || 'unknown'}
- Chat History: ${formatChatHistory(chatHistory)}

REQUIREMENTS:
1. Extract the core insight or lesson from the chat
2. Write in a professional but authentic tone (LinkedIn-style)
3. Structure as Markdown with: Title, Body, Tags
4. DO NOT hallucinate facts - stay grounded in what the user shared
5. Focus on the "transformation" - how the user's thinking evolved
6. If it was a struggle, frame it as a learning opportunity
7. Keep it concise (300-500 words for the body)

OUTPUT FORMAT:
\`\`\`markdown
# [Compelling Title]

[2-4 paragraphs that tell the story of the insight]

**Tags:** [3-5 relevant tags]
\`\`\`
`;
}
```

**Prompt Engineering Notes:**
- The prompt should emphasize "grounded in user input" to prevent hallucinations
- For "venting" intent, focus on "reframing struggle as lesson"
- For "insight" intent, focus on "articulating the breakthrough"
- Include the full chat history as context
- Title generation is critical - should be catchy but authentic

### Data Schema Specifications

**Dexie Schema - Drafts Table:**
```typescript
// Add to src/lib/db/schema.ts
db.version(1).stores({
  chatLogs: 'id, sessionId, timestamp, role, intent',
  sessions: 'id, createdAt, updatedAt, isFastTrackMode, currentIntent',
  drafts: 'id, sessionId, createdAt, status'  // NEW
});

export interface DraftRecord {
  id: string;
  sessionId: string;
  title: string;
  content: string;      // Markdown formatted
  tags: string[];       // Array of tag strings
  createdAt: number;
  status: 'draft' | 'completed' | 'regenerated';
}
```

**Session-Draft Relationship:**
- Each draft is linked to a session via `sessionId`
- A session can have multiple drafts (regenerations)
- The latest draft for a session is shown in history
- Status tracks draft lifecycle: draft -> completed (user approved) or regenerated

### Performance Requirements

**NFR-01 Compliance (Generation Latency):**
- Draft generation should complete in < 5 seconds total
- First token should appear within 3 seconds
- Streaming should show progressive build-up of the draft

**NFR-06 Compliance (Data Persistence):**
- Draft must be auto-saved to IndexedDB immediately on completion
- No data loss if user closes app during generation
- Drafts persist offline and can be accessed from history

**State Updates:**
- `isDrafting` state should update immediately on trigger
- Draft content should stream into UI as tokens arrive
- Draft should be queryable from history view immediately after completion

### Security & Privacy Requirements

**NFR-03 & NFR-04 Compliance:**
- User content sent to LLM API for inference only (not training)
- No draft content stored on server
- Drafts stored 100% client-side in IndexedDB
- API keys hidden via Edge Function proxy

**Content Safety:**
- Ghostwriter prompt should include guardrails against:
  - Toxic or offensive content
  - Factually incorrect technical claims
  - Overly promotional language
- If LLM generates concerning content, flag for user review

### Project Structure Notes

**Following Feature-First Lite Pattern:**
- New component: `src/components/features/chat/DraftingIndicator.tsx`
- New service: `src/lib/db/draft-service.ts` (could also be in `src/services/`)
- Store updates: `src/lib/store/chat-store.ts`
- Schema updates: `src/lib/db/schema.ts`

**Alignment with Unified Project Structure:**
- All feature code under `src/components/features/`
- Services orchestrate logic, don't touch DB directly from UI
- State managed centrally in Zustand stores
- Database schema versioned properly with Dexie

**No Conflicts Detected:**
- Ghostwriter fits cleanly into existing architecture
- Drafts table is new, no migration conflicts
- Extends existing LLM service pattern

### References

**Epic Reference:**
- [Epic 2: "The Magic Mirror" - Ghostwriter & Draft Refinement](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/epics.md#epic-2-the-magic-mirror---ghostwriter--draft-refinement)
- [Story 2.1: Ghostwriter Agent & Markdown Generation](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/epics.md#story-21-ghostwriter-agent--markdown-generation)
- FR-03: "Ghostwriter Agent can transform the structured interview data into a grammatically correct and structured 'Enlightenment' artifact"

**Architecture Documents:**
- [Project Context: Logic Sandwich](file:///home/maximilienmao/Projects/Test01/_bmad-output/project-context.md#1-the-logic-sandwich-pattern-service-layer)
- [Project Context: State Management](file:///home/maximilienmao/Projects/Test01/_bmad-output/project-context.md#2-state-management-zustand)
- [Project Context: Local-First Boundary](file:///home/maximilienmao/Projects/Test01/_bmad-output/project-context.md#3-local-first-data-boundary)
- [Architecture: Service Boundaries](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/architecture.md#architectural-boundaries)
- [Architecture: Data Architecture](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/architecture.md#data-architecture)

**UX Design Specifications:**
- [UX: Experience Mechanics - The Magic](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#2-experience-mechanics)
- [UX: Typography System](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#typography-system)
- [UX: Component Strategy - DraftCard](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md#component-strategy)

**PRD Requirements:**
- [PRD: Dual-Agent Pipeline](file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/prd.md#dual-agent-pipeline-core-innovation)
- FR-03: "Ghostwriter Agent can transform the structured interview data into a grammatically correct and structured 'Enlightenment' artifact"
- NFR-01: "< 3 seconds for first token, < 5 seconds total generation"

**Previous Stories:**
- [Story 1.4: Fast Track Mode](file:///home/maximilienmao/Projects/Test01/_bmad-output/implementation-artifacts/1-4-fast-track-mode.md) - Fast Track directly triggers Ghostwriter

## Dev Agent Record

### Agent Model Used

Claude Opus 4.5 (model ID: 'claude-opus-4-5-20251101')

### Debug Log References

Session file: `/tmp/claude/-home-maximilienmao-Projects-Test01/e83dd24d-bb58-4fba-ac25-3628cdeae3e8/scratchpad`

### Completion Notes List

**Story Analysis Completed:**
- Extracted story requirements from Epic 2, Story 2.1
- Analyzed previous Epic 1 stories for established patterns
- Reviewed architecture for compliance requirements (Logic Sandwich, State Management, Local-First)
- Reviewed UX specification for visual design and interaction patterns
- Identified all files to create and modify

**Implementation Completed:**
All tasks and subtasks have been implemented:

1. **Ghostwriter Prompt Engine** (`src/lib/llm/prompt-engine.ts`)
   - Added `generateGhostwriterPrompt()` function with chat history context, intent-specific guidance
   - Prompt enforces: professional LinkedIn tone, no hallucinations, grounded in user input
   - Output format: Markdown with Title, Body, Tags sections
   - 21 new tests added for prompt generation (all passing)

2. **Ghostwriter LLM Service** (`src/services/llm-service.ts`)
   - Added `getGhostwriterResponse()` for non-streaming draft generation
   - Added `getGhostwriterResponseStream()` for streaming draft generation
   - Includes retry logic and comprehensive error handling
   - Returns structured Draft object with Markdown content
   - 13 new tests added (all passing)

3. **Draft State Management** (`src/lib/store/chat-store.ts`)
   - Added `currentDraft`, `isDrafting` state to ChatStore
   - Added `generateDraft()` action to trigger Ghostwriter
   - Added `clearDraft()` action for state reset
   - Drafts automatically persisted to IndexedDB via DraftService
   - Follows atomic selector pattern for state access

4. **Drafting Indicator** (`src/components/features/chat/DraftingIndicator.tsx`)
   - Created component with shimmer/pulse animation
   - Distinct from typing indicator (dots vs skeleton)
   - Shows "Drafting your post..." message

5. **Draft Storage Schema** (`src/lib/db/index.ts`)
   - Added `DraftRecord` interface with all required fields
   - Added `drafts` table to Dexie database with indexes
   - Database version 1 with proper schema definition

6. **ChatService Integration** (`src/services/chat-service.ts`)
   - Added `generateGhostwriterDraft()` method
   - Orchestrates Ghostwriter LLM service and DraftService
   - Handles title/content parsing from Markdown
   - Returns structured response with draft ID

7. **Fast Track Integration** (`src/lib/store/chat-store.ts`)
   - Fast Track mode now triggers Ghostwriter Agent
   - Generates actual draft instead of placeholder response
   - Draft saved to IndexedDB and loaded into currentDraft state

**Key Technical Decisions:**
1. **Prompt Engineering:** Ghostwriter prompt structure with chat history context, output format requirements, hallucination guardrails
2. **State Management:** Add draft state to ChatStore following atomic selector pattern
3. **Data Schema:** New `drafts` table in IndexedDB with proper indexing
4. **Service Pattern:** DraftService for CRUD operations (follows established pattern)
5. **Streaming:** Use same streaming pattern as Teacher agent for draft generation
6. **Fast Track:** Now calls Ghostwriter instead of returning placeholder (Epic 2 integration)

**Dependencies:**
- No new dependencies required
- Reuses existing Zustand, Dexie, LLM service infrastructure
- Extends existing prompt engine and LLM service

**Integration Points:**
- Connected to existing ChatStore state management
- Ghostwriter triggered by ChatService after interview completion or Fast Track
- Reuses Edge API proxy (`/api/llm`) for LLM calls
- Draft stored in IndexedDB for history access (Epic 3)

**Testing Summary:**
- 146 tests passing (all tests passing)
- All Ghostwriter functionality tested with unit and integration tests
- Error handling tested for timeout, rate limit, network errors
- Edge cases tested: empty history, long history, undefined intent
- Fast Track integration test updated to mock ChatService.generateGhostwriterDraft

**Behavior Changes:**
- Fast Track mode (Story 1.4) now triggers Ghostwriter Agent
- Returns actual draft instead of placeholder response
- Integration test updated to mock ChatService.generateGhostwriterDraft and DraftService.getDraftBySessionId

### File List

**New Files Created:**
- `src/lib/db/draft-service.ts` - Draft CRUD operations service
- `src/lib/db/draft-service.test.ts` - Draft service tests (11 tests)
- `src/components/features/chat/DraftingIndicator.tsx` - Drafting animation component

**Files Modified:**
- `src/lib/db/index.ts` - Added DraftRecord interface and drafts table to Dexie schema
- `src/lib/llm/prompt-engine.ts` - Added `generateGhostwriterPrompt()` function with intent-specific guidance
- `src/services/llm-service.ts` - Added Ghostwriter methods with streaming and error handling
- `src/services/chat-service.ts` - Added `generateGhostwriterDraft()` orchestration method
- `src/lib/store/chat-store.ts` - Added draft state, generateDraft/clearDraft actions, Fast Track integration
- `src/lib/llm/prompt-engine.test.ts` - Added 21 Ghostwriter prompt tests
- `src/services/llm-service.test.ts` - Added 13 Ghostwriter service tests
- `src/integration/fast-track.test.ts` - Updated Fast Track test to mock Ghostwriter services
