dopple-ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +412 -0
- package/dist/chunk-FA7ZWJOA.js +365 -0
- package/dist/chunk-FA7ZWJOA.js.map +1 -0
- package/dist/chunk-KEWXLWAO.js +247 -0
- package/dist/chunk-KEWXLWAO.js.map +1 -0
- package/dist/chunk-PGZVVIL6.js +249 -0
- package/dist/chunk-PGZVVIL6.js.map +1 -0
- package/dist/chunk-QR5GEK27.js +302 -0
- package/dist/chunk-QR5GEK27.js.map +1 -0
- package/dist/chunk-RXD7VZ7P.js +193 -0
- package/dist/chunk-RXD7VZ7P.js.map +1 -0
- package/dist/chunk-XETRT4X6.js +300 -0
- package/dist/chunk-XETRT4X6.js.map +1 -0
- package/dist/cli.js +4603 -0
- package/dist/cli.js.map +1 -0
- package/dist/figma-N554M5KW.js +107 -0
- package/dist/figma-N554M5KW.js.map +1 -0
- package/dist/graph-P5GYGDF7.js +9 -0
- package/dist/graph-P5GYGDF7.js.map +1 -0
- package/dist/index.d.ts +2056 -0
- package/dist/index.js +4543 -0
- package/dist/index.js.map +1 -0
- package/dist/ocean-BIG4XCMX.js +17 -0
- package/dist/ocean-BIG4XCMX.js.map +1 -0
- package/dist/ocean-SIPS4NY7.js +18 -0
- package/dist/ocean-SIPS4NY7.js.map +1 -0
- package/dist/provider-7PWDG74H.js +16 -0
- package/dist/provider-7PWDG74H.js.map +1 -0
- package/dist/query-GEL76KSF.js +16 -0
- package/dist/query-GEL76KSF.js.map +1 -0
- package/dist/query-ZZJQOTD6.js +15 -0
- package/dist/query-ZZJQOTD6.js.map +1 -0
- package/dist/review-DNYYHU2M.js +77 -0
- package/dist/review-DNYYHU2M.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/llm/provider.ts","../src/traits/compiler.ts","../src/memory/memory.ts","../src/persona/persona.ts","../src/persona/generator.ts","../src/groups/segment.ts","../src/validation/validate.ts","../src/adapters/base.ts","../src/storage/store.ts","../src/traces/store.ts","../src/survey/survey.ts","../src/simulation/focus-group.ts","../src/graph/graph.ts","../src/graph/builder.ts","../src/insights/analyzer.ts","../src/insights/insights.ts","../src/calibration/extract.ts","../src/calibration/test.ts","../src/calibration/calibrate.ts","../src/calibration/structured.ts","../src/adapters/posthog.ts","../src/adapters/csv.ts","../src/adapters/context.ts","../src/adapters/stripe.ts","../src/adapters/document.ts","../src/adapters/amplitude.ts","../src/adapters/mixpanel.ts","../src/adapters/hubspot.ts","../src/adapters/intercom.ts","../src/engine.ts","../src/cli/format.ts","../src/integrations/figma.ts","../src/integrations/slack.ts","../src/integrations/review.ts"],"sourcesContent":["/**\n * Multi-model LLM provider.\n *\n * Uses \"provider/model\" format (like LiteLLM):\n * \"anthropic/claude-sonnet-4-20250514\"\n * \"openai/gpt-4o\"\n * \"google/gemini-2.5-pro\"\n * \"ollama/llama3\"\n *\n * Each provider maps to a compatible API endpoint. All providers use the\n * OpenAI-compatible chat completions format where possible.\n */\n\nimport type { LLMProvider } from \"../types.js\";\n\ninterface ProviderSpec {\n name: string;\n baseUrl: string;\n apiKeyEnv: string;\n /** Whether this provider uses OpenAI-compatible API */\n openaiCompat: boolean;\n}\n\nconst PROVIDERS: Record<string, ProviderSpec> = {\n anthropic: {\n name: \"Anthropic\",\n baseUrl: \"https://api.anthropic.com\",\n apiKeyEnv: \"ANTHROPIC_API_KEY\",\n openaiCompat: false,\n },\n openai: {\n name: \"OpenAI\",\n baseUrl: \"https://api.openai.com/v1\",\n apiKeyEnv: \"OPENAI_API_KEY\",\n openaiCompat: true,\n },\n google: {\n name: \"Google\",\n baseUrl: \"https://generativelanguage.googleapis.com/v1beta\",\n apiKeyEnv: \"GOOGLE_API_KEY\",\n openaiCompat: false,\n },\n groq: {\n name: \"Groq\",\n baseUrl: \"https://api.groq.com/openai/v1\",\n apiKeyEnv: \"GROQ_API_KEY\",\n openaiCompat: true,\n },\n together: {\n name: \"Together\",\n baseUrl: \"https://api.together.xyz/v1\",\n apiKeyEnv: \"TOGETHER_API_KEY\",\n openaiCompat: true,\n },\n fireworks: {\n name: \"Fireworks\",\n baseUrl: \"https://api.fireworks.ai/inference/v1\",\n apiKeyEnv: \"FIREWORKS_API_KEY\",\n openaiCompat: true,\n },\n ollama: {\n name: \"Ollama\",\n baseUrl: \"http://localhost:11434/v1\",\n apiKeyEnv: \"\",\n openaiCompat: true,\n },\n openrouter: {\n name: \"OpenRouter\",\n baseUrl: \"https://openrouter.ai/api/v1\",\n apiKeyEnv: \"OPENROUTER_API_KEY\",\n openaiCompat: true,\n },\n};\n\n/**\n * Parse a \"provider/model\" string.\n * If no provider prefix, defaults to \"anthropic\".\n */\nexport function parseModelId(modelId: string): {\n provider: string;\n model: string;\n} {\n const slash = modelId.indexOf(\"/\");\n if (slash === -1) {\n return { provider: \"anthropic\", model: modelId };\n }\n return {\n provider: modelId.slice(0, slash),\n model: modelId.slice(slash + 1),\n };\n}\n\n/**\n * Create an LLM provider from a \"provider/model\" string.\n */\nexport function createProvider(\n modelId?: string,\n apiKey?: string,\n baseUrl?: string\n): LLMProvider {\n const { provider, model } = parseModelId(\n modelId ?? \"anthropic/claude-sonnet-4-20250514\"\n );\n\n const spec = PROVIDERS[provider];\n if (!spec) {\n throw new Error(\n `Unknown provider: \"${provider}\". Available: ${Object.keys(PROVIDERS).join(\", \")}`\n );\n }\n\n const resolvedKey =\n apiKey ?? (spec.apiKeyEnv ? process.env[spec.apiKeyEnv] : undefined) ?? \"\";\n const resolvedBaseUrl = baseUrl ?? spec.baseUrl;\n\n if (provider === \"anthropic\") {\n return new AnthropicProvider(model, resolvedKey, resolvedBaseUrl);\n }\n\n // Everything else uses OpenAI-compatible API\n return new OpenAICompatProvider(\n provider,\n model,\n resolvedKey,\n resolvedBaseUrl\n );\n}\n\n// ---------------------------------------------------------------------------\n// Anthropic provider (native API)\n// ---------------------------------------------------------------------------\n\nexport class AnthropicProvider implements LLMProvider {\n readonly providerId: string;\n private model: string;\n private apiKey: string;\n private baseUrl: string;\n\n constructor(model: string, apiKey: string, baseUrl: string) {\n this.providerId = `anthropic/${model}`;\n this.model = model;\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n }\n\n async generate(systemPrompt: string, userPrompt: string): Promise<string> {\n const res = await fetch(`${this.baseUrl}/v1/messages`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n body: JSON.stringify({\n model: this.model,\n max_tokens: 2048,\n system: systemPrompt,\n messages: [{ role: \"user\", content: userPrompt }],\n }),\n });\n\n if (!res.ok) {\n const err = await res.text();\n throw new Error(`Anthropic API error ${res.status}: ${err}`);\n }\n\n const data = (await res.json()) as {\n content: Array<{ type: string; text: string }>;\n };\n const block = data.content[0];\n if (block?.type === \"text\") return block.text;\n throw new Error(\"Unexpected response format from Anthropic API\");\n }\n\n async generateJSON<T>(\n systemPrompt: string,\n userPrompt: string\n ): Promise<T> {\n const raw = await this.generate(\n systemPrompt +\n \"\\n\\nIMPORTANT: Respond with valid JSON only. No markdown, no code fences, no explanation.\",\n userPrompt\n );\n return parseJSONResponse<T>(raw);\n }\n}\n\n// ---------------------------------------------------------------------------\n// OpenAI-compatible provider (works with OpenAI, Groq, Together, Ollama, etc.)\n// ---------------------------------------------------------------------------\n\nexport class OpenAICompatProvider implements LLMProvider {\n readonly providerId: string;\n private model: string;\n private apiKey: string;\n private baseUrl: string;\n\n constructor(\n provider: string,\n model: string,\n apiKey: string,\n baseUrl: string\n ) {\n this.providerId = `${provider}/${model}`;\n this.model = model;\n this.apiKey = apiKey;\n this.baseUrl = baseUrl;\n }\n\n async generate(systemPrompt: string, userPrompt: string): Promise<string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (this.apiKey) {\n headers[\"Authorization\"] = `Bearer ${this.apiKey}`;\n }\n\n const res = await fetch(`${this.baseUrl}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n model: this.model,\n messages: [\n { role: \"system\", content: systemPrompt },\n { role: \"user\", content: userPrompt },\n ],\n max_tokens: 2048,\n }),\n });\n\n if (!res.ok) {\n const err = await res.text();\n throw new Error(`${this.providerId} API error ${res.status}: ${err}`);\n }\n\n const data = (await res.json()) as {\n choices: Array<{ message: { content: string } }>;\n };\n return data.choices[0]?.message?.content ?? \"\";\n }\n\n async generateJSON<T>(\n systemPrompt: string,\n userPrompt: string\n ): Promise<T> {\n const raw = await this.generate(\n systemPrompt +\n \"\\n\\nIMPORTANT: Respond with valid JSON only. No markdown, no code fences, no explanation.\",\n userPrompt\n );\n return parseJSONResponse<T>(raw);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Shared\n// ---------------------------------------------------------------------------\n\nfunction parseJSONResponse<T>(raw: string): T {\n const cleaned = raw\n .replace(/^```json?\\s*/i, \"\")\n .replace(/```\\s*$/, \"\")\n .trim();\n return JSON.parse(cleaned) as T;\n}\n\n/** List all supported providers. */\nexport function listProviders(): Array<{\n id: string;\n name: string;\n envVar: string;\n}> {\n return Object.entries(PROVIDERS).map(([id, spec]) => ({\n id,\n name: spec.name,\n envVar: spec.apiKeyEnv,\n }));\n}\n","/**\n * Trait vector → system prompt compiler.\n *\n * Takes a persona definition and compiles it into a deterministic LLM system\n * prompt. Same trait vector + demographics = same prompt every time.\n */\n\nimport type { PersonaDefinition, OceanTrait } from \"../types.js\";\nimport { TRAIT_PROFILES } from \"./ocean.js\";\n\n/**\n * Describe a trait score as a human-readable intensity.\n */\nfunction describeTraitLevel(score: number): string {\n if (score >= 0.85) return \"very high\";\n if (score >= 0.65) return \"high\";\n if (score >= 0.45) return \"moderate\";\n if (score >= 0.25) return \"low\";\n return \"very low\";\n}\n\n/**\n * Compile a persona definition into a system prompt for an LLM.\n *\n * The prompt is deterministic: same inputs → same prompt.\n */\nexport function compilePersonaPrompt(persona: PersonaDefinition): string {\n const sections: string[] = [];\n\n // Identity\n sections.push(`You are ${persona.name}.`);\n\n // Demographics\n const demo = persona.demographics;\n const demoLines: string[] = [];\n if (demo.age) demoLines.push(`${demo.age} years old`);\n if (demo.gender) demoLines.push(demo.gender);\n if (demo.location) demoLines.push(`lives in ${demo.location}`);\n if (demo.occupation) demoLines.push(`works as ${demo.occupation}`);\n if (demo.income) demoLines.push(`income: ${demo.income}`);\n if (demo.education) demoLines.push(`education: ${demo.education}`);\n if (demoLines.length > 0) {\n sections.push(`Demographics: ${demoLines.join(\", \")}.`);\n }\n\n // Personality traits — compiled from OCEAN vector\n const traitNames: OceanTrait[] = [\n \"openness\",\n \"conscientiousness\",\n \"extraversion\",\n \"agreeableness\",\n \"neuroticism\",\n ];\n\n const traitDesc: string[] = [];\n for (const t of traitNames) {\n const score = persona.traits[t];\n const level = describeTraitLevel(score);\n const profile = TRAIT_PROFILES[t];\n const behaviors =\n score >= 0.5 ? profile.highBehaviors : profile.lowBehaviors;\n // Pick top 2 behaviors for the prompt\n const topBehaviors = behaviors.slice(0, 2).join(\"; \");\n traitDesc.push(\n `- ${t} (${level}, ${score.toFixed(2)}): ${topBehaviors}`\n );\n }\n sections.push(`Personality (Big Five / OCEAN):\\n${traitDesc.join(\"\\n\")}`);\n\n // Behavioral rules\n const bp = persona.behavioralProfile;\n sections.push(\n `Behavioral tendencies:\\n${bp.rules.map((r) => `- ${r}`).join(\"\\n\")}`\n );\n\n // Product context\n if (persona.context.product) {\n sections.push(\n `You are being asked about: ${persona.context.product}. Answer from your genuine perspective as a user/potential user of this product.`\n );\n }\n\n // Grounding data\n if (\n persona.context.behaviorData &&\n persona.context.behaviorData.length > 0\n ) {\n sections.push(\n `Real behavioral data about users like you:\\n${persona.context.behaviorData.map((d) => `- ${d}`).join(\"\\n\")}`\n );\n }\n\n if (persona.context.supportData && persona.context.supportData.length > 0) {\n sections.push(\n `Real support conversations from users like you:\\n${persona.context.supportData.map((d) => `- ${d}`).join(\"\\n\")}`\n );\n }\n\n if (persona.context.paymentData && persona.context.paymentData.length > 0) {\n sections.push(\n `Real payment behavior from users like you:\\n${persona.context.paymentData.map((d) => `- ${d}`).join(\"\\n\")}`\n );\n }\n\n if (\n persona.context.freeformContext &&\n persona.context.freeformContext.length > 0\n ) {\n sections.push(\n `Additional context:\\n${persona.context.freeformContext.map((d) => `- ${d}`).join(\"\\n\")}`\n );\n }\n\n // Instructions\n sections.push(\n [\n \"INSTRUCTIONS:\",\n \"- Respond authentically as this person. Your personality traits should shape HOW you respond.\",\n \"- Be specific and concrete — mention real details, not generic platitudes.\",\n \"- If you would not care about something, say so directly.\",\n \"- If you would be excited about something, show genuine enthusiasm.\",\n \"- If you would be confused or skeptical, express that.\",\n \"- Do NOT break character. Do NOT mention that you are an AI or a persona.\",\n \"- Keep responses concise (2-4 sentences) unless asked for detail.\",\n `- Your confidence level is ${persona.confidence.toUpperCase()}. ${\n persona.confidence === \"low\"\n ? \"Your responses are based on general knowledge, not real user data.\"\n : persona.confidence === \"medium\"\n ? \"Your responses are partially grounded in real behavioral data.\"\n : \"Your responses are grounded in real user data from multiple sources.\"\n }`,\n ].join(\"\\n\")\n );\n\n return sections.join(\"\\n\\n\");\n}\n","/**\n * Persona memory system.\n *\n * Three layers, each solving a different consistency problem:\n *\n * 1. Fact Store — immutable ground truths from data adapters.\n * \"34% churn rate after price increase\" can't be contradicted.\n *\n * 2. Episodic Memory — what the persona said in past sessions.\n * Prevents flip-flopping on the same question across days.\n *\n * 3. Stance Tracker — distilled positions on specific topics.\n * Compact summary: \"AGAINST price increases, FOR dark mode.\"\n * Derived from episodic memory, loaded into every prompt.\n *\n * All three persist to .dopple/memory/<persona-id>/ as JSONL.\n */\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { LLMProvider, SourceCitation } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface Fact {\n /** Where this fact came from: \"posthog\", \"stripe\", \"intercom\", \"csv\" */\n source: string;\n /** The fact itself */\n content: string;\n /** When this fact was recorded */\n recordedAt: string;\n /** How important this fact is (0-1). Higher = always included in prompt. */\n salience: number;\n}\n\nexport interface Episode {\n question: string;\n answer: string;\n /** Topic tags for retrieval */\n topics: string[];\n timestamp: string;\n}\n\nexport type StancePosition = \"strongly_for\" | \"for\" | \"neutral\" | \"against\" | \"strongly_against\";\n\nexport interface Stance {\n topic: string;\n position: StancePosition;\n /** The reasoning behind this stance */\n reason: string;\n /** How many episodes support this stance */\n evidence: number;\n lastUpdated: string;\n}\n\n// ---------------------------------------------------------------------------\n// PersonaMemory\n// ---------------------------------------------------------------------------\n\nexport class PersonaMemory {\n readonly personaId: string;\n private baseDir: string;\n private facts: Fact[] = [];\n private episodes: Episode[] = [];\n private stances: Stance[] = [];\n private loaded = false;\n\n constructor(personaId: string, storageDir?: string) {\n this.personaId = personaId;\n this.baseDir = join(\n storageDir ?? join(process.cwd(), \".dopple\"),\n \"memory\",\n personaId\n );\n }\n\n // -------------------------------------------------------------------------\n // Fact Store\n // -------------------------------------------------------------------------\n\n /**\n * Add a ground truth fact from a data source.\n * Facts are immutable — they represent real data, not opinions.\n */\n addFact(source: string, content: string, salience: number = 0.5): void {\n this.facts.push({\n source,\n content,\n recordedAt: new Date().toISOString(),\n salience: Math.max(0, Math.min(1, salience)),\n });\n }\n\n /**\n * Add multiple facts from adapter data.\n */\n addFacts(facts: Array<{ source: string; content: string; salience?: number }>): void {\n for (const f of facts) {\n this.addFact(f.source, f.content, f.salience);\n }\n }\n\n /**\n * Get facts above a salience threshold (default: include all).\n */\n getFacts(minSalience: number = 0): Fact[] {\n return this.facts\n .filter((f) => f.salience >= minSalience)\n .sort((a, b) => b.salience - a.salience);\n }\n\n // -------------------------------------------------------------------------\n // Episodic Memory\n // -------------------------------------------------------------------------\n\n /**\n * Record a conversation exchange.\n */\n addEpisode(question: string, answer: string, topics: string[] = []): void {\n this.episodes.push({\n question,\n answer,\n topics,\n timestamp: new Date().toISOString(),\n });\n }\n\n /**\n * Retrieve relevant past episodes.\n *\n * Retrieval strategy: topic match > recency.\n * Returns the most relevant episodes up to `limit`.\n */\n recall(query: string, limit: number = 5): Episode[] {\n if (this.episodes.length === 0) return [];\n\n // Simple keyword matching for topic relevance\n const queryWords = new Set(\n query.toLowerCase().split(/\\W+/).filter((w) => w.length > 3)\n );\n\n const scored = this.episodes.map((ep) => {\n // Topic relevance\n const epWords = new Set([\n ...ep.question.toLowerCase().split(/\\W+/).filter((w) => w.length > 3),\n ...ep.topics.map((t) => t.toLowerCase()),\n ]);\n const overlap = [...queryWords].filter((w) => epWords.has(w)).length;\n const topicScore = queryWords.size > 0 ? overlap / queryWords.size : 0;\n\n // Recency score (exponential decay over 30 days)\n const ageMs = Date.now() - new Date(ep.timestamp).getTime();\n const ageDays = ageMs / (1000 * 60 * 60 * 24);\n const recencyScore = Math.exp(-ageDays / 30);\n\n return { episode: ep, score: topicScore * 0.7 + recencyScore * 0.3 };\n });\n\n return scored\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map((s) => s.episode);\n }\n\n /**\n * Get all episodes (chronological).\n */\n getAllEpisodes(): Episode[] {\n return [...this.episodes];\n }\n\n // -------------------------------------------------------------------------\n // Stance Tracker\n // -------------------------------------------------------------------------\n\n /**\n * Update or create a stance on a topic.\n */\n setStance(topic: string, position: StancePosition, reason: string): void {\n const existing = this.stances.find(\n (s) => s.topic.toLowerCase() === topic.toLowerCase()\n );\n\n if (existing) {\n existing.position = position;\n existing.reason = reason;\n existing.evidence++;\n existing.lastUpdated = new Date().toISOString();\n } else {\n this.stances.push({\n topic,\n position,\n reason,\n evidence: 1,\n lastUpdated: new Date().toISOString(),\n });\n }\n }\n\n /**\n * Get all stances.\n */\n getStances(): Stance[] {\n return [...this.stances];\n }\n\n /**\n * Get stance on a specific topic (or null if no stance).\n */\n getStance(topic: string): Stance | null {\n return (\n this.stances.find(\n (s) => s.topic.toLowerCase() === topic.toLowerCase()\n ) ?? null\n );\n }\n\n /**\n * Auto-derive stances from episodic memory using an LLM.\n */\n async deriveStances(llm: LLMProvider): Promise<Stance[]> {\n if (this.episodes.length < 3) return this.stances;\n\n const episodeSummary = this.episodes\n .slice(-20) // last 20 episodes\n .map((e) => `Q: ${e.question}\\nA: ${e.answer}`)\n .join(\"\\n\\n\");\n\n const derived = await llm.generateJSON<\n Array<{ topic: string; position: StancePosition; reason: string }>\n >(\n \"You analyze conversation history to identify consistent positions/stances a person holds.\",\n `Based on these past conversations, identify the key topics this person has taken a clear position on.\n\n${episodeSummary}\n\nReturn a JSON array of stances:\n[{\"topic\": \"<topic>\", \"position\": \"strongly_for\" | \"for\" | \"neutral\" | \"against\" | \"strongly_against\", \"reason\": \"<why they hold this position>\"}]\n\nOnly include topics where the person has expressed a clear, consistent position. If they've been ambiguous or contradictory, mark as \"neutral\".`\n );\n\n for (const d of derived) {\n this.setStance(d.topic, d.position, d.reason);\n }\n\n return this.stances;\n }\n\n // -------------------------------------------------------------------------\n // Prompt compilation\n // -------------------------------------------------------------------------\n\n /**\n * Compile memory into prompt segments to inject into the persona's system prompt.\n * This is the main integration point — call this before every LLM query.\n */\n compileForPrompt(query: string): string {\n const sections: string[] = [];\n\n // High-salience facts (always included)\n const importantFacts = this.getFacts(0.5);\n if (importantFacts.length > 0) {\n sections.push(\n `GROUND TRUTH FACTS (you must not contradict these):\\n${importantFacts.map((f) => `- [${f.source}] ${f.content}`).join(\"\\n\")}`\n );\n }\n\n // Relevant past episodes\n const relevant = this.recall(query, 3);\n if (relevant.length > 0) {\n sections.push(\n `YOUR PAST STATEMENTS (be consistent with these):\\n${relevant.map((e) => `- When asked \"${e.question}\", you said: \"${e.answer}\"`).join(\"\\n\")}`\n );\n }\n\n // Active stances\n if (this.stances.length > 0) {\n const stanceLines = this.stances.map((s) => {\n const emoji =\n s.position === \"strongly_for\" || s.position === \"for\"\n ? \"FOR\"\n : s.position === \"against\" || s.position === \"strongly_against\"\n ? \"AGAINST\"\n : \"NEUTRAL\";\n return `- ${s.topic}: ${emoji} — ${s.reason}`;\n });\n sections.push(`YOUR KNOWN POSITIONS:\\n${stanceLines.join(\"\\n\")}`);\n }\n\n return sections.length > 0 ? \"\\n\\n\" + sections.join(\"\\n\\n\") : \"\";\n }\n\n // -------------------------------------------------------------------------\n // Persistence (JSONL)\n // -------------------------------------------------------------------------\n\n async save(): Promise<void> {\n await mkdir(this.baseDir, { recursive: true });\n\n await writeFile(\n join(this.baseDir, \"facts.jsonl\"),\n this.facts.map((f) => JSON.stringify(f)).join(\"\\n\") + \"\\n\"\n );\n\n await writeFile(\n join(this.baseDir, \"episodes.jsonl\"),\n this.episodes.map((e) => JSON.stringify(e)).join(\"\\n\") + \"\\n\"\n );\n\n await writeFile(\n join(this.baseDir, \"stances.jsonl\"),\n this.stances.map((s) => JSON.stringify(s)).join(\"\\n\") + \"\\n\"\n );\n }\n\n async load(): Promise<void> {\n if (this.loaded) return;\n\n this.facts = await loadJsonl<Fact>(join(this.baseDir, \"facts.jsonl\"));\n this.episodes = await loadJsonl<Episode>(\n join(this.baseDir, \"episodes.jsonl\")\n );\n this.stances = await loadJsonl<Stance>(\n join(this.baseDir, \"stances.jsonl\")\n );\n this.loaded = true;\n }\n\n /**\n * Stats about this persona's memory.\n */\n stats(): { facts: number; episodes: number; stances: number } {\n return {\n facts: this.facts.length,\n episodes: this.episodes.length,\n stances: this.stances.length,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function loadJsonl<T>(path: string): Promise<T[]> {\n try {\n const content = await readFile(path, \"utf-8\");\n return content\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line) as T);\n } catch {\n return [];\n }\n}\n","/**\n * Persona class — the core interactive object.\n *\n * A persona is a mathematical object (trait vector + demographics + context)\n * that can be queried via an LLM. All responses are constrained by the trait\n * vector and grounded in available data.\n *\n * Memory system (3 layers):\n * - Fact Store: immutable ground truths from data → can't be contradicted\n * - Episodic Memory: past conversations → ensures consistency across sessions\n * - Stance Tracker: distilled positions → compact, always in prompt\n */\n\nimport type {\n PersonaDefinition,\n PersonaResponse,\n LLMProvider,\n SourceCitation,\n} from \"../types.js\";\nimport { compilePersonaPrompt } from \"../traits/compiler.js\";\nimport { PersonaMemory } from \"../memory/memory.js\";\n\ninterface StructuredResponse {\n response: string;\n reasoning: string;\n citations: SourceCitation[];\n}\n\nexport class Persona {\n readonly definition: PersonaDefinition;\n readonly memory: PersonaMemory;\n private llm: LLMProvider;\n private systemPrompt: string;\n private history: Array<{ role: \"user\" | \"persona\"; content: string }> = [];\n\n constructor(\n definition: PersonaDefinition,\n llm: LLMProvider,\n storageDir?: string\n ) {\n this.definition = definition;\n this.llm = llm;\n this.systemPrompt = compilePersonaPrompt(definition);\n this.memory = new PersonaMemory(definition.id, storageDir);\n }\n\n get id(): string {\n return this.definition.id;\n }\n get name(): string {\n return this.definition.name;\n }\n get confidence(): string {\n return this.definition.confidence;\n }\n\n /**\n * Load persisted memory from disk.\n * Call this once after construction if you want cross-session memory.\n */\n async loadMemory(): Promise<void> {\n await this.memory.load();\n }\n\n /**\n * Save memory to disk.\n * Call this after a session to persist episodic memory and stances.\n */\n async saveMemory(): Promise<void> {\n await this.memory.save();\n }\n\n /**\n * Ask the persona a question and get a sourced response.\n * Memory (facts, past episodes, stances) is automatically injected.\n */\n async ask(question: string): Promise<PersonaResponse> {\n // Compile memory context for this specific question\n const memoryContext = this.memory.compileForPrompt(question);\n\n const historyContext =\n this.history.length > 0\n ? `\\n\\nPrevious conversation:\\n${this.history\n .slice(-6)\n .map(\n (h) =>\n `${h.role === \"user\" ? \"Interviewer\" : this.definition.name}: ${h.content}`\n )\n .join(\"\\n\")}`\n : \"\";\n\n const structured = await this.llm.generateJSON<StructuredResponse>(\n this.systemPrompt + memoryContext + historyContext + CITATION_INSTRUCTIONS,\n `Question: \"${question}\"\n\nRespond as JSON:\n{\n \"response\": \"<your answer as this persona, 2-4 sentences>\",\n \"reasoning\": \"<which of your personality traits and experiences drive this answer>\",\n \"citations\": [\n {\"source\": \"<source type>\", \"detail\": \"<specific data point>\", \"weight\": <0.0-1.0>}\n ]\n}`\n );\n\n // Record in conversation history\n this.history.push({ role: \"user\", content: question });\n this.history.push({ role: \"persona\", content: structured.response });\n\n // Record in episodic memory\n this.memory.addEpisode(question, structured.response);\n\n return {\n personaId: this.definition.id,\n personaName: this.definition.name,\n question,\n response: structured.response,\n confidence: this.definition.confidence,\n groundedIn: this.definition.dataSources,\n citations: structured.citations ?? [],\n reasoning: structured.reasoning ?? \"\",\n traits: this.definition.traits,\n };\n }\n\n /**\n * Get the persona's reaction to a stimulus (feature, price change, etc.)\n */\n async react(stimulus: string): Promise<PersonaResponse> {\n return this.ask(\n `React to this as yourself — what's your honest first reaction? \"${stimulus}\"`\n );\n }\n\n /**\n * Compare two options from the persona's perspective.\n */\n async compare(optionA: string, optionB: string): Promise<PersonaResponse> {\n return this.ask(\n `Compare these two options and tell me which you'd choose and why:\\nOption A: ${optionA}\\nOption B: ${optionB}`\n );\n }\n\n /**\n * Derive stances from past conversations.\n * Call after multiple ask() calls to distill positions.\n */\n async deriveStances(): Promise<void> {\n await this.memory.deriveStances(this.llm);\n }\n\n /**\n * Get the compiled system prompt (useful for debugging).\n */\n toPrompt(): string {\n return this.systemPrompt;\n }\n\n /**\n * Get the full prompt including memory (useful for debugging).\n */\n toFullPrompt(query: string = \"\"): string {\n return this.systemPrompt + this.memory.compileForPrompt(query);\n }\n\n /**\n * Serialize to JSON.\n */\n toJSON(): PersonaDefinition {\n return this.definition;\n }\n\n /**\n * One-line summary.\n */\n summary(): string {\n const d = this.definition.demographics;\n const parts = [this.definition.name];\n if (d.age) parts.push(`${d.age}`);\n if (d.occupation) parts.push(d.occupation);\n if (d.location) parts.push(d.location);\n return parts.join(\", \");\n }\n\n /**\n * Reset conversation history (does NOT clear persistent memory).\n */\n clearHistory(): void {\n this.history = [];\n }\n}\n\nconst CITATION_INSTRUCTIONS = `\n\nCITATION REQUIREMENTS:\nFor every response, you MUST explain your reasoning and cite sources.\n\nFor \"citations\", use these source types:\n- \"trait-model\" — your personality traits (OCEAN scores) drive this answer. Detail should name the specific trait.\n- \"behavioral-data\" — real user behavior data informs this. Detail should quote the specific pattern.\n- \"support-data\" — real support conversations inform this. Detail should reference what users said.\n- \"payment-data\" — real payment/subscription data informs this. Detail should reference specific patterns.\n- \"memory\" — your past statements or known positions inform this. Detail should reference what you said before.\n- \"general-knowledge\" — general market knowledge, not grounded in specific data.\n\nAssign weight 0.0-1.0 based on how much this citation influenced your answer.\nIf you only have trait-model and general-knowledge to cite, be honest about that.\nIf GROUND TRUTH FACTS or YOUR PAST STATEMENTS are provided, you MUST cite them when relevant.`;\n","/**\n * Persona generator.\n *\n * Three generation modes:\n *\n * Mode 1 — FROM DATA (PostHog, Stripe, CSV):\n * Real user data → segment → representative persona per segment.\n * Backstory grounded in actual behavioral patterns.\n *\n * Mode 2 — FROM DOCUMENTS (PDF, MD, TXT):\n * Documents → extract stakeholder types → individual persona per stakeholder.\n * Backstory grounded in document context.\n *\n * Mode 3 — COMBINED (data + documents):\n * Both → segment from data, enrich with document context.\n * Richest backstories, highest confidence.\n *\n * Every persona gets:\n * - A name, demographics, occupation\n * - OCEAN trait vector (compiled into behavioral rules)\n * - A rich narrative backstory (not bullet points — a person you can picture)\n * - Data-grounded facts in memory\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type {\n PersonaDefinition,\n PersonaContext,\n LLMProvider,\n OceanVector,\n ConfidenceLevel,\n UserRecord,\n} from \"../types.js\";\nimport {\n compileBehavioralProfile,\n traitDistance,\n} from \"../traits/ocean.js\";\n\nexport interface GenerateOptions {\n /** What the product/brand/project is */\n product?: string;\n /** Number of personas to generate */\n count?: number;\n /** Additional freeform context */\n context?: string[];\n /** Real user data from adapters */\n userData?: UserRecord[];\n /** Minimum trait distance between personas (0-1, default 0.3) */\n diversityThreshold?: number;\n}\n\ninterface LLMPersonaSuggestion {\n name: string;\n age: number;\n gender: string;\n location: string;\n occupation: string;\n income: string;\n education: string;\n traits: OceanVector;\n backstory: string;\n painPoints: string[];\n goals: string[];\n relationship: string;\n}\n\n/**\n * Generate diverse personas grounded in OCEAN trait vectors.\n * Automatically selects the best generation mode based on available inputs.\n */\nexport async function generatePersonas(\n llm: LLMProvider,\n options: GenerateOptions\n): Promise<PersonaDefinition[]> {\n const count = options.count ?? 5;\n const diversityThreshold = options.diversityThreshold ?? 0.3;\n\n // Separate document records from data records\n const documents: UserRecord[] = [];\n const dataRecords: UserRecord[] = [];\n\n for (const r of options.userData ?? []) {\n if (r.properties._type === \"document\" || r.properties._type === \"context\") {\n documents.push(r);\n } else {\n dataRecords.push(r);\n }\n }\n\n const hasData = dataRecords.length > 0;\n const hasDocs = documents.length > 0;\n\n // Determine mode and confidence\n let confidence: ConfidenceLevel;\n let mode: \"data\" | \"documents\" | \"combined\" | \"context-only\";\n const dataSources: string[] = [];\n\n if (hasData && hasDocs) {\n mode = \"combined\";\n confidence = \"high\";\n dataSources.push(\"user-data\", \"documents\");\n } else if (hasData) {\n mode = \"data\";\n confidence = \"medium\";\n dataSources.push(\"user-data\");\n } else if (hasDocs) {\n mode = \"documents\";\n confidence = \"medium\";\n dataSources.push(\"documents\");\n } else {\n mode = \"context-only\";\n confidence = \"low\";\n }\n\n if (options.context?.length) dataSources.push(\"context\");\n if (options.product) dataSources.push(\"product-description\");\n\n // Build the generation prompt based on mode\n const contextParts: string[] = [];\n\n if (options.product) {\n contextParts.push(`Product/Domain: ${options.product}`);\n }\n if (options.context?.length) {\n contextParts.push(`Additional context:\\n${options.context.join(\"\\n\")}`);\n }\n if (hasData) {\n contextParts.push(\n `Real user data summary:\\n${summarizeUserData(dataRecords)}`\n );\n }\n if (hasDocs) {\n contextParts.push(\n `Document content:\\n${summarizeDocuments(documents)}`\n );\n }\n\n const systemPrompt =\n mode === \"documents\"\n ? STAKEHOLDER_SYSTEM_PROMPT\n : mode === \"combined\"\n ? COMBINED_SYSTEM_PROMPT\n : GENERATION_SYSTEM_PROMPT;\n\n const prompt = buildGenerationPrompt(\n contextParts.join(\"\\n\\n\"),\n count,\n mode\n );\n\n const suggestions = await llm.generateJSON<LLMPersonaSuggestion[]>(\n systemPrompt,\n prompt\n );\n\n // Build persona definitions with diversity enforcement\n const personas: PersonaDefinition[] = [];\n\n for (const suggestion of (suggestions ?? []).slice(0, count)) {\n const traits = enforceTraitBounds(suggestion.traits);\n\n const tooSimilar = personas.some(\n (p) => traitDistance(p.traits, traits) < diversityThreshold\n );\n const finalTraits = tooSimilar\n ? perturbVector(traits, diversityThreshold)\n : traits;\n\n const behavioralProfile = compileBehavioralProfile(finalTraits);\n\n const personaContext: PersonaContext = {\n product: options.product,\n freeformContext: [\n ...(options.context ?? []),\n // Inject the rich backstory and motivations into context\n suggestion.backstory,\n ...(suggestion.painPoints ?? []).map((p) => `Pain point: ${p}`),\n ...(suggestion.goals ?? []).map((g) => `Goal: ${g}`),\n suggestion.relationship\n ? `Relationship to product: ${suggestion.relationship}`\n : \"\",\n ].filter(Boolean),\n };\n\n // Enrich with data if available\n if (hasData) {\n const enrichment = enrichFromUserData(dataRecords);\n personaContext.behaviorData = enrichment.behaviors;\n personaContext.paymentData = enrichment.payments;\n personaContext.supportData = enrichment.support;\n }\n\n // Enrich with document content if available\n if (hasDocs) {\n personaContext.freeformContext = [\n ...(personaContext.freeformContext ?? []),\n ...documents\n .slice(0, 3)\n .map(\n (d) =>\n `From document \"${d.properties._filename}\": ${String(d.properties._content).slice(0, 2000)}`\n ),\n ];\n }\n\n personas.push({\n id: randomUUID(),\n name: suggestion.name,\n traits: finalTraits,\n demographics: {\n age: suggestion.age,\n gender: suggestion.gender,\n location: suggestion.location,\n occupation: suggestion.occupation,\n income: suggestion.income,\n education: suggestion.education,\n },\n context: personaContext,\n behavioralProfile,\n dataSources,\n confidence,\n createdAt: new Date().toISOString(),\n });\n }\n\n return personas;\n}\n\n// ---------------------------------------------------------------------------\n// System prompts per mode\n// ---------------------------------------------------------------------------\n\nconst GENERATION_SYSTEM_PROMPT = `You are a psychometrician and consumer behavior researcher. You generate realistic synthetic user personas grounded in Big Five (OCEAN) personality psychology.\n\nYour personas must feel like REAL PEOPLE, not data points. Each persona needs:\n\n1. A RICH BACKSTORY — not \"34, PM, Singapore.\" Instead: \"Mei Lin is 34, a product manager at a mid-size fintech in Singapore. She joined three years ago after burning out at a big bank. She's methodical — she keeps a spreadsheet of every SaaS tool her team uses, color-coded by renewal date. She discovered this product through a colleague's recommendation and signed up during a free trial, but almost cancelled when she couldn't figure out the export feature. She stayed because the dashboard saved her 2 hours a week on reporting.\"\n\n2. PAIN POINTS — specific frustrations, not generic ones. What keeps them up at night?\n\n3. GOALS — what are they trying to achieve? Not just with the product, but in their life/work.\n\n4. RELATIONSHIP TO THE PRODUCT — how did they find it? How do they use it? What would make them leave? What would make them stay forever?\n\n5. INTERNALLY CONSISTENT OCEAN TRAITS — a high-neuroticism person worries about price changes. A low-openness person resists new features. Make the backstory MATCH the traits.\n\nWhen assigning OCEAN scores, use the full range. Real humans cluster at extremes, not 0.5.`;\n\nconst STAKEHOLDER_SYSTEM_PROMPT = `You are a psychometrician and policy researcher. Given documents about a domain, you identify the key STAKEHOLDER TYPES and generate a realistic persona for each.\n\nYour job is to READ the documents carefully and figure out: who are the different types of people affected by this? What are their different perspectives, concerns, and behaviors?\n\nFor each stakeholder, generate a RICH, SPECIFIC persona:\n\n1. A NARRATIVE BACKSTORY — not demographics. A story. \"Ah Kow is 72, a retired hawker who's lived in his ground-floor Bishan HDB flat for 38 years. He remembers the big flood in 1978 and still keeps sandbags by the door. His children keep telling him to move upstairs but he says the garden is his life. He reads Lianhe Zaobao every morning and trusts what the government says about drainage, but he noticed the water comes faster now than it used to.\"\n\n2. Their SPECIFIC RELATIONSHIP to the domain/policy — not generic. What do they stand to gain or lose?\n\n3. Their INFORMATION SOURCES — how do they learn about this topic? Who do they trust?\n\n4. OCEAN TRAITS that match their described behavior — the cautious elderly man is high conscientiousness, moderate neuroticism.\n\nExtract stakeholder types DIRECTLY from the document content. Don't invent types the documents don't mention.`;\n\nconst COMBINED_SYSTEM_PROMPT = `You are a psychometrician and researcher. You have BOTH real user data AND domain documents. Generate personas that combine behavioral patterns from the data with contextual knowledge from the documents.\n\nEach persona should:\n\n1. REPRESENT a real behavioral segment from the data (their actions, usage patterns, payment behavior are grounded in real numbers)\n\n2. Be ENRICHED with domain knowledge from the documents (they understand the broader context — policy implications, industry trends, competitive landscape)\n\n3. Have a RICH NARRATIVE BACKSTORY that weaves together their data-revealed behavior with document-revealed context. Not: \"Power user, exports a lot.\" Instead: \"Raj is the kind of user who builds his workflow around your tool. He's automated his entire weekly report using your CSV exports — 340 exports last month. But he saw the blog post about the new API-first strategy and he's worried that CSV exports are going to be deprecated. He's already evaluating alternatives, not because he wants to leave, but because he needs a backup plan. His boss asks for that report every Monday at 9am and there's no room for 'the export feature changed.'\"\n\n4. Have OCEAN TRAITS consistent with both their observed behavior AND their described personality.\n\nGround every claim in either the real data or the documents. If you're speculating, say so.`;\n\n// ---------------------------------------------------------------------------\n// Prompt builders\n// ---------------------------------------------------------------------------\n\nfunction buildGenerationPrompt(\n context: string,\n count: number,\n mode: string\n): string {\n const modeInstruction =\n mode === \"documents\"\n ? \"Identify the key stakeholder types mentioned or implied in the documents. Generate one persona per stakeholder type.\"\n : mode === \"combined\"\n ? \"Combine behavioral segments from the user data with stakeholder perspectives from the documents.\"\n : \"Generate diverse personas that represent the range of users for this product/domain.\";\n\n return `${modeInstruction}\n\n${context}\n\nGenerate a JSON array of ${count} personas. Each must have:\n- name: A realistic full first name (culturally appropriate for the context)\n- age: number\n- gender: string\n- location: string (specific — neighborhood/city, not just country)\n- occupation: string (specific role, not just industry)\n- income: string (range)\n- education: string\n- traits: { openness, conscientiousness, extraversion, agreeableness, neuroticism } (each 0.0 to 1.0)\n- backstory: 3-5 sentences. A NARRATIVE about this person — their history, daily life, how they relate to this product/domain. Make them feel real. Include specific details only this person would have.\n- painPoints: array of 2-3 specific frustrations (not generic)\n- goals: array of 2-3 things they're trying to achieve\n- relationship: 1-2 sentences on how they found/use/feel about this product or domain\n\nMake personas GENUINELY DIVERSE:\n- Spread across age ranges, income levels, and personality types\n- Include skeptics and critics, not just fans\n- Include edge cases — the person who almost left, the person who uses it \"wrong\"\n- OCEAN traits should use the full 0-1 range, not cluster around 0.5\n\nReturn ONLY a JSON array.`;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction enforceTraitBounds(traits: OceanVector): OceanVector {\n const bounded: OceanVector = { ...traits };\n for (const key of Object.keys(bounded) as (keyof OceanVector)[]) {\n bounded[key] = Math.max(0, Math.min(1, bounded[key]));\n }\n return bounded;\n}\n\nfunction perturbVector(\n traits: OceanVector,\n minDistance: number\n): OceanVector {\n const perturbed = { ...traits };\n const keys = Object.keys(perturbed) as (keyof OceanVector)[];\n const shuffled = keys.sort(() => Math.random() - 0.5);\n for (const key of shuffled.slice(0, 3)) {\n const shift = (Math.random() - 0.5) * minDistance * 2;\n perturbed[key] = Math.max(0, Math.min(1, perturbed[key] + shift));\n }\n return perturbed;\n}\n\nfunction summarizeUserData(records: UserRecord[]): string {\n const lines: string[] = [];\n lines.push(`Total users: ${records.length}`);\n\n const propCounts: Record<string, Record<string, number>> = {};\n for (const r of records) {\n for (const [key, value] of Object.entries(r.properties)) {\n if (key.startsWith(\"_\")) continue;\n if (!propCounts[key]) propCounts[key] = {};\n const v = String(value);\n propCounts[key][v] = (propCounts[key][v] ?? 0) + 1;\n }\n }\n\n for (const [prop, values] of Object.entries(propCounts)) {\n const sorted = Object.entries(values)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5);\n const total = Object.values(values).reduce((a, b) => a + b, 0);\n const dist = sorted\n .map(([v, c]) => `${v} (${((c / total) * 100).toFixed(0)}%)`)\n .join(\", \");\n lines.push(`${prop}: ${dist}`);\n }\n\n const eventCounts: Record<string, number> = {};\n for (const r of records) {\n for (const e of r.events ?? []) {\n eventCounts[e.name] = (eventCounts[e.name] ?? 0) + 1;\n }\n }\n if (Object.keys(eventCounts).length > 0) {\n const topEvents = Object.entries(eventCounts)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([name, count]) => `${name} (${count}x)`)\n .join(\", \");\n lines.push(`Top events: ${topEvents}`);\n }\n\n const paymentStatuses: Record<string, number> = {};\n const cancelReasons: string[] = [];\n for (const r of records) {\n for (const p of r.payments ?? []) {\n paymentStatuses[p.status] = (paymentStatuses[p.status] ?? 0) + 1;\n if (p.cancelReason) cancelReasons.push(p.cancelReason);\n }\n }\n if (Object.keys(paymentStatuses).length > 0) {\n lines.push(\n `Payment statuses: ${Object.entries(paymentStatuses)\n .map(([s, c]) => `${s}: ${c}`)\n .join(\", \")}`\n );\n }\n if (cancelReasons.length > 0) {\n lines.push(`Cancel reasons: ${cancelReasons.slice(0, 10).join(\"; \")}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction summarizeDocuments(docs: UserRecord[]): string {\n return docs\n .map((d) => {\n const filename = d.properties._filename ?? \"unknown\";\n const content = String(d.properties._content ?? \"\").slice(0, 3000);\n return `--- ${filename} ---\\n${content}`;\n })\n .join(\"\\n\\n\");\n}\n\nfunction enrichFromUserData(\n records: UserRecord[]\n): { behaviors: string[]; payments: string[]; support: string[] } {\n const behaviors: string[] = [];\n const payments: string[] = [];\n const support: string[] = [];\n\n const eventCounts: Record<string, number> = {};\n for (const r of records) {\n for (const e of r.events ?? []) {\n eventCounts[e.name] = (eventCounts[e.name] ?? 0) + 1;\n }\n }\n for (const [name, count] of Object.entries(eventCounts)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5)) {\n behaviors.push(\n `${name}: occurs ${count} times across ${records.length} users`\n );\n }\n\n for (const r of records) {\n for (const p of r.payments ?? []) {\n if (p.cancelReason) {\n payments.push(`Cancelled: \"${p.cancelReason}\" (plan: ${p.plan})`);\n }\n }\n }\n\n for (const r of records) {\n for (const c of r.conversations ?? []) {\n support.push(c);\n }\n }\n\n return {\n behaviors: behaviors.slice(0, 10),\n payments: payments.slice(0, 10),\n support: support.slice(0, 10),\n };\n}\n","/**\n * Segmentation engine.\n *\n * Takes raw user data and discovers natural groups/segments. Each segment\n * gets a representative persona with traits derived from the segment's\n * behavioral patterns.\n *\n * Uses LLM-assisted clustering: the LLM analyzes user data patterns and\n * identifies meaningful segments, then assigns OCEAN traits based on\n * observed behaviors.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type {\n UserRecord,\n Segment,\n DiscoveryResult,\n DataSuggestion,\n LLMProvider,\n OceanVector,\n PersonaDefinition,\n ConfidenceLevel,\n} from \"../types.js\";\nimport {\n compileBehavioralProfile,\n averageOceanVector,\n} from \"../traits/ocean.js\";\n\ninterface DiscoverOptions {\n /** Product being analyzed */\n product?: string;\n /** Number of segments to discover (default: auto, 3-6) */\n segmentCount?: number;\n}\n\ninterface LLMSegmentSuggestion {\n name: string;\n description: string;\n percentage: number;\n patterns: string[];\n persona: {\n name: string;\n age: number;\n gender: string;\n occupation: string;\n location: string;\n income: string;\n traits: OceanVector;\n };\n}\n\n/**\n * Discover segments from user data.\n */\nexport async function discoverSegments(\n llm: LLMProvider,\n records: UserRecord[],\n options: DiscoverOptions = {}\n): Promise<DiscoveryResult> {\n if (records.length === 0) {\n return {\n segments: [],\n totalUsersAnalyzed: 0,\n confidence: \"low\",\n suggestions: [\n {\n source: \"dopple\",\n suggestion: \"No user data available. Connect a data source first.\",\n impact: \"high\",\n },\n ],\n };\n }\n\n // Summarize the data for the LLM\n const dataSummary = buildDataSummary(records);\n const segmentCount = options.segmentCount ?? \"3-6\";\n\n // Ask LLM to identify segments\n const prompt = `Analyze this user data and identify ${segmentCount} distinct segments.\n\nProduct: ${options.product ?? \"Unknown\"}\n\nUser Data Summary:\n${dataSummary}\n\nFor each segment, return:\n- name: A memorable nickname (e.g., \"The Optimizer\", \"The Casual\")\n- description: 1-2 sentence description\n- percentage: Estimated % of users in this segment\n- patterns: Array of 3-5 key behavioral patterns\n- persona: A representative person with name, age, gender, occupation, location, income, and OCEAN traits (0.0-1.0)\n\nIMPORTANT:\n- Segments should be meaningfully different, not just age brackets\n- Ground segments in the ACTUAL data patterns you see, not generic archetypes\n- OCEAN traits must reflect the behavioral patterns (e.g., methodical researchers = high conscientiousness)\n- Percentages must sum to ~100%\n\nReturn a JSON array of segments.`;\n\n const suggestions = await llm.generateJSON<LLMSegmentSuggestion[]>(\n DISCOVERY_SYSTEM_PROMPT,\n prompt\n );\n\n // Build segments with full persona definitions\n const segments: Segment[] = suggestions.map((s) => {\n const traits = enforceTraitBounds(s.persona.traits);\n const behavioralProfile = compileBehavioralProfile(traits);\n\n const persona: PersonaDefinition = {\n id: randomUUID(),\n name: s.persona.name,\n traits,\n demographics: {\n age: s.persona.age,\n gender: s.persona.gender,\n occupation: s.persona.occupation,\n location: s.persona.location,\n income: s.persona.income,\n },\n context: {\n product: options.product,\n behaviorData: s.patterns,\n },\n behavioralProfile,\n dataSources: [\"user-data\", \"segmentation\"],\n confidence: computeConfidence(records),\n createdAt: new Date().toISOString(),\n };\n\n return {\n id: randomUUID(),\n name: s.name,\n description: s.description,\n userCount: Math.round((s.percentage / 100) * records.length),\n percentage: s.percentage,\n averageTraits: traits,\n patterns: s.patterns,\n persona,\n };\n });\n\n // Generate data improvement suggestions\n const dataSuggestions = analyzeDataGaps(records);\n\n return {\n segments,\n totalUsersAnalyzed: records.length,\n confidence: computeConfidence(records),\n suggestions: dataSuggestions,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildDataSummary(records: UserRecord[]): string {\n const lines: string[] = [`Total users: ${records.length}`];\n\n // Property distributions\n const propCounts: Record<string, Record<string, number>> = {};\n for (const r of records) {\n for (const [key, value] of Object.entries(r.properties)) {\n if (key.startsWith(\"_\")) continue;\n if (!propCounts[key]) propCounts[key] = {};\n const v = String(value);\n propCounts[key][v] = (propCounts[key][v] ?? 0) + 1;\n }\n }\n\n for (const [prop, values] of Object.entries(propCounts)) {\n const uniqueValues = Object.keys(values).length;\n if (uniqueValues > 20) {\n // Numeric-like property — show range\n const nums = Object.keys(values)\n .map(Number)\n .filter((n) => !isNaN(n));\n if (nums.length > 0) {\n lines.push(\n `${prop}: range ${Math.min(...nums)}-${Math.max(...nums)}, ${uniqueValues} unique values`\n );\n } else {\n lines.push(`${prop}: ${uniqueValues} unique values`);\n }\n } else {\n // Categorical — show distribution\n const sorted = Object.entries(values)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 8);\n const total = records.length;\n const dist = sorted\n .map(([v, c]) => `${v} (${((c / total) * 100).toFixed(0)}%)`)\n .join(\", \");\n lines.push(`${prop}: ${dist}`);\n }\n }\n\n // Event patterns\n const eventCounts: Record<string, number> = {};\n let totalEvents = 0;\n for (const r of records) {\n for (const e of r.events ?? []) {\n eventCounts[e.name] = (eventCounts[e.name] ?? 0) + 1;\n totalEvents++;\n }\n }\n if (totalEvents > 0) {\n lines.push(`\\nTotal events: ${totalEvents}`);\n const topEvents = Object.entries(eventCounts)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)\n .map(([name, count]) => ` ${name}: ${count}x`)\n .join(\"\\n\");\n lines.push(`Top events:\\n${topEvents}`);\n }\n\n // Payment patterns\n let hasPayments = false;\n const statuses: Record<string, number> = {};\n const plans: Record<string, number> = {};\n const cancelReasons: string[] = [];\n for (const r of records) {\n for (const p of r.payments ?? []) {\n hasPayments = true;\n statuses[p.status] = (statuses[p.status] ?? 0) + 1;\n if (p.plan) plans[p.plan] = (plans[p.plan] ?? 0) + 1;\n if (p.cancelReason) cancelReasons.push(p.cancelReason);\n }\n }\n if (hasPayments) {\n lines.push(\n `\\nPayment statuses: ${Object.entries(statuses)\n .map(([s, c]) => `${s}: ${c}`)\n .join(\", \")}`\n );\n if (Object.keys(plans).length > 0) {\n lines.push(\n `Plans: ${Object.entries(plans)\n .map(([p, c]) => `${p}: ${c}`)\n .join(\", \")}`\n );\n }\n if (cancelReasons.length > 0) {\n lines.push(`Cancel reasons: ${cancelReasons.slice(0, 10).join(\"; \")}`);\n }\n }\n\n // Conversations\n let convCount = 0;\n for (const r of records) {\n convCount += (r.conversations ?? []).length;\n }\n if (convCount > 0) {\n lines.push(`\\nSupport conversations: ${convCount} total`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction computeConfidence(records: UserRecord[]): ConfidenceLevel {\n let score = 0;\n\n // More users = higher confidence\n if (records.length >= 100) score += 2;\n else if (records.length >= 20) score += 1;\n\n // Events present\n const hasEvents = records.some((r) => (r.events ?? []).length > 0);\n if (hasEvents) score += 1;\n\n // Payments present\n const hasPayments = records.some((r) => (r.payments ?? []).length > 0);\n if (hasPayments) score += 1;\n\n // Conversations present\n const hasConversations = records.some(\n (r) => (r.conversations ?? []).length > 0\n );\n if (hasConversations) score += 1;\n\n if (score >= 4) return \"high\";\n if (score >= 2) return \"medium\";\n return \"low\";\n}\n\nfunction analyzeDataGaps(records: UserRecord[]): DataSuggestion[] {\n const suggestions: DataSuggestion[] = [];\n\n // Check for missing data types\n const hasEvents = records.some((r) => (r.events ?? []).length > 0);\n const hasPayments = records.some((r) => (r.payments ?? []).length > 0);\n const hasConversations = records.some(\n (r) => (r.conversations ?? []).length > 0\n );\n\n if (!hasEvents) {\n suggestions.push({\n source: \"posthog\",\n suggestion:\n \"Connect PostHog to get event data. Behavioral events dramatically improve segment accuracy.\",\n impact: \"high\",\n });\n }\n\n if (!hasPayments) {\n suggestions.push({\n source: \"stripe\",\n suggestion:\n \"Connect Stripe to get payment data. Subscription status and cancel reasons ground pricing personas.\",\n impact: \"high\",\n });\n }\n\n if (!hasConversations) {\n suggestions.push({\n source: \"intercom\",\n suggestion:\n \"Connect a support tool to get real conversations. User words improve persona authenticity.\",\n impact: \"medium\",\n });\n }\n\n // Check property coverage\n const propCoverage: Record<string, number> = {};\n for (const r of records) {\n for (const key of Object.keys(r.properties)) {\n propCoverage[key] = (propCoverage[key] ?? 0) + 1;\n }\n }\n\n for (const [prop, count] of Object.entries(propCoverage)) {\n const coverage = count / records.length;\n if (coverage < 0.3 && !prop.startsWith(\"_\")) {\n suggestions.push({\n source: \"data-quality\",\n suggestion: `Property \"${prop}\" is only set for ${(coverage * 100).toFixed(0)}% of users. Improve coverage for better segmentation.`,\n impact: \"low\",\n });\n }\n }\n\n if (records.length < 20) {\n suggestions.push({\n source: \"data-volume\",\n suggestion: `Only ${records.length} users. Segments improve significantly with 50+ users.`,\n impact: \"medium\",\n });\n }\n\n return suggestions;\n}\n\nfunction enforceTraitBounds(traits: OceanVector): OceanVector {\n const bounded: OceanVector = { ...traits };\n for (const key of Object.keys(bounded) as (keyof OceanVector)[]) {\n bounded[key] = Math.max(0, Math.min(1, bounded[key]));\n }\n return bounded;\n}\n\nconst DISCOVERY_SYSTEM_PROMPT = `You are a behavioral data scientist and psychometrician. You analyze product usage data to discover meaningful user segments.\n\nYour segmentation must:\n1. Be grounded in ACTUAL patterns in the data — not generic marketing personas\n2. Identify segments that differ in behavior, motivation, and personality — not just demographics\n3. Assign OCEAN trait vectors that are CONSISTENT with observed behaviors\n4. Include edge cases and minority segments — not just the obvious majority\n5. Be actionable — each segment should suggest different product decisions\n\nWhen assigning OCEAN scores, map behaviors to traits:\n- Methodical/planning behavior → high conscientiousness\n- Social/sharing behavior → high extraversion\n- Price complaints/comparison shopping → high neuroticism, low agreeableness\n- Early adopter/feature requests → high openness\n- Loyalty despite issues → high agreeableness`;\n","/**\n * Validation suite.\n *\n * Proves that personas are psychometrically valid, consistent, and\n * discriminative. No product-specific data needed.\n *\n * Tests:\n * 1. Psychometric — Does a persona with openness=0.8 actually behave open?\n * 2. Consistency — Same persona, same question, similar answers?\n * 3. Discrimination — Different traits produce different responses?\n * 4. Trait alignment — Do responses match the trait vector?\n */\n\nimport type {\n PersonaDefinition,\n LLMProvider,\n ValidationResult,\n ValidationReport,\n OceanTrait,\n} from \"../types.js\";\nimport { Persona } from \"../persona/persona.js\";\nimport { traitDistance } from \"../traits/ocean.js\";\n\n// ---------------------------------------------------------------------------\n// Psychometric validation questions (based on IPIP-NEO-120 items)\n// ---------------------------------------------------------------------------\n\ninterface PsychometricItem {\n question: string;\n trait: OceanTrait;\n /** True if agreeing indicates HIGH trait score */\n positiveKeyed: boolean;\n}\n\nconst PSYCHOMETRIC_ITEMS: PsychometricItem[] = [\n // Openness\n {\n question: \"Would you enjoy visiting an art museum?\",\n trait: \"openness\",\n positiveKeyed: true,\n },\n {\n question:\n \"Do you prefer sticking to tried-and-true methods rather than trying new approaches?\",\n trait: \"openness\",\n positiveKeyed: false,\n },\n {\n question:\n \"Are you curious about things even if they aren't immediately useful to you?\",\n trait: \"openness\",\n positiveKeyed: true,\n },\n\n // Conscientiousness\n {\n question: \"Do you make a plan and follow through with it?\",\n trait: \"conscientiousness\",\n positiveKeyed: true,\n },\n {\n question: \"Do you often forget to put things back in their proper place?\",\n trait: \"conscientiousness\",\n positiveKeyed: false,\n },\n {\n question: \"Do you research products thoroughly before buying them?\",\n trait: \"conscientiousness\",\n positiveKeyed: true,\n },\n\n // Extraversion\n {\n question: \"Do you enjoy being the center of attention?\",\n trait: \"extraversion\",\n positiveKeyed: true,\n },\n {\n question:\n \"Would you rather spend a Saturday night at home alone than at a party?\",\n trait: \"extraversion\",\n positiveKeyed: false,\n },\n {\n question:\n \"Do you share your opinions about products with friends and family?\",\n trait: \"extraversion\",\n positiveKeyed: true,\n },\n\n // Agreeableness\n {\n question:\n \"Do you generally trust that people have good intentions?\",\n trait: \"agreeableness\",\n positiveKeyed: true,\n },\n {\n question:\n \"Would you publicly complain about a product that disappointed you?\",\n trait: \"agreeableness\",\n positiveKeyed: false,\n },\n {\n question:\n \"Do you give products a second chance even after a bad experience?\",\n trait: \"agreeableness\",\n positiveKeyed: true,\n },\n\n // Neuroticism\n {\n question: \"Do you worry about making the wrong purchase decision?\",\n trait: \"neuroticism\",\n positiveKeyed: true,\n },\n {\n question: \"Do you handle unexpected price increases calmly?\",\n trait: \"neuroticism\",\n positiveKeyed: false,\n },\n {\n question:\n \"Do you read negative reviews more carefully than positive ones?\",\n trait: \"neuroticism\",\n positiveKeyed: true,\n },\n];\n\n// ---------------------------------------------------------------------------\n// Test: Psychometric Fidelity\n// ---------------------------------------------------------------------------\n\nasync function testPsychometricFidelity(\n persona: Persona,\n llm: LLMProvider\n): Promise<ValidationResult> {\n const traitScores: Record<OceanTrait, number[]> = {\n openness: [],\n conscientiousness: [],\n extraversion: [],\n agreeableness: [],\n neuroticism: [],\n };\n\n for (const item of PSYCHOMETRIC_ITEMS) {\n const response = await llm.generateJSON<{ answer: \"yes\" | \"no\" | \"maybe\" }>(\n `You are answering personality questions as ${persona.name}. Based on the persona's personality, answer honestly.\n\nPersona traits: O=${persona.definition.traits.openness.toFixed(2)} C=${persona.definition.traits.conscientiousness.toFixed(2)} E=${persona.definition.traits.extraversion.toFixed(2)} A=${persona.definition.traits.agreeableness.toFixed(2)} N=${persona.definition.traits.neuroticism.toFixed(2)}`,\n `Question: \"${item.question}\"\\n\\nRespond with JSON: {\"answer\": \"yes\" | \"no\" | \"maybe\"}`\n );\n\n let score: number;\n if (response.answer === \"yes\") score = item.positiveKeyed ? 1 : 0;\n else if (response.answer === \"no\") score = item.positiveKeyed ? 0 : 1;\n else score = 0.5;\n\n traitScores[item.trait].push(score);\n }\n\n // Compare measured scores to defined traits\n let totalError = 0;\n let traitCount = 0;\n\n for (const trait of Object.keys(traitScores) as OceanTrait[]) {\n const scores = traitScores[trait];\n if (scores.length === 0) continue;\n const measured = scores.reduce((a, b) => a + b, 0) / scores.length;\n const defined = persona.definition.traits[trait];\n totalError += Math.abs(measured - defined);\n traitCount++;\n }\n\n const avgError = traitCount > 0 ? totalError / traitCount : 1;\n const score = Math.max(0, 1 - avgError);\n\n return {\n testName: \"Psychometric Fidelity\",\n passed: score >= 0.6,\n score: Math.round(score * 100),\n maxScore: 100,\n details:\n score >= 0.6\n ? `Persona responses align with defined traits (${(avgError * 100).toFixed(0)}% average error)`\n : `Persona responses diverge from defined traits (${(avgError * 100).toFixed(0)}% average error). Trait compilation may need calibration.`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Test: Consistency\n// ---------------------------------------------------------------------------\n\nasync function testConsistency(\n persona: Persona,\n _llm: LLMProvider\n): Promise<ValidationResult> {\n const question = \"What matters most to you when choosing a new product or service?\";\n const runs = 3;\n const responses: string[] = [];\n\n for (let i = 0; i < runs; i++) {\n persona.clearHistory();\n const r = await persona.ask(question);\n responses.push(r.response);\n }\n\n // Simple consistency check — look for overlapping key themes\n const allWords = responses.map((r) =>\n r.toLowerCase().split(/\\W+/).filter((w) => w.length > 4)\n );\n const wordSets = allWords.map((words) => new Set(words));\n\n let overlapScore = 0;\n let comparisons = 0;\n for (let i = 0; i < wordSets.length; i++) {\n for (let j = i + 1; j < wordSets.length; j++) {\n const intersection = new Set(\n [...wordSets[i]].filter((w) => wordSets[j].has(w))\n );\n const union = new Set([...wordSets[i], ...wordSets[j]]);\n overlapScore += union.size > 0 ? intersection.size / union.size : 0;\n comparisons++;\n }\n }\n\n const avgOverlap = comparisons > 0 ? overlapScore / comparisons : 0;\n const score = Math.min(1, avgOverlap * 3); // Scale up — even 33% overlap is good for natural language\n\n return {\n testName: \"Response Consistency\",\n passed: score >= 0.3,\n score: Math.round(score * 100),\n maxScore: 100,\n details:\n score >= 0.3\n ? `Persona gives thematically consistent responses across ${runs} runs (${(avgOverlap * 100).toFixed(0)}% lexical overlap)`\n : `Persona responses vary too much across runs. Consider stronger trait constraints.`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Test: Discrimination\n// ---------------------------------------------------------------------------\n\nasync function testDiscrimination(\n persona: Persona,\n llm: LLMProvider\n): Promise<ValidationResult> {\n // Create an opposite persona\n const oppositeTraits = {\n openness: 1 - persona.definition.traits.openness,\n conscientiousness: 1 - persona.definition.traits.conscientiousness,\n extraversion: 1 - persona.definition.traits.extraversion,\n agreeableness: 1 - persona.definition.traits.agreeableness,\n neuroticism: 1 - persona.definition.traits.neuroticism,\n };\n\n const oppositeDef = {\n ...persona.definition,\n id: \"opposite-\" + persona.definition.id,\n name: \"Opposite-\" + persona.definition.name,\n traits: oppositeTraits,\n };\n\n const { compileBehavioralProfile } = await import(\"../traits/ocean.js\");\n oppositeDef.behavioralProfile = compileBehavioralProfile(oppositeTraits);\n\n const oppositePersona = new Persona(oppositeDef, llm);\n\n // Ask both the same question\n const question =\n \"A new product you've never heard of is being recommended by a friend. It costs 20% more than what you currently use. What do you do?\";\n\n persona.clearHistory();\n oppositePersona.clearHistory();\n const r1 = await persona.ask(question);\n const r2 = await oppositePersona.ask(question);\n\n // Measure how different the responses are\n const words1 = new Set(\n r1.response.toLowerCase().split(/\\W+/).filter((w) => w.length > 4)\n );\n const words2 = new Set(\n r2.response.toLowerCase().split(/\\W+/).filter((w) => w.length > 4)\n );\n\n const intersection = new Set([...words1].filter((w) => words2.has(w)));\n const union = new Set([...words1, ...words2]);\n const similarity = union.size > 0 ? intersection.size / union.size : 0;\n const difference = 1 - similarity;\n\n return {\n testName: \"Trait Discrimination\",\n passed: difference >= 0.4,\n score: Math.round(difference * 100),\n maxScore: 100,\n details:\n difference >= 0.4\n ? `Opposite personas give meaningfully different responses (${(difference * 100).toFixed(0)}% different)`\n : `Opposite personas give too-similar responses. Trait constraints may not be strong enough.`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Run the full validation suite on a persona.\n */\nexport async function validatePersona(\n definition: PersonaDefinition,\n llm: LLMProvider\n): Promise<ValidationReport> {\n const persona = new Persona(definition, llm);\n const tests: ValidationResult[] = [];\n\n tests.push(await testPsychometricFidelity(persona, llm));\n tests.push(await testConsistency(persona, llm));\n tests.push(await testDiscrimination(persona, llm));\n\n const totalScore = tests.reduce((sum, t) => sum + t.score, 0);\n const maxScore = tests.reduce((sum, t) => sum + t.maxScore, 0);\n const allPassed = tests.every((t) => t.passed);\n\n return {\n overall: { passed: allPassed, score: totalScore, maxScore },\n tests,\n timestamp: new Date().toISOString(),\n };\n}\n","/**\n * Adapter base interface and registry.\n *\n * Adapters pull user data from external sources. Each adapter returns\n * UserRecord[] — the engine doesn't care where the data came from.\n */\n\nimport type { Adapter, AdapterConfig } from \"../types.js\";\n\ntype AdapterFactory = (config: AdapterConfig) => Adapter;\n\nconst registry = new Map<string, AdapterFactory>();\n\nexport function registerAdapter(type: string, factory: AdapterFactory): void {\n registry.set(type, factory);\n}\n\nexport function createAdapter(config: AdapterConfig): Adapter {\n const factory = registry.get(config.type);\n if (!factory) {\n throw new Error(\n `Unknown adapter type: \"${config.type}\". Available: ${[...registry.keys()].join(\", \")}`\n );\n }\n return factory(config);\n}\n\nexport function listAdapterTypes(): string[] {\n return [...registry.keys()];\n}\n","/**\n * JSONL-based local storage for personas and survey results.\n *\n * Stores data in .dopple/ directory:\n * .dopple/panels/ — saved persona panels\n * .dopple/surveys/ — survey results\n * .dopple/history/ — conversation history\n *\n * Format: JSONL (one JSON object per line). Appendable, human-readable,\n * git-friendly, easy to grep.\n */\n\nimport { readFile, writeFile, mkdir, readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n PersonaDefinition,\n StoredPanel,\n SurveyResult,\n PersonaResponse,\n} from \"../types.js\";\n\nexport class DoppleStore {\n private baseDir: string;\n\n constructor(baseDir?: string) {\n this.baseDir = baseDir ?? join(process.cwd(), \".dopple\");\n }\n\n // -------------------------------------------------------------------------\n // Panels\n // -------------------------------------------------------------------------\n\n async savePanel(\n name: string,\n personas: PersonaDefinition[],\n product?: string\n ): Promise<StoredPanel> {\n const dir = join(this.baseDir, \"panels\");\n await mkdir(dir, { recursive: true });\n\n const panel: StoredPanel = {\n id: randomUUID(),\n name,\n product,\n personas,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n const filename = `${slugify(name)}-${panel.id.slice(0, 8)}.jsonl`;\n const lines = personas.map((p) => JSON.stringify(p));\n // First line is panel metadata\n const meta = JSON.stringify({\n _type: \"panel\",\n id: panel.id,\n name: panel.name,\n product: panel.product,\n personaCount: personas.length,\n createdAt: panel.createdAt,\n });\n\n await writeFile(join(dir, filename), [meta, ...lines].join(\"\\n\") + \"\\n\");\n return panel;\n }\n\n async loadPanel(nameOrId: string): Promise<StoredPanel | null> {\n const dir = join(this.baseDir, \"panels\");\n try {\n const files = await readdir(dir);\n const match = files.find(\n (f) => f.includes(nameOrId) || f.startsWith(slugify(nameOrId))\n );\n if (!match) return null;\n\n const content = await readFile(join(dir, match), \"utf-8\");\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n if (lines.length === 0) return null;\n\n const meta = JSON.parse(lines[0]) as Record<string, unknown>;\n const personas = lines.slice(1).map((l) => JSON.parse(l) as PersonaDefinition);\n\n return {\n id: meta.id as string,\n name: meta.name as string,\n product: meta.product as string | undefined,\n personas,\n createdAt: meta.createdAt as string,\n updatedAt: meta.createdAt as string,\n };\n } catch {\n return null;\n }\n }\n\n async listPanels(): Promise<\n Array<{ id: string; name: string; personaCount: number; createdAt: string }>\n > {\n const dir = join(this.baseDir, \"panels\");\n try {\n const files = await readdir(dir);\n const panels = [];\n\n for (const file of files) {\n if (!file.endsWith(\".jsonl\")) continue;\n const content = await readFile(join(dir, file), \"utf-8\");\n const firstLine = content.split(\"\\n\")[0];\n if (!firstLine) continue;\n const meta = JSON.parse(firstLine) as Record<string, unknown>;\n if (meta._type === \"panel\") {\n panels.push({\n id: meta.id as string,\n name: meta.name as string,\n personaCount: meta.personaCount as number,\n createdAt: meta.createdAt as string,\n });\n }\n }\n\n return panels.sort(\n (a, b) =>\n new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\n );\n } catch {\n return [];\n }\n }\n\n // -------------------------------------------------------------------------\n // Surveys\n // -------------------------------------------------------------------------\n\n async saveSurvey(result: SurveyResult): Promise<string> {\n const dir = join(this.baseDir, \"surveys\");\n await mkdir(dir, { recursive: true });\n\n const filename = `${slugify(result.surveyName)}-${Date.now()}.jsonl`;\n const meta = JSON.stringify({\n _type: \"survey\",\n surveyName: result.surveyName,\n personaCount: result.personaCount,\n questionCount: result.questions.length,\n timestamp: result.timestamp,\n });\n const questions = JSON.stringify({\n _type: \"questions\",\n questions: result.questions,\n });\n const summary = JSON.stringify({\n _type: \"summary\",\n summary: result.summary,\n });\n const responseLines = result.responses.map((r) => JSON.stringify(r));\n\n await writeFile(\n join(dir, filename),\n [meta, questions, summary, ...responseLines].join(\"\\n\") + \"\\n\"\n );\n\n return filename;\n }\n\n // -------------------------------------------------------------------------\n // Conversation History\n // -------------------------------------------------------------------------\n\n async appendResponse(response: PersonaResponse): Promise<void> {\n const dir = join(this.baseDir, \"history\");\n await mkdir(dir, { recursive: true });\n\n const filename = `${response.personaId}.jsonl`;\n const line =\n JSON.stringify({\n ...response,\n timestamp: new Date().toISOString(),\n }) + \"\\n\";\n\n const filepath = join(dir, filename);\n try {\n const existing = await readFile(filepath, \"utf-8\");\n await writeFile(filepath, existing + line);\n } catch {\n await writeFile(filepath, line);\n }\n }\n}\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n","/**\n * Trace storage — JSONL per product.\n *\n * .dopple/traces/<product-slug>/\n * index.jsonl — lightweight summary\n * <trace-id>.jsonl — full trace\n */\n\nimport { readFile, writeFile, mkdir, readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Trace, TraceIndex, ProductTraceHistory } from \"./types.js\";\n\nexport class TraceStore {\n private baseDir: string;\n\n constructor(storageDir?: string) {\n this.baseDir = join(storageDir ?? join(process.cwd(), \".dopple\"), \"traces\");\n }\n\n /**\n * Save a trace.\n */\n async save(trace: Trace): Promise<void> {\n const dir = join(this.baseDir, slugify(trace.product));\n await mkdir(dir, { recursive: true });\n\n // Save full trace\n await writeFile(\n join(dir, `${trace.id}.jsonl`),\n JSON.stringify(trace) + \"\\n\"\n );\n\n // Update index\n await this.updateIndex(trace.product);\n }\n\n /**\n * Record an outcome for an existing trace.\n */\n async recordOutcome(\n product: string,\n traceId: string,\n outcome: NonNullable<Trace[\"outcome\"]>,\n accuracy: NonNullable<Trace[\"accuracy\"]>\n ): Promise<void> {\n const dir = join(this.baseDir, slugify(product));\n const path = join(dir, `${traceId}.jsonl`);\n\n const content = await readFile(path, \"utf-8\");\n const trace = JSON.parse(content.trim()) as Trace;\n trace.outcome = outcome;\n trace.accuracy = accuracy;\n\n await writeFile(path, JSON.stringify(trace) + \"\\n\");\n await this.updateIndex(product);\n }\n\n /**\n * Get all traces for a product.\n */\n async getProductHistory(product: string): Promise<ProductTraceHistory> {\n const dir = join(this.baseDir, slugify(product));\n const traces = await this.loadTraces(dir);\n\n return {\n product,\n traces,\n calibrationSummary: computeCalibrationSummary(traces, product),\n };\n }\n\n /**\n * Get recent traces for a product (for prompt injection).\n */\n async getRecentTraces(\n product: string,\n limit: number = 10\n ): Promise<Trace[]> {\n const dir = join(this.baseDir, slugify(product));\n const traces = await this.loadTraces(dir);\n return traces\n .sort(\n (a, b) =>\n new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()\n )\n .slice(0, limit);\n }\n\n /**\n * Get traces that have outcomes (for calibration history).\n */\n async getTracesWithOutcomes(product: string): Promise<Trace[]> {\n const dir = join(this.baseDir, slugify(product));\n const traces = await this.loadTraces(dir);\n return traces.filter((t) => t.outcome !== null);\n }\n\n /**\n * Get the calibration narrative for prompt injection.\n * This is the key method — it tells the LLM how accurate past predictions were.\n */\n async getCalibrationContext(product: string): Promise<string> {\n const history = await this.getProductHistory(product);\n return history.calibrationSummary.narrative;\n }\n\n /**\n * List all products that have traces.\n */\n async listProducts(): Promise<TraceIndex[]> {\n try {\n const dirs = await readdir(this.baseDir);\n const indices: TraceIndex[] = [];\n\n for (const dir of dirs) {\n const indexPath = join(this.baseDir, dir, \"index.jsonl\");\n try {\n const content = await readFile(indexPath, \"utf-8\");\n indices.push(JSON.parse(content.trim()) as TraceIndex);\n } catch {\n // No index yet\n }\n }\n\n return indices.sort(\n (a, b) =>\n new Date(b.lastTraceAt).getTime() -\n new Date(a.lastTraceAt).getTime()\n );\n } catch {\n return [];\n }\n }\n\n // -------------------------------------------------------------------------\n // Internal\n // -------------------------------------------------------------------------\n\n private async loadTraces(dir: string): Promise<Trace[]> {\n try {\n const files = await readdir(dir);\n const traces: Trace[] = [];\n\n for (const file of files) {\n if (file === \"index.jsonl\" || !file.endsWith(\".jsonl\")) continue;\n try {\n const content = await readFile(join(dir, file), \"utf-8\");\n traces.push(JSON.parse(content.trim()) as Trace);\n } catch {\n // Skip corrupt files\n }\n }\n\n return traces;\n } catch {\n return [];\n }\n }\n\n private async updateIndex(product: string): Promise<void> {\n const dir = join(this.baseDir, slugify(product));\n const traces = await this.loadTraces(dir);\n\n const withOutcomes = traces.filter((t) => t.outcome !== null);\n const maes = withOutcomes\n .map((t) => t.accuracy?.mae)\n .filter((m): m is number => m !== undefined && m !== null);\n\n const typeCounts: Record<string, number> = {};\n for (const t of traces) {\n typeCounts[t.type] = (typeCounts[t.type] ?? 0) + 1;\n }\n\n const index: TraceIndex = {\n product,\n traceCount: traces.length,\n withOutcomes: withOutcomes.length,\n averageAccuracy: maes.length > 0\n ? maes.reduce((a, b) => a + b, 0) / maes.length\n : null,\n lastTraceAt:\n traces.length > 0\n ? traces.sort(\n (a, b) =>\n new Date(b.timestamp).getTime() -\n new Date(a.timestamp).getTime()\n )[0].timestamp\n : new Date().toISOString(),\n traceTypes: typeCounts as Record<import(\"./types.js\").TraceType, number>,\n };\n\n await writeFile(join(dir, \"index.jsonl\"), JSON.stringify(index) + \"\\n\");\n }\n}\n\nfunction computeCalibrationSummary(\n traces: Trace[],\n product: string\n): ProductTraceHistory[\"calibrationSummary\"] {\n const withOutcomes = traces.filter((t) => t.outcome !== null);\n\n if (withOutcomes.length === 0) {\n return {\n totalPredictions: traces.length,\n withOutcomes: 0,\n averageMAE: null,\n directionAccuracy: null,\n narrative:\n traces.length > 0\n ? `${traces.length} predictions made for \"${product}\" but no outcomes recorded yet. Predictions are uncalibrated.`\n : `No prediction history for \"${product}\".`,\n };\n }\n\n const maes = withOutcomes\n .map((t) => t.accuracy?.mae)\n .filter((m): m is number => m !== undefined && m !== null);\n\n const directionResults = withOutcomes\n .map((t) => t.accuracy?.directionCorrect)\n .filter((d): d is boolean => d !== undefined && d !== null);\n\n const averageMAE =\n maes.length > 0 ? maes.reduce((a, b) => a + b, 0) / maes.length : null;\n\n const directionAccuracy =\n directionResults.length > 0\n ? directionResults.filter(Boolean).length / directionResults.length\n : null;\n\n // Build the narrative that gets injected into future prompts\n const parts: string[] = [];\n\n parts.push(\n `CALIBRATION HISTORY for \"${product}\": ${withOutcomes.length} predictions have been verified against real outcomes.`\n );\n\n if (averageMAE !== null) {\n if (averageMAE <= 0.05) {\n parts.push(\n `Average error: ${(averageMAE * 100).toFixed(1)}pp — excellent accuracy. Your predictions closely match reality.`\n );\n } else if (averageMAE <= 0.10) {\n parts.push(\n `Average error: ${(averageMAE * 100).toFixed(1)}pp — good accuracy. Minor adjustments may improve predictions.`\n );\n } else {\n parts.push(\n `Average error: ${(averageMAE * 100).toFixed(1)}pp — moderate accuracy. Be conservative with confidence levels.`\n );\n }\n }\n\n if (directionAccuracy !== null) {\n parts.push(\n `Directional accuracy: ${(directionAccuracy * 100).toFixed(0)}% of predictions got the direction right.`\n );\n }\n\n // Add specific lessons from past traces\n const overEstimates = withOutcomes.filter(\n (t) =>\n t.accuracy?.notes?.toLowerCase().includes(\"over\") ||\n (t.accuracy?.mae && t.accuracy.mae > 0.05)\n );\n\n const underEstimates = withOutcomes.filter(\n (t) => t.accuracy?.notes?.toLowerCase().includes(\"under\")\n );\n\n if (overEstimates.length > underEstimates.length) {\n parts.push(\n \"Tendency: you tend to OVER-estimate negative outcomes (churn, dissatisfaction). Adjust predictions slightly more optimistic.\"\n );\n } else if (underEstimates.length > overEstimates.length) {\n parts.push(\n \"Tendency: you tend to UNDER-estimate negative outcomes. Adjust predictions slightly more conservative.\"\n );\n }\n\n // Add the most recent verified prediction as context\n const mostRecent = withOutcomes.sort(\n (a, b) =>\n new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()\n )[0];\n\n if (mostRecent) {\n parts.push(\n `Most recent verified prediction: \"${mostRecent.prediction.summary}\" → Actual: \"${mostRecent.outcome!.actual}\" (${mostRecent.accuracy?.notes ?? \"no notes\"})`\n );\n }\n\n return {\n totalPredictions: traces.length,\n withOutcomes: withOutcomes.length,\n averageMAE,\n directionAccuracy,\n narrative: parts.join(\"\\n\"),\n };\n}\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n","/**\n * Composable survey builder.\n *\n * Design surveys programmatically, run them against persona panels,\n * get structured results with citations.\n *\n * @example\n * ```ts\n * const survey = new Survey(\"pricing-research\")\n * .freeText(\"pain\", \"What's the hardest part about managing expenses?\")\n * .multipleChoice(\"switch\", \"Would you switch to a cheaper alternative?\", [\n * \"Yes, immediately\",\n * \"Maybe, if it had the same features\",\n * \"No, I'm loyal to what I use\",\n * ])\n * .likert5(\"satisfaction\", \"How satisfied are you with your current tool?\")\n * .yesNo(\"recommend\", \"Would you recommend your current tool to a friend?\")\n * .numerical(\"budget\", \"What's the most you'd pay per month?\", 0, 100);\n *\n * const results = await survey.run(personas, llm);\n * ```\n */\n\nimport type {\n SurveyQuestion,\n SurveyResponse,\n SurveyResult,\n QuestionType,\n LLMProvider,\n SourceCitation,\n} from \"../types.js\";\nimport { Persona } from \"../persona/persona.js\";\n\nexport class Survey {\n readonly name: string;\n private questions: SurveyQuestion[] = [];\n\n constructor(name: string) {\n this.name = name;\n }\n\n freeText(name: string, text: string): this {\n this.questions.push({ name, text, type: \"free_text\" });\n return this;\n }\n\n multipleChoice(name: string, text: string, options: string[]): this {\n this.questions.push({ name, text, type: \"multiple_choice\", options });\n return this;\n }\n\n likert5(name: string, text: string): this {\n this.questions.push({\n name,\n text,\n type: \"likert_5\",\n options: [\n \"Strongly disagree\",\n \"Disagree\",\n \"Neutral\",\n \"Agree\",\n \"Strongly agree\",\n ],\n });\n return this;\n }\n\n likert7(name: string, text: string): this {\n this.questions.push({\n name,\n text,\n type: \"likert_7\",\n options: [\n \"Strongly disagree\",\n \"Disagree\",\n \"Somewhat disagree\",\n \"Neutral\",\n \"Somewhat agree\",\n \"Agree\",\n \"Strongly agree\",\n ],\n });\n return this;\n }\n\n yesNo(name: string, text: string): this {\n this.questions.push({ name, text, type: \"yes_no\" });\n return this;\n }\n\n numerical(name: string, text: string, min?: number, max?: number): this {\n this.questions.push({ name, text, type: \"numerical\", min, max });\n return this;\n }\n\n ranking(name: string, text: string, options: string[]): this {\n this.questions.push({ name, text, type: \"ranking\", options });\n return this;\n }\n\n /**\n * Run the survey against a panel of personas.\n */\n async run(personas: Persona[], llm: LLMProvider): Promise<SurveyResult> {\n const allResponses: SurveyResponse[] = [];\n\n for (const persona of personas) {\n for (const question of this.questions) {\n const response = await askSurveyQuestion(persona, question, llm);\n allResponses.push(response);\n }\n }\n\n // Build summary\n const summary: SurveyResult[\"summary\"] = {};\n for (const q of this.questions) {\n const qResponses = allResponses.filter((r) => r.questionName === q.name);\n summary[q.name] = {\n questionText: q.text,\n type: q.type,\n distribution: buildDistribution(q, qResponses),\n themes:\n q.type === \"free_text\"\n ? extractThemes(qResponses)\n : undefined,\n };\n }\n\n return {\n surveyName: this.name,\n questions: this.questions,\n responses: allResponses,\n summary,\n personaCount: personas.length,\n timestamp: new Date().toISOString(),\n };\n }\n\n getQuestions(): SurveyQuestion[] {\n return [...this.questions];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal\n// ---------------------------------------------------------------------------\n\nasync function askSurveyQuestion(\n persona: Persona,\n question: SurveyQuestion,\n llm: LLMProvider\n): Promise<SurveyResponse> {\n const formatInstruction = getFormatInstruction(question);\n\n const prompt = `Survey question: \"${question.text}\"\n\n${formatInstruction}\n\nRespond as JSON with this exact structure:\n{\n \"answer\": <your answer matching the format above>,\n \"reasoning\": \"<1-2 sentences explaining WHY you answered this way, referencing your personality traits or experiences>\",\n \"citations\": [\n {\"source\": \"<data source>\", \"detail\": \"<specific data point>\", \"weight\": <0-1>}\n ]\n}\n\nFor citations.source, use one of: \"trait-model\", \"behavioral-data\", \"support-data\", \"payment-data\", \"general-knowledge\"\nIf you have no specific data to cite, use \"trait-model\" and reference which personality trait drives this answer.`;\n\n const systemPrompt = persona.toPrompt();\n\n const raw = await llm.generateJSON<{\n answer: string | number | string[];\n reasoning: string;\n citations: SourceCitation[];\n }>(systemPrompt, prompt);\n\n return {\n personaId: persona.id,\n personaName: persona.name,\n questionName: question.name,\n questionText: question.text,\n answer: raw.answer,\n confidence: persona.definition.confidence,\n citations: raw.citations ?? [],\n reasoning: raw.reasoning ?? \"\",\n };\n}\n\nfunction getFormatInstruction(q: SurveyQuestion): string {\n switch (q.type) {\n case \"free_text\":\n return 'Answer in 1-3 sentences. Set \"answer\" to your text response.';\n case \"multiple_choice\":\n return `Choose ONE of these options exactly: ${q.options!.map((o) => `\"${o}\"`).join(\", \")}. Set \"answer\" to the chosen option string.`;\n case \"likert_5\":\n return `Rate on this scale: ${q.options!.join(\", \")}. Set \"answer\" to one of these exact strings.`;\n case \"likert_7\":\n return `Rate on this scale: ${q.options!.join(\", \")}. Set \"answer\" to one of these exact strings.`;\n case \"yes_no\":\n return 'Answer \"Yes\" or \"No\". Set \"answer\" to \"Yes\" or \"No\".';\n case \"numerical\":\n return `Provide a number${q.min != null ? ` (min: ${q.min}` : \"\"}${q.max != null ? `, max: ${q.max})` : q.min != null ? \")\" : \"\"}. Set \"answer\" to the number.`;\n case \"ranking\":\n return `Rank these from most to least important: ${q.options!.join(\", \")}. Set \"answer\" to an array of strings in your preferred order.`;\n }\n}\n\nfunction buildDistribution(\n q: SurveyQuestion,\n responses: SurveyResponse[]\n): Record<string, number> {\n const dist: Record<string, number> = {};\n\n if (q.type === \"free_text\") {\n // For free text, just count responses\n dist[\"total_responses\"] = responses.length;\n return dist;\n }\n\n for (const r of responses) {\n const key = String(r.answer);\n dist[key] = (dist[key] ?? 0) + 1;\n }\n\n return dist;\n}\n\nfunction extractThemes(responses: SurveyResponse[]): string[] {\n // Simple word frequency for theme extraction\n const wordCounts: Record<string, number> = {};\n for (const r of responses) {\n const words = String(r.answer)\n .toLowerCase()\n .split(/\\W+/)\n .filter((w) => w.length > 4);\n for (const w of words) {\n wordCounts[w] = (wordCounts[w] ?? 0) + 1;\n }\n }\n\n return Object.entries(wordCounts)\n .filter(([, count]) => count >= 2)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([word]) => word);\n}\n","/**\n * Focus Group simulation.\n *\n * Multiple personas in the same conversation, reacting to each other —\n * not just answering in isolation. Supports:\n * - Context injection mid-session (product changes, news, competitor moves)\n * - Opinion shift tracking (personas can change their minds)\n * - Information asymmetry (each persona can have private context)\n *\n * Supports:\n * - Shared environments with turn-based discussion\n * - Stimuli injection and information asymmetry\n * - Memory-driven reflection and opinion shifts\n *\n * @example\n * ```ts\n * const group = new FocusGroup(\"pricing-discussion\", {\n * topic: \"Should we raise prices from $10 to $15/mo?\",\n * product: \"project management SaaS\",\n * });\n *\n * group.addPersonas(personas);\n *\n * // Give specific personas private context\n * group.setPersonaContext(\"Sarah\", [\"You cancelled your subscription last month\"]);\n * group.setPersonaContext(\"Mike\", [\"You upgraded to the pro plan 2 weeks ago\"]);\n *\n * const round1 = await group.discuss(llm);\n *\n * // Inject new context mid-discussion\n * group.inject(\"A competitor just launched a free tier\");\n *\n * const round2 = await group.discuss(llm);\n *\n * // Check who changed their mind\n * const shifts = group.getOpinionShifts();\n *\n * const summary = await group.summarize(llm);\n * ```\n */\n\nimport type {\n LLMProvider,\n SourceCitation,\n} from \"../types.js\";\nimport { Persona } from \"../persona/persona.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface FocusGroupConfig {\n /** The main topic or question being discussed */\n topic: string;\n /** Product or brand context */\n product?: string;\n /** Maximum discussion rounds (default: 5) */\n maxRounds?: number;\n /** Per-persona private context. Key = persona name or ID. */\n personaContext?: Record<string, string[]>;\n}\n\nexport interface OpinionShift {\n personaId: string;\n personaName: string;\n previousPosition: string;\n newPosition: string;\n trigger: {\n personaName: string;\n message: string;\n };\n round: number;\n reasoning: string;\n}\n\nexport interface FocusGroupMessage {\n round: number;\n personaId: string;\n personaName: string;\n message: string;\n reasoning: string;\n citations: SourceCitation[];\n /** What this message was reacting to */\n reactingTo: string | null;\n /** Did this persona change their mind this turn? */\n opinionShift: {\n previousPosition: string;\n newPosition: string;\n triggeredBy: string;\n } | null;\n timestamp: string;\n}\n\nexport interface FocusGroupRound {\n round: number;\n messages: FocusGroupMessage[];\n /** Any context injected before this round */\n injectedContext: string | null;\n}\n\ninterface StructuredGroupResponse {\n message: string;\n reasoning: string;\n citations: SourceCitation[];\n opinionShift: {\n previousPosition: string;\n newPosition: string;\n triggeredBy: string;\n } | null;\n}\n\n// ---------------------------------------------------------------------------\n// FocusGroup\n// ---------------------------------------------------------------------------\n\nexport class FocusGroup {\n readonly name: string;\n readonly config: FocusGroupConfig;\n private personas: Persona[] = [];\n private rounds: FocusGroupRound[] = [];\n private currentRound = 0;\n private injectedContextQueue: string[] = [];\n private privateContext: Record<string, string[]> = {};\n\n constructor(name: string, config: FocusGroupConfig) {\n this.name = name;\n this.config = config;\n if (config.personaContext) {\n this.privateContext = { ...config.personaContext };\n }\n }\n\n addPersonas(personas: Persona[]): void {\n this.personas.push(...personas);\n }\n\n /**\n * Set private context for a specific persona.\n * This information is only visible to them — other participants don't see it.\n */\n setPersonaContext(nameOrId: string, context: string[]): void {\n this.privateContext[nameOrId] = context;\n }\n\n /**\n * Inject new context visible to ALL personas at the start of the next round.\n */\n inject(context: string): void {\n this.injectedContextQueue.push(context);\n }\n\n /**\n * Run one round of discussion.\n * Each persona speaks in turn, seeing what others said before them.\n * Personas can change their minds if persuaded.\n */\n async discuss(llm: LLMProvider): Promise<FocusGroupRound> {\n this.currentRound++;\n const maxRounds = this.config.maxRounds ?? 5;\n\n if (this.currentRound > maxRounds) {\n throw new Error(\n `Maximum rounds (${maxRounds}) reached. Create a new focus group or increase maxRounds.`\n );\n }\n\n const injected =\n this.injectedContextQueue.length > 0\n ? this.injectedContextQueue.splice(0).join(\"\\n\")\n : null;\n\n const messages: FocusGroupMessage[] = [];\n const roundMessages: string[] = [];\n\n // Shuffle speaking order to avoid bias\n const speakingOrder = [...this.personas].sort(() => Math.random() - 0.5);\n\n for (const persona of speakingOrder) {\n // Look up private context for this persona (by name or ID)\n const personalContext =\n this.privateContext[persona.name] ??\n this.privateContext[persona.id] ??\n null;\n\n const prompt = buildGroupPrompt(\n this.config,\n this.currentRound,\n this.getRecentHistory(3),\n roundMessages,\n injected,\n persona.name,\n personalContext\n );\n\n const response = await llm.generateJSON<StructuredGroupResponse>(\n persona.toPrompt() + GROUP_INSTRUCTIONS,\n prompt\n );\n\n const msg: FocusGroupMessage = {\n round: this.currentRound,\n personaId: persona.id,\n personaName: persona.name,\n message: response.message,\n reasoning: response.reasoning,\n citations: response.citations ?? [],\n reactingTo:\n roundMessages.length > 0\n ? roundMessages[roundMessages.length - 1]\n : null,\n opinionShift: response.opinionShift ?? null,\n timestamp: new Date().toISOString(),\n };\n\n messages.push(msg);\n roundMessages.push(`${persona.name}: ${response.message}`);\n }\n\n const round: FocusGroupRound = {\n round: this.currentRound,\n messages,\n injectedContext: injected,\n };\n\n this.rounds.push(round);\n return round;\n }\n\n /**\n * Get all opinion shifts across all rounds.\n */\n getOpinionShifts(): OpinionShift[] {\n const shifts: OpinionShift[] = [];\n for (const round of this.rounds) {\n for (const msg of round.messages) {\n if (msg.opinionShift) {\n shifts.push({\n personaId: msg.personaId,\n personaName: msg.personaName,\n previousPosition: msg.opinionShift.previousPosition,\n newPosition: msg.opinionShift.newPosition,\n trigger: {\n personaName: msg.opinionShift.triggeredBy,\n message:\n round.messages.find(\n (m) => m.personaName === msg.opinionShift!.triggeredBy\n )?.message ?? \"\",\n },\n round: round.round,\n reasoning: msg.reasoning,\n });\n }\n }\n }\n return shifts;\n }\n\n getTranscript(): FocusGroupRound[] {\n return [...this.rounds];\n }\n\n getAllMessages(): FocusGroupMessage[] {\n return this.rounds.flatMap((r) => r.messages);\n }\n\n /**\n * Summarize the discussion — themes, agreements, disagreements, insights, opinion shifts.\n */\n async summarize(\n llm: LLMProvider\n ): Promise<{\n themes: string[];\n agreements: string[];\n disagreements: string[];\n insights: string[];\n opinionShifts: OpinionShift[];\n }> {\n const transcript = this.rounds\n .flatMap((r) => {\n const lines: string[] = [];\n if (r.injectedContext) {\n lines.push(`[MODERATOR: ${r.injectedContext}]`);\n }\n for (const m of r.messages) {\n let line = `${m.personaName}: ${m.message}`;\n if (m.opinionShift) {\n line += ` [SHIFTED: from \"${m.opinionShift.previousPosition}\" to \"${m.opinionShift.newPosition}\", triggered by ${m.opinionShift.triggeredBy}]`;\n }\n lines.push(line);\n }\n return lines;\n })\n .join(\"\\n\");\n\n const analysis = await llm.generateJSON<{\n themes: string[];\n agreements: string[];\n disagreements: string[];\n insights: string[];\n }>(\n \"You are a focus group moderator analyzing a discussion transcript.\",\n `Analyze this focus group discussion about \"${this.config.topic}\" for ${this.config.product ?? \"a product\"}.\n\nTranscript:\n${transcript}\n\nReturn JSON with:\n- themes: top 3-5 recurring themes\n- agreements: points where most participants agreed\n- disagreements: points of contention or split opinions\n- insights: 2-3 non-obvious insights that emerged from the discussion (pay special attention to opinion shifts — when someone changed their mind, that's often the most revealing moment)`\n );\n\n return {\n ...analysis,\n opinionShifts: this.getOpinionShifts(),\n };\n }\n\n get roundCount(): number {\n return this.currentRound;\n }\n\n get size(): number {\n return this.personas.length;\n }\n\n // -------------------------------------------------------------------------\n // Internal\n // -------------------------------------------------------------------------\n\n private getRecentHistory(rounds: number): string[] {\n const recent = this.rounds.slice(-rounds);\n const lines: string[] = [];\n for (const r of recent) {\n if (r.injectedContext) {\n lines.push(`[New information: ${r.injectedContext}]`);\n }\n for (const m of r.messages) {\n lines.push(`${m.personaName}: ${m.message}`);\n }\n }\n return lines;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Prompt building\n// ---------------------------------------------------------------------------\n\nfunction buildGroupPrompt(\n config: FocusGroupConfig,\n round: number,\n history: string[],\n currentRoundMessages: string[],\n injectedContext: string | null,\n speakerName: string,\n personalContext: string[] | null\n): string {\n const parts: string[] = [];\n\n parts.push(\n `You are in a focus group discussion (round ${round}) about: \"${config.topic}\"`\n );\n\n if (config.product) {\n parts.push(`Product: ${config.product}`);\n }\n\n // Private context — only this persona sees this\n if (personalContext && personalContext.length > 0) {\n parts.push(\n `INFORMATION ONLY YOU HAVE (other participants don't know this):\\n${personalContext.map((c) => `- ${c}`).join(\"\\n\")}`\n );\n }\n\n if (history.length > 0) {\n parts.push(`Previous discussion:\\n${history.join(\"\\n\")}`);\n }\n\n if (injectedContext) {\n parts.push(\n `NEW INFORMATION just shared by the moderator:\\n\"${injectedContext}\"\\n\\nReact to this new information in your response.`\n );\n }\n\n if (currentRoundMessages.length > 0) {\n parts.push(\n `Others have already spoken this round:\\n${currentRoundMessages.join(\"\\n\")}\\n\\nYou may agree, disagree, or build on what they said.`\n );\n }\n\n parts.push(`Now it's YOUR turn to speak, ${speakerName}.\n\nRespond as JSON:\n{\n \"message\": \"<your contribution, 2-4 sentences. Be natural — agree, disagree, ask questions, share experiences>\",\n \"reasoning\": \"<which personality traits drive your perspective here>\",\n \"citations\": [{\"source\": \"<source>\", \"detail\": \"<detail>\", \"weight\": <0-1>}],\n \"opinionShift\": null\n}\n\nIf someone genuinely changed your mind this round, set opinionShift to:\n{\n \"previousPosition\": \"<what you believed before>\",\n \"newPosition\": \"<what you believe now>\",\n \"triggeredBy\": \"<name of person whose argument convinced you>\"\n}\nOtherwise keep opinionShift as null. Opinion shifts should be rare and genuine.`);\n\n return parts.join(\"\\n\\n\");\n}\n\nconst GROUP_INSTRUCTIONS = `\n\nFOCUS GROUP RULES:\n- You are in a group discussion with other people. React to what others say.\n- Be natural — agree, disagree, interrupt, ask questions, share personal experiences.\n- Don't just answer the moderator's question. Engage with other participants.\n- If someone says something you disagree with, push back respectfully.\n- If new information is introduced, react honestly — it might change your mind or reinforce your position.\n- If someone makes a genuinely persuasive point, you ARE allowed to change your mind. Be honest about it.\n- Opinion shifts should be rare and meaningful. Don't flip-flop — only shift when truly convinced.\n- If you have private information, you may reference it naturally without explicitly saying \"I have private info.\"\n- Stay in character. Your personality traits should be obvious from how you interact.\n- Keep responses to 2-4 sentences. This is a conversation, not a monologue.`;\n","/**\n * DoppleGraph — in-memory knowledge graph with persistence.\n */\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport type { LLMProvider, UserRecord, OceanVector } from \"../types.js\";\nimport type { KnowledgeGraph, GraphNode, GraphPattern } from \"./types.js\";\nimport { buildGraph } from \"./builder.js\";\nimport {\n getNeighborhood,\n findPatterns,\n getPersonaContext,\n graphSummary,\n queryGraph,\n type GraphQueryResult,\n} from \"./query.js\";\n\nexport class DoppleGraph {\n private graph: KnowledgeGraph | null = null;\n\n /**\n * Build the graph from user data and optional context texts.\n */\n async build(\n llm: LLMProvider,\n userData: UserRecord[],\n contextTexts?: string[]\n ): Promise<void> {\n this.graph = await buildGraph(llm, userData, contextTexts);\n }\n\n /**\n * Get nodes within N hops of a starting node.\n */\n query(nodeId: string, depth: number = 2): GraphNode[] {\n this.ensureBuilt();\n return getNeighborhood(this.graph!, nodeId, depth);\n }\n\n /**\n * Get context strings relevant to a persona's traits.\n */\n getPersonaContext(traits: OceanVector): string[] {\n this.ensureBuilt();\n return getPersonaContext(this.graph!, traits);\n }\n\n /**\n * Find patterns in the graph (hubs, churn, complaints, usage).\n */\n patterns(): GraphPattern[] {\n this.ensureBuilt();\n return findPatterns(this.graph!);\n }\n\n /**\n * Human-readable summary.\n */\n get summary(): string {\n this.ensureBuilt();\n return graphSummary(this.graph!);\n }\n\n /**\n * Get the raw graph data.\n */\n get raw(): KnowledgeGraph {\n this.ensureBuilt();\n return this.graph!;\n }\n\n /**\n * Ask the graph a natural language question.\n * LLM interprets the question, finds relevant data, returns grounded answer.\n */\n async ask(llm: LLMProvider, question: string): Promise<GraphQueryResult> {\n this.ensureBuilt();\n return queryGraph(llm, this.graph!, question);\n }\n\n get isBuilt(): boolean {\n return this.graph !== null;\n }\n\n /**\n * Save graph to JSONL file.\n */\n async save(path: string): Promise<void> {\n this.ensureBuilt();\n await mkdir(dirname(path), { recursive: true });\n\n const lines: string[] = [];\n lines.push(JSON.stringify({ _type: \"metadata\", ...this.graph!.metadata }));\n for (const node of this.graph!.nodes) {\n lines.push(JSON.stringify({ _type: \"node\", ...node }));\n }\n for (const edge of this.graph!.edges) {\n lines.push(JSON.stringify({ _type: \"edge\", ...edge }));\n }\n\n await writeFile(path, lines.join(\"\\n\") + \"\\n\");\n }\n\n /**\n * Load graph from JSONL file.\n */\n async load(path: string): Promise<void> {\n const content = await readFile(path, \"utf-8\");\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n\n const nodes: GraphNode[] = [];\n const edges: import(\"./types.js\").GraphEdge[] = [];\n let metadata: KnowledgeGraph[\"metadata\"] = {\n createdAt: \"\",\n sources: [],\n nodeCount: 0,\n edgeCount: 0,\n };\n\n for (const line of lines) {\n const obj = JSON.parse(line) as Record<string, unknown>;\n const type = obj._type;\n delete obj._type;\n\n if (type === \"metadata\") {\n metadata = obj as unknown as KnowledgeGraph[\"metadata\"];\n } else if (type === \"node\") {\n nodes.push(obj as unknown as GraphNode);\n } else if (type === \"edge\") {\n edges.push(obj as unknown as import(\"./types.js\").GraphEdge);\n }\n }\n\n this.graph = { nodes, edges, metadata };\n }\n\n private ensureBuilt(): void {\n if (!this.graph) {\n throw new Error(\"Graph not built yet. Call build() or load() first.\");\n }\n }\n}\n","/**\n * Knowledge graph builder.\n *\n * Two modes:\n * 1. Structured: UserRecord[] from adapters → deterministic entity extraction → graph\n * 2. Unstructured: freeform text → LLM entity extraction → graph\n *\n * No external graph database dependency — in-memory with JSONL persistence.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { LLMProvider, UserRecord } from \"../types.js\";\nimport type { GraphNode, GraphEdge, KnowledgeGraph, NodeType } from \"./types.js\";\n\n/**\n * Build a knowledge graph from structured user data and optional unstructured context.\n */\nexport async function buildGraph(\n llm: LLMProvider,\n userData: UserRecord[],\n contextTexts?: string[]\n): Promise<KnowledgeGraph> {\n const nodes: GraphNode[] = [];\n const edges: GraphEdge[] = [];\n const sources = new Set<string>();\n\n // Phase 1: Extract entities from structured data (deterministic)\n extractFromUserData(userData, nodes, edges, sources);\n\n // Phase 2: Extract entities from unstructured text (LLM-assisted)\n if (contextTexts && contextTexts.length > 0) {\n await extractFromText(llm, contextTexts, nodes, edges, sources);\n }\n\n // Phase 3: Ask LLM to discover relationships between existing nodes\n if (nodes.length > 2) {\n await discoverRelationships(llm, nodes, edges);\n }\n\n // Deduplicate nodes by label (case-insensitive)\n const deduped = deduplicateNodes(nodes, edges);\n\n return {\n nodes: deduped.nodes,\n edges: deduped.edges,\n metadata: {\n createdAt: new Date().toISOString(),\n sources: [...sources],\n nodeCount: deduped.nodes.length,\n edgeCount: deduped.edges.length,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Phase 1: Structured data extraction\n// ---------------------------------------------------------------------------\n\nfunction extractFromUserData(\n records: UserRecord[],\n nodes: GraphNode[],\n edges: GraphEdge[],\n sources: Set<string>\n): void {\n // Track feature/event usage across users\n const eventCounts: Record<string, number> = {};\n const propertyValues: Record<string, Record<string, number>> = {};\n\n for (const record of records) {\n sources.add(\"user-data\");\n\n // Create user node\n const userId = `user-${record.id}`;\n nodes.push({\n id: userId,\n type: \"user\",\n label: String(record.properties.name ?? record.properties.email ?? record.id),\n properties: record.properties,\n source: \"user-data\",\n });\n\n // Extract events as behavior/feature nodes\n for (const event of record.events ?? []) {\n eventCounts[event.name] = (eventCounts[event.name] ?? 0) + 1;\n\n const eventNodeId = `event-${event.name}`;\n if (!nodes.some((n) => n.id === eventNodeId)) {\n nodes.push({\n id: eventNodeId,\n type: \"event\",\n label: event.name,\n properties: { totalOccurrences: 0 },\n source: \"user-data\",\n });\n }\n\n // Edge: user → uses → event\n edges.push({\n id: randomUUID(),\n from: userId,\n to: eventNodeId,\n type: \"triggered\",\n weight: 0.5,\n properties: { timestamp: event.timestamp },\n source: \"user-data\",\n });\n }\n\n // Extract payment data\n for (const payment of record.payments ?? []) {\n const planId = `plan-${payment.plan ?? \"unknown\"}`;\n if (!nodes.some((n) => n.id === planId)) {\n nodes.push({\n id: planId,\n type: \"product\",\n label: payment.plan ?? \"unknown plan\",\n properties: { amount: payment.amount, currency: payment.currency },\n source: \"payment-data\",\n });\n }\n\n edges.push({\n id: randomUUID(),\n from: userId,\n to: planId,\n type: payment.status === \"cancelled\" ? \"cancelled\" : \"subscribes_to\",\n weight: payment.status === \"active\" ? 0.8 : 0.3,\n properties: {\n status: payment.status,\n cancelReason: payment.cancelReason,\n },\n source: \"payment-data\",\n });\n\n // Create complaint node from cancel reason\n if (payment.cancelReason) {\n const complaintId = `complaint-${randomUUID().slice(0, 8)}`;\n nodes.push({\n id: complaintId,\n type: \"complaint\",\n label: payment.cancelReason,\n properties: {},\n source: \"payment-data\",\n });\n edges.push({\n id: randomUUID(),\n from: userId,\n to: complaintId,\n type: \"complained_about\",\n weight: 0.9,\n properties: {},\n source: \"payment-data\",\n });\n }\n }\n\n // Extract conversations as complaint/feature nodes\n for (const conv of record.conversations ?? []) {\n const convId = `conv-${randomUUID().slice(0, 8)}`;\n nodes.push({\n id: convId,\n type: \"complaint\",\n label: conv.slice(0, 100),\n properties: { fullText: conv },\n source: \"support-data\",\n });\n edges.push({\n id: randomUUID(),\n from: userId,\n to: convId,\n type: \"said\",\n weight: 0.7,\n properties: {},\n source: \"support-data\",\n });\n }\n\n // Track property distributions\n for (const [key, value] of Object.entries(record.properties)) {\n if (!propertyValues[key]) propertyValues[key] = {};\n const v = String(value);\n propertyValues[key][v] = (propertyValues[key][v] ?? 0) + 1;\n }\n }\n\n // Update event occurrence counts\n for (const node of nodes) {\n if (node.type === \"event\" && eventCounts[node.label]) {\n node.properties.totalOccurrences = eventCounts[node.label];\n }\n }\n\n // Create metric nodes for key aggregates\n const totalUsers = records.length;\n if (totalUsers > 0) {\n nodes.push({\n id: \"metric-total-users\",\n type: \"metric\",\n label: `${totalUsers} total users`,\n properties: { value: totalUsers },\n source: \"user-data\",\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2: Unstructured text extraction\n// ---------------------------------------------------------------------------\n\nasync function extractFromText(\n llm: LLMProvider,\n texts: string[],\n nodes: GraphNode[],\n edges: GraphEdge[],\n sources: Set<string>\n): Promise<void> {\n sources.add(\"context\");\n\n const combined = texts.join(\"\\n\\n\").slice(0, 8000); // Cap context length\n\n const extracted = await llm.generateJSON<{\n entities: Array<{\n label: string;\n type: NodeType;\n properties: Record<string, unknown>;\n }>;\n relationships: Array<{\n from: string;\n to: string;\n type: string;\n weight: number;\n }>;\n }>(\n \"You extract entities and relationships from text to build a knowledge graph.\",\n `Extract entities and relationships from this text.\n\n${combined}\n\nReturn JSON:\n{\n \"entities\": [{\"label\": \"<name>\", \"type\": \"user|behavior|feature|complaint|segment|event|product|metric|entity\", \"properties\": {}}],\n \"relationships\": [{\"from\": \"<entity label>\", \"to\": \"<entity label>\", \"type\": \"<relationship>\", \"weight\": <0-1>}]\n}\n\nFocus on: people, products, features, complaints, behaviors, events, and how they relate.\nUse descriptive relationship types like \"uses\", \"complained_about\", \"wants\", \"avoids\", \"correlates_with\".`\n );\n\n const labelToId: Record<string, string> = {};\n\n for (const entity of extracted.entities ?? []) {\n const id = `ctx-${randomUUID().slice(0, 8)}`;\n labelToId[entity.label] = id;\n nodes.push({\n id,\n type: entity.type,\n label: entity.label,\n properties: entity.properties ?? {},\n source: \"context\",\n });\n }\n\n for (const rel of extracted.relationships ?? []) {\n const fromId = labelToId[rel.from];\n const toId = labelToId[rel.to];\n if (fromId && toId) {\n edges.push({\n id: randomUUID(),\n from: fromId,\n to: toId,\n type: rel.type,\n weight: rel.weight ?? 0.5,\n properties: {},\n source: \"context\",\n });\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Phase 3: Relationship discovery\n// ---------------------------------------------------------------------------\n\nasync function discoverRelationships(\n llm: LLMProvider,\n nodes: GraphNode[],\n edges: GraphEdge[]\n): Promise<void> {\n // Only use non-user nodes for relationship discovery (to keep prompt small)\n const significantNodes = nodes\n .filter((n) => n.type !== \"user\")\n .slice(0, 30)\n .map((n) => `${n.type}:${n.label}`);\n\n if (significantNodes.length < 3) return;\n\n const discovered = await llm.generateJSON<\n Array<{ from: string; to: string; type: string; weight: number }>\n >(\n \"You identify non-obvious relationships between entities in a product knowledge graph.\",\n `Given these entities from a product's user data, identify relationships between them that aren't already obvious.\n\nEntities:\n${significantNodes.join(\"\\n\")}\n\nReturn a JSON array of relationships:\n[{\"from\": \"<entity label>\", \"to\": \"<entity label>\", \"type\": \"<relationship>\", \"weight\": <0-1>}]\n\nFocus on: correlations (\"users who do X also do Y\"), causal patterns (\"feature X leads to churn\"), and segments (\"these behaviors cluster together\").\nReturn at most 10 relationships. Only include ones with real analytical value.`\n );\n\n for (const rel of discovered ?? []) {\n const fromNode = nodes.find((n) => n.label === rel.from.replace(/^\\w+:/, \"\"));\n const toNode = nodes.find((n) => n.label === rel.to.replace(/^\\w+:/, \"\"));\n if (fromNode && toNode) {\n edges.push({\n id: randomUUID(),\n from: fromNode.id,\n to: toNode.id,\n type: rel.type,\n weight: rel.weight ?? 0.5,\n properties: { discovered: true },\n source: \"llm-analysis\",\n });\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Deduplication\n// ---------------------------------------------------------------------------\n\nfunction deduplicateNodes(\n nodes: GraphNode[],\n edges: GraphEdge[]\n): { nodes: GraphNode[]; edges: GraphEdge[] } {\n const seen = new Map<string, GraphNode>();\n const idMapping = new Map<string, string>();\n\n for (const node of nodes) {\n const key = `${node.type}:${node.label.toLowerCase()}`;\n if (seen.has(key)) {\n idMapping.set(node.id, seen.get(key)!.id);\n } else {\n seen.set(key, node);\n }\n }\n\n // Remap edge references\n const remappedEdges = edges.map((e) => ({\n ...e,\n from: idMapping.get(e.from) ?? e.from,\n to: idMapping.get(e.to) ?? e.to,\n }));\n\n // Remove self-referencing edges\n const cleanEdges = remappedEdges.filter((e) => e.from !== e.to);\n\n return { nodes: [...seen.values()], edges: cleanEdges };\n}\n","/**\n * Insight discovery engine.\n *\n * No hardcoded categories. Analyzes your data + graph patterns + persona\n * responses to discover what matters. Questions are generated dynamically\n * based on what the data reveals.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type {\n LLMProvider,\n UserRecord,\n Segment,\n Insight,\n InsightEvidence,\n} from \"../types.js\";\nimport type { KnowledgeGraph, GraphPattern } from \"../graph/types.js\";\nimport { Persona } from \"../persona/persona.js\";\n\ninterface DiscoveredQuestion {\n question: string;\n rationale: string;\n relevantSegments: string[];\n}\n\n/**\n * Discover insights from data, segments, graph, and persona responses.\n *\n * Pipeline:\n * 1. Analyze data patterns + graph patterns\n * 2. Generate targeted questions based on what the data reveals\n * 3. Run questions against segment personas\n * 4. Cross-reference persona responses with real data\n * 5. Score and rank insights\n */\nexport async function discoverInsights(\n llm: LLMProvider,\n userData: UserRecord[],\n segments: Segment[],\n personas: Persona[],\n product: string,\n graph?: KnowledgeGraph,\n graphPatterns?: GraphPattern[]\n): Promise<Insight[]> {\n // Step 1: Analyze data to discover what questions to ask\n const questions = await generateQuestions(\n llm,\n userData,\n segments,\n product,\n graphPatterns\n );\n\n // Step 2: Ask personas the discovered questions\n const personaResponses: Array<{\n question: string;\n personaName: string;\n segmentName: string;\n response: string;\n }> = [];\n\n for (const q of questions) {\n for (const persona of personas) {\n const r = await persona.ask(q.question);\n const segment = segments.find((s) => s.persona.id === persona.id);\n personaResponses.push({\n question: q.question,\n personaName: persona.name,\n segmentName: segment?.name ?? \"unknown\",\n response: r.response,\n });\n }\n }\n\n // Step 3: Cross-reference and synthesize insights\n const insights = await synthesizeInsights(\n llm,\n userData,\n segments,\n personaResponses,\n product,\n graphPatterns\n );\n\n // Step 4: Score and rank\n return insights\n .sort((a, b) => b.impact * b.confidence - a.impact * a.confidence)\n .slice(0, 7);\n}\n\n// ---------------------------------------------------------------------------\n// Step 1: Generate questions from data\n// ---------------------------------------------------------------------------\n\nasync function generateQuestions(\n llm: LLMProvider,\n userData: UserRecord[],\n segments: Segment[],\n product: string,\n graphPatterns?: GraphPattern[]\n): Promise<DiscoveredQuestion[]> {\n const dataSummary = buildDataSummary(userData);\n const segmentSummary = segments\n .map(\n (s) =>\n `\"${s.name}\" (${s.percentage}%): ${s.description}. Patterns: ${s.patterns.join(\", \")}`\n )\n .join(\"\\n\");\n\n const patternSummary = graphPatterns\n ? graphPatterns\n .slice(0, 5)\n .map((p) => p.description)\n .join(\"\\n\")\n : \"No graph patterns available.\";\n\n const questions = await llm.generateJSON<DiscoveredQuestion[]>(\n `You are a product analyst. Given user data, segments, and patterns, you generate the most important questions to ask users to uncover actionable insights. Do NOT use generic questions — every question must be grounded in something specific from the data.`,\n `Product: ${product}\n\nUser Data Summary:\n${dataSummary}\n\nDiscovered Segments:\n${segmentSummary}\n\nGraph Patterns:\n${patternSummary}\n\nGenerate 5-8 targeted questions that would reveal the most valuable product insights. Each question should be grounded in a specific data pattern or anomaly you noticed.\n\nReturn JSON array:\n[{\n \"question\": \"<specific question grounded in the data>\",\n \"rationale\": \"<why this question matters — what data pattern prompted it>\",\n \"relevantSegments\": [\"<segment names this question is most relevant to>\"]\n}]\n\nFocus on questions that would reveal:\n- Why users behave the way they do (motivation behind data patterns)\n- What would change their behavior (actionable)\n- Where the biggest opportunities or risks are\n- Discrepancies between what different segments might want`\n );\n\n return questions ?? [];\n}\n\n// ---------------------------------------------------------------------------\n// Step 3: Synthesize insights\n// ---------------------------------------------------------------------------\n\nasync function synthesizeInsights(\n llm: LLMProvider,\n userData: UserRecord[],\n segments: Segment[],\n personaResponses: Array<{\n question: string;\n personaName: string;\n segmentName: string;\n response: string;\n }>,\n product: string,\n graphPatterns?: GraphPattern[]\n): Promise<Insight[]> {\n const responsesSummary = personaResponses\n .map(\n (r) =>\n `[${r.segmentName}] ${r.personaName}: \"${r.response}\" (Q: ${r.question})`\n )\n .join(\"\\n\");\n\n const dataSummary = buildDataSummary(userData);\n\n const patternSummary = graphPatterns\n ? graphPatterns\n .slice(0, 5)\n .map((p) => p.description)\n .join(\"\\n\")\n : \"\";\n\n const raw = await llm.generateJSON<\n Array<{\n title: string;\n description: string;\n confidence: number;\n impact: number;\n evidence: InsightEvidence[];\n recommendation: string;\n segments: string[];\n }>\n >(\n `You are a senior product analyst synthesizing insights from user data, persona responses, and behavioral patterns. Your insights must be:\n1. ACTIONABLE — each insight has a clear \"do this\" recommendation\n2. GROUNDED — every claim cites specific data or persona responses\n3. NON-OBVIOUS — don't just restate the data, find the \"so what?\"\n4. HONEST — if confidence is low, say so. Don't inflate.`,\n `Product: ${product}\n\nReal User Data:\n${dataSummary}\n\n${patternSummary ? `Graph Patterns:\\n${patternSummary}\\n` : \"\"}\n\nPersona Responses:\n${responsesSummary}\n\nSynthesize 3-7 actionable insights. For each insight:\n- Cross-reference what personas said with what the real data shows\n- Look for discrepancies (persona says X but data shows Y — these are gold)\n- Look for agreement across segments (strong signal)\n- Look for polarization between segments (opportunity for segmented approach)\n\nReturn JSON array:\n[{\n \"title\": \"<short, punchy insight title>\",\n \"description\": \"<2-3 sentence explanation>\",\n \"confidence\": <0.0-1.0>,\n \"impact\": <0.0-1.0>,\n \"evidence\": [{\"type\": \"data_pattern|persona_response|graph_pattern|cross_reference\", \"source\": \"<source>\", \"detail\": \"<specific evidence>\", \"weight\": <0-1>}],\n \"recommendation\": \"<specific action to take>\",\n \"segments\": [\"<which segments this applies to>\"]\n}]`\n );\n\n return (raw ?? []).map((r) => ({\n id: randomUUID(),\n ...r,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildDataSummary(records: UserRecord[]): string {\n const lines: string[] = [`Total users: ${records.length}`];\n\n // Property distributions\n const propCounts: Record<string, Record<string, number>> = {};\n for (const r of records) {\n for (const [key, value] of Object.entries(r.properties)) {\n if (key.startsWith(\"_\")) continue;\n if (!propCounts[key]) propCounts[key] = {};\n const v = String(value);\n propCounts[key][v] = (propCounts[key][v] ?? 0) + 1;\n }\n }\n\n for (const [prop, values] of Object.entries(propCounts)) {\n const sorted = Object.entries(values)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5);\n const total = records.length;\n const dist = sorted\n .map(([v, c]) => `${v} (${((c / total) * 100).toFixed(0)}%)`)\n .join(\", \");\n lines.push(`${prop}: ${dist}`);\n }\n\n // Events\n const eventCounts: Record<string, number> = {};\n for (const r of records) {\n for (const e of r.events ?? []) {\n eventCounts[e.name] = (eventCounts[e.name] ?? 0) + 1;\n }\n }\n if (Object.keys(eventCounts).length > 0) {\n const top = Object.entries(eventCounts)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n .map(([n, c]) => `${n} (${c}x)`)\n .join(\", \");\n lines.push(`Top events: ${top}`);\n }\n\n // Payments\n const statuses: Record<string, number> = {};\n const cancelReasons: string[] = [];\n for (const r of records) {\n for (const p of r.payments ?? []) {\n statuses[p.status] = (statuses[p.status] ?? 0) + 1;\n if (p.cancelReason) cancelReasons.push(p.cancelReason);\n }\n }\n if (Object.keys(statuses).length > 0) {\n lines.push(\n `Payments: ${Object.entries(statuses).map(([s, c]) => `${s}: ${c}`).join(\", \")}`\n );\n }\n if (cancelReasons.length > 0) {\n lines.push(`Cancel reasons: ${cancelReasons.slice(0, 5).join(\"; \")}`);\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Insights pipeline orchestrator.\n *\n * The main product feature. Connects data → builds graph → discovers segments →\n * generates personas → discovers insights → returns actionable report.\n *\n * @example\n * ```ts\n * const report = await runInsightsPipeline(dopple, llm, {\n * product: \"my SaaS app\",\n * useGraph: true,\n * });\n * console.log(report.insights); // 3-7 actionable insights\n * ```\n */\n\nimport type {\n LLMProvider,\n InsightReport,\n ConfidenceLevel,\n} from \"../types.js\";\nimport type { Dopple } from \"../engine.js\";\nimport { Persona } from \"../persona/persona.js\";\nimport { discoverInsights } from \"./analyzer.js\";\nimport type { KnowledgeGraph, GraphPattern } from \"../graph/types.js\";\n\nexport interface InsightsOptions {\n product: string;\n /** Build and use a knowledge graph for deeper analysis */\n useGraph?: boolean;\n /** Additional context strings */\n context?: string[];\n /** Number of personas per segment (default: 1) */\n personasPerSegment?: number;\n}\n\nexport async function runInsightsPipeline(\n dopple: Dopple,\n llm: LLMProvider,\n options: InsightsOptions\n): Promise<InsightReport> {\n const startTime = Date.now();\n\n // Step 1: Fetch all data\n const userData = await dopple.fetchData();\n\n // Step 2: Optionally build knowledge graph\n let graph: KnowledgeGraph | undefined;\n let graphPatterns: GraphPattern[] | undefined;\n\n if (options.useGraph && userData.length > 0) {\n const doppleGraph = await dopple.buildGraph(options.context);\n graph = doppleGraph.raw;\n graphPatterns = doppleGraph.patterns();\n }\n\n // Step 3: Discover segments\n const discovery = await dopple.discover({ product: options.product });\n\n // Step 4: Create personas from segment representatives\n const personas: Persona[] = [];\n for (const segment of discovery.segments) {\n // Use the segment's built-in representative persona\n const persona = new Persona(segment.persona, llm);\n\n // If graph exists, enrich persona with graph context\n if (graph) {\n const { getPersonaContext } = await import(\"../graph/query.js\");\n const graphContext = getPersonaContext(graph, segment.persona.traits);\n persona.memory.addFacts(\n graphContext.map((c) => ({\n source: \"knowledge-graph\",\n content: c,\n salience: 0.7,\n }))\n );\n }\n\n personas.push(persona);\n }\n\n // Step 5: Discover insights\n const insights = await discoverInsights(\n llm,\n userData,\n discovery.segments,\n personas,\n options.product,\n graph,\n graphPatterns\n );\n\n const durationMs = Date.now() - startTime;\n\n return {\n product: options.product,\n generatedAt: new Date().toISOString(),\n dataQuality: {\n sources: [...new Set(userData.flatMap(() => [\"user-data\"]))],\n userCount: userData.length,\n confidence: discovery.confidence,\n gaps: discovery.suggestions,\n },\n graph: graph\n ? {\n nodeCount: graph.metadata.nodeCount,\n edgeCount: graph.metadata.edgeCount,\n patterns: (graphPatterns ?? []).map((p) => p.description),\n }\n : undefined,\n segments: discovery.segments,\n insights,\n metadata: {\n model: llm.providerId,\n segmentCount: discovery.segments.length,\n personaCount: personas.length,\n durationMs,\n },\n };\n}\n","/**\n * Rule extraction from calibration sources.\n *\n * Takes policy documents, survey data, outcome data, or benchmarks\n * and extracts testable rules/expectations.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type {\n LLMProvider,\n CalibrationSource,\n CalibrationRule,\n} from \"../types.js\";\n\n/**\n * Extract testable rules from a calibration source.\n *\n * The LLM reads the source content and identifies:\n * - Explicit rules (\"customers get 30-day returns\")\n * - Implicit expectations (\"premium positioning → users should perceive high value\")\n * - Behavioral predictions (\"22% churn rate → most users stay\")\n * - Compliance requirements (\"users must accept terms before purchase\")\n */\nexport async function extractRules(\n llm: LLMProvider,\n source: CalibrationSource\n): Promise<CalibrationRule[]> {\n const prompt = getExtractionPrompt(source);\n\n const extracted = await llm.generateJSON<\n Array<{\n rule: string;\n testScenario: string;\n }>\n >(\n `You extract testable rules and expectations from documents. Each rule should be something you can verify by asking a synthetic persona a question or putting them in a scenario.\n\nFocus on rules that are:\n1. TESTABLE — you can design a scenario that checks if someone follows/understands this\n2. BEHAVIORAL — about what people would do, think, or expect\n3. SPECIFIC — not vague platitudes, but concrete predictions`,\n prompt\n );\n\n return (extracted ?? []).map((r) => ({\n id: randomUUID(),\n rule: r.rule,\n source: source.name,\n testScenario: r.testScenario,\n }));\n}\n\nfunction getExtractionPrompt(source: CalibrationSource): string {\n switch (source.type) {\n case \"policy\":\n return `Extract testable rules from this policy document. For each rule, create a scenario that tests whether a user/citizen understands and would comply with this policy.\n\nPolicy document \"${source.name}\":\n${source.content}\n\nReturn JSON array:\n[{\"rule\": \"<the policy rule or expectation>\", \"testScenario\": \"<a scenario/question that tests if a persona follows this rule>\"}]\n\nExtract 5-15 rules. Focus on rules that affect user behavior, not internal procedures.`;\n\n case \"survey\":\n return `Extract expected response patterns from this survey data. Each \"rule\" is a known finding — something real users actually said/chose.\n\nSurvey data \"${source.name}\":\n${source.content}\n\nReturn JSON array:\n[{\"rule\": \"<the known finding from real users, e.g. '62% prefer monthly billing'>\", \"testScenario\": \"<ask the same question to check if synthetic personas match>\"}]\n\nExtract the most significant findings as rules.`;\n\n case \"outcomes\":\n return `Extract behavioral outcomes from this data. Each \"rule\" is something that actually happened — a measurable result.\n\nOutcome data \"${source.name}\":\n${source.content}\n\nReturn JSON array:\n[{\"rule\": \"<what actually happened, e.g. '22% churned after price increase'>\", \"testScenario\": \"<a scenario that tests if personas would predict this outcome>\"}]\n\nFocus on outcomes that reveal user motivations and decision-making.`;\n\n case \"benchmarks\":\n return `Extract industry benchmarks from this data. Each \"rule\" is a known metric or standard.\n\nBenchmark data \"${source.name}\":\n${source.content}\n\nReturn JSON array:\n[{\"rule\": \"<the benchmark, e.g. 'average SaaS NPS is 31'>\", \"testScenario\": \"<a scenario that tests if personas align with this benchmark>\"}]`;\n\n case \"document\":\n default:\n return `Extract testable expectations from this document. Identify anything that makes a claim about how users/people behave, think, or make decisions.\n\nDocument \"${source.name}\":\n${source.content}\n\nReturn JSON array:\n[{\"rule\": \"<the expectation or claim>\", \"testScenario\": \"<a scenario that tests if a persona matches this expectation>\"}]`;\n }\n}\n","/**\n * Calibration testing — run rules against personas and measure alignment.\n */\n\nimport type {\n LLMProvider,\n CalibrationRule,\n CalibrationResult,\n} from \"../types.js\";\nimport { Persona } from \"../persona/persona.js\";\n\n/**\n * Test a single rule against a panel of personas.\n *\n * Asks each persona the test scenario, then evaluates whether\n * the collective response aligns with the rule.\n */\nexport async function testRule(\n llm: LLMProvider,\n rule: CalibrationRule,\n personas: Persona[]\n): Promise<CalibrationResult> {\n // Collect responses from all personas\n const responses: string[] = [];\n for (const persona of personas) {\n const r = await persona.ask(rule.testScenario);\n responses.push(`${persona.name}: ${r.response}`);\n }\n\n // Evaluate alignment\n const evaluation = await llm.generateJSON<{\n aligned: boolean;\n score: number;\n personaSummary: string;\n expected: string;\n gapAnalysis: string;\n }>(\n `You evaluate whether synthetic persona responses align with a known rule or expectation. Be rigorous — a vague match is not alignment. Look for specific behavioral alignment.`,\n `Rule: \"${rule.rule}\"\nSource: ${rule.source}\n\nPersona responses to the scenario \"${rule.testScenario}\":\n${responses.join(\"\\n\")}\n\nEvaluate alignment. Return JSON:\n{\n \"aligned\": <true if the majority of personas match the rule's expectation>,\n \"score\": <0.0-1.0 alignment score. 1.0 = perfect match, 0.0 = complete mismatch>,\n \"personaSummary\": \"<what the personas collectively said/did>\",\n \"expected\": \"<what was expected based on the rule>\",\n \"gapAnalysis\": \"<if misaligned: WHY the gap exists and what it means. If aligned: what confirms it>\"\n}`\n );\n\n return {\n ruleId: rule.id,\n rule: rule.rule,\n aligned: evaluation.aligned,\n score: Math.max(0, Math.min(1, evaluation.score)),\n personaResponse: evaluation.personaSummary,\n expected: evaluation.expected,\n gapAnalysis: evaluation.gapAnalysis,\n };\n}\n\n/**\n * Run all rules against a persona panel.\n */\nexport async function testAllRules(\n llm: LLMProvider,\n rules: CalibrationRule[],\n personas: Persona[]\n): Promise<CalibrationResult[]> {\n const results: CalibrationResult[] = [];\n\n for (const rule of rules) {\n // Clear persona history between rules to avoid cross-contamination\n for (const p of personas) p.clearHistory();\n const result = await testRule(llm, rule, personas);\n results.push(result);\n }\n\n return results;\n}\n","/**\n * Calibration pipeline orchestrator.\n *\n * Source → extract rules → test against personas → analyze gaps → report.\n *\n * @example\n * ```ts\n * const report = await calibrate(llm, personas, [\n * { type: \"policy\", name: \"Return Policy\", content: \"...\" },\n * { type: \"outcomes\", name: \"Q1 Churn Data\", content: \"22% churned...\" },\n * ]);\n *\n * console.log(report.overallScore); // 0.73\n * console.log(report.topMisalignments); // the most valuable findings\n * ```\n */\n\nimport type {\n LLMProvider,\n CalibrationSource,\n CalibrationReport,\n CalibrationRule,\n CalibrationResult,\n} from \"../types.js\";\nimport { Persona } from \"../persona/persona.js\";\nimport { extractRules } from \"./extract.js\";\nimport { testAllRules } from \"./test.js\";\n\nexport interface CalibrateOptions {\n /** Maximum rules to extract per source (default: 10) */\n maxRulesPerSource?: number;\n}\n\n/**\n * Run the full calibration pipeline.\n *\n * 1. Extract testable rules from each source\n * 2. Test each rule against the persona panel\n * 3. Analyze gaps and generate recommendations\n * 4. Return structured report\n */\nexport async function calibrate(\n llm: LLMProvider,\n personas: Persona[],\n sources: CalibrationSource[],\n options: CalibrateOptions = {}\n): Promise<CalibrationReport> {\n const startTime = Date.now();\n const maxRules = options.maxRulesPerSource ?? 10;\n\n // Step 1: Extract rules from all sources\n const allRules: CalibrationRule[] = [];\n for (const source of sources) {\n const rules = await extractRules(llm, source);\n allRules.push(...rules.slice(0, maxRules));\n }\n\n if (allRules.length === 0) {\n return emptyReport(sources, personas.length, Date.now() - startTime);\n }\n\n // Step 2: Test all rules against personas\n const results = await testAllRules(llm, allRules, personas);\n\n // Step 3: Compute scores\n const alignedCount = results.filter((r) => r.aligned).length;\n const misalignedCount = results.length - alignedCount;\n const overallScore =\n results.length > 0\n ? results.reduce((sum, r) => sum + r.score, 0) / results.length\n : 0;\n\n // Step 4: Get top misalignments (sorted by gap size)\n const topMisalignments = results\n .filter((r) => !r.aligned)\n .sort((a, b) => a.score - b.score)\n .slice(0, 5);\n\n // Step 5: Generate recommendations from misalignments\n const recommendations = await generateRecommendations(\n llm,\n topMisalignments,\n sources\n );\n\n return {\n overallScore,\n totalRules: results.length,\n alignedCount,\n misalignedCount,\n results,\n topMisalignments,\n recommendations,\n sources: sources.map((s) => ({ type: s.type, name: s.name })),\n timestamp: new Date().toISOString(),\n metadata: {\n personaCount: personas.length,\n ruleCount: allRules.length,\n durationMs: Date.now() - startTime,\n },\n };\n}\n\nasync function generateRecommendations(\n llm: LLMProvider,\n misalignments: CalibrationResult[],\n sources: CalibrationSource[]\n): Promise<string[]> {\n if (misalignments.length === 0) {\n return [\"All rules aligned. Persona panel matches the calibration sources well.\"];\n }\n\n const gapSummary = misalignments\n .map(\n (m) =>\n `Rule: \"${m.rule}\"\\nExpected: ${m.expected}\\nActual: ${m.personaResponse}\\nGap: ${m.gapAnalysis}`\n )\n .join(\"\\n\\n\");\n\n const sourceTypes = sources.map((s) => `${s.type}: \"${s.name}\"`).join(\", \");\n\n const recs = await llm.generateJSON<string[]>(\n \"You generate actionable recommendations from calibration gaps between synthetic personas and ground truth data.\",\n `These are the top misalignments between synthetic personas and calibration sources (${sourceTypes}):\n\n${gapSummary}\n\nGenerate 3-5 actionable recommendations. Each should:\n1. Name the specific gap\n2. Explain what it means for the product/policy\n3. Suggest a concrete action\n\nReturn a JSON array of strings.`\n );\n\n return recs ?? [];\n}\n\nfunction emptyReport(\n sources: CalibrationSource[],\n personaCount: number,\n durationMs: number\n): CalibrationReport {\n return {\n overallScore: 0,\n totalRules: 0,\n alignedCount: 0,\n misalignedCount: 0,\n results: [],\n topMisalignments: [],\n recommendations: [\n \"No testable rules could be extracted from the provided sources. Try providing more detailed policy documents or survey data.\",\n ],\n sources: sources.map((s) => ({ type: s.type, name: s.name })),\n timestamp: new Date().toISOString(),\n metadata: { personaCount, ruleCount: 0, durationMs },\n };\n}\n","/**\n * Structured calibration — real math, no LLM judgment.\n *\n * Takes real survey data (question → response distribution) and synthetic\n * survey results. Compares them with actual statistical measures:\n *\n * - Mean Absolute Error (MAE) per question and overall\n * - Pearson correlation across all question distributions\n * - Per-option error analysis\n * - Threshold-based pass/fail (default: 10 percentage points)\n *\n * This is the credible mode — publishable in a paper.\n */\n\nimport type {\n StructuredCalibrationInput,\n StructuredCalibrationReport,\n QuestionCalibrationResult,\n SurveyResult,\n} from \"../types.js\";\n\nexport interface StructuredCalibrateOptions {\n /** Maximum acceptable MAE per question (default: 0.10 = 10pp) */\n threshold?: number;\n}\n\n/**\n * Compare synthetic survey results against real survey data.\n *\n * @param realData - Real survey responses with distributions\n * @param syntheticResults - Results from running the same survey through Dopple personas\n * @param options - Threshold and configuration\n */\nexport function calibrateStructured(\n realData: StructuredCalibrationInput,\n syntheticResults: SurveyResult,\n options: StructuredCalibrateOptions = {}\n): StructuredCalibrationReport {\n const startTime = Date.now();\n const threshold = options.threshold ?? 0.10;\n\n const questionResults: QuestionCalibrationResult[] = [];\n\n // All (real%, synthetic%) pairs across all questions for correlation\n const allRealPcts: number[] = [];\n const allSyntheticPcts: number[] = [];\n\n for (const realQ of realData.questions) {\n // Find matching synthetic question\n const syntheticSummary = syntheticResults.summary[realQ.name];\n if (!syntheticSummary) continue;\n\n // Normalize real distribution to percentages\n const realTotal = realQ.totalRespondents;\n const realPcts: Record<string, number> = {};\n for (const [opt, count] of Object.entries(realQ.distribution)) {\n realPcts[opt] = realTotal > 0 ? count / realTotal : 0;\n }\n\n // Normalize synthetic distribution to percentages\n const syntheticDist = syntheticSummary.distribution;\n const syntheticTotal = Object.values(syntheticDist).reduce(\n (a, b) => a + b,\n 0\n );\n const syntheticPcts: Record<string, number> = {};\n for (const [opt, count] of Object.entries(syntheticDist)) {\n syntheticPcts[opt] = syntheticTotal > 0 ? count / syntheticTotal : 0;\n }\n\n // Get all unique options across both distributions\n const allOptions = [\n ...new Set([...Object.keys(realPcts), ...Object.keys(syntheticPcts)]),\n ];\n\n // Compute per-option error\n const perOptionError: Record<string, number> = {};\n let totalError = 0;\n\n for (const opt of allOptions) {\n const realPct = realPcts[opt] ?? 0;\n const synthPct = syntheticPcts[opt] ?? 0;\n const error = Math.abs(realPct - synthPct);\n perOptionError[opt] = error;\n totalError += error;\n\n // Collect for correlation\n allRealPcts.push(realPct);\n allSyntheticPcts.push(synthPct);\n }\n\n // MAE = average absolute error across options\n const mae = allOptions.length > 0 ? totalError / allOptions.length : 0;\n\n // Find largest divergence\n let largest = { option: \"\", realPct: 0, syntheticPct: 0, error: 0 };\n for (const opt of allOptions) {\n const err = perOptionError[opt];\n if (err > largest.error) {\n largest = {\n option: opt,\n realPct: realPcts[opt] ?? 0,\n syntheticPct: syntheticPcts[opt] ?? 0,\n error: err,\n };\n }\n }\n\n questionResults.push({\n questionName: realQ.name,\n questionText: realQ.text,\n type: realQ.type,\n mae,\n withinThreshold: mae <= threshold,\n realDistribution: Object.fromEntries(\n Object.entries(realPcts).map(([k, v]) => [k, round(v * 100)])\n ),\n syntheticDistribution: Object.fromEntries(\n Object.entries(syntheticPcts).map(([k, v]) => [k, round(v * 100)])\n ),\n perOptionError: Object.fromEntries(\n Object.entries(perOptionError).map(([k, v]) => [k, round(v * 100)])\n ),\n largestDivergence: {\n option: largest.option,\n realPct: round(largest.realPct * 100),\n syntheticPct: round(largest.syntheticPct * 100),\n error: round(largest.error * 100),\n },\n });\n }\n\n // Sort by error (worst first)\n questionResults.sort((a, b) => b.mae - a.mae);\n\n // Overall MAE\n const overallMAE =\n questionResults.length > 0\n ? questionResults.reduce((sum, q) => sum + q.mae, 0) /\n questionResults.length\n : 0;\n\n // Pearson correlation\n const correlation = pearsonCorrelation(allRealPcts, allSyntheticPcts);\n\n // Threshold count\n const questionsWithinThreshold = questionResults.filter(\n (q) => q.withinThreshold\n ).length;\n\n // Stats\n const sortedByMAE = [...questionResults].sort((a, b) => a.mae - b.mae);\n const medianIdx = Math.floor(sortedByMAE.length / 2);\n\n const maxRespondents = Math.max(\n ...realData.questions.map((q) => q.totalRespondents),\n 0\n );\n\n return {\n name: realData.name,\n overallMAE: round(overallMAE * 100) / 100,\n correlation: round(correlation * 1000) / 1000,\n questionsWithinThreshold,\n totalQuestions: questionResults.length,\n threshold,\n questions: questionResults,\n stats: {\n bestQuestion:\n sortedByMAE.length > 0\n ? {\n name: sortedByMAE[0].questionName,\n mae: round(sortedByMAE[0].mae * 100),\n }\n : { name: \"none\", mae: 0 },\n worstQuestion:\n sortedByMAE.length > 0\n ? {\n name: sortedByMAE[sortedByMAE.length - 1].questionName,\n mae: round(\n sortedByMAE[sortedByMAE.length - 1].mae * 100\n ),\n }\n : { name: \"none\", mae: 0 },\n medianMAE:\n sortedByMAE.length > 0\n ? round(sortedByMAE[medianIdx].mae * 100)\n : 0,\n personaCount: syntheticResults.personaCount,\n realRespondentCount: maxRespondents,\n },\n timestamp: new Date().toISOString(),\n durationMs: Date.now() - startTime,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Statistics\n// ---------------------------------------------------------------------------\n\n/**\n * Pearson correlation coefficient between two arrays.\n * Returns value between -1 and 1. 1 = perfect positive correlation.\n */\nfunction pearsonCorrelation(x: number[], y: number[]): number {\n const n = x.length;\n if (n === 0 || n !== y.length) return 0;\n\n const meanX = x.reduce((a, b) => a + b, 0) / n;\n const meanY = y.reduce((a, b) => a + b, 0) / n;\n\n let numerator = 0;\n let denomX = 0;\n let denomY = 0;\n\n for (let i = 0; i < n; i++) {\n const dx = x[i] - meanX;\n const dy = y[i] - meanY;\n numerator += dx * dy;\n denomX += dx * dx;\n denomY += dy * dy;\n }\n\n const denom = Math.sqrt(denomX * denomY);\n if (denom === 0) return 0;\n\n return numerator / denom;\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * PostHog adapter.\n *\n * Pulls user properties, events, and cohort data from PostHog's API.\n * Converts PostHog persons + events into UserRecord[].\n */\n\nimport type { Adapter, UserRecord, EventRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface PostHogAdapterConfig extends AdapterConfig {\n type: \"posthog\";\n /** PostHog project API key (read-only) */\n apiKey: string;\n /** PostHog host (default: https://us.posthog.com) */\n host?: string;\n /** Project ID */\n projectId?: string;\n /** Max persons to fetch (default: 100) */\n limit?: number;\n /** Filter by cohort ID */\n cohortId?: string;\n /** Filter by person properties */\n propertyFilters?: Array<{\n key: string;\n value: string | number | boolean;\n operator?: \"exact\" | \"is_not\" | \"icontains\" | \"gt\" | \"lt\";\n }>;\n}\n\nexport class PostHogAdapter implements Adapter {\n readonly type = \"posthog\";\n private config: PostHogAdapterConfig;\n private baseUrl: string;\n\n constructor(config: PostHogAdapterConfig) {\n this.config = config;\n this.baseUrl = (config.host ?? \"https://us.posthog.com\").replace(\n /\\/$/,\n \"\"\n );\n }\n\n async fetch(): Promise<UserRecord[]> {\n const persons = await this.fetchPersons();\n const records: UserRecord[] = [];\n\n for (const person of persons) {\n const events = await this.fetchPersonEvents(person.distinct_ids[0]);\n records.push({\n id: person.distinct_ids[0] ?? person.id,\n properties: person.properties ?? {},\n events: events.map(\n (e): EventRecord => ({\n name: e.event,\n timestamp: e.timestamp,\n properties: e.properties ?? {},\n })\n ),\n });\n }\n\n return records;\n }\n\n private async fetchPersons(): Promise<PostHogPerson[]> {\n const limit = this.config.limit ?? 100;\n const url = new URL(`${this.baseUrl}/api/projects/@current/persons/`);\n url.searchParams.set(\"limit\", String(limit));\n\n if (this.config.propertyFilters) {\n url.searchParams.set(\n \"properties\",\n JSON.stringify(\n this.config.propertyFilters.map((f) => ({\n key: f.key,\n value: f.value,\n operator: f.operator ?? \"exact\",\n type: \"person\",\n }))\n )\n );\n }\n\n const res = await globalThis.fetch(url.toString(), {\n headers: {\n Authorization: `Bearer ${this.config.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!res.ok) {\n throw new Error(\n `PostHog API error: ${res.status} ${res.statusText}`\n );\n }\n\n const data = (await res.json()) as { results: PostHogPerson[] };\n return data.results ?? [];\n }\n\n private async fetchPersonEvents(\n distinctId: string\n ): Promise<PostHogEvent[]> {\n const url = new URL(`${this.baseUrl}/api/projects/@current/events/`);\n url.searchParams.set(\"person_id\", distinctId);\n url.searchParams.set(\"limit\", \"50\");\n url.searchParams.set(\"orderBy\", JSON.stringify([\"-timestamp\"]));\n\n const res = await globalThis.fetch(url.toString(), {\n headers: {\n Authorization: `Bearer ${this.config.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!res.ok) {\n return []; // Non-critical — persona can still work without events\n }\n\n const data = (await res.json()) as { results: PostHogEvent[] };\n return data.results ?? [];\n }\n}\n\ninterface PostHogPerson {\n id: string;\n distinct_ids: string[];\n properties: Record<string, unknown>;\n}\n\ninterface PostHogEvent {\n event: string;\n timestamp: string;\n properties: Record<string, unknown>;\n}\n\n// Register the adapter\nregisterAdapter(\"posthog\", (config) => new PostHogAdapter(config as PostHogAdapterConfig));\n","/**\n * CSV / JSON file adapter.\n *\n * Loads user records from local CSV or JSON files.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"csv-parse/sync\";\nimport type { Adapter, UserRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface FileAdapterConfig extends AdapterConfig {\n type: \"csv\" | \"json\";\n /** Path to the file */\n path: string;\n /** Column to use as user ID (CSV only, default: first column) */\n idColumn?: string;\n}\n\nexport class FileAdapter implements Adapter {\n readonly type: string;\n private config: FileAdapterConfig;\n\n constructor(config: FileAdapterConfig) {\n this.type = config.type;\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const content = await readFile(this.config.path, \"utf-8\");\n\n if (this.config.type === \"json\") {\n return this.parseJSON(content);\n }\n return this.parseCSV(content);\n }\n\n private parseJSON(content: string): UserRecord[] {\n const data = JSON.parse(content);\n const rows: unknown[] = Array.isArray(data) ? data : [data];\n\n return rows.map((row, i) => {\n const obj = row as Record<string, unknown>;\n const id = String(obj.id ?? obj.user_id ?? obj.email ?? `row-${i}`);\n return {\n id,\n properties: obj,\n };\n });\n }\n\n private parseCSV(content: string): UserRecord[] {\n const records = parse(content, {\n columns: true,\n skip_empty_lines: true,\n trim: true,\n }) as Record<string, string>[];\n\n return records.map((row, i) => {\n const id =\n (this.config.idColumn ? row[this.config.idColumn] : undefined) ??\n row.id ??\n row.user_id ??\n row.email ??\n `row-${i}`;\n\n return {\n id,\n properties: row,\n };\n });\n }\n}\n\nregisterAdapter(\"csv\", (config) => new FileAdapter(config as FileAdapterConfig));\nregisterAdapter(\"json\", (config) => new FileAdapter(config as FileAdapterConfig));\n","/**\n * Context adapter.\n *\n * Converts freeform text, URLs, and raw strings into user context.\n * This is the simplest adapter — no real user data, just context\n * for the LLM to generate personas from.\n */\n\nimport type { Adapter, UserRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface ContextAdapterConfig extends AdapterConfig {\n type: \"context\";\n /** Raw text describing the product, market, or users */\n text?: string;\n /** URLs to fetch and use as context */\n urls?: string[];\n}\n\nexport class ContextAdapter implements Adapter {\n readonly type = \"context\";\n private config: ContextAdapterConfig;\n\n constructor(config: ContextAdapterConfig) {\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const contextParts: string[] = [];\n\n if (this.config.text) {\n contextParts.push(this.config.text);\n }\n\n if (this.config.urls) {\n for (const url of this.config.urls) {\n try {\n const res = await globalThis.fetch(url);\n if (res.ok) {\n const html = await res.text();\n // Basic HTML → text extraction (strip tags)\n const text = html\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, \"\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, 5000); // Cap at 5K chars per URL\n contextParts.push(`Content from ${url}:\\n${text}`);\n }\n } catch {\n // Skip failed URLs silently\n }\n }\n }\n\n // Return as a single \"pseudo-user\" record with context as properties\n if (contextParts.length === 0) return [];\n\n return [\n {\n id: \"context\",\n properties: {\n _type: \"context\",\n _content: contextParts.join(\"\\n\\n\"),\n },\n },\n ];\n }\n}\n\nregisterAdapter(\"context\", (config) => new ContextAdapter(config as ContextAdapterConfig));\n","/**\n * Stripe adapter.\n *\n * Pulls subscription, payment, and churn data from Stripe's API.\n * Converts customers + subscriptions into UserRecord[] with payment data.\n */\n\nimport type { Adapter, UserRecord, PaymentRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface StripeAdapterConfig extends AdapterConfig {\n type: \"stripe\";\n /** Stripe secret key (sk_...) */\n apiKey: string;\n /** Max customers to fetch (default: 100) */\n limit?: number;\n}\n\nexport class StripeAdapter implements Adapter {\n readonly type = \"stripe\";\n private config: StripeAdapterConfig;\n\n constructor(config: StripeAdapterConfig) {\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const customers = await this.fetchCustomers();\n const records: UserRecord[] = [];\n\n for (const customer of customers) {\n const subscriptions = await this.fetchSubscriptions(customer.id);\n\n const payments: PaymentRecord[] = subscriptions.map((sub) => ({\n amount: sub.plan?.amount ? sub.plan.amount / 100 : 0,\n currency: sub.currency ?? \"usd\",\n status: mapStatus(sub.status),\n plan: sub.plan?.nickname ?? sub.plan?.id ?? undefined,\n cancelReason: sub.cancellation_details?.reason ?? undefined,\n createdAt: new Date(sub.created * 1000).toISOString(),\n }));\n\n records.push({\n id: customer.id,\n properties: {\n email: customer.email,\n name: customer.name,\n created: new Date(customer.created * 1000).toISOString(),\n currency: customer.currency,\n delinquent: customer.delinquent,\n },\n payments,\n });\n }\n\n return records;\n }\n\n private async fetchCustomers(): Promise<StripeCustomer[]> {\n const limit = this.config.limit ?? 100;\n const res = await fetch(\n `https://api.stripe.com/v1/customers?limit=${limit}`,\n {\n headers: { Authorization: `Bearer ${this.config.apiKey}` },\n }\n );\n\n if (!res.ok) {\n throw new Error(`Stripe API error: ${res.status} ${res.statusText}`);\n }\n\n const data = (await res.json()) as { data: StripeCustomer[] };\n return data.data ?? [];\n }\n\n private async fetchSubscriptions(\n customerId: string\n ): Promise<StripeSubscription[]> {\n const res = await fetch(\n `https://api.stripe.com/v1/subscriptions?customer=${customerId}&limit=10&status=all`,\n {\n headers: { Authorization: `Bearer ${this.config.apiKey}` },\n }\n );\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as { data: StripeSubscription[] };\n return data.data ?? [];\n }\n}\n\nfunction mapStatus(\n status: string\n): \"active\" | \"cancelled\" | \"past_due\" | \"trialing\" {\n switch (status) {\n case \"active\":\n return \"active\";\n case \"canceled\":\n return \"cancelled\";\n case \"past_due\":\n return \"past_due\";\n case \"trialing\":\n return \"trialing\";\n default:\n return \"cancelled\";\n }\n}\n\ninterface StripeCustomer {\n id: string;\n email: string | null;\n name: string | null;\n created: number;\n currency: string | null;\n delinquent: boolean;\n}\n\ninterface StripeSubscription {\n id: string;\n status: string;\n currency: string;\n created: number;\n plan?: {\n id: string;\n nickname: string | null;\n amount: number;\n };\n cancellation_details?: {\n reason: string | null;\n };\n}\n\nregisterAdapter(\n \"stripe\",\n (config) => new StripeAdapter(config as StripeAdapterConfig)\n);\n","/**\n * Document adapter.\n *\n * Ingests unstructured documents (PDF, Markdown, TXT, DOCX) and extracts\n * text content for persona generation and graph building.\n *\n * Supports:\n * - PDF (.pdf) — via pdf-parse\n * - Markdown (.md, .mdx)\n * - Plain text (.txt)\n * - HTML (.html, .htm) — strips tags\n * - Any other text file — reads as UTF-8\n *\n * Each document becomes a UserRecord with the full text as context.\n * Multiple documents can be loaded at once (directory or file list).\n */\n\nimport { readFile, readdir, stat } from \"node:fs/promises\";\nimport { join, extname, basename } from \"node:path\";\nimport type { Adapter, UserRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface DocumentAdapterConfig extends AdapterConfig {\n type: \"document\";\n /** Path to a file or directory of files */\n path: string;\n /** Specific file extensions to include (default: all supported) */\n extensions?: string[];\n /** Max characters per document (default: 50000) */\n maxChars?: number;\n}\n\nconst SUPPORTED_EXTENSIONS = new Set([\n \".pdf\",\n \".md\",\n \".mdx\",\n \".txt\",\n \".html\",\n \".htm\",\n \".doc\",\n \".rtf\",\n \".csv\",\n \".json\",\n]);\n\nexport class DocumentAdapter implements Adapter {\n readonly type = \"document\";\n private config: DocumentAdapterConfig;\n\n constructor(config: DocumentAdapterConfig) {\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const maxChars = this.config.maxChars ?? 50000;\n const allowedExts = this.config.extensions\n ? new Set(this.config.extensions.map((e) => (e.startsWith(\".\") ? e : `.${e}`)))\n : SUPPORTED_EXTENSIONS;\n\n const files = await this.resolveFiles(allowedExts);\n const records: UserRecord[] = [];\n\n for (const filePath of files) {\n const ext = extname(filePath).toLowerCase();\n const name = basename(filePath);\n\n let text: string;\n try {\n text = await extractText(filePath, ext);\n } catch {\n continue; // Skip files that can't be parsed\n }\n\n if (!text.trim()) continue;\n\n // Truncate if too long\n const content = text.length > maxChars ? text.slice(0, maxChars) : text;\n\n records.push({\n id: `doc-${name}`,\n properties: {\n _type: \"document\",\n _source: filePath,\n _filename: name,\n _extension: ext,\n _charCount: content.length,\n _content: content,\n },\n });\n }\n\n return records;\n }\n\n private async resolveFiles(allowedExts: Set<string>): Promise<string[]> {\n const p = this.config.path;\n\n const info = await stat(p);\n if (info.isFile()) {\n return [p];\n }\n\n if (info.isDirectory()) {\n const entries = await readdir(p);\n return entries\n .filter((e) => allowedExts.has(extname(e).toLowerCase()))\n .map((e) => join(p, e));\n }\n\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Text extraction per format\n// ---------------------------------------------------------------------------\n\nasync function extractText(filePath: string, ext: string): Promise<string> {\n switch (ext) {\n case \".pdf\":\n return extractPDF(filePath);\n case \".html\":\n case \".htm\":\n return extractHTML(filePath);\n case \".md\":\n case \".mdx\":\n case \".txt\":\n case \".rtf\":\n default:\n return readFile(filePath, \"utf-8\");\n }\n}\n\nasync function extractPDF(filePath: string): Promise<string> {\n const mod = await import(\"pdf-parse\");\n const pdfParse = (mod as Record<string, unknown>).default ?? mod;\n const buffer = await readFile(filePath);\n const data = await (pdfParse as (buf: Buffer) => Promise<{ text: string }>)(buffer);\n return data.text;\n}\n\nasync function extractHTML(filePath: string): Promise<string> {\n const html = await readFile(filePath, \"utf-8\");\n return html\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, \"\")\n .replace(/<[^>]+>/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n// Register\nregisterAdapter(\n \"document\",\n (config) => new DocumentAdapter(config as DocumentAdapterConfig)\n);\n","/**\n * Amplitude adapter.\n *\n * Pulls user properties and event data from Amplitude's Export API.\n * Read-only — never writes to Amplitude.\n */\n\nimport type { Adapter, UserRecord, EventRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface AmplitudeAdapterConfig extends AdapterConfig {\n type: \"amplitude\";\n /** Amplitude API key */\n apiKey: string;\n /** Amplitude Secret key */\n secretKey: string;\n /** Start date for event export (YYYYMMDD, default: 30 days ago) */\n startDate?: string;\n /** End date for event export (YYYYMMDD, default: today) */\n endDate?: string;\n}\n\nexport class AmplitudeAdapter implements Adapter {\n readonly type = \"amplitude\";\n private config: AmplitudeAdapterConfig;\n\n constructor(config: AmplitudeAdapterConfig) {\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const now = new Date();\n const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);\n\n const start =\n this.config.startDate ?? formatDate(thirtyDaysAgo) + \"T00\";\n const end = this.config.endDate ?? formatDate(now) + \"T23\";\n\n const auth = Buffer.from(\n `${this.config.apiKey}:${this.config.secretKey}`\n ).toString(\"base64\");\n\n const res = await fetch(\n `https://amplitude.com/api/2/export?start=${start}&end=${end}`,\n {\n headers: { Authorization: `Basic ${auth}` },\n }\n );\n\n if (!res.ok) {\n throw new Error(`Amplitude API error: ${res.status} ${res.statusText}`);\n }\n\n // Export API returns gzipped JSONL — each line is an event\n const text = await res.text();\n const lines = text.trim().split(\"\\n\").filter(Boolean);\n\n // Group events by user\n const userMap = new Map<string, { properties: Record<string, unknown>; events: EventRecord[] }>();\n\n for (const line of lines) {\n try {\n const event = JSON.parse(line) as AmplitudeEvent;\n const userId = event.user_id ?? event.device_id ?? \"anonymous\";\n\n if (!userMap.has(userId)) {\n userMap.set(userId, {\n properties: event.user_properties ?? {},\n events: [],\n });\n }\n\n const user = userMap.get(userId)!;\n user.events.push({\n name: event.event_type,\n timestamp: event.event_time ?? new Date().toISOString(),\n properties: event.event_properties ?? {},\n });\n } catch {\n // Skip malformed lines\n }\n }\n\n return [...userMap.entries()].map(([id, data]) => ({\n id,\n properties: data.properties,\n events: data.events,\n }));\n }\n}\n\ninterface AmplitudeEvent {\n user_id?: string;\n device_id?: string;\n event_type: string;\n event_time?: string;\n event_properties?: Record<string, unknown>;\n user_properties?: Record<string, unknown>;\n}\n\nfunction formatDate(d: Date): string {\n return d.toISOString().slice(0, 10).replace(/-/g, \"\");\n}\n\nregisterAdapter(\n \"amplitude\",\n (config) => new AmplitudeAdapter(config as AmplitudeAdapterConfig)\n);\n","/**\n * Mixpanel adapter.\n *\n * Pulls user profiles and event data from Mixpanel's Data Export API.\n * Read-only — never writes to Mixpanel.\n */\n\nimport type { Adapter, UserRecord, EventRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface MixpanelAdapterConfig extends AdapterConfig {\n type: \"mixpanel\";\n /** Mixpanel project token or service account credentials */\n token: string;\n /** Mixpanel API secret (for export API) */\n apiSecret: string;\n /** Project ID */\n projectId?: string;\n /** Days of events to fetch (default: 30) */\n days?: number;\n}\n\nexport class MixpanelAdapter implements Adapter {\n readonly type = \"mixpanel\";\n private config: MixpanelAdapterConfig;\n\n constructor(config: MixpanelAdapterConfig) {\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const profiles = await this.fetchProfiles();\n const events = await this.fetchEvents();\n\n // Group events by distinct_id\n const eventsByUser = new Map<string, EventRecord[]>();\n for (const event of events) {\n const userId = event.properties?.distinct_id as string ?? \"anonymous\";\n if (!eventsByUser.has(userId)) eventsByUser.set(userId, []);\n eventsByUser.get(userId)!.push({\n name: event.event,\n timestamp: event.properties?.time\n ? new Date((event.properties.time as number) * 1000).toISOString()\n : new Date().toISOString(),\n properties: event.properties ?? {},\n });\n }\n\n // Merge profiles with events\n const userMap = new Map<string, UserRecord>();\n\n for (const profile of profiles) {\n const id = String(profile.$distinct_id ?? profile.$properties?.$email ?? \"unknown\");\n userMap.set(id, {\n id,\n properties: profile.$properties ?? {},\n events: eventsByUser.get(String(id)) ?? [],\n });\n }\n\n // Add users that have events but no profile\n for (const [userId, userEvents] of eventsByUser) {\n if (!userMap.has(userId)) {\n userMap.set(userId, {\n id: userId,\n properties: {},\n events: userEvents,\n });\n }\n }\n\n return [...userMap.values()];\n }\n\n private async fetchProfiles(): Promise<MixpanelProfile[]> {\n const auth = Buffer.from(`${this.config.apiSecret}:`).toString(\"base64\");\n\n const res = await fetch(\n \"https://mixpanel.com/api/2.0/engage?page_size=100\",\n {\n headers: {\n Authorization: `Basic ${auth}`,\n Accept: \"application/json\",\n },\n }\n );\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as { results: MixpanelProfile[] };\n return data.results ?? [];\n }\n\n private async fetchEvents(): Promise<MixpanelEvent[]> {\n const days = this.config.days ?? 30;\n const now = new Date();\n const from = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);\n\n const fromStr = from.toISOString().slice(0, 10);\n const toStr = now.toISOString().slice(0, 10);\n\n const auth = Buffer.from(`${this.config.apiSecret}:`).toString(\"base64\");\n\n const res = await fetch(\n `https://data.mixpanel.com/api/2.0/export?from_date=${fromStr}&to_date=${toStr}`,\n {\n headers: { Authorization: `Basic ${auth}` },\n }\n );\n\n if (!res.ok) return [];\n\n const text = await res.text();\n return text\n .trim()\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => {\n try {\n return JSON.parse(line) as MixpanelEvent;\n } catch {\n return null;\n }\n })\n .filter((e): e is MixpanelEvent => e !== null);\n }\n}\n\ninterface MixpanelProfile {\n $distinct_id?: string;\n $properties?: Record<string, unknown>;\n}\n\ninterface MixpanelEvent {\n event: string;\n properties?: Record<string, unknown>;\n}\n\nregisterAdapter(\n \"mixpanel\",\n (config) => new MixpanelAdapter(config as MixpanelAdapterConfig)\n);\n","/**\n * HubSpot adapter.\n *\n * Pulls contacts, deals, and engagement data from HubSpot's CRM API.\n * Read-only — never writes to HubSpot.\n */\n\nimport type { Adapter, UserRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface HubSpotAdapterConfig extends AdapterConfig {\n type: \"hubspot\";\n /** HubSpot private app access token */\n accessToken: string;\n /** Max contacts to fetch (default: 100) */\n limit?: number;\n /** Include deal data */\n includeDeals?: boolean;\n}\n\nexport class HubSpotAdapter implements Adapter {\n readonly type = \"hubspot\";\n private config: HubSpotAdapterConfig;\n\n constructor(config: HubSpotAdapterConfig) {\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const contacts = await this.fetchContacts();\n const records: UserRecord[] = [];\n\n for (const contact of contacts) {\n const props = contact.properties ?? {};\n\n const record: UserRecord = {\n id: contact.id,\n properties: {\n email: props.email,\n firstName: props.firstname,\n lastName: props.lastname,\n company: props.company,\n jobTitle: props.jobtitle,\n phone: props.phone,\n city: props.city,\n state: props.state,\n country: props.country,\n lifecycleStage: props.lifecyclestage,\n leadStatus: props.hs_lead_status,\n lastActivityDate: props.notes_last_updated,\n createDate: props.createdate,\n },\n };\n\n // Fetch associated deals if requested\n if (this.config.includeDeals) {\n const deals = await this.fetchContactDeals(contact.id);\n if (deals.length > 0) {\n record.payments = deals.map((deal) => ({\n amount: parseFloat(deal.properties?.amount ?? \"0\"),\n currency: deal.properties?.deal_currency_code ?? \"USD\",\n status:\n deal.properties?.dealstage === \"closedwon\"\n ? (\"active\" as const)\n : deal.properties?.dealstage === \"closedlost\"\n ? (\"cancelled\" as const)\n : (\"trialing\" as const),\n plan: deal.properties?.dealname ?? undefined,\n cancelReason: deal.properties?.closed_lost_reason ?? undefined,\n createdAt: deal.properties?.createdate ?? new Date().toISOString(),\n }));\n }\n }\n\n records.push(record);\n }\n\n return records;\n }\n\n private async fetchContacts(): Promise<HubSpotContact[]> {\n const limit = this.config.limit ?? 100;\n\n const res = await fetch(\n `https://api.hubapi.com/crm/v3/objects/contacts?limit=${limit}&properties=email,firstname,lastname,company,jobtitle,phone,city,state,country,lifecyclestage,hs_lead_status,notes_last_updated,createdate`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n if (!res.ok) {\n throw new Error(`HubSpot API error: ${res.status} ${res.statusText}`);\n }\n\n const data = (await res.json()) as { results: HubSpotContact[] };\n return data.results ?? [];\n }\n\n private async fetchContactDeals(contactId: string): Promise<HubSpotDeal[]> {\n const res = await fetch(\n `https://api.hubapi.com/crm/v3/objects/contacts/${contactId}/associations/deals`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n }\n );\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as {\n results: Array<{ id: string }>;\n };\n\n const deals: HubSpotDeal[] = [];\n for (const assoc of data.results ?? []) {\n const dealRes = await fetch(\n `https://api.hubapi.com/crm/v3/objects/deals/${assoc.id}?properties=dealname,amount,dealstage,deal_currency_code,closed_lost_reason,createdate`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n }\n );\n if (dealRes.ok) {\n deals.push((await dealRes.json()) as HubSpotDeal);\n }\n }\n\n return deals;\n }\n}\n\ninterface HubSpotContact {\n id: string;\n properties: Record<string, string>;\n}\n\ninterface HubSpotDeal {\n id: string;\n properties: Record<string, string>;\n}\n\nregisterAdapter(\n \"hubspot\",\n (config) => new HubSpotAdapter(config as HubSpotAdapterConfig)\n);\n","/**\n * Intercom adapter.\n *\n * Pulls contacts and conversations from Intercom's API.\n * Read-only — never writes to Intercom.\n */\n\nimport type { Adapter, UserRecord, AdapterConfig } from \"../types.js\";\nimport { registerAdapter } from \"./base.js\";\n\nexport interface IntercomAdapterConfig extends AdapterConfig {\n type: \"intercom\";\n /** Intercom access token */\n accessToken: string;\n /** Max contacts to fetch (default: 50) */\n limit?: number;\n /** Include conversation history */\n includeConversations?: boolean;\n}\n\nexport class IntercomAdapter implements Adapter {\n readonly type = \"intercom\";\n private config: IntercomAdapterConfig;\n private baseUrl = \"https://api.intercom.io\";\n\n constructor(config: IntercomAdapterConfig) {\n this.config = config;\n }\n\n async fetch(): Promise<UserRecord[]> {\n const contacts = await this.fetchContacts();\n const records: UserRecord[] = [];\n\n for (const contact of contacts) {\n const record: UserRecord = {\n id: contact.id,\n properties: {\n email: contact.email,\n name: contact.name,\n role: contact.role,\n signedUpAt: contact.signed_up_at,\n lastSeenAt: contact.last_seen_at,\n lastContactedAt: contact.last_contacted_at,\n browser: contact.browser,\n os: contact.os,\n city: contact.location?.city,\n country: contact.location?.country,\n unsubscribedFromEmails: contact.unsubscribed_from_emails,\n tags: contact.tags?.tags?.map((t: { name: string }) => t.name) ?? [],\n customAttributes: contact.custom_attributes,\n },\n };\n\n // Fetch conversations\n if (this.config.includeConversations) {\n const conversations = await this.fetchContactConversations(contact.id);\n record.conversations = conversations;\n }\n\n records.push(record);\n }\n\n return records;\n }\n\n private async fetchContacts(): Promise<IntercomContact[]> {\n const res = await fetch(`${this.baseUrl}/contacts?per_page=${this.config.limit ?? 50}`, {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n });\n\n if (!res.ok) {\n throw new Error(`Intercom API error: ${res.status} ${res.statusText}`);\n }\n\n const data = (await res.json()) as { data: IntercomContact[] };\n return data.data ?? [];\n }\n\n private async fetchContactConversations(\n contactId: string\n ): Promise<string[]> {\n const res = await fetch(\n `${this.baseUrl}/conversations/search`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify({\n query: {\n field: \"contact_ids\",\n operator: \"=\",\n value: contactId,\n },\n }),\n }\n );\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as {\n conversations: IntercomConversation[];\n };\n\n const messages: string[] = [];\n for (const conv of data.conversations ?? []) {\n // Get the first customer message from each conversation\n if (conv.source?.body) {\n const text = conv.source.body\n .replace(/<[^>]+>/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n if (text) messages.push(text);\n }\n }\n\n return messages.slice(0, 10);\n }\n}\n\ninterface IntercomContact {\n id: string;\n email?: string;\n name?: string;\n role?: string;\n signed_up_at?: string;\n last_seen_at?: string;\n last_contacted_at?: string;\n browser?: string;\n os?: string;\n location?: { city?: string; country?: string };\n unsubscribed_from_emails?: boolean;\n tags?: { tags: Array<{ name: string }> };\n custom_attributes?: Record<string, unknown>;\n}\n\ninterface IntercomConversation {\n id: string;\n source?: { body?: string };\n}\n\nregisterAdapter(\n \"intercom\",\n (config) => new IntercomAdapter(config as IntercomAdapterConfig)\n);\n","/**\n * Dopple engine — the main orchestrator.\n *\n * Usage:\n * const dopple = new Dopple({\n * model: \"anthropic/claude-sonnet-4-20250514\",\n * adapters: [{ type: \"posthog\", apiKey: \"phx_...\" }],\n * });\n * const personas = await dopple.generate({ product: \"my SaaS\" });\n * const response = await personas[0].ask(\"Would you pay for this?\");\n */\n\nimport type {\n DoppleConfig,\n UserRecord,\n PersonaDefinition,\n LLMProvider,\n DiscoveryResult,\n ValidationReport,\n Adapter,\n AdapterConfig,\n SurveyResult,\n} from \"./types.js\";\nimport { createProvider } from \"./llm/provider.js\";\nimport { Persona } from \"./persona/persona.js\";\nimport { generatePersonas } from \"./persona/generator.js\";\nimport { discoverSegments } from \"./groups/segment.js\";\nimport { validatePersona } from \"./validation/validate.js\";\nimport { createAdapter } from \"./adapters/base.js\";\nimport { DoppleStore } from \"./storage/store.js\";\nimport { TraceStore } from \"./traces/store.js\";\nimport type { Trace } from \"./traces/types.js\";\nimport { Survey } from \"./survey/survey.js\";\nimport {\n FocusGroup,\n type FocusGroupConfig,\n} from \"./simulation/focus-group.js\";\nimport { DoppleGraph } from \"./graph/graph.js\";\nimport {\n runInsightsPipeline,\n type InsightsOptions,\n} from \"./insights/insights.js\";\nimport {\n calibrate,\n type CalibrateOptions,\n} from \"./calibration/calibrate.js\";\nimport {\n calibrateStructured,\n type StructuredCalibrateOptions,\n} from \"./calibration/structured.js\";\n\n// Import adapters to register them\nimport \"./adapters/posthog.js\";\nimport \"./adapters/csv.js\";\nimport \"./adapters/context.js\";\nimport \"./adapters/stripe.js\";\nimport \"./adapters/document.js\";\nimport \"./adapters/amplitude.js\";\nimport \"./adapters/mixpanel.js\";\nimport \"./adapters/hubspot.js\";\nimport \"./adapters/intercom.js\";\n\nexport interface GenerateOptions {\n /** What the product/brand/project is */\n product?: string;\n /** Number of personas to generate */\n count?: number;\n /** Additional freeform context */\n context?: string[];\n /** Minimum diversity between personas (0-1) */\n diversityThreshold?: number;\n /** Save panel to local storage */\n save?: string;\n}\n\nexport interface DiscoverOptions {\n /** Product being analyzed */\n product?: string;\n /** Target number of segments */\n segmentCount?: number;\n}\n\nexport class Dopple {\n private llm: LLMProvider;\n private adapters: Adapter[];\n private contextStrings: string[];\n private cachedUserData: UserRecord[] | null = null;\n readonly store: DoppleStore;\n readonly traces: TraceStore;\n private cachedGraph: DoppleGraph | null = null;\n\n constructor(config: DoppleConfig = {}) {\n this.llm = createProvider(config.model, config.apiKey, config.baseUrl);\n this.adapters = (config.adapters ?? []).map((c) => createAdapter(c));\n this.contextStrings = config.context ?? [];\n this.store = new DoppleStore(config.storageDir);\n this.traces = new TraceStore(config.storageDir);\n }\n\n /**\n * Add an adapter dynamically.\n */\n addAdapter(config: AdapterConfig): void {\n this.adapters.push(createAdapter(config));\n this.cachedUserData = null;\n }\n\n /**\n * Add freeform context.\n */\n addContext(context: string): void {\n this.contextStrings.push(context);\n }\n\n /**\n * Fetch all user data from configured adapters.\n */\n async fetchData(): Promise<UserRecord[]> {\n if (this.cachedUserData) return this.cachedUserData;\n\n const allRecords: UserRecord[] = [];\n for (const adapter of this.adapters) {\n const records = await adapter.fetch();\n allRecords.push(...records);\n }\n\n this.cachedUserData = allRecords;\n return allRecords;\n }\n\n /**\n * Generate personas from context and/or data.\n */\n async generate(options: GenerateOptions = {}): Promise<Persona[]> {\n const userData = await this.fetchData();\n\n const definitions = await generatePersonas(this.llm, {\n product: options.product,\n count: options.count ?? 5,\n context: [...this.contextStrings, ...(options.context ?? [])],\n userData: userData.length > 0 ? userData : undefined,\n diversityThreshold: options.diversityThreshold,\n });\n\n const personas = definitions.map((d) => new Persona(d, this.llm));\n\n if (options.save) {\n await this.store.savePanel(options.save, definitions, options.product);\n }\n\n return personas;\n }\n\n /**\n * Load a previously saved persona panel.\n */\n async loadPanel(nameOrId: string): Promise<Persona[] | null> {\n const panel = await this.store.loadPanel(nameOrId);\n if (!panel) return null;\n return panel.personas.map((d) => new Persona(d, this.llm));\n }\n\n /**\n * Discover user segments from data.\n * Requires at least one data adapter to be configured.\n */\n async discover(options: DiscoverOptions = {}): Promise<DiscoveryResult> {\n const userData = await this.fetchData();\n return discoverSegments(this.llm, userData, {\n product: options.product,\n segmentCount: options.segmentCount,\n });\n }\n\n /**\n * Create a survey to run against personas.\n */\n createSurvey(name: string): Survey {\n return new Survey(name);\n }\n\n /**\n * Create a focus group — multiple personas discussing a topic together.\n * Supports context injection mid-discussion.\n *\n * @example\n * ```ts\n * const group = dopple.createFocusGroup(\"pricing\", {\n * topic: \"Should we raise prices?\",\n * product: \"my SaaS\",\n * });\n * group.addPersonas(personas);\n * const round1 = await group.discuss(this.llm);\n * group.inject(\"A competitor just launched a free tier\");\n * const round2 = await group.discuss(this.llm);\n * const summary = await group.summarize(this.llm);\n * ```\n */\n createFocusGroup(name: string, config: FocusGroupConfig): FocusGroup {\n return new FocusGroup(name, config);\n }\n\n /**\n * Run a focus group discussion and return the full transcript.\n * Convenience method that creates personas, runs N rounds, and summarizes.\n */\n async runFocusGroup(\n config: FocusGroupConfig & {\n personas?: Persona[];\n product?: string;\n personaCount?: number;\n rounds?: number;\n }\n ): Promise<{\n rounds: import(\"./simulation/focus-group.js\").FocusGroupRound[];\n summary: {\n themes: string[];\n agreements: string[];\n disagreements: string[];\n insights: string[];\n };\n }> {\n const personas =\n config.personas ??\n (await this.generate({\n product: config.product,\n count: config.personaCount ?? 5,\n }));\n\n const group = new FocusGroup(\"focus-group\", config);\n group.addPersonas(personas);\n\n const roundCount = config.rounds ?? 3;\n const allRounds: import(\"./simulation/focus-group.js\").FocusGroupRound[] =\n [];\n\n for (let i = 0; i < roundCount; i++) {\n const round = await group.discuss(this.llm);\n allRounds.push(round);\n }\n\n const summary = await group.summarize(this.llm);\n\n return { rounds: allRounds, summary };\n }\n\n /**\n * Run a survey against a persona panel and save results.\n */\n async runSurvey(\n survey: Survey,\n personas: Persona[]\n ): Promise<SurveyResult> {\n const result = await survey.run(personas, this.llm);\n await this.store.saveSurvey(result);\n return result;\n }\n\n /**\n * Validate a persona's psychometric accuracy.\n */\n async validate(persona: Persona): Promise<ValidationReport> {\n return validatePersona(persona.definition, this.llm);\n }\n\n /**\n * Ask all personas in a panel the same question.\n */\n async askPanel(\n personas: Persona[],\n question: string\n ): Promise<\n Array<{\n persona: string;\n response: string;\n confidence: string;\n reasoning: string;\n citations: Array<{ source: string; detail: string; weight: number }>;\n }>\n > {\n const results = await Promise.all(\n personas.map(async (p) => {\n const r = await p.ask(question);\n return {\n persona: p.name,\n response: r.response,\n confidence: r.confidence,\n reasoning: r.reasoning,\n citations: r.citations,\n };\n })\n );\n return results;\n }\n\n /**\n * Get data quality status and improvement suggestions.\n */\n async status(): Promise<{\n adapters: string[];\n userCount: number;\n confidence: string;\n suggestions: Array<{ source: string; suggestion: string; impact: string }>;\n panels: Array<{\n id: string;\n name: string;\n personaCount: number;\n createdAt: string;\n }>;\n }> {\n const userData = await this.fetchData();\n const discovery = await discoverSegments(this.llm, userData, {});\n const panels = await this.store.listPanels();\n\n return {\n adapters: this.adapters.map((a) => a.type),\n userCount: userData.length,\n confidence: discovery.confidence,\n suggestions: discovery.suggestions,\n panels,\n };\n }\n\n /**\n * Run the insights pipeline — THE main product feature.\n * Data in → actionable insights out.\n * Automatically records a trace and injects calibration history.\n */\n async runInsights(\n options: InsightsOptions\n ): Promise<import(\"./types.js\").InsightReport> {\n // Inject calibration history from past traces\n const calibrationContext = await this.traces.getCalibrationContext(\n options.product\n );\n const enrichedOptions = {\n ...options,\n context: [\n ...(options.context ?? []),\n ...(calibrationContext ? [calibrationContext] : []),\n ],\n };\n\n const report = await runInsightsPipeline(this, this.llm, enrichedOptions);\n\n // Record trace\n const { randomUUID } = await import(\"node:crypto\");\n const trace: Trace = {\n id: randomUUID(),\n product: options.product,\n type: \"insight\",\n timestamp: new Date().toISOString(),\n input: {\n command: \"insights\",\n dataSources: report.dataQuality.sources,\n userCount: report.dataQuality.userCount,\n context: options.context,\n },\n prediction: {\n summary: report.insights\n .slice(0, 3)\n .map((i) => i.title)\n .join(\"; \"),\n details: {\n insightCount: report.insights.length,\n topInsight: report.insights[0] ?? null,\n segmentCount: report.segments.length,\n },\n confidence:\n report.insights.length > 0\n ? report.insights.reduce((s, i) => s + i.confidence, 0) /\n report.insights.length\n : 0,\n model: report.metadata.model,\n personaCount: report.metadata.personaCount,\n },\n outcome: null,\n accuracy: null,\n };\n\n await this.traces.save(trace).catch(() => {}); // Non-blocking\n\n return report;\n }\n\n /**\n * Calibrate personas against a source of truth.\n * Policy documents, survey data, outcome data, benchmarks — any ground truth.\n */\n async calibrate(\n personas: Persona[],\n sources: import(\"./types.js\").CalibrationSource[],\n options?: CalibrateOptions\n ): Promise<import(\"./types.js\").CalibrationReport> {\n return calibrate(this.llm, personas, sources, options);\n }\n\n /**\n * Structured calibration — compare synthetic survey results against real data.\n * Uses real math (MAE, Pearson correlation), not LLM judgment.\n *\n * 1. Run the same survey through Dopple personas\n * 2. Provide real survey data with response distributions\n * 3. Get statistical comparison: MAE per question, correlation, threshold pass/fail\n */\n calibrateStructured(\n realData: import(\"./types.js\").StructuredCalibrationInput,\n syntheticResults: import(\"./types.js\").SurveyResult,\n options?: StructuredCalibrateOptions\n ): import(\"./types.js\").StructuredCalibrationReport {\n return calibrateStructured(realData, syntheticResults, options);\n }\n\n /**\n * Build a knowledge graph from all adapter data.\n * Graph is cached — call clearCache() to rebuild.\n */\n async buildGraph(contextTexts?: string[]): Promise<DoppleGraph> {\n if (this.cachedGraph) return this.cachedGraph;\n\n const userData = await this.fetchData();\n const graph = new DoppleGraph();\n await graph.build(this.llm, userData, contextTexts);\n this.cachedGraph = graph;\n return graph;\n }\n\n /**\n * Record a real outcome for a past prediction.\n * This closes the calibration loop — next predictions will be informed by this.\n */\n async recordOutcome(\n product: string,\n traceId: string,\n actual: string,\n source: string,\n accuracy?: { mae?: number; directionCorrect?: boolean; notes?: string }\n ): Promise<void> {\n await this.traces.recordOutcome(product, traceId, {\n actual,\n recordedAt: new Date().toISOString(),\n source,\n }, {\n mae: accuracy?.mae,\n directionCorrect: accuracy?.directionCorrect,\n notes: accuracy?.notes ?? \"\",\n });\n }\n\n /**\n * Get prediction history and calibration summary for a product.\n */\n async getTraceHistory(\n product: string\n ): Promise<import(\"./traces/types.js\").ProductTraceHistory> {\n return this.traces.getProductHistory(product);\n }\n\n /**\n * Get the underlying LLM provider info.\n */\n get provider(): string {\n return this.llm.providerId;\n }\n\n /**\n * Invalidate cached data (call after adapter changes).\n */\n clearCache(): void {\n this.cachedUserData = null;\n this.cachedGraph = null;\n }\n}\n","/**\n * Colored CLI output formatting.\n *\n * Uses picocolors for zero-dependency terminal colors.\n * Colored CLI output formatting.\n */\n\nimport pc from \"picocolors\";\nimport type {\n ConfidenceLevel,\n SourceCitation,\n DiscoveryResult,\n SurveyResult,\n InsightReport,\n CalibrationReport,\n StructuredCalibrationReport,\n} from \"../types.js\";\nimport type { Persona } from \"../persona/persona.js\";\nimport type { FocusGroupRound } from \"../simulation/focus-group.js\";\n\n// ---------------------------------------------------------------------------\n// Color assignments\n// ---------------------------------------------------------------------------\n\nconst PERSONA_COLORS = [pc.cyan, pc.magenta, pc.green, pc.yellow, pc.blue, pc.red];\n\nfunction personaColor(name: string): (s: string) => string {\n let hash = 0;\n for (let i = 0; i < name.length; i++) {\n hash = (hash * 31 + name.charCodeAt(i)) | 0;\n }\n return PERSONA_COLORS[Math.abs(hash) % PERSONA_COLORS.length];\n}\n\nexport function confidenceColor(level: ConfidenceLevel): (s: string) => string {\n switch (level) {\n case \"low\":\n return pc.red;\n case \"medium\":\n return pc.yellow;\n case \"high\":\n return pc.green;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Components\n// ---------------------------------------------------------------------------\n\nexport function formatPersonaName(name: string): string {\n const color = personaColor(name);\n return color(pc.bold(name));\n}\n\nexport function formatConfidence(level: ConfidenceLevel): string {\n const color = confidenceColor(level);\n return color(level.toUpperCase());\n}\n\nexport function formatCitation(c: SourceCitation): string {\n return pc.dim(` [${c.source}] ${c.detail} (${(c.weight * 100).toFixed(0)}%)`);\n}\n\nexport function formatOcean(traits: {\n openness: number;\n conscientiousness: number;\n extraversion: number;\n agreeableness: number;\n neuroticism: number;\n}): string {\n const fmt = (label: string, val: number) => {\n const bar = \"█\".repeat(Math.round(val * 10));\n const empty = \"░\".repeat(10 - Math.round(val * 10));\n return `${pc.dim(label)} ${pc.cyan(bar)}${pc.dim(empty)} ${val.toFixed(2)}`;\n };\n return [\n fmt(\"O\", traits.openness),\n fmt(\"C\", traits.conscientiousness),\n fmt(\"E\", traits.extraversion),\n fmt(\"A\", traits.agreeableness),\n fmt(\"N\", traits.neuroticism),\n ].join(\"\\n\");\n}\n\nexport function progressBar(\n percent: number,\n width: number = 20\n): string {\n const filled = Math.round((percent / 100) * width);\n const empty = width - filled;\n return `${pc.green(\"█\".repeat(filled))}${pc.dim(\"░\".repeat(empty))} ${percent}%`;\n}\n\n// ---------------------------------------------------------------------------\n// Printers\n// ---------------------------------------------------------------------------\n\nexport function printPersonas(personas: Persona[]): void {\n console.log(`\\n${pc.bold(`Generated ${personas.length} personas:`)}\\n`);\n for (const p of personas) {\n const d = p.definition;\n console.log(` ${formatPersonaName(p.name)} ${pc.dim(\"—\")} ${p.summary()}`);\n console.log(` ${formatOcean(d.traits).split(\"\\n\").join(\"\\n \")}`);\n console.log(` Confidence: ${formatConfidence(d.confidence)}`);\n console.log();\n }\n}\n\nexport function printPanelResponse(\n results: Array<{\n persona: string;\n response: string;\n confidence: string;\n reasoning: string;\n citations: SourceCitation[];\n }>\n): void {\n for (const r of results) {\n console.log(\n `\\n${formatPersonaName(r.persona)} ${pc.dim(`[${r.confidence}]`)}:`\n );\n console.log(` ${r.response}`);\n if (r.reasoning) {\n console.log(` ${pc.dim(\"Why:\")} ${pc.dim(r.reasoning)}`);\n }\n for (const c of r.citations) {\n console.log(formatCitation(c));\n }\n }\n console.log();\n}\n\nexport function printDiscovery(result: DiscoveryResult): void {\n console.log(\n `\\n${pc.bold(`Discovered ${result.segments.length} segments`)} from ${result.totalUsersAnalyzed} users:\\n`\n );\n for (const seg of result.segments) {\n const pct = progressBar(seg.percentage, 15);\n console.log(\n ` ${formatPersonaName(seg.name)} ${pc.dim(\"—\")} ${pct} (${seg.userCount} users)`\n );\n console.log(` ${pc.dim(seg.description)}`);\n for (const p of seg.patterns) {\n console.log(` ${pc.dim(\"•\")} ${p}`);\n }\n console.log(\n ` Representative: ${formatPersonaName(seg.persona.name)}`\n );\n console.log();\n }\n\n if (result.suggestions.length > 0) {\n console.log(` ${pc.bold(\"Suggestions to improve accuracy:\")}`);\n for (const s of result.suggestions) {\n const impact =\n s.impact === \"high\"\n ? pc.red(s.impact.toUpperCase())\n : s.impact === \"medium\"\n ? pc.yellow(s.impact.toUpperCase())\n : pc.dim(s.impact.toUpperCase());\n console.log(` [${impact}] ${pc.bold(s.source)}: ${s.suggestion}`);\n }\n console.log();\n }\n}\n\nexport function printSurvey(result: SurveyResult): void {\n console.log(\n `\\n${pc.bold(`Survey: \"${result.surveyName}\"`)} — ${result.personaCount} personas, ${result.questions.length} questions\\n`\n );\n for (const [qName, data] of Object.entries(result.summary)) {\n console.log(` ${pc.bold(data.questionText)}`);\n\n if (data.type === \"free_text\") {\n const responses = result.responses.filter(\n (r) => r.questionName === qName\n );\n for (const r of responses) {\n console.log(\n ` ${formatPersonaName(r.personaName)}: ${pc.dim(`\"${r.answer}\"`)}`\n );\n }\n } else {\n const total = Object.values(data.distribution).reduce(\n (a, b) => a + b,\n 0\n );\n for (const [answer, count] of Object.entries(data.distribution)) {\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n const barLen = Math.round(pct / 5);\n console.log(\n ` ${answer.padEnd(25)} ${pc.cyan(\"▓\".repeat(barLen))}${pc.dim(\"░\".repeat(20 - barLen))} ${pct}%`\n );\n }\n }\n console.log();\n }\n}\n\nexport function printFocusGroupRound(round: FocusGroupRound): void {\n console.log(`\\n${pc.bold(`Round ${round.round}`)}`);\n console.log(pc.dim(\"─\".repeat(50)));\n\n if (round.injectedContext) {\n console.log(\n ` ${pc.bgYellow(pc.black(\" MODERATOR \"))} ${round.injectedContext}`\n );\n console.log();\n }\n\n for (const msg of round.messages) {\n console.log(` ${formatPersonaName(msg.personaName)}:`);\n console.log(` ${msg.message}`);\n if (msg.opinionShift) {\n console.log(\n ` ${pc.yellow(\"⟳ SHIFTED:\")} ${pc.dim(msg.opinionShift.previousPosition)} → ${pc.bold(msg.opinionShift.newPosition)} ${pc.dim(`(convinced by ${msg.opinionShift.triggeredBy})`)}`\n );\n }\n if (msg.reasoning) {\n console.log(` ${pc.dim(`Why: ${msg.reasoning}`)}`);\n }\n }\n console.log();\n}\n\nexport function printValidation(\n name: string,\n report: {\n overall: { passed: boolean; score: number; maxScore: number };\n tests: Array<{\n testName: string;\n passed: boolean;\n score: number;\n maxScore: number;\n details: string;\n }>;\n }\n): void {\n console.log(`\\n${pc.bold(`Validation Report for ${name}`)}`);\n console.log(pc.dim(\"═\".repeat(50)));\n\n const overall = report.overall.passed\n ? pc.green(pc.bold(\"PASS\"))\n : pc.red(pc.bold(\"FAIL\"));\n console.log(\n `Overall: ${overall} (${report.overall.score}/${report.overall.maxScore})`\n );\n\n for (const test of report.tests) {\n const icon = test.passed ? pc.green(\"+\") : pc.red(\"-\");\n const score =\n test.score >= test.maxScore * 0.7\n ? pc.green(`${test.score}/${test.maxScore}`)\n : test.score >= test.maxScore * 0.4\n ? pc.yellow(`${test.score}/${test.maxScore}`)\n : pc.red(`${test.score}/${test.maxScore}`);\n console.log(`\\n [${icon}] ${pc.bold(test.testName)}: ${score}`);\n console.log(` ${pc.dim(test.details)}`);\n }\n console.log();\n}\n\nexport function printInsightReport(report: InsightReport): void {\n console.log(`\\n${pc.bold(\"Dopple Insights\")}`);\n console.log(pc.dim(\"═\".repeat(60)));\n console.log(` Product: ${pc.bold(report.product)}`);\n console.log(\n ` Data: ${report.dataQuality.userCount} users from ${report.dataQuality.sources.join(\", \") || \"none\"}`\n );\n console.log(\n ` Confidence: ${formatConfidence(report.dataQuality.confidence)}`\n );\n\n if (report.graph) {\n console.log(\n ` Graph: ${pc.cyan(String(report.graph.nodeCount))} nodes, ${pc.cyan(String(report.graph.edgeCount))} edges`\n );\n }\n\n console.log(\n ` Segments: ${report.segments.length} | Duration: ${(report.metadata.durationMs / 1000).toFixed(1)}s`\n );\n\n if (report.dataQuality.gaps.length > 0) {\n console.log(`\\n ${pc.dim(\"Data gaps:\")}`);\n for (const g of report.dataQuality.gaps.slice(0, 3)) {\n const impact =\n g.impact === \"high\"\n ? pc.red(\"HIGH\")\n : g.impact === \"medium\"\n ? pc.yellow(\"MED\")\n : pc.dim(\"LOW\");\n console.log(` [${impact}] ${g.suggestion}`);\n }\n }\n\n console.log(`\\n${pc.bold(`${report.insights.length} Insights:`)}\\n`);\n for (let i = 0; i < report.insights.length; i++) {\n const insight = report.insights[i];\n const confBar = progressBar(Math.round(insight.confidence * 100), 10);\n const impactBar = progressBar(Math.round(insight.impact * 100), 10);\n\n console.log(` ${pc.bold(`${i + 1}. ${insight.title}`)}`);\n console.log(` ${insight.description}`);\n console.log(` Confidence: ${confBar} Impact: ${impactBar}`);\n console.log(\n ` Segments: ${insight.segments.map((s) => pc.cyan(s)).join(\", \")}`\n );\n\n if (insight.evidence.length > 0) {\n for (const e of insight.evidence.slice(0, 3)) {\n console.log(\n ` ${pc.dim(`[${e.type}] ${e.detail}`)}`\n );\n }\n }\n\n console.log(` ${pc.green(\"→\")} ${pc.green(insight.recommendation)}`);\n console.log();\n }\n}\n\nexport function printCalibrationReport(report: CalibrationReport): void {\n console.log(`\\n${pc.bold(\"Calibration Report\")}`);\n console.log(pc.dim(\"═\".repeat(60)));\n\n // Overall score\n const scoreColor =\n report.overallScore >= 0.7\n ? pc.green\n : report.overallScore >= 0.4\n ? pc.yellow\n : pc.red;\n console.log(\n ` Overall alignment: ${scoreColor(pc.bold(`${(report.overallScore * 100).toFixed(0)}%`))} ${progressBar(Math.round(report.overallScore * 100), 15)}`\n );\n console.log(\n ` Rules tested: ${report.totalRules} | ${pc.green(`${report.alignedCount} aligned`)} | ${pc.red(`${report.misalignedCount} misaligned`)}`\n );\n console.log(\n ` Sources: ${report.sources.map((s) => `${s.type}:\"${s.name}\"`).join(\", \")}`\n );\n console.log(\n ` Duration: ${(report.metadata.durationMs / 1000).toFixed(1)}s`\n );\n\n // Top misalignments — these are the gold\n if (report.topMisalignments.length > 0) {\n console.log(`\\n${pc.bold(pc.red(\"Top Misalignments:\"))}\\n`);\n for (const m of report.topMisalignments) {\n const bar = progressBar(Math.round(m.score * 100), 10);\n console.log(` ${pc.red(\"✗\")} ${pc.bold(m.rule)}`);\n console.log(` Score: ${bar}`);\n console.log(` Expected: ${pc.dim(m.expected)}`);\n console.log(` Got: ${m.personaResponse}`);\n console.log(` Gap: ${pc.yellow(m.gapAnalysis)}`);\n console.log();\n }\n }\n\n // Aligned rules (compact)\n const aligned = report.results.filter((r) => r.aligned);\n if (aligned.length > 0) {\n console.log(`${pc.bold(pc.green(\"Aligned Rules:\"))}`);\n for (const r of aligned) {\n console.log(` ${pc.green(\"✓\")} ${r.rule} ${pc.dim(`(${(r.score * 100).toFixed(0)}%)`)}`);\n }\n console.log();\n }\n\n // Recommendations\n if (report.recommendations.length > 0) {\n console.log(`${pc.bold(\"Recommendations:\")}`);\n for (const rec of report.recommendations) {\n console.log(` ${pc.yellow(\"→\")} ${rec}`);\n }\n console.log();\n }\n}\n\nexport function printStructuredCalibration(\n report: StructuredCalibrationReport\n): void {\n console.log(`\\n${pc.bold(\"Structured Calibration Report\")}`);\n console.log(pc.dim(\"═\".repeat(60)));\n console.log(` ${pc.bold(report.name)}`);\n\n // Overall metrics\n const maeColor =\n report.overallMAE <= 0.05\n ? pc.green\n : report.overallMAE <= 0.1\n ? pc.yellow\n : pc.red;\n const corrColor =\n report.correlation >= 0.8\n ? pc.green\n : report.correlation >= 0.5\n ? pc.yellow\n : pc.red;\n\n console.log(\n ` Overall MAE: ${maeColor(pc.bold(`${(report.overallMAE * 100).toFixed(1)}pp`))} ${pc.dim(\"(lower = better, <10pp is good)\")}`\n );\n console.log(\n ` Correlation: ${corrColor(pc.bold(`r=${report.correlation.toFixed(3)}`))} ${pc.dim(\"(higher = better, >0.8 is strong)\")}`\n );\n console.log(\n ` Questions within ${(report.threshold * 100).toFixed(0)}pp threshold: ${report.questionsWithinThreshold}/${report.totalQuestions}`\n );\n console.log(\n ` Personas: ${report.stats.personaCount} | Real respondents: ${report.stats.realRespondentCount}`\n );\n\n // Stats\n console.log(\n `\\n Best: ${pc.green(report.stats.bestQuestion.name)} (${report.stats.bestQuestion.mae}pp MAE)`\n );\n console.log(\n ` Worst: ${pc.red(report.stats.worstQuestion.name)} (${report.stats.worstQuestion.mae}pp MAE)`\n );\n console.log(\n ` Median: ${report.stats.medianMAE}pp MAE`\n );\n\n // Per-question breakdown\n console.log(`\\n${pc.bold(\"Per-Question Results:\")}\\n`);\n\n for (const q of report.questions) {\n const icon = q.withinThreshold ? pc.green(\"✓\") : pc.red(\"✗\");\n const maeStr =\n q.mae <= report.threshold\n ? pc.green(`${(q.mae * 100).toFixed(1)}pp`)\n : pc.red(`${(q.mae * 100).toFixed(1)}pp`);\n\n console.log(` ${icon} ${pc.bold(q.questionName)} — ${maeStr} MAE`);\n console.log(` ${pc.dim(q.questionText)}`);\n\n // Side-by-side distribution\n const allOpts = [\n ...new Set([\n ...Object.keys(q.realDistribution),\n ...Object.keys(q.syntheticDistribution),\n ]),\n ];\n\n for (const opt of allOpts) {\n const real = q.realDistribution[opt] ?? 0;\n const synth = q.syntheticDistribution[opt] ?? 0;\n const err = q.perOptionError[opt] ?? 0;\n\n const realBar = pc.cyan(\"▓\".repeat(Math.round(real / 5)));\n const synthBar = pc.magenta(\"▓\".repeat(Math.round(synth / 5)));\n const errStr = err > 5 ? pc.yellow(` Δ${err.toFixed(0)}pp`) : \"\";\n\n const label = opt.length > 30 ? opt.slice(0, 30) + \"...\" : opt;\n console.log(\n ` ${label.padEnd(33)} Real: ${realBar} ${String(real.toFixed(0)).padStart(3)}% Synth: ${synthBar} ${String(synth.toFixed(0)).padStart(3)}%${errStr}`\n );\n }\n\n console.log();\n }\n}\n","/**\n * Figma integration.\n *\n * Fetches a Figma design file and has personas review it.\n * Personas evaluate: layout, messaging, clarity, emotional response, usability.\n *\n * @example\n * ```ts\n * const review = await reviewFigmaDesign(llm, personas, {\n * fileKey: \"abc123\",\n * accessToken: \"fig_...\",\n * nodeId: \"42:0\", // optional — specific frame\n * });\n * ```\n */\n\nimport type { LLMProvider, SourceCitation } from \"../types.js\";\nimport { Persona } from \"../persona/persona.js\";\n\nexport interface FigmaConfig {\n /** Figma personal access token */\n accessToken: string;\n /** Figma file key (from the URL) */\n fileKey: string;\n /** Specific node/frame ID to review (optional — reviews whole file if omitted) */\n nodeId?: string;\n}\n\nexport interface DesignReviewResponse {\n personaId: string;\n personaName: string;\n /** Overall impression (1-2 sentences) */\n firstImpression: string;\n /** Does the messaging resonate with this persona? */\n messagingFeedback: string;\n /** Is the layout clear and usable? */\n usabilityFeedback: string;\n /** Emotional response — how does the design make them feel? */\n emotionalResponse: string;\n /** What would they change? */\n suggestions: string[];\n /** Would they trust/use this product based on the design? */\n trustSignal: \"high\" | \"medium\" | \"low\";\n citations: SourceCitation[];\n}\n\nexport interface DesignReview {\n fileKey: string;\n fileName: string;\n nodeId?: string;\n reviews: DesignReviewResponse[];\n /** Aggregated: what most personas agree on */\n consensus: {\n strengths: string[];\n weaknesses: string[];\n suggestions: string[];\n };\n timestamp: string;\n}\n\n/**\n * Fetch a Figma design and have personas review it.\n */\nexport async function reviewFigmaDesign(\n llm: LLMProvider,\n personas: Persona[],\n config: FigmaConfig\n): Promise<DesignReview> {\n // Fetch file metadata\n const fileData = await fetchFigmaFile(config);\n\n // Build a text description of the design for personas\n const designDescription = buildDesignDescription(fileData, config.nodeId);\n\n // Get image URL for the frame\n const imageUrl = await fetchFigmaImage(config);\n\n // Have each persona review\n const reviews: DesignReviewResponse[] = [];\n\n for (const persona of personas) {\n const review = await llm.generateJSON<{\n firstImpression: string;\n messagingFeedback: string;\n usabilityFeedback: string;\n emotionalResponse: string;\n suggestions: string[];\n trustSignal: \"high\" | \"medium\" | \"low\";\n citations: SourceCitation[];\n }>(\n persona.toFullPrompt(\"design review\") +\n \"\\n\\nYou are reviewing a product design. React as yourself — drawing on your full context: your personality, how you use this product, what you need from it, your frustrations. This isn't generic feedback — it's YOUR reaction.\",\n `Review this design:\n\nDesign structure:\n${designDescription}\n\n${imageUrl ? `Design image: ${imageUrl}` : \"\"}\n\nAs this persona, provide your honest review. Return JSON:\n{\n \"firstImpression\": \"<1-2 sentences: your gut reaction seeing this design>\",\n \"messagingFeedback\": \"<does the text/copy speak to you? Is it clear? Does it resonate?>\",\n \"usabilityFeedback\": \"<can you figure out what to do? Is the layout intuitive?>\",\n \"emotionalResponse\": \"<how does this design make you feel? Excited? Confused? Trusted? Skeptical?>\",\n \"suggestions\": [\"<specific thing to change>\", \"<another>\"],\n \"trustSignal\": \"<high|medium|low — would you trust this product based on the design?>\",\n \"citations\": [{\"source\": \"trait-model\", \"detail\": \"<which trait drives this feedback>\", \"weight\": <0-1>}]\n}`\n );\n\n reviews.push({\n personaId: persona.id,\n personaName: persona.name,\n ...review,\n });\n }\n\n // Aggregate consensus\n const consensus = await llm.generateJSON<{\n strengths: string[];\n weaknesses: string[];\n suggestions: string[];\n }>(\n \"You synthesize design feedback from multiple reviewers into actionable consensus.\",\n `${reviews.length} personas reviewed a design. Their feedback:\n\n${reviews.map((r) => `${r.personaName}:\n Impression: ${r.firstImpression}\n Messaging: ${r.messagingFeedback}\n Usability: ${r.usabilityFeedback}\n Emotion: ${r.emotionalResponse}\n Trust: ${r.trustSignal}\n Suggestions: ${r.suggestions.join(\"; \")}`).join(\"\\n\\n\")}\n\nSynthesize into JSON:\n{\n \"strengths\": [\"<things most personas liked>\"],\n \"weaknesses\": [\"<things most personas struggled with>\"],\n \"suggestions\": [\"<top 3-5 actionable changes, ranked by how many personas mentioned them>\"]\n}`\n );\n\n return {\n fileKey: config.fileKey,\n fileName: fileData.name ?? config.fileKey,\n nodeId: config.nodeId,\n reviews,\n consensus,\n timestamp: new Date().toISOString(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Figma API helpers\n// ---------------------------------------------------------------------------\n\nasync function fetchFigmaFile(\n config: FigmaConfig\n): Promise<{ name: string; document: FigmaNode }> {\n const url = config.nodeId\n ? `https://api.figma.com/v1/files/${config.fileKey}/nodes?ids=${config.nodeId}`\n : `https://api.figma.com/v1/files/${config.fileKey}?depth=3`;\n\n const res = await fetch(url, {\n headers: { \"X-Figma-Token\": config.accessToken },\n });\n\n if (!res.ok) {\n throw new Error(`Figma API error: ${res.status} ${res.statusText}`);\n }\n\n return (await res.json()) as { name: string; document: FigmaNode };\n}\n\nasync function fetchFigmaImage(\n config: FigmaConfig\n): Promise<string | null> {\n if (!config.nodeId) return null;\n\n const res = await fetch(\n `https://api.figma.com/v1/images/${config.fileKey}?ids=${config.nodeId}&format=png&scale=2`,\n {\n headers: { \"X-Figma-Token\": config.accessToken },\n }\n );\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as {\n images: Record<string, string>;\n };\n return Object.values(data.images)[0] ?? null;\n}\n\nfunction buildDesignDescription(\n fileData: { name: string; document: FigmaNode },\n nodeId?: string\n): string {\n const lines: string[] = [`File: ${fileData.name}`];\n extractTextFromNode(fileData.document, lines, 0);\n return lines.join(\"\\n\");\n}\n\nfunction extractTextFromNode(\n node: FigmaNode,\n lines: string[],\n depth: number\n): void {\n const indent = \" \".repeat(depth);\n\n if (node.type === \"TEXT\" && node.characters) {\n lines.push(`${indent}[Text] \"${node.characters}\"`);\n } else if (node.name) {\n lines.push(`${indent}[${node.type}] ${node.name}`);\n }\n\n if (node.children) {\n for (const child of node.children) {\n extractTextFromNode(child, lines, depth + 1);\n }\n }\n}\n\ninterface FigmaNode {\n type: string;\n name?: string;\n characters?: string;\n children?: FigmaNode[];\n}\n","/**\n * Slack integration.\n *\n * Post persona responses, survey results, and insights to Slack channels.\n * Also can pull conversation history from channels as a data source.\n *\n * @example\n * ```ts\n * const slack = new SlackIntegration({ token: \"xoxb-...\", channel: \"#research\" });\n *\n * // Post survey results to Slack\n * await slack.postSurveyResults(surveyResult);\n *\n * // Post persona panel response\n * await slack.postPanelResponse(\"Would users pay $15?\", panelResults);\n *\n * // Post insights report\n * await slack.postInsights(insightReport);\n *\n * // Pull channel messages as context for persona generation\n * const messages = await slack.fetchChannelMessages(\"#product-feedback\");\n * ```\n */\n\nimport type {\n SurveyResult,\n InsightReport,\n SourceCitation,\n} from \"../types.js\";\n\nexport interface SlackConfig {\n /** Slack Bot OAuth token (xoxb-...) */\n token: string;\n /** Default channel to post to */\n channel: string;\n}\n\nexport class SlackIntegration {\n private config: SlackConfig;\n\n constructor(config: SlackConfig) {\n this.config = config;\n }\n\n /**\n * Post a panel response (multiple personas answering one question) to Slack.\n */\n async postPanelResponse(\n question: string,\n results: Array<{\n persona: string;\n response: string;\n confidence: string;\n reasoning: string;\n }>,\n channel?: string\n ): Promise<void> {\n const blocks = [\n {\n type: \"header\",\n text: { type: \"plain_text\", text: `Dopple: \"${question}\"` },\n },\n { type: \"divider\" },\n ...results.map((r) => ({\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `*${r.persona}* [${r.confidence}]\\n${r.response}\\n_${r.reasoning}_`,\n },\n })),\n ];\n\n await this.postMessage(blocks, channel);\n }\n\n /**\n * Post survey results summary to Slack.\n */\n async postSurveyResults(\n result: SurveyResult,\n channel?: string\n ): Promise<void> {\n const blocks: SlackBlock[] = [\n {\n type: \"header\",\n text: {\n type: \"plain_text\",\n text: `Dopple Survey: \"${result.surveyName}\"`,\n },\n },\n {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `*${result.personaCount} personas* | *${result.questions.length} questions*`,\n },\n },\n { type: \"divider\" },\n ];\n\n for (const [qName, data] of Object.entries(result.summary)) {\n let text = `*${data.questionText}*\\n`;\n\n if (data.type === \"free_text\") {\n const responses = result.responses.filter(\n (r) => r.questionName === qName\n );\n text += responses\n .map((r) => `> _${r.personaName}_: \"${r.answer}\"`)\n .join(\"\\n\");\n } else {\n const total = Object.values(data.distribution).reduce(\n (a, b) => a + b,\n 0\n );\n for (const [answer, count] of Object.entries(data.distribution)) {\n const pct = total > 0 ? Math.round((count / total) * 100) : 0;\n const bar = \"█\".repeat(Math.round(pct / 5));\n text += `${answer}: ${bar} ${pct}%\\n`;\n }\n }\n\n blocks.push({ type: \"section\", text: { type: \"mrkdwn\", text } });\n }\n\n await this.postMessage(blocks, channel);\n }\n\n /**\n * Post top insights to Slack.\n */\n async postInsights(\n report: InsightReport,\n channel?: string\n ): Promise<void> {\n const blocks: SlackBlock[] = [\n {\n type: \"header\",\n text: {\n type: \"plain_text\",\n text: `Dopple Insights: ${report.product}`,\n },\n },\n {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `*${report.insights.length} insights* | Confidence: ${report.dataQuality.confidence.toUpperCase()} | ${report.segments.length} segments`,\n },\n },\n { type: \"divider\" },\n ];\n\n for (const insight of report.insights.slice(0, 5)) {\n const conf = Math.round(insight.confidence * 100);\n const impact = Math.round(insight.impact * 100);\n\n blocks.push({\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `*${insight.title}*\\n${insight.description}\\n\\nConfidence: ${conf}% | Impact: ${impact}%\\n→ ${insight.recommendation}`,\n },\n });\n }\n\n await this.postMessage(blocks, channel);\n }\n\n /**\n * Fetch messages from a Slack channel (as context for persona generation).\n * Returns messages as plain text strings.\n */\n async fetchChannelMessages(\n channel: string,\n limit: number = 100\n ): Promise<string[]> {\n // Resolve channel name to ID if needed\n const channelId = channel.startsWith(\"C\")\n ? channel\n : await this.resolveChannelId(channel);\n\n if (!channelId) return [];\n\n const res = await fetch(\n `https://slack.com/api/conversations.history?channel=${channelId}&limit=${limit}`,\n {\n headers: { Authorization: `Bearer ${this.config.token}` },\n }\n );\n\n if (!res.ok) return [];\n\n const data = (await res.json()) as {\n ok: boolean;\n messages: Array<{ text: string; user: string; ts: string }>;\n };\n\n if (!data.ok) return [];\n\n return data.messages\n .filter((m) => m.text && !m.text.startsWith(\"<@\"))\n .map((m) => m.text);\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private async postMessage(\n blocks: SlackBlock[],\n channel?: string\n ): Promise<void> {\n const res = await fetch(\"https://slack.com/api/chat.postMessage\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.config.token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n channel: channel ?? this.config.channel,\n blocks,\n }),\n });\n\n if (!res.ok) {\n throw new Error(`Slack API error: ${res.status}`);\n }\n }\n\n private async resolveChannelId(name: string): Promise<string | null> {\n const cleanName = name.replace(/^#/, \"\");\n\n const res = await fetch(\n `https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=200`,\n {\n headers: { Authorization: `Bearer ${this.config.token}` },\n }\n );\n\n if (!res.ok) return null;\n\n const data = (await res.json()) as {\n ok: boolean;\n channels: Array<{ id: string; name: string }>;\n };\n\n return (\n data.channels?.find((c) => c.name === cleanName)?.id ?? null\n );\n }\n}\n\ninterface SlackBlock {\n type: string;\n text?: { type: string; text: string };\n}\n","/**\n * Design and messaging review.\n *\n * Give personas something to look at (screenshot URL, copy text, landing page)\n * and get structured feedback from each persona's perspective.\n *\n * Works with any visual or text content — not just Figma.\n *\n * @example\n * ```ts\n * // Review a landing page\n * const review = await reviewContent(llm, personas, {\n * type: \"landing_page\",\n * content: \"https://myapp.com\",\n * description: \"Our new landing page for the pro plan\",\n * });\n *\n * // Review copy/messaging\n * const review = await reviewContent(llm, personas, {\n * type: \"messaging\",\n * content: \"Ship faster with AI-powered analytics. Start free, upgrade when you're ready.\",\n * description: \"Hero section copy for our landing page\",\n * });\n *\n * // Review a screenshot\n * const review = await reviewContent(llm, personas, {\n * type: \"design\",\n * content: \"https://cdn.example.com/screenshot.png\",\n * description: \"New dashboard design mockup\",\n * });\n * ```\n */\n\nimport type { LLMProvider, SourceCitation } from \"../types.js\";\nimport { Persona } from \"../persona/persona.js\";\n\nexport type ReviewType =\n | \"design\"\n | \"messaging\"\n | \"landing_page\"\n | \"email\"\n | \"ad\"\n | \"pricing_page\"\n | \"onboarding\"\n | \"feature\";\n\nexport interface ReviewInput {\n /** What kind of content is being reviewed */\n type: ReviewType;\n /** The content itself — URL, text, or description */\n content: string;\n /** Additional description or context */\n description?: string;\n /** Specific questions to answer about this content */\n questions?: string[];\n}\n\nexport interface PersonaReview {\n personaId: string;\n personaName: string;\n /** Gut reaction (1-2 sentences) */\n firstImpression: string;\n /** Does this speak to them? */\n relevance: \"high\" | \"medium\" | \"low\";\n /** Is the message clear? */\n clarity: \"clear\" | \"somewhat_clear\" | \"confusing\";\n /** Would they take the desired action? */\n wouldAct: boolean;\n /** What stops them or motivates them? */\n reasoning: string;\n /** Specific feedback points */\n feedback: string[];\n /** Answers to custom questions (if provided) */\n customAnswers?: Record<string, string>;\n citations: SourceCitation[];\n}\n\nexport interface ContentReview {\n input: ReviewInput;\n reviews: PersonaReview[];\n /** What most personas agree on */\n consensus: {\n overallSentiment: \"positive\" | \"mixed\" | \"negative\";\n wouldActRate: number;\n strengths: string[];\n weaknesses: string[];\n suggestions: string[];\n };\n timestamp: string;\n}\n\n/**\n * Have personas review any content — designs, messaging, landing pages, emails.\n */\nexport async function reviewContent(\n llm: LLMProvider,\n personas: Persona[],\n input: ReviewInput\n): Promise<ContentReview> {\n const reviews: PersonaReview[] = [];\n\n for (const persona of personas) {\n const customQBlock =\n input.questions && input.questions.length > 0\n ? `\\n\\nAlso answer these specific questions:\\n${input.questions.map((q, i) => `${i + 1}. ${q}`).join(\"\\n\")}\\nInclude answers in \"customAnswers\" as {\"question\": \"answer\"} pairs.`\n : \"\";\n\n const review = await llm.generateJSON<{\n firstImpression: string;\n relevance: \"high\" | \"medium\" | \"low\";\n clarity: \"clear\" | \"somewhat_clear\" | \"confusing\";\n wouldAct: boolean;\n reasoning: string;\n feedback: string[];\n customAnswers?: Record<string, string>;\n citations: SourceCitation[];\n }>(\n persona.toFullPrompt(input.content) +\n `\\n\\nYou are reviewing ${input.type} content. React honestly as yourself — drawing on your full context: your personality, your past experiences with this product, what you know about it, and how you actually use it. Your review should reflect YOUR specific situation, not generic feedback.`,\n `You're encountering this ${input.type} in your normal life — as a real user of this product, with your history and your opinions.\n\nThe ${input.type}:\n${input.content}\n\n${input.description ? `Context: ${input.description}` : \"\"}\n\nReact based on YOUR specific situation: your usage patterns, your frustrations, your needs, your personality. Don't give generic design feedback — give YOUR feedback.\n${customQBlock}\n\nReturn JSON:\n{\n \"firstImpression\": \"<gut reaction, 1-2 sentences>\",\n \"relevance\": \"<high|medium|low — does this speak to you personally?>\",\n \"clarity\": \"<clear|somewhat_clear|confusing — do you understand what's being offered?>\",\n \"wouldAct\": <true|false — would you sign up / click / buy / take the desired action?>,\n \"reasoning\": \"<why or why not — what personality traits or life circumstances drive this reaction>\",\n \"feedback\": [\"<specific point>\", \"<another>\"],\n ${input.questions ? '\"customAnswers\": {\"<question>\": \"<answer>\"},' : \"\"}\n \"citations\": [{\"source\": \"trait-model\", \"detail\": \"<which trait drives this>\", \"weight\": <0-1>}]\n}`\n );\n\n reviews.push({\n personaId: persona.id,\n personaName: persona.name,\n ...review,\n });\n }\n\n // Aggregate consensus\n const wouldActCount = reviews.filter((r) => r.wouldAct).length;\n const wouldActRate = reviews.length > 0 ? wouldActCount / reviews.length : 0;\n\n const positiveCount = reviews.filter((r) => r.relevance === \"high\").length;\n const negativeCount = reviews.filter((r) => r.relevance === \"low\").length;\n const overallSentiment: \"positive\" | \"mixed\" | \"negative\" =\n positiveCount > reviews.length * 0.6\n ? \"positive\"\n : negativeCount > reviews.length * 0.4\n ? \"negative\"\n : \"mixed\";\n\n const consensus = await llm.generateJSON<{\n strengths: string[];\n weaknesses: string[];\n suggestions: string[];\n }>(\n \"Synthesize design/messaging feedback into actionable consensus.\",\n `${reviews.length} personas reviewed ${input.type} content. Feedback:\n\n${reviews.map((r) => `${r.personaName}: ${r.firstImpression} (relevance: ${r.relevance}, would act: ${r.wouldAct})\\nFeedback: ${r.feedback.join(\"; \")}`).join(\"\\n\\n\")}\n\nReturn JSON:\n{\n \"strengths\": [\"<what most agreed on positively>\"],\n \"weaknesses\": [\"<what most found problematic>\"],\n \"suggestions\": [\"<top 3-5 actionable improvements>\"]\n}`\n );\n\n return {\n input,\n reviews,\n consensus: {\n overallSentiment,\n wouldActRate,\n ...consensus,\n },\n timestamp: new Date().toISOString(),\n };\n}\n"],"mappings":"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","names":["randomUUID","enforceTraitBounds","randomUUID","score","compileBehavioralProfile","readFile","writeFile","mkdir","join","randomUUID","readFile","writeFile","mkdir","readdir","join","slugify","round","readFile","writeFile","mkdir","randomUUID","mkdir","writeFile","readFile","randomUUID","buildDataSummary","getPersonaContext","randomUUID","readFile","readFile","readFile","readdir","join","readdir","join","readFile","round","randomUUID"]}
|