Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@ name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"

jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'

runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -38,17 +26,53 @@ jobs:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
use_sticky_comment: true
prompt: |
Please review this pull request and provide feedback on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security concerns
- Test coverage
You are reviewing a PR for supermemory - a Turbo monorepo with multiple apps and packages.

## Repository Structure Context

**Apps (apps/):**
- `web` - Next.js web application (no tests)
- `mcp` - Model Context Protocol server on Cloudflare Workers (no tests)
- `browser-extension` - WXT-based browser extension (no tests)
- `raycast-extension` - Raycast app extension (no tests)
- `docs` - Mintlify documentation site

**Published Packages (packages/) - with tests:**
- `tools` - AI SDK memory tools (Vitest)
- `ai-sdk` - supermemory AI SDK wrapper (Vitest)
- `openai-sdk-python` - Python OpenAI integration (pytest)
- `pipecat-sdk-python` - Python Pipecat integration (pytest)

**Internal Packages (packages/) - no tests:**
- `ui` - Shared React/Radix UI components
- `lib` - Shared utilities
- `hooks` - Custom React hooks
- `memory-graph` - D3-based graph visualization
- `validation` - Zod schemas

## Review Instructions

1. First, run `gh pr diff` to see what files changed

2. Based on the changes, review for:
- **Code quality**: Follow Biome linting rules (double quotes, tabs, no default exports)
- **Type safety**: Ensure proper TypeScript usage
- **Security**: Check for injection vulnerabilities, credential exposure, unsafe patterns
- **Performance**: Look for unnecessary re-renders (React), N+1 queries, memory leaks

3. **Test coverage** - ONLY review if changes touch these packages:
- `packages/tools/**` or `packages/ai-sdk/**` → Check for Vitest tests
- `packages/openai-sdk-python/**` or `packages/pipecat-sdk-python/**` → Check for pytest tests
- Skip test coverage review for apps and other packages (they have no test setup)

4. **Package-specific concerns:**
- Published packages (tools, ai-sdk, memory-graph, *-python): Check for breaking API changes
- UI package: Check accessibility (a11y) and component API consistency
- Web app: Check for proper data fetching patterns (TanStack Query), Zustand state management
- MCP server: Check Hono routing and Cloudflare Workers compatibility

Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
5. Be concise and actionable. Focus on issues that matter, not style nitpicks (Biome handles that).

Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.

# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
4 changes: 3 additions & 1 deletion apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
NEXT_PUBLIC_BACKEND_URL=https://api.supermemory.ai
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_KEY=
EXA_API_KEY=
XAI_API_KEY=
14 changes: 11 additions & 3 deletions apps/web/app/(auth)/login/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { InitialHeader } from "@/components/initial-header"
import { useRouter, useSearchParams } from "next/navigation"
import { useState, useEffect } from "react"
import { motion } from "motion/react"
import { dmSansClassName } from "@/utils/fonts"
import { dmSansClassName } from "@/lib/fonts"
import { cn } from "@lib/utils"
import { Logo } from "@ui/assets/Logo"

