import type { Triple, EntityNode, EpisodicNode, StatementNode, } from "@core/types"; import crypto from "crypto"; export interface OnboardingQuestion { id: string; title: string; description?: string; type: "single-select" | "multi-select" | "text"; options?: OnboardingOption[]; placeholder?: string; required?: boolean; } export interface OnboardingOption { id: string; label: string; value: string; } export interface OnboardingAnswer { questionId: string; value: string | string[]; } // Onboarding questions in order export const ONBOARDING_QUESTIONS: OnboardingQuestion[] = [ { id: "role", title: "What best describes you?", description: 'Role / identity → anchors the "user" node', type: "single-select", options: [ { id: "developer", label: "Developer", value: "Developer" }, { id: "designer", label: "Designer", value: "Designer" }, { id: "product-manager", label: "Product Manager", value: "Product Manager", }, { id: "engineering-manager", label: "Engineering Manager", value: "Engineering Manager", }, { id: "founder", label: "Founder / Executive", value: "Founder / Executive", }, { id: "other", label: "Other", value: "Other" }, ], required: true, }, { id: "goal", title: "What's your primary goal with CORE?", description: 'Motivation → drives the "objective" branch of graph', type: "single-select", options: [ { id: "personal-memory", label: "Build a personal memory system", value: "Build a personal memory system", }, { id: "team-knowledge", label: "Manage team/project knowledge", value: "Manage team/project knowledge", }, { id: "automate-workflows", label: "Automate workflows across tools", value: "Automate workflows across tools", }, { id: "ai-assistant", label: "Power an AI assistant / agent with context", value: "Power an AI assistant / agent with context", }, { id: "explore-core", label: "Explore core", value: "Explore core", }, ], required: true, }, { id: "tools", title: "Which tools do you care about most?", description: "Context → lets you connect integration nodes live", type: "multi-select", options: [ { id: "claude", label: "Claude", value: "Claude" }, { id: "claude-code", label: "Claude Code", value: "Claude Code" }, { id: "cursor", label: "Cursor", value: "Cursor" }, { id: "windsurf", label: "Windsurf", value: "Windsurf" }, { id: "zed", label: "Zed", value: "Zed" }, { id: "github", label: "GitHub", value: "GitHub" }, { id: "slack", label: "Slack", value: "Slack" }, { id: "notion", label: "Notion", value: "Notion" }, { id: "obsidian", label: "Obsidian", value: "Obsidian" }, { id: "gmail", label: "Gmail", value: "Gmail" }, { id: "linear", label: "Linear", value: "Linear" }, { id: "figma", label: "Figma", value: "Figma", }, ], required: true, }, ]; // Helper function to create entity nodes (client-side, no embeddings) function createEntity( name: string, type: string, userId: string, space?: string, ): EntityNode { return { uuid: crypto.randomUUID(), name, type, attributes: {}, nameEmbedding: [], // Empty placeholder for client-side preview typeEmbedding: [], // Empty placeholder for client-side preview createdAt: new Date(), userId, space, }; } // Helper function to create episodic node (client-side, no embeddings) function createEpisode( content: string, userId: string, space?: string, ): EpisodicNode { return { uuid: crypto.randomUUID(), content, originalContent: content, contentEmbedding: [], // Empty placeholder for client-side preview metadata: { source: "onboarding" }, source: "onboarding", createdAt: new Date(), validAt: new Date(), labels: ["onboarding"], userId, space, }; } // Helper function to create statement node (client-side, no embeddings) function createStatement( fact: string, userId: string, space?: string, ): StatementNode { return { uuid: crypto.randomUUID(), fact, factEmbedding: [], // Empty placeholder for client-side preview createdAt: new Date(), validAt: new Date(), invalidAt: null, attributes: {}, userId, space, }; } // Create triplet from onboarding answer using reified knowledge graph structure (client-side, no embeddings) export function createOnboardingTriplet( username: string, questionId: string, answer: string | string[], userId: string, space?: string, ): Triple[] { const triplets: Triple[] = []; // Convert array answers to individual triplets const answers = Array.isArray(answer) ? answer : [answer]; for (const singleAnswer of answers) { // Get the statement mapping for this question type const { predicateType, objectType, factTemplate } = getStatementMapping(questionId); // Create the statement fact (e.g., "Manoj uses GitHub") const fact = factTemplate(username, singleAnswer); // Create entities following CORE's reified structure (client-side preview only) const subject = createEntity(username, "Person", userId, space); const predicate = createEntity( predicateType.toLowerCase().replace("_", " "), // "uses tool" instead of "USES_TOOL" "Predicate", // Use "Predicate" type instead of "Relationship" userId, space, ); const object = createEntity(singleAnswer, objectType, userId, space); // Create statement node as first-class object (client-side preview only) const statement = createStatement(fact, userId, space); // Create provenance episode (client-side preview only) const provenance = createEpisode( `Onboarding question: ${questionId} - Answer: ${singleAnswer}`, userId, space, ); // Create the reified triple structure (no embeddings for client preview) triplets.push({ statement, subject, predicate, object, provenance, }); } return triplets; } // Create initial identity statement for preview using reified knowledge graph structure export function createInitialIdentityStatement(displayName: string): any { const timestamp = Date.now(); const now = new Date().toISOString(); // Create the identity statement: "I'm [DisplayName]" using reified structure const fact = `I'm ${displayName}`; return { // Statement node (center) statementNode: { uuid: `identity-statement-${timestamp}`, name: fact, labels: ["Statement"], attributes: { nodeType: "Statement", type: "Statement", fact: fact, source: "onboarding", validAt: now, }, createdAt: now, }, // Subject entity ("I") subjectNode: { uuid: `pronoun-${timestamp}`, name: "I", labels: ["Entity"], attributes: { nodeType: "Entity", type: "Pronoun", source: "onboarding", }, createdAt: now, }, // Predicate entity ("am") predicateNode: { uuid: `predicate-identity-${timestamp}`, name: "am", labels: ["Entity"], attributes: { nodeType: "Entity", type: "Predicate", source: "onboarding", }, createdAt: now, }, // Object entity (DisplayName) objectNode: { uuid: `user-${timestamp}`, name: displayName, labels: ["Entity"], attributes: { nodeType: "Entity", type: "Person", source: "onboarding", }, createdAt: now, }, // Edges connecting statement to subject, predicate, object edges: { hasSubject: { uuid: `identity-has-subject-${timestamp}`, type: "HAS_SUBJECT", source_node_uuid: `identity-statement-${timestamp}`, target_node_uuid: `pronoun-${timestamp}`, createdAt: now, }, hasPredicate: { uuid: `identity-has-predicate-${timestamp}`, type: "HAS_PREDICATE", source_node_uuid: `identity-statement-${timestamp}`, target_node_uuid: `predicate-identity-${timestamp}`, createdAt: now, }, hasObject: { uuid: `identity-has-object-${timestamp}`, type: "HAS_OBJECT", source_node_uuid: `identity-statement-${timestamp}`, target_node_uuid: `user-${timestamp}`, createdAt: now, }, }, }; } // Create progressive episode content as user answers questions export function createProgressiveEpisode( username: string, answers: OnboardingAnswer[], ): string { // Start with identity let episodeContent = `I'm ${username}.`; // Build episode progressively based on answers for (const answer of answers) { const values = Array.isArray(answer.value) ? answer.value : [answer.value]; switch (answer.questionId) { case "role": episodeContent += ` I'm a ${values[0]}.`; break; case "goal": episodeContent += ` My primary goal with CORE is to ${values[0].toLowerCase()}.`; break; case "tools": if (values.length === 1) { episodeContent += ` I use ${values[0]}.`; } else if (values.length === 2) { episodeContent += ` I use ${values[0]} and ${values[1]}.`; } else { // Create a copy to avoid mutating the original array const toolsCopy = [...values]; const lastTool = toolsCopy.pop(); episodeContent += ` I use ${toolsCopy.join(", ")}, and ${lastTool}.`; } break; } } return episodeContent; } // Create preview statements for real-time visualization (reified structure) // Including episode hierarchy: Episode → Statements → Entities export function createPreviewStatements( username: string, answers: OnboardingAnswer[], ): { episode: any; statements: any[] } { const allStatements: any[] = []; const now = new Date().toISOString(); const baseTimestamp = Date.now(); // Create the cumulative episode content const episodeContent = createProgressiveEpisode(username, answers); // Create episode node that contains all statements const episode = { uuid: `onboarding-episode-${baseTimestamp}`, name: username, content: episodeContent, labels: ["Episode"], attributes: { nodeType: "Episode", type: "Episode", source: "onboarding", content: episodeContent, validAt: now, }, createdAt: now, }; // Create user entity that will be the subject of all statements const userEntityId = `user-${baseTimestamp}`; for (let i = 0; i < answers.length; i++) { const answer = answers[i]; const values = Array.isArray(answer.value) ? answer.value : [answer.value]; for (let j = 0; j < values.length; j++) { const value = values[j]; const uniqueId = `${baseTimestamp}-${i}-${j}`; // Get the relationship mapping for this question const { predicateType, objectType, factTemplate } = getStatementMapping( answer.questionId, ); // Create the statement fact (e.g., "Manoj uses GitHub") const fact = factTemplate(username, value); // Create statement visualization as a reified structure const statement = { // Statement node (center) statementNode: { uuid: `statement-${uniqueId}`, name: fact, labels: ["Statement"], attributes: { nodeType: "Statement", type: "Statement", fact: fact, source: "onboarding", validAt: now, }, createdAt: now, }, // Subject entity (user) subjectNode: { uuid: userEntityId, name: username, labels: ["Entity"], attributes: { nodeType: "Entity", type: "Person", source: "onboarding", }, createdAt: now, }, // Predicate entity (relationship type) predicateNode: { uuid: `predicate-${predicateType}-${uniqueId}`, name: predicateType.toLowerCase().replace("_", " "), labels: ["Entity"], attributes: { nodeType: "Entity", type: "Predicate", source: "onboarding", }, createdAt: now, }, // Object entity (the thing being related to) objectNode: { uuid: `object-${uniqueId}`, name: value, labels: ["Entity"], attributes: { nodeType: "Entity", type: objectType, source: "onboarding", }, createdAt: now, }, // Edges connecting statement to subject, predicate, object edges: { hasSubject: { uuid: `has-subject-${uniqueId}`, type: "HAS_SUBJECT", source_node_uuid: `statement-${uniqueId}`, target_node_uuid: userEntityId, createdAt: now, }, hasPredicate: { uuid: `has-predicate-${uniqueId}`, type: "HAS_PREDICATE", source_node_uuid: `statement-${uniqueId}`, target_node_uuid: `predicate-${predicateType}-${uniqueId}`, createdAt: now, }, hasObject: { uuid: `has-object-${uniqueId}`, type: "HAS_OBJECT", source_node_uuid: `statement-${uniqueId}`, target_node_uuid: `object-${uniqueId}`, createdAt: now, }, // Provenance connection: Episode → Statement hasProvenance: { uuid: `provenance-${uniqueId}`, type: "HAS_PROVENANCE", source_node_uuid: `statement-${uniqueId}`, target_node_uuid: episode.uuid, createdAt: now, }, }, }; allStatements.push(statement); } } return { episode, statements: allStatements }; } // Helper function to map question types to statement templates with natural English phrasing function getStatementMapping(questionId: string): { predicateType: string; objectType: string; factTemplate: (subject: string, object: string) => string; } { switch (questionId) { case "role": return { predicateType: "IS_A", objectType: "Role", factTemplate: (subject, object) => `${subject} is a ${object.toLowerCase()}`, }; case "goal": return { predicateType: "WANTS_TO", objectType: "Goal", factTemplate: (subject, object) => `${subject} wants to ${object.toLowerCase()}`, }; case "tools": return { predicateType: "USES", objectType: "Tool", factTemplate: (subject, object) => `${subject} uses ${object}`, }; default: return { predicateType: "HAS", objectType: "Attribute", factTemplate: (subject, object) => `${subject} has ${object}`, }; } } // Create main onboarding episode (client-side preview, no embeddings) export function createOnboardingEpisode( username: string, answers: OnboardingAnswer[], userId: string, space?: string, ): EpisodicNode { // Generate progressive episode content const episodeContent = createProgressiveEpisode(username, answers); // Create the main onboarding episode for client preview const episode: EpisodicNode = { uuid: crypto.randomUUID(), content: episodeContent, originalContent: episodeContent, // Same as content for onboarding contentEmbedding: [], // Empty placeholder for client-side preview source: "onboarding", metadata: { completedAt: new Date().toISOString(), questionCount: answers.length, answersData: answers, // Store original answers for reference }, createdAt: new Date(), validAt: new Date(), labels: ["onboarding", "user-profile"], userId, space, sessionId: crypto.randomUUID(), // Generate unique session for onboarding }; return episode; }