diff --git a/.evo-deploy/state.json b/.evo-deploy/state.json index 66f05d0..83ab1dd 100644 --- a/.evo-deploy/state.json +++ b/.evo-deploy/state.json @@ -1,7 +1,7 @@ { "previousSha": "HEAD", - "deployedSha": "is now at 83e88d0 feat: security headers + onboarding scan & profile creation flow", - "ref": "83e88d05f3efa00635a5ac5956fe637e45d71057", + "deployedSha": "is now at 2fe3dc5 feat(api): real onboarding scan — filesystem, Git repos, profile compilation", + "ref": "2fe3dc5971d55ca9604757bc79aae94feb9ec21d", "status": "success", - "at": "2026-09-23T23:36:23Z" + "at": "2026-09-23T23:52:26Z" } diff --git a/GROK-CLAUDE-HANDOFF.md b/GROK-CLAUDE-HANDOFF.md index 400214f..eeb7a17 100644 --- a/GROK-CLAUDE-HANDOFF.md +++ b/GROK-CLAUDE-HANDOFF.md @@ -9,3 +9,4 @@ - Autopilot restart path: A rejected (evoo-stack = Rakazo Docker, broken symlink /srv/rakazo/stack-up.sh → missing /srv/evoo/rakazo/…). Chose new user unit evoo-local.service + ~/.local/bin/evoo-dev-stack.sh; live :3100/:5173 migrated; GH vars EVO_RESTART_MODE=systemd EVO_SYSTEMD_UNIT=evoo-local.service. Do not use evoo-stack for evo-deploy. - Cancelled queued evo-deploy 35933339459 (would reset --hard over Claude WIP now that restart=systemd). Re-dispatch after WIP committed/stashed. +2026-09-23 23:53:08 UTC evoo-tools disabled (missing app.py) diff --git a/apps/api/src/onboarding-scan.ts b/apps/api/src/onboarding-scan.ts index da6bdc5..bee109e 100644 --- a/apps/api/src/onboarding-scan.ts +++ b/apps/api/src/onboarding-scan.ts @@ -15,6 +15,7 @@ interface ScanResult { profileSummary: ProfileSummary; agentBriefing: string; createdProjects: CreatedProject[]; + subAgentRoles: SubAgentRole[]; } interface RepoInfo { @@ -75,7 +76,7 @@ interface ScanRecord { const scanRegistry = new Map(); -export async function startOnboardingScan(botId: string): Promise<{ scanId: string }> { +export async function startOnboardingScan(botId: string, prisma: any): Promise<{ scanId: string }> { const scanId = `scan_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const record: ScanRecord = { id: scanId, botId, status: "scanning", progress: 0, message: "Scanning your computer...", @@ -87,7 +88,7 @@ export async function startOnboardingScan(botId: string): Promise<{ scanId: stri (async () => { try { const homeDir = process.env.HOME ?? "/home/cynar"; - const result = await executeFullScan(homeDir, botId); + const result = await executeFullScan(homeDir, botId, prisma); record.status = "complete"; record.progress = 100; record.message = "Scan complete — profile ready"; @@ -116,7 +117,7 @@ export async function getScanStatus(scanId: string): Promise<{ // Full scan pipeline // --------------------------------------------------------------------------- -async function executeFullScan(homeDir: string, _botId: string): Promise { +async function executeFullScan(homeDir: string, _botId: string, prisma: any): Promise { // Phase 1: Local file scan const [fileSummary, projectFolders] = await scanFiles(homeDir); @@ -127,13 +128,16 @@ async function executeFullScan(homeDir: string, _botId: string): Promise { +async function createProjectFolders(repos: RepoInfo[], prisma: any): Promise { const created: CreatedProject[] = []; for (const repo of repos) { - // Each repo becomes a project space - // In production, this would call the database API to create a space - // For now, we return the info for the caller to persist - created.push({ - id: `proj_${repo.name}_${Date.now()}`, - name: repo.name, - spaceId: `space_${repo.name.toLowerCase().replace(/[^a-z0-9]/g, "_")}`, - description: repo.remote || `Local project: ${repo.path}`, - }); + try { + // Check if space already exists + const existing = await prisma.space.findFirst({ where: { title: repo.name } }); + if (existing) { + created.push({ id: existing.id, name: repo.name, spaceId: existing.id, description: repo.remote || repo.path }); + continue; + } + // Create new space + const space = await prisma.space.create({ + data: { title: repo.name, path: repo.path }, + }); + created.push({ id: space.id, name: repo.name, spaceId: space.id, description: repo.remote || repo.path }); + } catch { + // Space might already exist or DB unavailable — skip + } } return created; } +// --------------------------------------------------------------------------- +// Phase 6: Sub-agent role creation +// --------------------------------------------------------------------------- + +interface SubAgentRole { + name: string; + role: string; + description: string; + instructions: string; + skills: string[]; +} + +function generateSubAgentRoles(profile: ProfileSummary, repos: RepoInfo[]): SubAgentRole[] { + const roles: SubAgentRole[] = []; + const hasCode = profile.languages?.length > 0; + const hasDesign = profile.interests?.some(i => i.toLowerCase().includes("design") || i.toLowerCase().includes("ui")); + const hasData = profile.interests?.some(i => i.toLowerCase().includes("data") || i.toLowerCase().includes("analytics") || i.toLowerCase().includes("database")); + const hasDevOps = profile.interests?.some(i => i.toLowerCase().includes("infra") || i.toLowerCase().includes("ops") || i.toLowerCase().includes("deploy")); + const hasMusic = profile.interests?.some(i => i.toLowerCase().includes("music")); + const hasWriting = profile.interests?.some(i => i.toLowerCase().includes("write") || i.toLowerCase().includes("blog")); + + if (hasCode) { + const primaryLang = profile.languages?.[0] || "TypeScript"; + roles.push({ + name: "Code Engineer", + role: "software-engineer", + description: `Specializes in ${primaryLang} development and code review`, + instructions: `You are the Code Engineer specialist. Your expertise is in ${profile.languages?.join(", ") || "software development"}. Review code, write tests, debug issues, and implement features. You have access to the user's local repositories and can run commands to build and test code.`, + skills: profile.skills.filter(s => s.toLowerCase().includes("code") || s.toLowerCase().includes("program")), + }); + } + + if (hasDesign) { + roles.push({ + name: "UI/UX Designer", + role: "ui-designer", + description: "Creates polished interfaces and design systems", + instructions: "You are the UI/UX Designer specialist. Focus on creating beautiful, accessible interfaces. Review designs, suggest improvements, and ensure consistency across the user's projects.", + skills: ["design", "UI/UX"], + }); + } + + if (hasData) { + roles.push({ + name: "Data Analyst", + role: "data-analyst", + description: "Analyzes data, creates reports, and extracts insights", + instructions: "You are the Data Analyst specialist. Process data from the user's projects, create visualizations, generate reports, and provide actionable insights.", + skills: ["data analysis", "statistics"], + }); + } + + if (hasDevOps) { + roles.push({ + name: "DevOps Engineer", + role: "devops-engineer", + description: "Manages infrastructure, deployments, and CI/CD", + instructions: "You are the DevOps Engineer specialist. Handle deployments, manage infrastructure, configure CI/CD pipelines, and ensure system reliability.", + skills: ["DevOps", "infrastructure"], + }); + } + + if (hasMusic) { + roles.push({ + name: "Music Producer", + role: "music-producer", + description: "Assists with music production and audio engineering", + instructions: "You are the Music Producer specialist. Help with music composition, audio engineering, mixing, and mastering. You understand the user's music projects and can provide expert advice.", + skills: ["music production", "audio engineering"], + }); + } + + if (hasWriting) { + roles.push({ + name: "Content Writer", + role: "content-writer", + description: "Creates and edits written content", + instructions: "You are the Content Writer specialist. Write blog posts, documentation, articles, and other content for the user's projects and personal use.", + skills: ["writing", "content creation"], + }); + } + + // Always include a general assistant + roles.unshift({ + name: "General Assistant", + role: "general-assistant", + description: "Handles general tasks and coordinates other specialists", + instructions: `You are the General Assistant and team coordinator. Your role is to handle general queries, coordinate with other specialists (${roles.slice(1).map(r => r.name).join(", ") || "none yet"}), and manage the user's daily workflow. You have access to all tools and can delegate tasks to specialists when needed.`, + skills: ["task management", "coordination"], + }); + + return roles; +} + // --------------------------------------------------------------------------- // Utility // --------------------------------------------------------------------------- diff --git a/apps/api/src/onboarding.ts b/apps/api/src/onboarding.ts index 2d07569..899267c 100644 --- a/apps/api/src/onboarding.ts +++ b/apps/api/src/onboarding.ts @@ -8,6 +8,7 @@ import { type Prisma, type PrismaClient, type ThreadEvents, + createPool, } from "@rakazo/db"; /** diff --git a/apps/api/src/router.ts b/apps/api/src/router.ts index 9438ac0..f5d3fc8 100644 --- a/apps/api/src/router.ts +++ b/apps/api/src/router.ts @@ -3318,7 +3318,7 @@ export function createRouter(deps: RouterDeps) { return { ok: true as const }; }), scan: authed.onboarding.scan.handler(async ({ context, input }) => { - const { scanId } = await startOnboardingScan(input.botId); + const { scanId } = await startOnboardingScan(input.botId, deps.prisma); return { scanId }; }), scanStatus: authed.onboarding.scanStatus.handler(async ({ input }) => {