Expand All @@ -30,7 +30,11 @@ function AnimatedGradientBackground() {
}}
transition={{
y: { duration: 0.75, ease: "easeOut" },
opacity: { duration: 8, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" },
opacity: {
duration: 8,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
},
}}
/>
<motion.div
Expand All @@ -42,7 +46,11 @@ function AnimatedGradientBackground() {
}}
transition={{
y: { duration: 0.75, ease: "easeOut" },
opacity: { duration: 8, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" },
opacity: {
duration: 8,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
},
}}
/>
<motion.div
Expand Down
81 changes: 81 additions & 0 deletions apps/web/app/api/onboarding/research/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { xai } from "@ai-sdk/xai"
import { generateText } from "ai"

interface ResearchRequest {
xUrl: string
name?: string
email?: string
}

// prompt to get user context from X/Twitter profile
function finalPrompt(xUrl: string, userContext: string) {
return `You are researching a user based on their X/Twitter profile to help personalize their experience.

X/Twitter Profile URL: ${xUrl}${userContext}

Please analyze this X/Twitter profile and provide a comprehensive but concise summary of the user. Include:
- Professional background and current role (if available)
- Key interests and topics they engage with
- Notable projects, achievements, or affiliations
- Their expertise areas
- Any other relevant information that helps understand who they are

Format the response as clear, readable paragraphs. Focus on factual information from their profile. If certain information is not available, skip that section rather than speculating.`
}

export async function POST(req: Request) {
try {
const { xUrl, name, email }: ResearchRequest = await req.json()

if (!xUrl?.trim()) {
return Response.json(
{ error: "X/Twitter URL is required" },
{ status: 400 },
)
}

const lowerUrl = xUrl.toLowerCase()
if (!lowerUrl.includes("x.com") && !lowerUrl.includes("twitter.com")) {
return Response.json(
{ error: "URL must be an X/Twitter profile link" },
{ status: 400 },
)
}

const contextParts: string[] = []
if (name) contextParts.push(`Name: ${name}`)
if (email) contextParts.push(`Email: ${email}`)
const userContext =
contextParts.length > 0
? `\n\nAdditional context about the user:\n${contextParts.join("\n")}`
: ""

const { text } = await generateText({
model: xai("grok-4-1-fast-reasoning"),
prompt: finalPrompt(xUrl, userContext),
providerOptions: {
xai: {
searchParameters: {
mode: "on",
sources: [
{
type: "web",
safeSearch: true,
},
{
type: "x",
includedXHandles: [lowerUrl.replace("https://x.com/", "").replace("https://twitter.com/", "")],
postFavoriteCount: 10,
},
],
},
},
},
})

return Response.json({ text })
} catch (error) {
console.error("Research API error:", error)
return Response.json({ error: "Internal server error" }, { status: 500 })
}
}
22 changes: 11 additions & 11 deletions apps/web/app/new/onboarding/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import { useState, useEffect } from "react"
import { useAuth } from "@lib/auth-context"
import { cn } from "@lib/utils"

import { InputStep } from "./welcome/input-step"
import { GreetingStep } from "./welcome/greeting-step"
import { WelcomeStep } from "./welcome/welcome-step"
import { ContinueStep } from "./welcome/continue-step"
import { FeaturesStep } from "./welcome/features-step"
import { MemoriesStep } from "./welcome/memories-step"
import { RelatableQuestion } from "./setup/relatable-question"
import { IntegrationsStep } from "./setup/integrations-step"
import { InputStep } from "../../../components/new/onboarding/welcome/input-step"
import { GreetingStep } from "../../../components/new/onboarding/welcome/greeting-step"
import { WelcomeStep } from "../../../components/new/onboarding/welcome/welcome-step"
import { ContinueStep } from "../../../components/new/onboarding/welcome/continue-step"
import { FeaturesStep } from "../../../components/new/onboarding/welcome/features-step"
import { ProfileStep } from "../../../components/new/onboarding/welcome/profile-step"
import { RelatableQuestion } from "../../../components/new/onboarding/setup/relatable-question"
import { IntegrationsStep } from "../../../components/new/onboarding/setup/integrations-step"

import { InitialHeader } from "@/components/initial-header"
import { SetupHeader } from "./setup/header"
import { ChatSidebar } from "./setup/chat-sidebar"
import { SetupHeader } from "../../../components/new/onboarding/setup/header"
import { ChatSidebar } from "../../../components/new/onboarding/setup/chat-sidebar"
import { Logo } from "@ui/assets/Logo"
import NovaOrb from "@/components/nova/nova-orb"
import { AnimatedGradientBackground } from "@/components/new/animated-gradient-background"
Expand Down Expand Up @@ -152,7 +152,7 @@ export default function OnboardingPage() {
case "features":
return <FeaturesStep key="features" />
case "memories":
return <MemoriesStep key="memories" onSubmit={setMemoryFormData} />
return <ProfileStep key="profile" onSubmit={setMemoryFormData} />
default:
return null
}
Expand Down
8 changes: 6 additions & 2 deletions apps/web/app/new/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { motion } from "motion/react"
import NovaOrb from "@/components/nova/nova-orb"
import { useState, useEffect, useRef } from "react"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/utils/fonts"
import { dmSansClassName } from "@/lib/fonts"
import Account from "@/components/new/settings/account"
import Integrations from "@/components/new/settings/integrations"
import ConnectionsMCP from "@/components/new/settings/connections-mcp"
Expand Down Expand Up @@ -175,7 +175,11 @@ export default function SettingsPage() {
return (
<div className="h-screen flex flex-col overflow-hidden">
<header className="flex justify-between items-center px-6 py-3 shrink-0">
<button type="button" onClick={() => router.push("/new")} className="cursor-pointer">
<button
type="button"
onClick={() => router.push("/new")}
className="cursor-pointer"
>
<Logo className="h-7" />
</button>
<div className="flex items-center gap-2">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/new/add-document/connections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Check, Loader, Trash2, Zap } from "lucide-react"
import { useEffect, useState } from "react"
import { toast } from "sonner"
import type { z } from "zod"
import { dmSansClassName } from "@/utils/fonts"
import { dmSansClassName } from "@/lib/fonts"
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"

Expand Down
21 changes: 17 additions & 4 deletions apps/web/components/new/add-document/file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useState, useEffect } from "react"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/utils/fonts"
import { dmSansClassName } from "@/lib/fonts"
import { FileIcon } from "lucide-react"
import { useHotkeys } from "react-hotkeys-hook"

Expand All @@ -19,7 +19,12 @@ interface FileContentProps {
isOpen?: boolean
}

export function FileContent({ onSubmit, onDataChange, isSubmitting, isOpen }: FileContentProps) {
export function FileContent({
onSubmit,
onDataChange,
isSubmitting,
isOpen,
}: FileContentProps) {
const [isDragging, setIsDragging] = useState(false)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
const [title, setTitle] = useState("")
Expand All @@ -33,8 +38,16 @@ export function FileContent({ onSubmit, onDataChange, isSubmitting, isOpen }: Fi
}
}

const updateData = (newFile: File | null, newTitle: string, newDescription: string) => {
onDataChange?.({ file: newFile, title: newTitle, description: newDescription })
const updateData = (
newFile: File | null,
newTitle: string,
newDescription: string,
) => {
onDataChange?.({
file: newFile,
title: newTitle,
description: newDescription,
})
}

const handleFileChange = (file: File | null) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/new/add-document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState, useEffect, useMemo, useCallback } from "react"
import { Dialog, DialogContent, DialogTitle } from "@repo/ui/components/dialog"
import { cn } from "@lib/utils"
import { dmSansClassName } from "@/utils/fonts"
import { dmSansClassName } from "@/lib/fonts"
import {
FileTextIcon,
GlobeIcon,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/new/add-document/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState, useEffect } from "react"
import { cn } from "@lib/utils"
import { Button } from "@ui/components/button"
import { dmSansClassName } from "@/utils/fonts"
import { dmSansClassName } from "@/lib/fonts"
import { useHotkeys } from "react-hotkeys-hook"
import { Image as ImageIcon, Loader2 } from "lucide-react"
import { toast } from "sonner"
Expand Down
21 changes: 8 additions & 13 deletions apps/web/components/new/add-document/note.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client"

import { useState, useEffect } from "react"
import { useHotkeys } from "react-hotkeys-hook"
import { TextEditor } from "../text-editor"

interface NoteContentProps {
onSubmit?: (content: string) => void
Expand Down Expand Up @@ -31,11 +31,6 @@ export function NoteContent({
onContentChange?.(newContent)
}

useHotkeys("mod+enter", handleSubmit, {
enabled: isOpen && canSubmit,
enableOnFormTags: ["TEXTAREA"],
})

// Reset content when modal closes
useEffect(() => {
if (!isOpen) {
Expand All @@ -45,12 +40,12 @@ export function NoteContent({
}, [isOpen, onContentChange])

return (
<textarea
value={content}
onChange={(e) => handleContentChange(e.target.value)}
placeholder="Write your note here..."
disabled={isSubmitting}
className="w-full h-full p-4 mb-4! rounded-[14px] bg-[#14161A] shadow-inside-out resize-none disabled:opacity-50 outline-none"
/>
<div className="p-4 overflow-y-auto flex-1 w-full h-full mb-4! bg-[#14161A] shadow-inside-out rounded-[14px]">
<TextEditor
content={undefined}
onContentChange={handleContentChange}
onSubmit={handleSubmit}
/>
</div>
)
}
Loading
Loading