fix: Remove all session validation to prevent continuation errors after /exit
Root cause: Hooks provide session_id as the source of truth. We were adding unnecessary validation (checking if sessions exist, checking status, etc.) which caused 409 conflicts when continuing sessions after /exit. Changes: 1. worker-service.ts: Removed 409 "Session already exists" check in handleInit 2. SessionStore.ts: Made createSDKSession idempotent using INSERT OR IGNORE 3. new-hook.ts: Simplified to just call createSDKSession - no findActiveSDKSession, no reactivateSession logic, no status management 4. save-hook.ts: Removed session validation, use fixed port instead of session.worker_port 5. summary-hook.ts: Removed session validation, use fixed port instead of session.worker_port Philosophy: Hooks manage lifecycle, we just save data with whatever session_id they give us. No validation, no status checks, no guessing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+297
@@ -0,0 +1,297 @@
|
||||
# Claude Never Forgets
|
||||
|
||||
**Give Claude a memory that spans your entire project.**
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
```
|
||||
You: "Remember that bug we fixed last Tuesday with the auth flow?"
|
||||
Claude: "I don't have access to previous conversations..."
|
||||
```
|
||||
|
||||
Every `/clear` wipes Claude's memory. Every new session starts from zero. You repeat yourself constantly.
|
||||
|
||||
**Until now.**
|
||||
|
||||
---
|
||||
|
||||
## What Changes
|
||||
|
||||
### Before claude-mem
|
||||
```typescript
|
||||
// Monday: You debug an issue
|
||||
You: "Why is the database connection failing?"
|
||||
Claude: [Helps you fix it]
|
||||
|
||||
// Wednesday: Similar issue appears
|
||||
You: "The database is timing out again"
|
||||
Claude: "Let me investigate..." [Starts from scratch]
|
||||
```
|
||||
|
||||
### After claude-mem
|
||||
```typescript
|
||||
// Monday: You debug an issue
|
||||
You: "Why is the database connection failing?"
|
||||
Claude: [Helps you fix it]
|
||||
✓ Remembers: connection pool exhaustion pattern
|
||||
|
||||
// Wednesday: Similar issue appears
|
||||
You: "The database is timing out again"
|
||||
Claude: "Based on Monday's session, this looks like the same
|
||||
connection pool issue. Let me check the pool size config..."
|
||||
```
|
||||
|
||||
Claude **remembers**. Claude **learns**. Claude gets **better** over time.
|
||||
|
||||
---
|
||||
|
||||
## Real Examples
|
||||
|
||||
### 1. **Context Across Sessions**
|
||||
|
||||
**Without claude-mem:**
|
||||
```
|
||||
Session 1: "We use Redux for state management"
|
||||
Session 2: "What state management do you use?" ❌
|
||||
```
|
||||
|
||||
**With claude-mem:**
|
||||
```
|
||||
Session 1: "We use Redux for state management"
|
||||
Session 2: Claude already knows you use Redux ✓
|
||||
Suggests Redux patterns automatically ✓
|
||||
References your store structure ✓
|
||||
```
|
||||
|
||||
### 2. **Architectural Memory**
|
||||
|
||||
**Your third session of the day:**
|
||||
```
|
||||
You: "Add a new API endpoint for user preferences"
|
||||
|
||||
Claude: "I see from previous sessions that:
|
||||
- Your API follows REST conventions in src/api/
|
||||
- You use Zod for validation
|
||||
- Auth middleware is required for user routes
|
||||
- You prefer async/await over promises
|
||||
|
||||
I'll create the endpoint following these patterns..."
|
||||
```
|
||||
|
||||
**No explaining. No repeating. Just building.**
|
||||
|
||||
### 3. **Bug Pattern Recognition**
|
||||
|
||||
```
|
||||
Week 1: Fixed race condition in webhook handler
|
||||
Week 2: Different race condition in event processor
|
||||
|
||||
Claude: "This looks similar to the webhook race condition
|
||||
we fixed last week. The same solution should work..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ You code with │
|
||||
│ Claude today │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ claude-mem captures & │
|
||||
│ compresses everything │
|
||||
│ into structured memories │
|
||||
└────────┬────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Tomorrow, Claude starts │
|
||||
│ with full context of │
|
||||
│ your project history │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
**Automatic. Zero effort. Always on.**
|
||||
|
||||
---
|
||||
|
||||
## What Gets Remembered
|
||||
|
||||
✓ **Decisions**: "Why did we choose this architecture?"
|
||||
✓ **Bugs Fixed**: "How did we solve this before?"
|
||||
✓ **Code Patterns**: "What's our convention for this?"
|
||||
✓ **File Changes**: "What did we modify last session?"
|
||||
✓ **Refactorings**: "What was the old implementation?"
|
||||
✓ **Dependencies**: "Which libraries are we using?"
|
||||
|
||||
Everything Claude does with you gets compressed into **searchable, reusable memory**.
|
||||
|
||||
---
|
||||
|
||||
## Powerful Search
|
||||
|
||||
Ask Claude to search your project history:
|
||||
|
||||
```
|
||||
You: "Find all the database migrations we did"
|
||||
Claude: [Searches across all sessions]
|
||||
"I found 7 database-related changes:
|
||||
- March 15: Added user_preferences table
|
||||
- March 12: Migration for OAuth tokens
|
||||
- March 8: Index optimization on sessions
|
||||
..."
|
||||
|
||||
You: "What decisions did we make about authentication?"
|
||||
Claude: [Retrieves decision observations]
|
||||
"We decided to use JWT tokens because..."
|
||||
```
|
||||
|
||||
7 specialized search tools. Instant recall. Full project history.
|
||||
|
||||
---
|
||||
|
||||
## The Numbers
|
||||
|
||||
| Metric | Before | After |
|
||||
|--------|--------|-------|
|
||||
| Context repetition | Every session | Never |
|
||||
| Onboarding time | 5-10 min per session | 0 seconds |
|
||||
| Bug re-investigation | Common | Rare |
|
||||
| Architectural questions | "What did we decide?" | Claude already knows |
|
||||
| Code pattern consistency | Manual enforcement | Automatic |
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Quick Start (2 minutes)
|
||||
|
||||
```bash
|
||||
# 1. Clone and install
|
||||
git clone https://github.com/thedotmack/claude-mem.git
|
||||
cd claude-mem
|
||||
|
||||
# 2. Add to Claude Code
|
||||
/plugin marketplace add .claude-plugin/marketplace.json
|
||||
|
||||
# 3. Install
|
||||
/plugin install claude-mem
|
||||
```
|
||||
|
||||
**Done.** Claude now has memory.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Choose your AI model (controls cost vs. quality of memory compression):
|
||||
|
||||
```bash
|
||||
./claude-mem-settings.sh
|
||||
```
|
||||
|
||||
**Models:**
|
||||
- `claude-haiku-4-5` - Fast & cheap
|
||||
- `claude-sonnet-4-5` - Balanced (default) ✓
|
||||
- `claude-opus-4` - Maximum quality
|
||||
|
||||
---
|
||||
|
||||
## Under The Hood
|
||||
|
||||
**Simple architecture, powerful results:**
|
||||
|
||||
1. **Hooks** capture every tool Claude uses
|
||||
2. **Worker service** compresses observations with AI
|
||||
3. **SQLite database** stores structured memories
|
||||
4. **MCP server** makes everything searchable
|
||||
5. **Context injection** gives Claude the right memories at the right time
|
||||
|
||||
**Zero maintenance. Runs in the background. Just works.**
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Solo Developers
|
||||
- Never lose context between coding sessions
|
||||
- Build on past decisions automatically
|
||||
- Remember why you made each choice
|
||||
|
||||
### Team Projects
|
||||
- Share architectural knowledge across sessions
|
||||
- Maintain consistency in code patterns
|
||||
- Document decisions as they happen
|
||||
|
||||
### Learning & Experiments
|
||||
- Track what you tried and what worked
|
||||
- Build a personal knowledge base
|
||||
- Learn from past mistakes
|
||||
|
||||
### Large Refactors
|
||||
- Remember what you changed across multiple sessions
|
||||
- Track progress on multi-day tasks
|
||||
- Maintain context through interruptions
|
||||
|
||||
---
|
||||
|
||||
## What Developers Say
|
||||
|
||||
> *"I used to spend 10 minutes every morning explaining my project to Claude. Now it just knows."*
|
||||
|
||||
> *"It's like having a teammate who was actually there for every line of code."*
|
||||
|
||||
> *"The search is incredible. I can ask about decisions we made weeks ago."*
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
**Does this slow down Claude?**
|
||||
No. Memory processing happens in the background. Claude responds instantly.
|
||||
|
||||
**How much does it cost?**
|
||||
Minimal. Memory compression uses your chosen model (default: Sonnet 4.5). Typical cost: $0.01-0.05 per coding session.
|
||||
|
||||
**Where is data stored?**
|
||||
Locally in `~/.claude-mem/claude-mem.db`. Fully private. Never leaves your machine.
|
||||
|
||||
**Can I search my memories?**
|
||||
Yes. 7 specialized search tools available through Claude.
|
||||
|
||||
**Does it work with existing projects?**
|
||||
Yes. Starts learning immediately when installed.
|
||||
|
||||
**What if I want to forget something?**
|
||||
Delete observations directly from the SQLite database, or start fresh by removing the DB file.
|
||||
|
||||
---
|
||||
|
||||
## Get Started
|
||||
|
||||
```bash
|
||||
git clone https://github.com/thedotmack/claude-mem.git
|
||||
cd claude-mem
|
||||
/plugin marketplace add .claude-plugin/marketplace.json
|
||||
/plugin install claude-mem
|
||||
```
|
||||
|
||||
**Give Claude a memory. Transform how you code.**
|
||||
|
||||
---
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Technical Documentation](./CLAUDE.md)
|
||||
- [GitHub Repository](https://github.com/thedotmack/claude-mem)
|
||||
- [Report Issues](https://github.com/thedotmack/claude-mem/issues)
|
||||
|
||||
**License**: AGPL-3.0
|
||||
**Version**: 4.1.0
|
||||
**Author**: Alex Newman ([@thedotmack](https://github.com/thedotmack))
|
||||
@@ -0,0 +1,283 @@
|
||||
# Magic UI Components Catalog
|
||||
|
||||
Complete catalog of Magic UI components with descriptions and use cases.
|
||||
|
||||
## Animation Components
|
||||
|
||||
### 1. **Animated List**
|
||||
- **Purpose**: Animates list items sequentially with delay
|
||||
- **Use Case**: Showcasing events, notifications, feature lists
|
||||
- **Key Props**: `delay` (ms between items)
|
||||
- **Effect**: Staggered reveal animation
|
||||
|
||||
### 2. **Text Animate**
|
||||
- **Purpose**: Sophisticated text animation effects
|
||||
- **Animation Types**:
|
||||
- `blurIn` - Characters fade from blur
|
||||
- `slideUp` - Words slide up
|
||||
- `scaleUp` - Text scales up
|
||||
- `fadeIn` - Lines fade in
|
||||
- `slideLeft` - Characters slide from left
|
||||
- **Animate By**: `character`, `word`, `text`, `line`
|
||||
- **Use Case**: Hero headlines, feature announcements, storytelling
|
||||
- **Customization**: Delay, duration, custom motion variants
|
||||
|
||||
### 3. **Flip Text**
|
||||
- **Purpose**: Vertical flip animation for text
|
||||
- **Key Props**: `duration`, `delayMultiple`, custom variants
|
||||
- **Use Case**: Eye-catching headlines, call-to-actions
|
||||
|
||||
### 4. **Morphing Text**
|
||||
- **Purpose**: Dynamic text transitions between multiple strings
|
||||
- **Key Props**: `texts` array (strings to morph between)
|
||||
- **Use Case**: Dynamic value propositions, rotating benefits
|
||||
|
||||
### 5. **Word Rotate**
|
||||
- **Purpose**: Vertical rotation of words
|
||||
- **Key Props**: `words` (string array), `duration` (2500ms default)
|
||||
- **Use Case**: Rotating feature names, dynamic headlines
|
||||
|
||||
### 6. **Aurora Text**
|
||||
- **Purpose**: Beautiful aurora text effect
|
||||
- **Key Props**: `colors` array, `speed` multiplier
|
||||
- **Default Colors**: `["#FF0080", "#7928CA", "#0070F3", "#38bdf8"]`
|
||||
- **Use Case**: Premium headlines, brand emphasis
|
||||
|
||||
## Visual Effect Components
|
||||
|
||||
### 7. **Orbiting Circles**
|
||||
- **Purpose**: Circles moving in orbit along circular paths
|
||||
- **Key Props**:
|
||||
- `radius` (orbit size)
|
||||
- `duration` (animation speed)
|
||||
- `reverse` (direction)
|
||||
- `delay`, `path` (show orbit path)
|
||||
- `iconSize`, `speed`
|
||||
- **Use Case**: Technology visualization, ecosystem diagrams, feature satellites
|
||||
|
||||
### 8. **Particles**
|
||||
- **Purpose**: Animated particle background with depth and interactivity
|
||||
- **Use Case**: Hero sections, immersive backgrounds
|
||||
|
||||
### 9. **Confetti**
|
||||
- **Purpose**: Celebration confetti effect
|
||||
- **Key Props**:
|
||||
- `particleCount`, `angle`, `spread`
|
||||
- `startVelocity`, `decay`, `gravity`
|
||||
- `colors`, `shapes` (square, circle, star)
|
||||
- `origin` point
|
||||
- **Includes**: `ConfettiButton` wrapper component
|
||||
- **Use Case**: Success states, milestones, achievements
|
||||
|
||||
### 10. **Border Beam**
|
||||
- **Purpose**: Animated beam effect along borders
|
||||
- **Key Props**: `reverse`, spring animations
|
||||
- **Use Case**: Card highlights, section emphasis
|
||||
|
||||
### 11. **Shine Border**
|
||||
- **Purpose**: Animated shining border effect
|
||||
- **Key Props**: `color` array, `borderWidth`, `duration`
|
||||
- **Use Case**: Premium cards, CTAs, feature boxes
|
||||
|
||||
### 12. **Magic Card**
|
||||
- **Purpose**: Spotlight effect following mouse cursor with border highlights
|
||||
- **Key Props**:
|
||||
- `gradientSize` (200 default)
|
||||
- `gradientColor` (#262626 default)
|
||||
- `gradientOpacity` (0.8)
|
||||
- `gradientFrom`/`gradientTo` (border colors)
|
||||
- **Use Case**: Interactive feature cards, pricing tables
|
||||
|
||||
## Background Components
|
||||
|
||||
### 13. **Grid Beams**
|
||||
- **Purpose**: Dynamic grid background with animated light beams
|
||||
- **Key Props**:
|
||||
- `gridSize` (40px default)
|
||||
- `gridColor` (rgba)
|
||||
- `rayCount` (15 default)
|
||||
- `rayOpacity` (0.35)
|
||||
- `raySpeed`, `rayLength` (45vh)
|
||||
- `gridFadeStart`/`gridFadeEnd` (%)
|
||||
- `backgroundColor`
|
||||
- **Use Case**: Hero sections, feature backgrounds, immersive layouts
|
||||
|
||||
### 14. **Warp Background**
|
||||
- **Purpose**: Warped perspective grid effect
|
||||
- **Key Props**:
|
||||
- `perspective` (depth)
|
||||
- `beamsPerSide` (4 default)
|
||||
- `beamSize` (thickness)
|
||||
- `beamDuration` (speed)
|
||||
- `gridColor`
|
||||
- **Use Case**: Futuristic hero sections, tech-focused pages
|
||||
|
||||
### 15. **Dot Pattern**
|
||||
- **Purpose**: Customizable dotted background pattern (SVG)
|
||||
- **Key Props**: `width`, `height`, `cx`, `cy`, `cr` (dot radius)
|
||||
- **Effects**: Supports glow effects
|
||||
- **Use Case**: Subtle backgrounds, section dividers
|
||||
|
||||
## Interactive Components
|
||||
|
||||
### 16. **Dock**
|
||||
- **Purpose**: macOS-style dock with magnification effect
|
||||
- **Key Props**:
|
||||
- `iconMagnification` (zoom amount)
|
||||
- `iconDistance` (hover range)
|
||||
- `direction` (middle, start, end)
|
||||
- **Child Component**: `DockIcon`
|
||||
- **Use Case**: Navigation, tool showcases, social links
|
||||
|
||||
### 17. **Scratch To Reveal**
|
||||
- **Purpose**: Interactive scratch-off effect revealing hidden content
|
||||
- **Key Props**:
|
||||
- `width`, `height`
|
||||
- `minScratchPercentage` (50 default, completion threshold)
|
||||
- `onComplete` callback
|
||||
- `gradientColors`
|
||||
- **Use Case**: Interactive reveals, gamification, teasers
|
||||
|
||||
### 18. **Highlighter**
|
||||
- **Purpose**: Animated text highlighting and underlining
|
||||
- **Key Props**:
|
||||
- `color`
|
||||
- `strokeWidth`
|
||||
- `action` (underline, highlight)
|
||||
- `animationDuration`
|
||||
- `iterations`, `padding`
|
||||
- **Use Case**: Emphasis on key phrases, call-outs
|
||||
|
||||
## Layout/Display Components
|
||||
|
||||
### 19. **Marquee**
|
||||
- **Purpose**: Scrolling content (horizontal or vertical)
|
||||
- **Key Props**:
|
||||
- `reverse` (direction)
|
||||
- `pauseOnHover`
|
||||
- `vertical` (orientation)
|
||||
- `repeat` (count)
|
||||
- **Effects**: 3D perspective option
|
||||
- **Use Case**: Testimonials, logo clouds, infinite scrollers
|
||||
|
||||
### 20. **Safari**
|
||||
- **Purpose**: Safari browser mockup for showcasing
|
||||
- **Key Props**:
|
||||
- `url` (address bar)
|
||||
- `imageSrc` or `videoSrc`
|
||||
- `width` (1203 default), `height` (753)
|
||||
- `mode` (default, simple)
|
||||
- **Use Case**: Product demos, website previews
|
||||
|
||||
### 21. **Bento Grid**
|
||||
- **Purpose**: Grid layout for organizing content
|
||||
- **Use Case**: Feature showcases, portfolios
|
||||
|
||||
### 22. **Avatar Circles**
|
||||
- **Purpose**: Overlapping circles of avatars
|
||||
- **Key Props**: `numPeople` (99 default, shown in last circle)
|
||||
- **Use Case**: Social proof, team displays, user counts
|
||||
|
||||
## Timeline Components
|
||||
|
||||
### 23. **Arc Timeline**
|
||||
- **Purpose**: Curved timeline visualizing milestones
|
||||
- **Key Props**:
|
||||
- `data` (array of {time, title})
|
||||
- `arcConfig`:
|
||||
- `circleWidth` (5000 default)
|
||||
- `angleBetweenMinorSteps` (0.35)
|
||||
- `lineCountFillBetweenSteps` (10)
|
||||
- `boundaryPlaceholderLinesCount` (50)
|
||||
- `defaultActiveStep` ({time, stepIndex})
|
||||
- **Use Case**: Project roadmaps, product evolution, version history
|
||||
|
||||
## Button Components
|
||||
|
||||
### 24. **Shiny Button**
|
||||
- **Purpose**: Button with shiny effect
|
||||
- **Features**: Dark/light mode support
|
||||
- **Use Case**: Primary CTAs, important actions
|
||||
|
||||
### 25. **Pulsating Button**
|
||||
- **Purpose**: Button with pulsing wave animation
|
||||
- **Key Props**: `pulseColor` (RGB), `duration`
|
||||
- **Use Case**: Attention-grabbing CTAs, urgent actions
|
||||
|
||||
### 26. **Rainbow Button**
|
||||
- **Purpose**: Rainbow gradient button effect
|
||||
- **Variants**: Default, Outline
|
||||
- **Use Case**: Premium CTAs, playful actions
|
||||
|
||||
## Theme Components
|
||||
|
||||
### 27. **Animated Theme Toggler**
|
||||
- **Purpose**: Smooth animated light/dark mode toggle
|
||||
- **Built With**: Tailwind CSS
|
||||
- **Use Case**: Theme switching UI
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Via CLI (recommended)
|
||||
npx shadcn-ui@latest add [component-name]
|
||||
|
||||
# Manual installation
|
||||
npm install magicui
|
||||
# or
|
||||
yarn add magicui
|
||||
```
|
||||
|
||||
## Component Categories Summary
|
||||
|
||||
| Category | Components | Best For |
|
||||
|----------|-----------|----------|
|
||||
| **Text Animation** | Animated List, Text Animate, Flip Text, Morphing Text, Word Rotate, Aurora Text | Headlines, feature lists, dynamic content |
|
||||
| **Visual Effects** | Orbiting Circles, Particles, Confetti, Border Beam, Shine Border, Magic Card | Visual interest, interactivity, emphasis |
|
||||
| **Backgrounds** | Grid Beams, Warp Background, Dot Pattern | Hero sections, immersive layouts |
|
||||
| **Interactive** | Dock, Scratch To Reveal, Highlighter | User engagement, gamification |
|
||||
| **Layout** | Marquee, Safari, Bento Grid, Avatar Circles | Content organization, showcases |
|
||||
| **Timeline** | Arc Timeline | Roadmaps, history, progression |
|
||||
| **Buttons** | Shiny Button, Pulsating Button, Rainbow Button | CTAs, actions |
|
||||
| **Utility** | Animated Theme Toggler | UI controls |
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Magic UI components focus on:
|
||||
- **Delight**: Unexpected animations that create joy
|
||||
- **Fluidity**: Smooth, natural motion
|
||||
- **Performance**: Optimized for web performance
|
||||
- **Flexibility**: Highly customizable via props
|
||||
- **Modern**: Built with React, Tailwind CSS, Framer Motion
|
||||
|
||||
## Use Case Recommendations by Landing Page Section
|
||||
|
||||
### Hero Sections
|
||||
- Grid Beams or Warp Background (background)
|
||||
- Aurora Text or Morphing Text (headline)
|
||||
- Pulsating Button or Rainbow Button (CTA)
|
||||
- Orbiting Circles (tech visualization)
|
||||
|
||||
### Feature Showcases
|
||||
- Bento Grid (layout)
|
||||
- Magic Card (individual features)
|
||||
- Animated List (feature details)
|
||||
- Highlighter (emphasis)
|
||||
|
||||
### Social Proof
|
||||
- Marquee (testimonials/logos)
|
||||
- Avatar Circles (user counts)
|
||||
|
||||
### Product Demos
|
||||
- Safari (browser mockups)
|
||||
- Border Beam or Shine Border (emphasis)
|
||||
|
||||
### Roadmaps/Progress
|
||||
- Arc Timeline (milestones)
|
||||
|
||||
### Interactive Elements
|
||||
- Scratch To Reveal (teasers)
|
||||
- Dock (navigation/tools)
|
||||
- Confetti (celebrations)
|
||||
@@ -0,0 +1,471 @@
|
||||
# Magic UI Landing Page Creative Ideas
|
||||
|
||||
Creative applications of Magic UI components for each section of the claude-mem landing page.
|
||||
|
||||
## Section 1: HERO - "Claude Never Forgets"
|
||||
|
||||
### Idea 1: "Fading Memory" Effect ⭐ WINNER
|
||||
**Components**: Morphing Text, Grid Beams, Orbiting Circles, Scratch To Reveal
|
||||
|
||||
**Vision**:
|
||||
- **Headline**: Morphing Text rotating between:
|
||||
- "Claude Never Forgets"
|
||||
- "Claude Always Remembers"
|
||||
- "Claude Learns Forever"
|
||||
- **Background**: Grid Beams in blue/purple gradient representing the "memory grid"
|
||||
- **Central Visual**: Orbiting Circles around a central "brain/database" icon
|
||||
- Inner orbit: File icons (representing code files)
|
||||
- Middle orbit: Lightbulb icons (decisions)
|
||||
- Outer orbit: Bug icons (fixes)
|
||||
- **Problem Statement**: Scratch To Reveal overlay
|
||||
- User must scratch to reveal: "Every /clear wipes Claude's memory"
|
||||
- Makes the pain point visceral and interactive
|
||||
- **CTA**: Pulsating Button "Give Claude a Memory"
|
||||
|
||||
### Idea 2: "Memory Timeline" Hero
|
||||
**Components**: Warp Background, Arc Timeline, Aurora Text, Word Rotate
|
||||
|
||||
**Vision**:
|
||||
- Warp Background creating perspective depth
|
||||
- Arc Timeline showing "Session 1 → Session 2 → Session 3" with memories persisting across
|
||||
- Aurora Text for main headline with flowing gradient
|
||||
- Word Rotate cycling pain points: "Repeating" → "Explaining" → "Re-discovering" → "Forgetting"
|
||||
|
||||
### Idea 3: "Brain Storage" Visualization
|
||||
**Components**: Particles, Magic Card, Highlighter, Pulsating Button
|
||||
|
||||
**Vision**:
|
||||
- Particles background (subtle, neuron-like firing)
|
||||
- Central Magic Card with spotlight effect containing headline
|
||||
- Highlighter emphasizing "Never" and "Forgets" with animated underlines
|
||||
- Pulsating Button for CTA with urgency
|
||||
|
||||
---
|
||||
|
||||
## Section 2: BEFORE/AFTER Comparison
|
||||
|
||||
### Idea 1: "Split Screen Wipe" ⭐ WINNER
|
||||
**Components**: Safari, Text Animate, Border Beam, Scratch To Reveal
|
||||
|
||||
**Vision**:
|
||||
- Two Safari browser mockups side by side
|
||||
- **Left (Before)**: Conversation fades/blurs out showing memory loss
|
||||
- **Right (After)**: Sharp, persistent content with Border Beam highlighting
|
||||
- Text Animate with slideUp for conversations appearing sequentially
|
||||
- Optional: Scratch To Reveal over "Before" side to uncover the painful reality
|
||||
|
||||
### Idea 2: "Memory Decay Visualization"
|
||||
**Components**: Text Animate, Shine Border, Magic Card, Animated Theme Toggler
|
||||
|
||||
**Vision**:
|
||||
- Before side: Text with progressive fade-out animation (opacity decreasing)
|
||||
- After side: Text with Shine Border, glowing persistently
|
||||
- Magic Card on After side with spotlight following mouse
|
||||
- Animated Theme Toggler metaphorically switching "forgetting" → "remembering"
|
||||
|
||||
### Idea 3: "Conversation Replay"
|
||||
**Components**: Animated List, Border Beam, Highlighter
|
||||
|
||||
**Vision**:
|
||||
- Animated List showing conversation history
|
||||
- Before: List items fade out and disappear sequentially
|
||||
- After: List items persist, Border Beam appears on referenced items
|
||||
- Highlighter underlining key persistent information on After side
|
||||
|
||||
---
|
||||
|
||||
## Section 3: REAL EXAMPLES (3 Scenarios)
|
||||
|
||||
### Idea 1: "Tabbed Experience"
|
||||
**Components**: Bento Grid, Magic Card, Text Animate, Flip Text, Confetti
|
||||
|
||||
**Vision**:
|
||||
- Bento Grid layout with 3 Magic Cards (one per scenario)
|
||||
- Each card has spotlight effect on hover
|
||||
- Text Animate (blurIn) for code examples appearing
|
||||
- Flip Text for headings revealing scenario names
|
||||
- Confetti burst when clicking through to third example (pattern recognized!)
|
||||
|
||||
### Idea 2: "Timeline Story" ⭐ WINNER
|
||||
**Components**: Arc Timeline, Orbiting Circles, Highlighter, Animated List
|
||||
|
||||
**Vision**:
|
||||
- Arc Timeline showing progression: "Monday Session" → "Wednesday Session"
|
||||
- Each timeline node expands to show the scenario details
|
||||
- Orbiting Circles around active node showing:
|
||||
- Files involved (icons in orbit)
|
||||
- Decisions made (decision icons)
|
||||
- Bugs fixed (bug icons)
|
||||
- Highlighter emphasizing key remembered details in the expanded content
|
||||
- Animated List revealing the bulleted "Claude remembers" items
|
||||
|
||||
### Idea 3: "Memory Card Flip"
|
||||
**Components**: Scratch To Reveal, Magic Card, Pulsating Button, Word Rotate
|
||||
|
||||
**Vision**:
|
||||
- Three cards with Scratch To Reveal overlay
|
||||
- Scratch to reveal what Claude remembers from previous sessions
|
||||
- Magic Card underneath with gradient borders
|
||||
- Pulsating Button to cycle through scenarios
|
||||
- Word Rotate showing memory types: "Patterns" → "Decisions" → "Context" → "Architecture"
|
||||
|
||||
---
|
||||
|
||||
## Section 4: HOW IT WORKS (Pipeline)
|
||||
|
||||
### Idea 1: "Animated Data Flow"
|
||||
**Components**: Magic Card, Orbiting Circles, Border Beam, Text Animate, Particles
|
||||
|
||||
**Vision**:
|
||||
- Three connected Magic Cards showing pipeline stages:
|
||||
1. "You code with Claude today"
|
||||
2. "claude-mem captures & compresses"
|
||||
3. "Tomorrow, Claude starts with context"
|
||||
- Orbiting Circles representing data flowing between stages
|
||||
- Border Beam animating along connections between cards
|
||||
- Text Animate (slideUp) for each stage description on scroll
|
||||
- Particles flowing from one stage to the next
|
||||
|
||||
### Idea 2: "Arc Timeline as Process"
|
||||
**Components**: Arc Timeline, Orbiting Circles, Warp Background, Animated List, Shine Border
|
||||
|
||||
**Vision**:
|
||||
- Arc Timeline showing 5 steps of memory system
|
||||
- Each node has Orbiting Circles showing components (hooks, worker, DB, MCP, context)
|
||||
- Warp Background creating depth
|
||||
- Animated List revealing sub-bullets under each stage
|
||||
- Shine Border on active/hovered stage
|
||||
|
||||
### Idea 3: "Layered Depth Model" ⭐ WINNER
|
||||
**Components**: Grid Beams, Magic Card, Border Beam, Morphing Text, Highlighter
|
||||
|
||||
**Vision**:
|
||||
- Grid Beams background representing the "storage layer"
|
||||
- Three floating Magic Cards in z-space (perspective/depth):
|
||||
- Top layer: "Hooks capture"
|
||||
- Middle layer: "AI compresses"
|
||||
- Bottom layer: "Database stores"
|
||||
- Arrows with Border Beam animation showing data flow downward
|
||||
- Morphing Text showing state transformation: "Capturing" → "Compressing" → "Storing" → "Retrieving"
|
||||
- Highlighter emphasizing "Automatic. Zero effort. Always on."
|
||||
|
||||
---
|
||||
|
||||
## Section 5: WHAT GETS REMEMBERED (Feature List)
|
||||
|
||||
### Idea 1: "Checkbox Delight"
|
||||
**Components**: Animated List, Text Animate, Highlighter, Confetti, Magic Card
|
||||
|
||||
**Vision**:
|
||||
- Animated List of 6 checkmark items
|
||||
- Each item appears with Text Animate (blurIn by character)
|
||||
- Highlighter underlining key words: "Decisions", "Bugs Fixed", "Code Patterns", etc.
|
||||
- Confetti burst when all items are visible (celebration of completeness)
|
||||
- Magic Card container with subtle spotlight effect
|
||||
|
||||
### Idea 2: "Memory Bank Slots" ⭐ WINNER
|
||||
**Components**: Bento Grid, Scratch To Reveal, Shine Border, Orbiting Circles, Pulsating Button
|
||||
|
||||
**Vision**:
|
||||
- Bento Grid of 6 cards (one per memory type)
|
||||
- Each card has Scratch To Reveal to discover what gets saved (gamification!)
|
||||
- Shine Border appears around revealed cards
|
||||
- Orbiting Circles showing example icons around each card type
|
||||
- Pulsating indicator on most important memory types
|
||||
|
||||
### Idea 3: "Collector Animation"
|
||||
**Components**: Magic Card, Border Beam, Flip Text, Aurora Text, Dot Pattern
|
||||
|
||||
**Vision**:
|
||||
- Six Magic Cards arranged in grid
|
||||
- Border Beam cascading through cards in sequence
|
||||
- Each card flips (Flip Text) to reveal icon + description
|
||||
- Aurora Text for section heading
|
||||
- Dot Pattern background with subtle glow on active items
|
||||
|
||||
---
|
||||
|
||||
## Section 6: POWERFUL SEARCH
|
||||
|
||||
### Idea 1: "Live Search Demo" ⭐ WINNER
|
||||
**Components**: Safari, Text Animate, Animated List, Highlighter, Border Beam, Morphing Text
|
||||
|
||||
**Vision**:
|
||||
- Safari browser mockup showing search interface
|
||||
- Text Animate typing out search queries with realistic timing:
|
||||
- "Find all database migrations..."
|
||||
- "What decisions about authentication..."
|
||||
- Animated List revealing search results sequentially
|
||||
- Highlighter emphasizing matched keywords in results
|
||||
- Border Beam around result cards
|
||||
- Morphing Text cycling search types: "Migrations" → "Decisions" → "Patterns" → "Bugs"
|
||||
|
||||
### Idea 2: "Search Radar"
|
||||
**Components**: Orbiting Circles, Magic Card, Particles, Text Animate, Grid Beams
|
||||
|
||||
**Vision**:
|
||||
- Orbiting Circles representing different search dimensions:
|
||||
- Inner orbit: File search
|
||||
- Middle orbit: Concept search
|
||||
- Outer orbit: Type/date filters
|
||||
- Central Magic Card with search query
|
||||
- Particles radiating outward to show search happening
|
||||
- Results appear with Text Animate (slideUp)
|
||||
- Grid Beams background representing indexed database
|
||||
|
||||
### Idea 3: "Memory Retrieval Visualization"
|
||||
**Components**: Warp Background, Aurora Text, Magic Card, Shine Border, Dock, Confetti
|
||||
|
||||
**Vision**:
|
||||
- Warp Background creating depth into "memory storage"
|
||||
- Search query in Aurora Text
|
||||
- Seven Magic Cards (one per search tool) with Shine Border
|
||||
- Dock component at bottom showing 7 search tool icons
|
||||
- Confetti when "perfect match" is found
|
||||
|
||||
---
|
||||
|
||||
## Section 7: THE NUMBERS (Metrics Table)
|
||||
|
||||
### Idea 1: "Counting Animation"
|
||||
**Components**: Safari, Text Animate, Aurora Text, Confetti, Border Beam
|
||||
|
||||
**Vision**:
|
||||
- Safari mockup showing comparison table
|
||||
- Text Animate for each metric value counting up
|
||||
- "Before" values in faded text
|
||||
- "After" values with Aurora Text effect (glowing)
|
||||
- Confetti when hovering over dramatic improvements
|
||||
- Border Beam highlighting entire "After" column
|
||||
|
||||
### Idea 2: "Progress Bar Transformation" ⭐ WINNER
|
||||
**Components**: Magic Card, Morphing Text, Shine Border, Pulsating Button
|
||||
|
||||
**Vision**:
|
||||
- Five Magic Cards, one per metric
|
||||
- Animated progress bars showing transformation:
|
||||
- Before (low/red) → After (high/green)
|
||||
- Visual representation of improvement
|
||||
- Morphing Text cycling through metrics
|
||||
- Shine Border on cards with biggest improvements
|
||||
- Pulsating Button: "See the Difference"
|
||||
|
||||
### Idea 3: "Flip Cards Reveal"
|
||||
**Components**: Scratch To Reveal, Magic Card, Highlighter, Animated List
|
||||
|
||||
**Vision**:
|
||||
- Five cards with Scratch To Reveal
|
||||
- Scratch "Before" to reveal "After" metrics
|
||||
- Each card has Magic Card spotlight effect
|
||||
- Highlighter emphasizing: "Never", "0 seconds", "Rare"
|
||||
- Animated List showing additional benefits below table
|
||||
|
||||
---
|
||||
|
||||
## Section 8: INSTALLATION (Quick Start)
|
||||
|
||||
### Idea 1: "Copy-Paste Delight" ⭐ WINNER
|
||||
**Components**: Safari, Text Animate, Shiny Button, Confetti, Animated List, Highlighter
|
||||
|
||||
**Vision**:
|
||||
- Safari mockup showing terminal window
|
||||
- Text Animate typing out commands with realistic timing (typewriter effect)
|
||||
- Shiny Button next to each command for copy
|
||||
- Confetti celebration when installation completes
|
||||
- Animated List showing 3 steps with checkmarks appearing
|
||||
- Highlighter emphasizing "2 minutes"
|
||||
|
||||
### Idea 2: "Progress Stepper"
|
||||
**Components**: Arc Timeline, Border Beam, Magic Card, Pulsating Button, Word Rotate
|
||||
|
||||
**Vision**:
|
||||
- Arc Timeline with 3 nodes: Clone → Add → Install
|
||||
- Each step expands to show code when active
|
||||
- Border Beam connecting steps as they complete
|
||||
- Magic Card for code blocks with spotlight
|
||||
- Pulsating Button for "Get Started"
|
||||
- Word Rotate: "Simple" → "Fast" → "Easy" → "Done"
|
||||
|
||||
### Idea 3: "Interactive Terminal"
|
||||
**Components**: Grid Beams, Safari, Text Animate, Orbiting Circles, Rainbow Button, Shine Border
|
||||
|
||||
**Vision**:
|
||||
- Grid Beams background (tech aesthetic)
|
||||
- Safari terminal mockup
|
||||
- Text Animate simulating command execution
|
||||
- Orbiting Circles showing installed components appearing
|
||||
- Rainbow Button for final "Installation Complete" CTA
|
||||
- Shine Border around success message
|
||||
|
||||
---
|
||||
|
||||
## Section 9: UNDER THE HOOD (Architecture)
|
||||
|
||||
### Idea 1: "System Diagram"
|
||||
**Components**: Bento Grid, Magic Card, Orbiting Circles, Border Beam, Dot Pattern, Text Animate, Highlighter
|
||||
|
||||
**Vision**:
|
||||
- Bento Grid showing 5 architecture components:
|
||||
- Hooks, Worker, Database, MCP, Context Injection
|
||||
- Each grid cell is a Magic Card with spotlight
|
||||
- Orbiting Circles showing data flow between components
|
||||
- Border Beam animating along connections
|
||||
- Dot Pattern background
|
||||
- Text Animate for each component description
|
||||
- Highlighter on "Zero maintenance. Just works."
|
||||
|
||||
### Idea 2: "Layered Stack" ⭐ WINNER
|
||||
**Components**: Magic Card, Warp Background, Morphing Text, Particles, Shine Border
|
||||
|
||||
**Vision**:
|
||||
- Five Magic Cards stacked with perspective (z-depth)
|
||||
- Each layer slides out on scroll to reveal architecture:
|
||||
1. Hooks (top)
|
||||
2. Worker Service
|
||||
3. SQLite Database (center)
|
||||
4. MCP Server
|
||||
5. Context Injection (bottom)
|
||||
- Warp Background creating depth
|
||||
- Morphing Text showing active layer: "Hooks" → "Worker" → "SQLite" → "MCP" → "Context"
|
||||
- Particles flowing between layers
|
||||
- Shine Border on active layer
|
||||
|
||||
### Idea 3: "Technical Orbits"
|
||||
**Components**: Orbiting Circles, Grid Beams, Magic Card, Text Animate
|
||||
|
||||
**Vision**:
|
||||
- Central "SQLite DB" icon
|
||||
- Orbiting Circles representing different systems:
|
||||
- Inner orbit: 5 hooks (SessionStart, UserPrompt, PostTool, Summary, SessionEnd)
|
||||
- Middle orbit: Worker service (PM2 process)
|
||||
- Outer orbit: MCP server (7 search tools)
|
||||
- Farthest orbit: Context injection
|
||||
- Grid Beams background
|
||||
- Magic Card for each orbit explanation
|
||||
- Text Animate (slideUp) for technical details
|
||||
|
||||
---
|
||||
|
||||
## Section 10: USE CASES (User Types)
|
||||
|
||||
### Idea 1: "User Journey Cards"
|
||||
**Components**: Bento Grid, Magic Card, Avatar Circles, Animated List, Flip Text, Border Beam
|
||||
|
||||
**Vision**:
|
||||
- Four Magic Cards in Bento Grid layout
|
||||
- Each card has different gradient colors (gradientFrom/To)
|
||||
- Avatar Circles showing user type icon
|
||||
- Animated List of benefits per user type
|
||||
- Flip Text for headings revealing user types
|
||||
- Border Beam highlighting active/hovered card
|
||||
|
||||
### Idea 2: "Role Selector" ⭐ WINNER
|
||||
**Components**: Dock, Magic Card, Text Animate, Highlighter, Confetti
|
||||
|
||||
**Vision**:
|
||||
- Dock component with 4 icons representing user types:
|
||||
- Solo Developer icon
|
||||
- Team icon
|
||||
- Learning/Student icon
|
||||
- Large Refactor icon
|
||||
- Click icon to expand that use case
|
||||
- Magic Card expands with spotlight effect
|
||||
- Text Animate revealing use case details
|
||||
- Highlighter emphasizing key benefits
|
||||
- Confetti celebration when selecting "your" use case (engagement!)
|
||||
|
||||
### Idea 3: "Story Scenarios"
|
||||
**Components**: Arc Timeline, Safari, Orbiting Circles, Morphing Text, Shine Border, Animated List
|
||||
|
||||
**Vision**:
|
||||
- Arc Timeline showing progression for each user type
|
||||
- Safari mockup showing actual usage scenario
|
||||
- Orbiting Circles around timeline nodes showing features being used
|
||||
- Morphing Text cycling through user types
|
||||
- Shine Border on selected use case
|
||||
- Animated List showing specific benefits
|
||||
|
||||
---
|
||||
|
||||
## Section 11: FAQ
|
||||
|
||||
### Idea 1: "Expandable Cards"
|
||||
**Components**: Magic Card, Text Animate, Border Beam, Highlighter, Dot Pattern, Animated List
|
||||
|
||||
**Vision**:
|
||||
- Six Magic Cards with questions visible
|
||||
- Click to expand with Text Animate (blurIn)
|
||||
- Border Beam appears on expanded card
|
||||
- Highlighter emphasizing key answer points
|
||||
- Dot Pattern background
|
||||
- Animated List for multi-point answers
|
||||
|
||||
### Idea 2: "Scratch to Answer" ⭐ WINNER
|
||||
**Components**: Scratch To Reveal, Confetti, Magic Card, Morphing Text
|
||||
|
||||
**Vision**:
|
||||
- Questions visible, answers hidden under scratch surface
|
||||
- Scratch To Reveal to see answers (highly engaging!)
|
||||
- Confetti on revealing particularly important answers (e.g., "Fully private")
|
||||
- Magic Card container with spotlight
|
||||
- Morphing Text cycling through common concerns: "Cost?" → "Speed?" → "Privacy?" → "Storage?"
|
||||
|
||||
### Idea 3: "Interactive Q&A"
|
||||
**Components**: Bento Grid, Border Beam, Shine Border, Text Animate, Pulsating Button, Avatar Circles
|
||||
|
||||
**Vision**:
|
||||
- Bento Grid of question cards
|
||||
- Hover triggers Border Beam
|
||||
- Click expands with Shine Border
|
||||
- Text Animate typing out answers
|
||||
- Pulsating Button for "More Questions?"
|
||||
- Avatar Circles showing "5,000+ developers trust claude-mem"
|
||||
|
||||
---
|
||||
|
||||
## BONUS: Testimonials Section (Not in Original)
|
||||
|
||||
### Idea 1: "Social Proof Marquee"
|
||||
**Components**: Marquee (3D), Magic Card, Avatar Circles, Highlighter, Shine Border
|
||||
|
||||
**Vision**:
|
||||
- Marquee component in 3D mode scrolling developer testimonials
|
||||
- Each testimonial in a Magic Card
|
||||
- Avatar Circles showing total developer count
|
||||
- Highlighter on impactful quote fragments
|
||||
- Shine Border around featured testimonial
|
||||
|
||||
---
|
||||
|
||||
## Component Usage Summary
|
||||
|
||||
| Component | Times Used (Winners) | Primary Purpose |
|
||||
|-----------|---------------------|-----------------|
|
||||
| **Magic Card** | 9 | Spotlight effects, containers |
|
||||
| **Text Animate** | 7 | Typing effects, reveals |
|
||||
| **Border Beam** | 6 | Connections, highlights |
|
||||
| **Highlighter** | 6 | Emphasis on key text |
|
||||
| **Animated List** | 5 | Sequential reveals |
|
||||
| **Confetti** | 5 | Celebrations, milestones |
|
||||
| **Shine Border** | 5 | Premium emphasis |
|
||||
| **Morphing Text** | 4 | Dynamic headlines |
|
||||
| **Orbiting Circles** | 4 | Data flow, ecosystems |
|
||||
| **Safari** | 4 | Browser mockups |
|
||||
| **Scratch To Reveal** | 4 | Interactive discovery |
|
||||
| **Pulsating Button** | 4 | CTAs |
|
||||
| **Grid Beams** | 3 | Tech backgrounds |
|
||||
| **Warp Background** | 3 | Depth, perspective |
|
||||
| **Arc Timeline** | 2 | Progress, history |
|
||||
| **Dock** | 1 | Navigation selector |
|
||||
| **Aurora Text** | 1 | Premium headlines |
|
||||
| **Bento Grid** | 1 | Layout organization |
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Show, Don't Tell**: Use animations to demonstrate concepts (memory persistence, data flow)
|
||||
2. **Interactive Discovery**: Scratch-to-reveal and interactive elements engage users
|
||||
3. **Visual Metaphors**: Orbiting circles for data flow, layers for architecture
|
||||
4. **Celebration**: Confetti at key moments creates joy
|
||||
5. **Progressive Disclosure**: Animated lists and timelines reveal information naturally
|
||||
6. **Spatial Depth**: Warp backgrounds and z-space create dimensional understanding
|
||||
7. **Consistent Magic**: Reuse components (Magic Card, Border Beam) for cohesion
|
||||
@@ -0,0 +1,389 @@
|
||||
# Landing Page Ideas - Ranked Analysis
|
||||
|
||||
Ranking criteria (1-10 scale):
|
||||
1. **Creativity**: How novel and unexpected is the approach?
|
||||
2. **Storytelling**: How effectively does it communicate the value?
|
||||
3. **Intuitive**: How easily will users understand it?
|
||||
4. **Feasibility**: How practical is implementation?
|
||||
5. **Delight**: How much joy does it create?
|
||||
|
||||
---
|
||||
|
||||
## HERO SECTION Rankings
|
||||
|
||||
### Idea 1: "Fading Memory" Effect ⭐ WINNER (42/50)
|
||||
- **Creativity**: 9/10 - Scratch-to-reveal pain point is unexpected
|
||||
- **Storytelling**: 9/10 - Morphing text and orbiting circles tell complete story
|
||||
- **Intuitive**: 8/10 - Orbiting content makes memory concept clear
|
||||
- **Feasibility**: 7/10 - Multiple complex components need coordination
|
||||
- **Delight**: 9/10 - Interactive scratch creates engagement
|
||||
|
||||
**Why it wins**: The scratch-to-reveal pain point makes the problem visceral. Orbiting circles create immediate visual understanding of what gets remembered.
|
||||
|
||||
### Idea 2: "Memory Timeline" Hero (39/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 8/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
### Idea 3: "Brain Storage" Visualization (36/50)
|
||||
- **Creativity**: 6/10
|
||||
- **Storytelling**: 7/10
|
||||
- **Intuitive**: 7/10
|
||||
- **Feasibility**: 9/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
---
|
||||
|
||||
## BEFORE/AFTER COMPARISON Rankings
|
||||
|
||||
### Idea 1: "Split Screen Wipe" ⭐ WINNER (44/50)
|
||||
- **Creativity**: 8/10 - Clean comparison approach
|
||||
- **Storytelling**: 10/10 - Side-by-side is the clearest possible comparison
|
||||
- **Intuitive**: 10/10 - Immediately obvious what's different
|
||||
- **Feasibility**: 8/10 - Safari mockups are well-supported
|
||||
- **Delight**: 8/10 - Fade vs persist is satisfying
|
||||
|
||||
**Why it wins**: Safari browser mockups provide immediate familiarity. The contrast between fading (Before) and persistent with Border Beam (After) is crystal clear storytelling.
|
||||
|
||||
### Idea 2: "Memory Decay Visualization" (40/50)
|
||||
- **Creativity**: 9/10
|
||||
- **Storytelling**: 9/10
|
||||
- **Intuitive**: 8/10
|
||||
- **Feasibility**: 7/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
### Idea 3: "Conversation Replay" (40/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 9/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
---
|
||||
|
||||
## REAL EXAMPLES Rankings
|
||||
|
||||
### Idea 2: "Timeline Story" ⭐ WINNER (44/50)
|
||||
- **Creativity**: 9/10 - Arc timeline for session progression is novel
|
||||
- **Storytelling**: 10/10 - Timeline naturally shows progression over time
|
||||
- **Intuitive**: 9/10 - Timeline metaphor is universally understood
|
||||
- **Feasibility**: 7/10 - Complex interaction between timeline and orbiting elements
|
||||
- **Delight**: 9/10 - Orbiting context around nodes is magical
|
||||
|
||||
**Why it wins**: Arc Timeline naturally communicates the "across sessions" aspect. Orbiting circles showing related context (files, decisions, bugs) is brilliant visual storytelling.
|
||||
|
||||
### Idea 1: "Tabbed Experience" (41/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 9/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 3: "Memory Card Flip" (35/50)
|
||||
- **Creativity**: 8/10
|
||||
- **Storytelling**: 7/10
|
||||
- **Intuitive**: 6/10
|
||||
- **Feasibility**: 6/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
---
|
||||
|
||||
## HOW IT WORKS Rankings
|
||||
|
||||
### Idea 3: "Layered Depth Model" ⭐ WINNER (43/50)
|
||||
- **Creativity**: 9/10 - Spatial depth for understanding layers is clever
|
||||
- **Storytelling**: 9/10 - Visual depth communicates layered architecture
|
||||
- **Intuitive**: 10/10 - Layers immediately convey hierarchy and flow
|
||||
- **Feasibility**: 6/10 - Z-space perspective requires careful implementation
|
||||
- **Delight**: 9/10 - Morphing text showing transformation is satisfying
|
||||
|
||||
**Why it wins**: Depth/perspective creates intuitive understanding of the layered architecture. Morphing text showing state transformation ("Capturing" → "Storing") tells the story perfectly.
|
||||
|
||||
### Idea 1: "Animated Data Flow" (42/50)
|
||||
- **Creativity**: 8/10
|
||||
- **Storytelling**: 9/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 8/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 2: "Arc Timeline as Process" (37/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 8/10
|
||||
- **Feasibility**: 7/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
---
|
||||
|
||||
## WHAT GETS REMEMBERED Rankings
|
||||
|
||||
### Idea 2: "Memory Bank Slots" ⭐ WINNER (41/50)
|
||||
- **Creativity**: 9/10 - Memory bank metaphor with scratch-to-reveal
|
||||
- **Storytelling**: 8/10 - Discovery process tells the story
|
||||
- **Intuitive**: 8/10 - Bank slot metaphor is clear
|
||||
- **Feasibility**: 7/10 - Six scratch-to-reveal elements need optimization
|
||||
- **Delight**: 9/10 - Gamification through scratching is highly engaging
|
||||
|
||||
**Why it wins**: Scratch-to-reveal gamification makes exploring features fun. Discovering what gets saved creates memorable engagement.
|
||||
|
||||
### Idea 3: "Collector Animation" (41/50 - tied)
|
||||
- **Creativity**: 8/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 8/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 1: "Checkbox Delight" (40/50)
|
||||
- **Creativity**: 6/10
|
||||
- **Storytelling**: 7/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 10/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
---
|
||||
|
||||
## POWERFUL SEARCH Rankings
|
||||
|
||||
### Idea 1: "Live Search Demo" ⭐ WINNER (44/50)
|
||||
- **Creativity**: 7/10 - Straightforward but effective
|
||||
- **Storytelling**: 10/10 - Actually showing search in action is perfect
|
||||
- **Intuitive**: 10/10 - Real search demo is immediately clear
|
||||
- **Feasibility**: 9/10 - Safari + Text Animate + Animated List is achievable
|
||||
- **Delight**: 8/10 - Watching search happen is satisfying
|
||||
|
||||
**Why it wins**: Showing actual search with real queries and results is the most effective storytelling. Users immediately understand the capability.
|
||||
|
||||
### Idea 2: "Search Radar" (39/50)
|
||||
- **Creativity**: 9/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 7/10
|
||||
- **Feasibility**: 6/10
|
||||
- **Delight**: 9/10
|
||||
|
||||
### Idea 3: "Memory Retrieval Visualization" (38/50)
|
||||
- **Creativity**: 8/10
|
||||
- **Storytelling**: 7/10
|
||||
- **Intuitive**: 8/10
|
||||
- **Feasibility**: 8/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
---
|
||||
|
||||
## THE NUMBERS Rankings
|
||||
|
||||
### Idea 2: "Progress Bar Transformation" ⭐ WINNER (45/50)
|
||||
- **Creativity**: 8/10 - Progress bars are familiar but effective
|
||||
- **Storytelling**: 10/10 - Visual transformation from bad to good is powerful
|
||||
- **Intuitive**: 10/10 - Everyone understands progress bars
|
||||
- **Feasibility**: 8/10 - Animated progress bars are well-supported
|
||||
- **Delight**: 9/10 - Watching bars transform is satisfying
|
||||
|
||||
**Why it wins**: Visual transformation of progress bars (red/low → green/high) is incredibly effective storytelling. Everyone intuitively understands the improvement.
|
||||
|
||||
### Idea 1: "Counting Animation" (42/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 9/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 9/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 3: "Flip Cards Reveal" (39/50)
|
||||
- **Creativity**: 8/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 7/10
|
||||
- **Feasibility**: 7/10
|
||||
- **Delight**: 9/10
|
||||
|
||||
---
|
||||
|
||||
## INSTALLATION Rankings
|
||||
|
||||
### Idea 1: "Copy-Paste Delight" ⭐ WINNER (44/50)
|
||||
- **Creativity**: 7/10 - Simple but right
|
||||
- **Storytelling**: 8/10 - Shows exactly what to do
|
||||
- **Intuitive**: 10/10 - Terminal + copy buttons is universal pattern
|
||||
- **Feasibility**: 10/10 - Very achievable
|
||||
- **Delight**: 9/10 - Confetti celebration seals the deal
|
||||
|
||||
**Why it wins**: Simplicity wins for installation instructions. Confetti celebration when done creates satisfying completion.
|
||||
|
||||
### Idea 2: "Progress Stepper" (42/50)
|
||||
- **Creativity**: 8/10
|
||||
- **Storytelling**: 9/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 8/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 3: "Interactive Terminal" (41/50)
|
||||
- **Creativity**: 9/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 8/10
|
||||
- **Feasibility**: 7/10
|
||||
- **Delight**: 9/10
|
||||
|
||||
---
|
||||
|
||||
## UNDER THE HOOD Rankings
|
||||
|
||||
### Idea 2: "Layered Stack" ⭐ WINNER (44/50)
|
||||
- **Creativity**: 9/10 - Layers with z-depth is clever
|
||||
- **Storytelling**: 9/10 - Stacking shows dependency hierarchy
|
||||
- **Intuitive**: 10/10 - Stack metaphor is perfect for architecture
|
||||
- **Feasibility**: 7/10 - Perspective effects require work
|
||||
- **Delight**: 9/10 - Slides revealing layers is satisfying
|
||||
|
||||
**Why it wins**: Layered stack with perspective visually explains the architecture hierarchy perfectly. Each layer sliding out to reveal itself tells the dependency story.
|
||||
|
||||
### Idea 3: "Technical Orbits" (43/50)
|
||||
- **Creativity**: 10/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 8/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 1: "System Diagram" (40/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 9/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
---
|
||||
|
||||
## USE CASES Rankings
|
||||
|
||||
### Idea 2: "Role Selector" ⭐ WINNER (46/50)
|
||||
- **Creativity**: 9/10 - Dock as role selector is novel
|
||||
- **Storytelling**: 9/10 - Interactive selection tells personalized stories
|
||||
- **Intuitive**: 10/10 - Dock interface is familiar and clear
|
||||
- **Feasibility**: 8/10 - Dock + expanding cards is achievable
|
||||
- **Delight**: 10/10 - Confetti for "your" use case is pure joy
|
||||
|
||||
**Why it wins**: Interactive Dock selector with confetti when you find "your" use case creates personal connection and delight. Highest delight score overall!
|
||||
|
||||
### Idea 3: "Story Scenarios" (41/50)
|
||||
- **Creativity**: 8/10
|
||||
- **Storytelling**: 10/10
|
||||
- **Intuitive**: 8/10
|
||||
- **Feasibility**: 7/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 1: "User Journey Cards" (40/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 9/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
---
|
||||
|
||||
## FAQ Rankings
|
||||
|
||||
### Idea 2: "Scratch to Answer" ⭐ WINNER (42/50)
|
||||
- **Creativity**: 9/10 - Making FAQ interactive is fresh
|
||||
- **Storytelling**: 8/10 - Discovery process engages
|
||||
- **Intuitive**: 8/10 - Scratch metaphor is understood
|
||||
- **Feasibility**: 7/10 - Multiple scratch elements need optimization
|
||||
- **Delight**: 10/10 - Scratching makes FAQs fun!
|
||||
|
||||
**Why it wins**: Scratch-to-reveal makes FAQs engaging instead of boring. Confetti on important answers (like "Fully private") creates moments of delight.
|
||||
|
||||
### Idea 3: "Interactive Q&A" (41/50)
|
||||
- **Creativity**: 7/10
|
||||
- **Storytelling**: 8/10
|
||||
- **Intuitive**: 9/10
|
||||
- **Feasibility**: 9/10
|
||||
- **Delight**: 8/10
|
||||
|
||||
### Idea 1: "Expandable Cards" (40/50)
|
||||
- **Creativity**: 6/10
|
||||
- **Storytelling**: 7/10
|
||||
- **Intuitive**: 10/10
|
||||
- **Feasibility**: 10/10
|
||||
- **Delight**: 7/10
|
||||
|
||||
---
|
||||
|
||||
## Overall Component Winners
|
||||
|
||||
### Most Effective Components
|
||||
1. **Magic Card** (9 winning sections) - Versatile spotlight effects
|
||||
2. **Text Animate** (7 sections) - Essential for reveals and typing effects
|
||||
3. **Border Beam** (6 sections) - Perfect for connections and highlights
|
||||
4. **Scratch To Reveal** (4 sections) - Highest delight factor
|
||||
5. **Confetti** (5 sections) - Celebration moments
|
||||
|
||||
### Highest Delight Components
|
||||
1. Scratch To Reveal - 10/10 in FAQ, 9/10 in Features
|
||||
2. Confetti - Creates joy at key moments
|
||||
3. Dock - 10/10 for use case selection
|
||||
4. Orbiting Circles - 9/10 for data visualization
|
||||
|
||||
### Best Storytelling Components
|
||||
1. Arc Timeline - Perfect for progression narratives
|
||||
2. Safari - Immediately familiar, great for demos
|
||||
3. Progress Bars - Universal understanding of improvement
|
||||
4. Layered Stack - Architecture hierarchy storytelling
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
### High Priority (Core Experience)
|
||||
1. **Hero** - First impression is critical
|
||||
2. **Before/After** - Core value proposition
|
||||
3. **Installation** - Conversion point
|
||||
4. **The Numbers** - Proof of value
|
||||
|
||||
### Medium Priority (Supporting Narrative)
|
||||
1. **Real Examples** - Concrete use cases
|
||||
2. **How It Works** - Understanding the system
|
||||
3. **Powerful Search** - Feature highlight
|
||||
|
||||
### Lower Priority (Deep Dive)
|
||||
1. **What Gets Remembered** - Feature details
|
||||
2. **Under The Hood** - Technical deep dive
|
||||
3. **Use Cases** - Audience segmentation
|
||||
4. **FAQ** - Support content
|
||||
|
||||
---
|
||||
|
||||
## Technical Considerations
|
||||
|
||||
### Performance Concerns
|
||||
- **Scratch To Reveal**: 4 instances across page - needs optimization
|
||||
- **Orbiting Circles**: Multiple orbits can be CPU intensive
|
||||
- **Particles**: Use sparingly, can impact performance
|
||||
- **Z-space/Perspective**: May have cross-browser issues
|
||||
|
||||
### Accessibility Considerations
|
||||
- **Scratch To Reveal**: Needs keyboard alternative
|
||||
- **Confetti**: Should respect `prefers-reduced-motion`
|
||||
- **Animations**: All should be pausable/stoppable
|
||||
- **Color Contrast**: Aurora/gradient text needs testing
|
||||
|
||||
### Browser Compatibility
|
||||
- **Safari Component**: Meta - using Safari to show Safari
|
||||
- **Grid Beams/Warp**: Check WebGL support
|
||||
- **Perspective transforms**: Test in Firefox, Safari
|
||||
|
||||
---
|
||||
|
||||
## Final Winning Lineup
|
||||
|
||||
1. **Hero**: Fading Memory (42/50)
|
||||
2. **Before/After**: Split Screen Wipe (44/50)
|
||||
3. **Real Examples**: Timeline Story (44/50)
|
||||
4. **How It Works**: Layered Depth Model (43/50)
|
||||
5. **What Gets Remembered**: Memory Bank Slots (41/50)
|
||||
6. **Powerful Search**: Live Search Demo (44/50)
|
||||
7. **The Numbers**: Progress Bar Transformation (45/50) ⭐ HIGHEST SCORE
|
||||
8. **Installation**: Copy-Paste Delight (44/50)
|
||||
9. **Under The Hood**: Layered Stack (44/50)
|
||||
10. **Use Cases**: Role Selector (46/50) ⭐ HIGHEST DELIGHT
|
||||
11. **FAQ**: Scratch to Answer (42/50)
|
||||
|
||||
**Average Score**: 43.5/50 (87%)
|
||||
**Total Delight**: 94/110 (85%)
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import P from"better-sqlite3";import{join as a,dirname as U,basename as q}from"path";import{homedir as R}from"os";import{existsSync as z,mkdirSync as w}from"fs";import{fileURLToPath as X}from"url";function M(){return typeof __dirname<"u"?__dirname:U(X(import.meta.url))}var F=M(),p=process.env.CLAUDE_MEM_DATA_DIR||a(R(),".claude-mem"),u=process.env.CLAUDE_CONFIG_DIR||a(R(),".claude"),ee=a(p,"archives"),se=a(p,"logs"),te=a(p,"trash"),re=a(p,"backups"),ne=a(p,"settings.json"),I=a(p,"claude-mem.db"),oe=a(u,"settings.json"),ie=a(u,"commands"),ae=a(u,"CLAUDE.md");function O(o){w(o,{recursive:!0})}function L(){return a(F,"..","..")}var _=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(_||{}),T=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=_[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),d=_[e].padEnd(5),c=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let E="";n!=null&&(this.level===0&&typeof n=="object"?E=`
|
||||
`+JSON.stringify(n,null,2):E=" "+this.formatData(n));let b="";if(r){let{sessionId:B,sdkSessionId:G,correlationId:$,...N}=r;Object.keys(N).length>0&&(b=` {${Object.entries(N).map(([y,x])=>`${y}=${x}`).join(", ")}}`)}let h=`[${i}] [${d}] [${c}] ${m}${t}${b}${E}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},v=new T;var l=class{db;constructor(){O(p),this.db=new P(I),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
import P from"better-sqlite3";import{join as c,dirname as U,basename as q}from"path";import{homedir as R}from"os";import{existsSync as z,mkdirSync as w}from"fs";import{fileURLToPath as X}from"url";function M(){return typeof __dirname<"u"?__dirname:U(X(import.meta.url))}var F=M(),p=process.env.CLAUDE_MEM_DATA_DIR||c(R(),".claude-mem"),u=process.env.CLAUDE_CONFIG_DIR||c(R(),".claude"),ee=c(p,"archives"),se=c(p,"logs"),te=c(p,"trash"),re=c(p,"backups"),ne=c(p,"settings.json"),I=c(p,"claude-mem.db"),oe=c(u,"settings.json"),ie=c(u,"commands"),ae=c(u,"CLAUDE.md");function O(o){w(o,{recursive:!0})}function L(){return c(F,"..","..")}var _=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(_||{}),T=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=_[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),a=_[e].padEnd(5),d=s.padEnd(6),l="";r?.correlationId?l=`[${r.correlationId}] `:r?.sessionId&&(l=`[session-${r.sessionId}] `);let E="";n!=null&&(this.level===0&&typeof n=="object"?E=`
|
||||
`+JSON.stringify(n,null,2):E=" "+this.formatData(n));let b="";if(r){let{sessionId:B,sdkSessionId:G,correlationId:$,...N}=r;Object.keys(N).length>0&&(b=` {${Object.entries(N).map(([y,x])=>`${y}=${x}`).join(", ")}}`)}let h=`[${i}] [${a}] [${d}] ${l}${t}${b}${E}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},v=new T;var m=class{db;constructor(){O(p),this.db=new P(I),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
version INTEGER UNIQUE NOT NULL,
|
||||
@@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
|
||||
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(c=>c.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(c=>c.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(c=>c.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(d=>d.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
CREATE TABLE session_summaries_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sdk_session_id TEXT NOT NULL,
|
||||
@@ -185,7 +185,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT files_read, files_modified
|
||||
FROM observations
|
||||
WHERE sdk_session_id = ?
|
||||
`).all(e),r=new Set,n=new Set;for(let i of t){if(i.files_read)try{let d=JSON.parse(i.files_read);Array.isArray(d)&&d.forEach(c=>r.add(c))}catch{}if(i.files_modified)try{let d=JSON.parse(i.files_modified);Array.isArray(d)&&d.forEach(c=>n.add(c))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
|
||||
`).all(e),r=new Set,n=new Set;for(let i of t){if(i.files_read)try{let a=JSON.parse(i.files_read);Array.isArray(a)&&a.forEach(d=>r.add(d))}catch{}if(i.files_modified)try{let a=JSON.parse(i.files_modified);Array.isArray(a)&&a.forEach(d=>n.add(d))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
|
||||
SELECT id, sdk_session_id, project, user_prompt
|
||||
FROM sdk_sessions
|
||||
WHERE id = ?
|
||||
@@ -212,11 +212,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(`
|
||||
INSERT INTO sdk_sessions
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),a=this.db.prepare(`
|
||||
INSERT OR IGNORE INTO sdk_sessions
|
||||
(claude_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'active')
|
||||
`).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
`).run(e,s,t,r.toISOString(),n);return a.lastInsertRowid===0||a.changes===0?this.db.prepare(`
|
||||
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
|
||||
`).get(e).id:a.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET sdk_session_id = ?
|
||||
WHERE id = ? AND sdk_session_id IS NULL
|
||||
@@ -252,4 +254,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE status = 'active'
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};import g from"path";import{existsSync as S}from"fs";import{spawn as H}from"child_process";var W=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),j=`http://127.0.0.1:${W}/health`;async function A(){try{return(await fetch(j,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function k(){try{if(await A())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=g.join(o,"plugin","scripts","worker-service.cjs");if(!S(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=g.join(o,"ecosystem.config.cjs"),t=g.join(o,"node_modules",".bin","pm2");if(!S(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!S(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=H(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await A())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}async function C(o){try{console.error("[claude-mem cleanup] Hook fired",{input:o?{session_id:o.session_id,cwd:o.cwd,reason:o.reason}:null}),o||(console.log("No input provided - this script is designed to run as a Claude Code SessionEnd hook"),console.log(`
|
||||
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:s}=o;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:s}),await k()||console.error("[claude-mem cleanup] Worker not available - skipping HTTP cleanup");let r=new l,n=r.findActiveSDKSession(e);n||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),r.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:n.id,sdk_session_id:n.sdk_session_id,project:n.project,worker_port:n.worker_port});try{r.markSessionCompleted(n.id),console.error("[claude-mem cleanup] Session marked as completed in database")}catch(i){console.error("[claude-mem cleanup] Failed to mark session as completed:",i)}r.close(),console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(e){console.error("[claude-mem cleanup] Unexpected error in hook",{error:e.message,stack:e.stack,name:e.name}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}import{stdin as D}from"process";var f="";D.on("data",o=>f+=o);D.on("end",async()=>{try{let o=f.trim()?JSON.parse(f):void 0;await C(o)}catch(o){console.error(`[claude-mem cleanup-hook error: ${o.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}});
|
||||
Expected input format:`),console.log(JSON.stringify({session_id:"string",cwd:"string",transcript_path:"string",hook_event_name:"SessionEnd",reason:"exit"},null,2)),process.exit(0));let{session_id:e,reason:s}=o;console.error("[claude-mem cleanup] Searching for active SDK session",{session_id:e,reason:s}),await k()||console.error("[claude-mem cleanup] Worker not available - skipping HTTP cleanup");let r=new m,n=r.findActiveSDKSession(e);n||(console.error("[claude-mem cleanup] No active SDK session found",{session_id:e}),r.close(),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)),console.error("[claude-mem cleanup] Active SDK session found",{session_id:n.id,sdk_session_id:n.sdk_session_id,project:n.project,worker_port:n.worker_port});try{r.markSessionCompleted(n.id),console.error("[claude-mem cleanup] Session marked as completed in database")}catch(i){console.error("[claude-mem cleanup] Failed to mark session as completed:",i)}r.close(),console.error("[claude-mem cleanup] Cleanup completed successfully"),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}catch(e){console.error("[claude-mem cleanup] Unexpected error in hook",{error:e.message,stack:e.stack,name:e.name}),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}}import{stdin as D}from"process";var f="";D.on("data",o=>f+=o);D.on("end",async()=>{try{let o=f.trim()?JSON.parse(f):void 0;await C(o)}catch(o){console.error(`[claude-mem cleanup-hook error: ${o.message}]`),console.log('{"continue": true, "suppressOutput": true}'),process.exit(0)}});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import q from"path";import W from"better-sqlite3";import{join as l,dirname as M,basename as V}from"path";import{homedir as v}from"os";import{existsSync as Z,mkdirSync as X}from"fs";import{fileURLToPath as F}from"url";function P(){return typeof __dirname<"u"?__dirname:M(F(import.meta.url))}var j=P(),E=process.env.CLAUDE_MEM_DATA_DIR||l(v(),".claude-mem"),b=process.env.CLAUDE_CONFIG_DIR||l(v(),".claude"),se=l(E,"archives"),te=l(E,"logs"),re=l(E,"trash"),ne=l(E,"backups"),ie=l(E,"settings.json"),A=l(E,"claude-mem.db"),oe=l(b,"settings.json"),ae=l(b,"commands"),de=l(b,"CLAUDE.md");function y(p){X(p,{recursive:!0})}function D(){return l(j,"..","..")}var S=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(S||{}),N=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=S[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,t){return`obs-${e}-${t}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Object.keys(e);return t.length===0?"{}":t.length<=3?JSON.stringify(e):`{${t.length} keys: ${t.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,t){if(!t)return e;try{let r=typeof t=="string"?JSON.parse(t):t;if(e==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${e}(${n})`}if(e==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${e}(${n})`}if(e==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${e}(${n})`}if(e==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${e}(${n})`}return e}catch{return e}}log(e,t,r,n,o){if(e<this.level)return;let d=new Date().toISOString().replace("T"," ").substring(0,23),s=S[e].padEnd(5),c=t.padEnd(6),T="";n?.correlationId?T=`[${n.correlationId}] `:n?.sessionId&&(T=`[session-${n.sessionId}] `);let a="";o!=null&&(this.level===0&&typeof o=="object"?a=`
|
||||
`+JSON.stringify(o,null,2):a=" "+this.formatData(o));let m="";if(n){let{sessionId:_,sdkSessionId:f,correlationId:C,...h}=n;Object.keys(h).length>0&&(m=` {${Object.entries(h).map(([U,w])=>`${U}=${w}`).join(", ")}}`)}let u=`[${d}] [${s}] [${c}] ${T}${r}${m}${a}`;e===3?console.error(u):console.log(u)}debug(e,t,r,n){this.log(0,e,t,r,n)}info(e,t,r,n){this.log(1,e,t,r,n)}warn(e,t,r,n){this.log(2,e,t,r,n)}error(e,t,r,n){this.log(3,e,t,r,n)}dataIn(e,t,r,n){this.info(e,`\u2192 ${t}`,r,n)}dataOut(e,t,r,n){this.info(e,`\u2190 ${t}`,r,n)}success(e,t,r,n){this.info(e,`\u2713 ${t}`,r,n)}failure(e,t,r,n){this.error(e,`\u2717 ${t}`,r,n)}timing(e,t,r,n){this.info(e,`\u23F1 ${t}`,n,{duration:`${r}ms`})}},k=new N;var g=class{db;constructor(){y(E),this.db=new W(A),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
import q from"path";import W from"better-sqlite3";import{join as l,dirname as M,basename as V}from"path";import{homedir as v}from"os";import{existsSync as Z,mkdirSync as X}from"fs";import{fileURLToPath as F}from"url";function P(){return typeof __dirname<"u"?__dirname:M(F(import.meta.url))}var j=P(),E=process.env.CLAUDE_MEM_DATA_DIR||l(v(),".claude-mem"),b=process.env.CLAUDE_CONFIG_DIR||l(v(),".claude"),se=l(E,"archives"),te=l(E,"logs"),re=l(E,"trash"),ne=l(E,"backups"),ie=l(E,"settings.json"),A=l(E,"claude-mem.db"),oe=l(b,"settings.json"),ae=l(b,"commands"),de=l(b,"CLAUDE.md");function y(p){X(p,{recursive:!0})}function k(){return l(j,"..","..")}var S=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(S||{}),R=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=S[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,t){return`obs-${e}-${t}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Object.keys(e);return t.length===0?"{}":t.length<=3?JSON.stringify(e):`{${t.length} keys: ${t.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,t){if(!t)return e;try{let r=typeof t=="string"?JSON.parse(t):t;if(e==="Bash"&&r.command){let n=r.command.length>50?r.command.substring(0,50)+"...":r.command;return`${e}(${n})`}if(e==="Read"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${e}(${n})`}if(e==="Edit"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${e}(${n})`}if(e==="Write"&&r.file_path){let n=r.file_path.split("/").pop()||r.file_path;return`${e}(${n})`}return e}catch{return e}}log(e,t,r,n,o){if(e<this.level)return;let d=new Date().toISOString().replace("T"," ").substring(0,23),s=S[e].padEnd(5),c=t.padEnd(6),_="";n?.correlationId?_=`[${n.correlationId}] `:n?.sessionId&&(_=`[session-${n.sessionId}] `);let a="";o!=null&&(this.level===0&&typeof o=="object"?a=`
|
||||
`+JSON.stringify(o,null,2):a=" "+this.formatData(o));let m="";if(n){let{sessionId:T,sdkSessionId:f,correlationId:C,...h}=n;Object.keys(h).length>0&&(m=` {${Object.entries(h).map(([U,w])=>`${U}=${w}`).join(", ")}}`)}let u=`[${d}] [${s}] [${c}] ${_}${r}${m}${a}`;e===3?console.error(u):console.log(u)}debug(e,t,r,n){this.log(0,e,t,r,n)}info(e,t,r,n){this.log(1,e,t,r,n)}warn(e,t,r,n){this.log(2,e,t,r,n)}error(e,t,r,n){this.log(3,e,t,r,n)}dataIn(e,t,r,n){this.info(e,`\u2192 ${t}`,r,n)}dataOut(e,t,r,n){this.info(e,`\u2190 ${t}`,r,n)}success(e,t,r,n){this.info(e,`\u2713 ${t}`,r,n)}failure(e,t,r,n){this.error(e,`\u2717 ${t}`,r,n)}timing(e,t,r,n){this.info(e,`\u23F1 ${t}`,n,{duration:`${r}ms`})}},D=new R;var g=class{db;constructor(){y(E),this.db=new W(A),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
version INTEGER UNIQUE NOT NULL,
|
||||
@@ -212,15 +212,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,t,r){let n=new Date,o=n.getTime();return this.db.prepare(`
|
||||
INSERT INTO sdk_sessions
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,t,r){let n=new Date,o=n.getTime(),s=this.db.prepare(`
|
||||
INSERT OR IGNORE INTO sdk_sessions
|
||||
(claude_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'active')
|
||||
`).run(e,t,r,n.toISOString(),o).lastInsertRowid}updateSDKSessionId(e,t){return this.db.prepare(`
|
||||
`).run(e,t,r,n.toISOString(),o);return s.lastInsertRowid===0||s.changes===0?this.db.prepare(`
|
||||
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
|
||||
`).get(e).id:s.lastInsertRowid}updateSDKSessionId(e,t){return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET sdk_session_id = ?
|
||||
WHERE id = ? AND sdk_session_id IS NULL
|
||||
`).run(t,e).changes===0?(k.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:t}),!1):!0}setWorkerPort(e,t){this.db.prepare(`
|
||||
`).run(t,e).changes===0?(D.debug("DB","sdk_session_id already set, skipping update",{sessionId:e,sdkSessionId:t}),!1):!0}setWorkerPort(e,t){this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET worker_port = ?
|
||||
WHERE id = ?
|
||||
@@ -251,13 +253,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let t=Obje
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE status = 'active'
|
||||
`).run(e.toISOString(),t).changes}close(){this.db.close()}};import R from"path";import{existsSync as I}from"fs";import{spawn as H}from"child_process";var B=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),G=`http://127.0.0.1:${B}/health`;async function $(){try{return(await fetch(G,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function x(){try{if(await $())return!0;console.error("[claude-mem] Worker not responding, starting...");let p=D(),e=R.join(p,"plugin","scripts","worker-service.cjs");if(!I(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let t=R.join(p,"ecosystem.config.cjs"),r=R.join(p,"node_modules",".bin","pm2");if(!I(r))throw new Error(`PM2 binary not found at ${r}. This is a bundled dependency - try running: npm install`);if(!I(t))throw new Error(`PM2 ecosystem config not found at ${t}. Plugin installation may be corrupted.`);let n=H(r,["start",t],{detached:!0,stdio:"ignore",cwd:p});n.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),n.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(d=>setTimeout(d,500)),await $())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(p){return console.error(`[claude-mem] Failed to start worker: ${p.message}`),!1}}var i={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m"};function O(p,e=!1,t=!1){x();let r=p?.cwd??process.cwd(),n=r?q.basename(r):"unknown-project",o=new g;try{let d=o.getRecentSummariesWithSessionInfo(n,3);if(d.length===0)return e?`
|
||||
`).run(e.toISOString(),t).changes}close(){this.db.close()}};import N from"path";import{existsSync as I}from"fs";import{spawn as H}from"child_process";var B=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),G=`http://127.0.0.1:${B}/health`;async function $(){try{return(await fetch(G,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function x(){try{if(await $())return!0;console.error("[claude-mem] Worker not responding, starting...");let p=k(),e=N.join(p,"plugin","scripts","worker-service.cjs");if(!I(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let t=N.join(p,"ecosystem.config.cjs"),r=N.join(p,"node_modules",".bin","pm2");if(!I(r))throw new Error(`PM2 binary not found at ${r}. This is a bundled dependency - try running: npm install`);if(!I(t))throw new Error(`PM2 ecosystem config not found at ${t}. Plugin installation may be corrupted.`);let n=H(r,["start",t],{detached:!0,stdio:"ignore",cwd:p});n.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),n.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(d=>setTimeout(d,500)),await $())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(p){return console.error(`[claude-mem] Failed to start worker: ${p.message}`),!1}}var i={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",gray:"\x1B[90m"};function O(p,e=!1,t=!1){x();let r=p?.cwd??process.cwd(),n=r?q.basename(r):"unknown-project",o=new g;try{let d=o.getRecentSummariesWithSessionInfo(n,3);if(d.length===0)return e?`
|
||||
${i.bright}${i.cyan}\u{1F4DD} [${n}] recent context${i.reset}
|
||||
${i.gray}${"\u2500".repeat(60)}${i.reset}
|
||||
|
||||
${i.dim}No previous summaries found for this project yet.${i.reset}
|
||||
`:`# [${n}] recent context
|
||||
|
||||
No previous summaries found for this project yet.`;let s=[];if(t){if(e?(s.push(""),s.push(`${i.bright}${i.cyan}\u{1F4DD} [${n}] recent context${i.reset}`),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),s.push("")):(s.push(`# [${n}] recent context`),s.push("")),d.length>1){e?(s.push(`${i.bright}${i.dim}Previous Requests:${i.reset}`),s.push("")):(s.push("**Previous Requests:**"),s.push(""));for(let _=d.length-1;_>=1;_--){let f=d[_],h=new Date(f.created_at).toLocaleString();e?s.push(`${i.dim}\u2022 ${h}:${i.reset} ${f.request||"(no request)"}`):s.push(`- ${h}: ${f.request||"(no request)"}`)}e?(s.push(""),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),s.push("")):(s.push(""),s.push("---"),s.push(""))}let a=d[0];a.request&&(e?(s.push(`${i.bright}${i.yellow}Request:${i.reset} ${a.request}`),s.push("")):(s.push(`**Request:** ${a.request}`),s.push(""))),a.learned&&(e?(s.push(`${i.bright}${i.blue}Learned:${i.reset} ${a.learned}`),s.push("")):(s.push(`**Learned:** ${a.learned}`),s.push(""))),a.completed&&(e?(s.push(`${i.bright}${i.green}Completed:${i.reset} ${a.completed}`),s.push("")):(s.push(`**Completed:** ${a.completed}`),s.push(""))),a.next_steps&&(e?(s.push(`${i.bright}${i.magenta}Next Steps:${i.reset} ${a.next_steps}`),s.push("")):(s.push(`**Next Steps:** ${a.next_steps}`),s.push("")));let m=o.getFilesForSession(a.sdk_session_id);m.filesRead.length>0&&(e?s.push(`${i.dim}Files Read: ${m.filesRead.join(", ")}${i.reset}`):s.push(`**Files Read:** ${m.filesRead.join(", ")}`)),m.filesModified.length>0&&(e?s.push(`${i.dim}Files Modified: ${m.filesModified.join(", ")}${i.reset}`):s.push(`**Files Modified:** ${m.filesModified.join(", ")}`));let u=new Date(a.created_at).toLocaleString();return e?s.push(`${i.dim}Date: ${u}${i.reset}`):s.push(`**Date:** ${u}`),e&&(s.push(""),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)),s.join(`
|
||||
`)}e?(s.push(""),s.push(`${i.bright}${i.cyan}\u{1F4DD} [${n}] recent context${i.reset}`),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)):(s.push(`# [${n}] recent context`),s.push(""));let c=null,T=!0;for(let a of d){c!==null&&a.sdk_session_id!==c?e?(s.push(""),s.push(`${i.dim}${"\u2500".repeat(23)} New Session ${"\u2500".repeat(24)}${i.reset}`),s.push("")):(s.push(""),s.push("--- New Session ---"),s.push("")):T?e&&s.push(""):e?(s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),s.push("")):(s.push("---"),s.push("")),T=!1,a.request&&(e?(s.push(`${i.bright}${i.yellow}Request:${i.reset} ${a.request}`),s.push("")):(s.push(`**Request:** ${a.request}`),s.push(""))),a.learned&&(e?(s.push(`${i.bright}${i.blue}Learned:${i.reset} ${a.learned}`),s.push("")):(s.push(`**Learned:** ${a.learned}`),s.push(""))),a.completed&&(e?(s.push(`${i.bright}${i.green}Completed:${i.reset} ${a.completed}`),s.push("")):(s.push(`**Completed:** ${a.completed}`),s.push(""))),a.next_steps&&(e?(s.push(`${i.bright}${i.magenta}Next Steps:${i.reset} ${a.next_steps}`),s.push("")):(s.push(`**Next Steps:** ${a.next_steps}`),s.push("")));let u=o.getFilesForSession(a.sdk_session_id);u.filesRead.length>0&&(e?s.push(`${i.dim}Files Read: ${u.filesRead.join(", ")}${i.reset}`):s.push(`**Files Read:** ${u.filesRead.join(", ")}`)),u.filesModified.length>0&&(e?s.push(`${i.dim}Files Modified: ${u.filesModified.join(", ")}${i.reset}`):s.push(`**Files Modified:** ${u.filesModified.join(", ")}`));let _=new Date(a.created_at).toLocaleString();e?s.push(`${i.dim}Date: ${_}${i.reset}`):s.push(`**Date:** ${_}`),e||s.push(""),c=a.sdk_session_id}return e&&(s.push(""),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)),s.join(`
|
||||
No previous summaries found for this project yet.`;let s=[];if(t){if(e?(s.push(""),s.push(`${i.bright}${i.cyan}\u{1F4DD} [${n}] recent context${i.reset}`),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),s.push("")):(s.push(`# [${n}] recent context`),s.push("")),d.length>1){e?(s.push(`${i.bright}${i.dim}Previous Requests:${i.reset}`),s.push("")):(s.push("**Previous Requests:**"),s.push(""));for(let T=d.length-1;T>=1;T--){let f=d[T],h=new Date(f.created_at).toLocaleString();e?s.push(`${i.dim}\u2022 ${h}:${i.reset} ${f.request||"(no request)"}`):s.push(`- ${h}: ${f.request||"(no request)"}`)}e?(s.push(""),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),s.push("")):(s.push(""),s.push("---"),s.push(""))}let a=d[0];a.request&&(e?(s.push(`${i.bright}${i.yellow}Request:${i.reset} ${a.request}`),s.push("")):(s.push(`**Request:** ${a.request}`),s.push(""))),a.learned&&(e?(s.push(`${i.bright}${i.blue}Learned:${i.reset} ${a.learned}`),s.push("")):(s.push(`**Learned:** ${a.learned}`),s.push(""))),a.completed&&(e?(s.push(`${i.bright}${i.green}Completed:${i.reset} ${a.completed}`),s.push("")):(s.push(`**Completed:** ${a.completed}`),s.push(""))),a.next_steps&&(e?(s.push(`${i.bright}${i.magenta}Next Steps:${i.reset} ${a.next_steps}`),s.push("")):(s.push(`**Next Steps:** ${a.next_steps}`),s.push("")));let m=o.getFilesForSession(a.sdk_session_id);m.filesRead.length>0&&(e?s.push(`${i.dim}Files Read: ${m.filesRead.join(", ")}${i.reset}`):s.push(`**Files Read:** ${m.filesRead.join(", ")}`)),m.filesModified.length>0&&(e?s.push(`${i.dim}Files Modified: ${m.filesModified.join(", ")}${i.reset}`):s.push(`**Files Modified:** ${m.filesModified.join(", ")}`));let u=new Date(a.created_at).toLocaleString();return e?s.push(`${i.dim}Date: ${u}${i.reset}`):s.push(`**Date:** ${u}`),e&&(s.push(""),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)),s.join(`
|
||||
`)}e?(s.push(""),s.push(`${i.bright}${i.cyan}\u{1F4DD} [${n}] recent context${i.reset}`),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)):(s.push(`# [${n}] recent context`),s.push(""));let c=null,_=!0;for(let a of d){c!==null&&a.sdk_session_id!==c?e?(s.push(""),s.push(`${i.dim}${"\u2500".repeat(23)} New Session ${"\u2500".repeat(24)}${i.reset}`),s.push("")):(s.push(""),s.push("--- New Session ---"),s.push("")):_?e&&s.push(""):e?(s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`),s.push("")):(s.push("---"),s.push("")),_=!1,a.request&&(e?(s.push(`${i.bright}${i.yellow}Request:${i.reset} ${a.request}`),s.push("")):(s.push(`**Request:** ${a.request}`),s.push(""))),a.learned&&(e?(s.push(`${i.bright}${i.blue}Learned:${i.reset} ${a.learned}`),s.push("")):(s.push(`**Learned:** ${a.learned}`),s.push(""))),a.completed&&(e?(s.push(`${i.bright}${i.green}Completed:${i.reset} ${a.completed}`),s.push("")):(s.push(`**Completed:** ${a.completed}`),s.push(""))),a.next_steps&&(e?(s.push(`${i.bright}${i.magenta}Next Steps:${i.reset} ${a.next_steps}`),s.push("")):(s.push(`**Next Steps:** ${a.next_steps}`),s.push("")));let u=o.getFilesForSession(a.sdk_session_id);u.filesRead.length>0&&(e?s.push(`${i.dim}Files Read: ${u.filesRead.join(", ")}${i.reset}`):s.push(`**Files Read:** ${u.filesRead.join(", ")}`)),u.filesModified.length>0&&(e?s.push(`${i.dim}Files Modified: ${u.filesModified.join(", ")}${i.reset}`):s.push(`**Files Modified:** ${u.filesModified.join(", ")}`));let T=new Date(a.created_at).toLocaleString();e?s.push(`${i.dim}Date: ${T}${i.reset}`):s.push(`**Date:** ${T}`),e||s.push(""),c=a.sdk_session_id}return e&&(s.push(""),s.push(`${i.gray}${"\u2500".repeat(60)}${i.reset}`)),s.join(`
|
||||
`)}finally{o.close()}}import{stdin as L}from"process";try{let p=process.argv.includes("--index");if(L.isTTY){let e=O(void 0,!0,p);console.log(e),process.exit(0)}else{let e="";L.on("data",t=>e+=t),L.on("end",()=>{let t=e.trim()?JSON.parse(e):void 0,n={hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:O(t,!1,p)}};console.log(JSON.stringify(n)),process.exit(0)})}}catch(p){console.error(`[claude-mem context-hook error: ${p.message}]`),process.exit(0)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import K from"path";import $ from"better-sqlite3";import{join as p,dirname as M,basename as z}from"path";import{homedir as h}from"os";import{existsSync as te,mkdirSync as P}from"fs";import{fileURLToPath as F}from"url";function H(){return typeof __dirname<"u"?__dirname:M(F(import.meta.url))}var W=H(),m=process.env.CLAUDE_MEM_DATA_DIR||p(h(),".claude-mem"),T=process.env.CLAUDE_CONFIG_DIR||p(h(),".claude"),ne=p(m,"archives"),oe=p(m,"logs"),ie=p(m,"trash"),ae=p(m,"backups"),de=p(m,"settings.json"),O=p(m,"claude-mem.db"),pe=p(T,"settings.json"),ce=p(T,"commands"),ue=p(T,"CLAUDE.md");function I(o){P(o,{recursive:!0})}function L(){return p(W,"..","..")}var g=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(g||{}),S=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=g[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),d=g[e].padEnd(5),a=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let l="";n!=null&&(this.level===0&&typeof n=="object"?l=`
|
||||
`+JSON.stringify(n,null,2):l=" "+this.formatData(n));let c="";if(r){let{sessionId:Y,sdkSessionId:q,correlationId:V,...R}=r;Object.keys(R).length>0&&(c=` {${Object.entries(R).map(([w,X])=>`${w}=${X}`).join(", ")}}`)}let u=`[${i}] [${d}] [${a}] ${E}${t}${c}${l}`;e===3?console.error(u):console.log(u)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},A=new S;var _=class{db;constructor(){I(m),this.db=new $(O),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
import Y from"path";import $ from"better-sqlite3";import{join as p,dirname as M,basename as z}from"path";import{homedir as N}from"os";import{existsSync as te,mkdirSync as P}from"fs";import{fileURLToPath as F}from"url";function H(){return typeof __dirname<"u"?__dirname:M(F(import.meta.url))}var W=H(),c=process.env.CLAUDE_MEM_DATA_DIR||p(N(),".claude-mem"),_=process.env.CLAUDE_CONFIG_DIR||p(N(),".claude"),ne=p(c,"archives"),oe=p(c,"logs"),ie=p(c,"trash"),ae=p(c,"backups"),de=p(c,"settings.json"),O=p(c,"claude-mem.db"),pe=p(_,"settings.json"),ce=p(_,"commands"),ue=p(_,"CLAUDE.md");function I(o){P(o,{recursive:!0})}function L(){return p(W,"..","..")}var T=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(T||{}),g=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),a=T[e].padEnd(5),d=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let u="";n!=null&&(this.level===0&&typeof n=="object"?u=`
|
||||
`+JSON.stringify(n,null,2):u=" "+this.formatData(n));let E="";if(r){let{sessionId:K,sdkSessionId:q,correlationId:V,...h}=r;Object.keys(h).length>0&&(E=` {${Object.entries(h).map(([w,X])=>`${w}=${X}`).join(", ")}}`)}let R=`[${i}] [${a}] [${d}] ${m}${t}${E}${u}`;e===3?console.error(R):console.log(R)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},A=new g;var l=class{db;constructor(){I(c),this.db=new $(O),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
version INTEGER UNIQUE NOT NULL,
|
||||
@@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
|
||||
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(a=>a.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(a=>a.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(a=>a.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(d=>d.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
CREATE TABLE session_summaries_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sdk_session_id TEXT NOT NULL,
|
||||
@@ -185,7 +185,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT files_read, files_modified
|
||||
FROM observations
|
||||
WHERE sdk_session_id = ?
|
||||
`).all(e),r=new Set,n=new Set;for(let i of t){if(i.files_read)try{let d=JSON.parse(i.files_read);Array.isArray(d)&&d.forEach(a=>r.add(a))}catch{}if(i.files_modified)try{let d=JSON.parse(i.files_modified);Array.isArray(d)&&d.forEach(a=>n.add(a))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
|
||||
`).all(e),r=new Set,n=new Set;for(let i of t){if(i.files_read)try{let a=JSON.parse(i.files_read);Array.isArray(a)&&a.forEach(d=>r.add(d))}catch{}if(i.files_modified)try{let a=JSON.parse(i.files_modified);Array.isArray(a)&&a.forEach(d=>n.add(d))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
|
||||
SELECT id, sdk_session_id, project, user_prompt
|
||||
FROM sdk_sessions
|
||||
WHERE id = ?
|
||||
@@ -212,11 +212,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(`
|
||||
INSERT INTO sdk_sessions
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),a=this.db.prepare(`
|
||||
INSERT OR IGNORE INTO sdk_sessions
|
||||
(claude_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'active')
|
||||
`).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
`).run(e,s,t,r.toISOString(),n);return a.lastInsertRowid===0||a.changes===0?this.db.prepare(`
|
||||
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
|
||||
`).get(e).id:a.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET sdk_session_id = ?
|
||||
WHERE id = ? AND sdk_session_id IS NULL
|
||||
@@ -251,4 +253,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE status = 'active'
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function j(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function v(o,e,s={}){let t=j(o,e,s);return JSON.stringify(t)}import f from"path";import{existsSync as b}from"fs";import{spawn as B}from"child_process";var C=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),G=`http://127.0.0.1:${C}/health`;async function k(){try{return(await fetch(G,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function D(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=f.join(o,"plugin","scripts","worker-service.cjs");if(!b(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=f.join(o,"ecosystem.config.cjs"),t=f.join(o,"node_modules",".bin","pm2");if(!b(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!b(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=B(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}function y(){return C}async function x(o){if(!o)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=o,r=K.basename(s);if(!await D())throw new Error("Worker service failed to start or become healthy");let i=new _;try{let d=i.findActiveSDKSession(e),a,E=!1;if(d){a=d.id;let c=i.incrementPromptCounter(a);console.error(`[new-hook] Continuing session ${a}, prompt #${c}`)}else{let c=i.findAnySDKSession(e);if(c){a=c.id,i.reactivateSession(a,t);let u=i.incrementPromptCounter(a);E=!0,console.error(`[new-hook] Reactivated session ${a}, prompt #${u}`)}else{a=i.createSDKSession(e,r,t);let u=i.incrementPromptCounter(a);E=!0,console.error(`[new-hook] Created new session ${a}, prompt #${u}`)}}let l=y();if(E){let c=await fetch(`http://127.0.0.1:${l}/sessions/${a}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!c.ok){let u=await c.text();throw new Error(`Failed to initialize session: ${c.status} ${u}`)}}console.log(v("UserPromptSubmit",!0))}finally{i.close()}}import{stdin as U}from"process";var N="";U.on("data",o=>N+=o);U.on("end",async()=>{let o=N.trim()?JSON.parse(N):void 0;await x(o),process.exit(0)});
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function j(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function v(o,e,s={}){let t=j(o,e,s);return JSON.stringify(t)}import S from"path";import{existsSync as f}from"fs";import{spawn as B}from"child_process";var C=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),G=`http://127.0.0.1:${C}/health`;async function k(){try{return(await fetch(G,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function D(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=L(),e=S.join(o,"plugin","scripts","worker-service.cjs");if(!f(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=S.join(o,"ecosystem.config.cjs"),t=S.join(o,"node_modules",".bin","pm2");if(!f(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!f(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=B(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}function y(){return C}async function x(o){if(!o)throw new Error("newHook requires input");let{session_id:e,cwd:s,prompt:t}=o,r=Y.basename(s);if(!await D())throw new Error("Worker service failed to start or become healthy");let i=new l;try{let a=i.createSDKSession(e,r,t),d=i.incrementPromptCounter(a);console.error(`[new-hook] Session ${a}, prompt #${d}`);let m=y(),u=await fetch(`http://127.0.0.1:${m}/sessions/${a}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({project:r,userPrompt:t}),signal:AbortSignal.timeout(5e3)});if(!u.ok){let E=await u.text();throw new Error(`Failed to initialize session: ${u.status} ${E}`)}console.log(v("UserPromptSubmit",!0))}finally{i.close()}}import{stdin as U}from"process";var b="";U.on("data",o=>b+=o);U.on("end",async()=>{let o=b.trim()?JSON.parse(b):void 0;await x(o),process.exit(0)});
|
||||
|
||||
+13
-11
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import H from"better-sqlite3";import{join as p,dirname as w,basename as Q}from"path";import{homedir as I}from"os";import{existsSync as se,mkdirSync as X}from"fs";import{fileURLToPath as M}from"url";function P(){return typeof __dirname<"u"?__dirname:w(M(import.meta.url))}var F=P(),u=process.env.CLAUDE_MEM_DATA_DIR||p(I(),".claude-mem"),g=process.env.CLAUDE_CONFIG_DIR||p(I(),".claude"),re=p(u,"archives"),oe=p(u,"logs"),ne=p(u,"trash"),ie=p(u,"backups"),ae=p(u,"settings.json"),L=p(u,"claude-mem.db"),de=p(g,"settings.json"),pe=p(g,"commands"),ce=p(g,"CLAUDE.md");function v(n){X(n,{recursive:!0})}function A(){return p(F,"..","..")}var f=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(f||{}),S=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=f[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,o){if(e<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),i=f[e].padEnd(5),d=s.padEnd(6),l="";r?.correlationId?l=`[${r.correlationId}] `:r?.sessionId&&(l=`[session-${r.sessionId}] `);let c="";o!=null&&(this.level===0&&typeof o=="object"?c=`
|
||||
`+JSON.stringify(o,null,2):c=" "+this.formatData(o));let m="";if(r){let{sessionId:K,sdkSessionId:Y,correlationId:q,...O}=r;Object.keys(O).length>0&&(m=` {${Object.entries(O).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let h=`[${a}] [${i}] [${d}] ${l}${t}${m}${c}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},E=new S;var _=class{db;constructor(){v(u),this.db=new H(L),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
import H from"better-sqlite3";import{join as p,dirname as w,basename as Q}from"path";import{homedir as I}from"os";import{existsSync as se,mkdirSync as X}from"fs";import{fileURLToPath as M}from"url";function P(){return typeof __dirname<"u"?__dirname:w(M(import.meta.url))}var F=P(),c=process.env.CLAUDE_MEM_DATA_DIR||p(I(),".claude-mem"),g=process.env.CLAUDE_CONFIG_DIR||p(I(),".claude"),re=p(c,"archives"),ne=p(c,"logs"),oe=p(c,"trash"),ie=p(c,"backups"),ae=p(c,"settings.json"),L=p(c,"claude-mem.db"),de=p(g,"settings.json"),pe=p(g,"commands"),ce=p(g,"CLAUDE.md");function v(o){X(o,{recursive:!0})}function A(){return p(F,"..","..")}var S=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(S||{}),f=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=S[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),i=S[e].padEnd(5),d=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let l="";n!=null&&(this.level===0&&typeof n=="object"?l=`
|
||||
`+JSON.stringify(n,null,2):l=" "+this.formatData(n));let u="";if(r){let{sessionId:K,sdkSessionId:Y,correlationId:q,...O}=r;Object.keys(O).length>0&&(u=` {${Object.entries(O).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let _=`[${a}] [${i}] [${d}] ${m}${t}${u}${l}`;e===3?console.error(_):console.log(_)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},E=new f;var T=class{db;constructor(){v(c),this.db=new H(L),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
version INTEGER UNIQUE NOT NULL,
|
||||
@@ -185,7 +185,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT files_read, files_modified
|
||||
FROM observations
|
||||
WHERE sdk_session_id = ?
|
||||
`).all(e),r=new Set,o=new Set;for(let a of t){if(a.files_read)try{let i=JSON.parse(a.files_read);Array.isArray(i)&&i.forEach(d=>r.add(d))}catch{}if(a.files_modified)try{let i=JSON.parse(a.files_modified);Array.isArray(i)&&i.forEach(d=>o.add(d))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(o)}}getSessionById(e){return this.db.prepare(`
|
||||
`).all(e),r=new Set,n=new Set;for(let a of t){if(a.files_read)try{let i=JSON.parse(a.files_read);Array.isArray(i)&&i.forEach(d=>r.add(d))}catch{}if(a.files_modified)try{let i=JSON.parse(a.files_modified);Array.isArray(i)&&i.forEach(d=>n.add(d))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
|
||||
SELECT id, sdk_session_id, project, user_prompt
|
||||
FROM sdk_sessions
|
||||
WHERE id = ?
|
||||
@@ -212,11 +212,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,o=r.getTime();return this.db.prepare(`
|
||||
INSERT INTO sdk_sessions
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),i=this.db.prepare(`
|
||||
INSERT OR IGNORE INTO sdk_sessions
|
||||
(claude_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'active')
|
||||
`).run(e,s,t,r.toISOString(),o).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
`).run(e,s,t,r.toISOString(),n);return i.lastInsertRowid===0||i.changes===0?this.db.prepare(`
|
||||
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
|
||||
`).get(e).id:i.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET sdk_session_id = ?
|
||||
WHERE id = ? AND sdk_session_id IS NULL
|
||||
@@ -229,17 +231,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
FROM sdk_sessions
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let o=new Date,a=o.getTime();this.db.prepare(`
|
||||
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(`
|
||||
INSERT INTO observations
|
||||
(sdk_session_id, project, type, title, subtitle, facts, narrative, concepts,
|
||||
files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,o.toISOString(),a)}storeSummary(e,s,t,r){let o=new Date,a=o.getTime();this.db.prepare(`
|
||||
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),a)}storeSummary(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(`
|
||||
INSERT INTO session_summaries
|
||||
(sdk_session_id, project, request, investigated, learned, completed,
|
||||
next_steps, notes, prompt_number, created_at, created_at_epoch)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,o.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
|
||||
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE id = ?
|
||||
@@ -251,4 +253,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE status = 'active'
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(n,e,s){return n==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:n==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:n==="UserPromptSubmit"||n==="PostToolUse"?{continue:!0,suppressOutput:!0}:n==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function T(n,e,s={}){let t=W(n,e,s);return JSON.stringify(t)}import b from"path";import{existsSync as N}from"fs";import{spawn as $}from"child_process";var j=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${j}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let n=A(),e=b.join(n,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=b.join(n,"ecosystem.config.cjs"),t=b.join(n,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=$(t,["start",s],{detached:!0,stdio:"ignore",cwd:n});r.on("error",o=>{throw new Error(`Failed to spawn PM2: ${o.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let o=0;o<3;o++)if(await new Promise(a=>setTimeout(a,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(n){return console.error(`[claude-mem] Failed to start worker: ${n.message}`),!1}}var G=new Set(["ListMcpResourcesTool"]);async function y(n){if(!n)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=n;if(G.has(s)){console.log(T("PostToolUse",!0));return}if(!await C())throw new Error("Worker service failed to start or become healthy");let a=new _,i=a.findActiveSDKSession(e);if(!i){a.close(),console.log(T("PostToolUse",!0));return}if(!i.worker_port)throw a.close(),E.error("HOOK","No worker port for session",{sessionId:i.id}),new Error("No worker port for session - session may not be properly initialized");let d=a.getPromptCounter(i.id);a.close();let l=E.formatTool(s,t);E.dataIn("HOOK",`PostToolUse: ${l}`,{sessionId:i.id,workerPort:i.worker_port});let c=await fetch(`http://127.0.0.1:${i.worker_port}/sessions/${i.id}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:d}),signal:AbortSignal.timeout(2e3)});if(!c.ok){let m=await c.text();throw E.failure("HOOK","Failed to send observation",{sessionId:i.id,status:c.status},m),new Error(`Failed to send observation to worker: ${c.status} ${m}`)}E.debug("HOOK","Observation sent successfully",{sessionId:i.id,toolName:s}),console.log(T("PostToolUse",!0))}import{stdin as D}from"process";var R="";D.on("data",n=>R+=n);D.on("end",async()=>{let n=R.trim()?JSON.parse(R):void 0;await y(n),process.exit(0)});
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function b(o,e,s={}){let t=W(o,e,s);return JSON.stringify(t)}import R from"path";import{existsSync as N}from"fs";import{spawn as $}from"child_process";var j=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${j}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=A(),e=R.join(o,"plugin","scripts","worker-service.cjs");if(!N(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=R.join(o,"ecosystem.config.cjs"),t=R.join(o,"node_modules",".bin","pm2");if(!N(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!N(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=$(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(a=>setTimeout(a,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}var G=new Set(["ListMcpResourcesTool"]);async function D(o){if(!o)throw new Error("saveHook requires input");let{session_id:e,tool_name:s,tool_input:t,tool_output:r}=o;if(G.has(s)){console.log(b("PostToolUse",!0));return}if(!await C())throw new Error("Worker service failed to start or become healthy");let a=new T,i=a.createSDKSession(e,"",""),d=a.getPromptCounter(i);a.close();let m=E.formatTool(s,t),l=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10);E.dataIn("HOOK",`PostToolUse: ${m}`,{sessionId:i,workerPort:l});let u=await fetch(`http://127.0.0.1:${l}/sessions/${i}/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tool_name:s,tool_input:t!==void 0?JSON.stringify(t):"{}",tool_output:r!==void 0?JSON.stringify(r):"{}",prompt_number:d}),signal:AbortSignal.timeout(2e3)});if(!u.ok){let _=await u.text();throw E.failure("HOOK","Failed to send observation",{sessionId:i,status:u.status},_),new Error(`Failed to send observation to worker: ${u.status} ${_}`)}E.debug("HOOK","Observation sent successfully",{sessionId:i,toolName:s}),console.log(b("PostToolUse",!0))}import{stdin as y}from"process";var h="";y.on("data",o=>h+=o);y.on("end",async()=>{let o=h.trim()?JSON.parse(h):void 0;await D(o),process.exit(0)});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import H from"better-sqlite3";import{join as d,dirname as w,basename as J}from"path";import{homedir as I}from"os";import{existsSync as ee,mkdirSync as X}from"fs";import{fileURLToPath as M}from"url";function P(){return typeof __dirname<"u"?__dirname:w(M(import.meta.url))}var F=P(),c=process.env.CLAUDE_MEM_DATA_DIR||d(I(),".claude-mem"),_=process.env.CLAUDE_CONFIG_DIR||d(I(),".claude"),te=d(c,"archives"),re=d(c,"logs"),ne=d(c,"trash"),oe=d(c,"backups"),ie=d(c,"settings.json"),L=d(c,"claude-mem.db"),ae=d(_,"settings.json"),de=d(_,"commands"),pe=d(_,"CLAUDE.md");function A(o){X(o,{recursive:!0})}function v(){return d(F,"..","..")}var T=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(T||{}),g=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let i=new Date().toISOString().replace("T"," ").substring(0,23),a=T[e].padEnd(5),p=s.padEnd(6),E="";r?.correlationId?E=`[${r.correlationId}] `:r?.sessionId&&(E=`[session-${r.sessionId}] `);let l="";n!=null&&(this.level===0&&typeof n=="object"?l=`
|
||||
`+JSON.stringify(n,null,2):l=" "+this.formatData(n));let N="";if(r){let{sessionId:G,sdkSessionId:K,correlationId:Y,...O}=r;Object.keys(O).length>0&&(N=` {${Object.entries(O).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let h=`[${i}] [${a}] [${p}] ${E}${t}${N}${l}`;e===3?console.error(h):console.log(h)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},u=new g;var m=class{db;constructor(){A(c),this.db=new H(L),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
import H from"better-sqlite3";import{join as p,dirname as w,basename as J}from"path";import{homedir as O}from"os";import{existsSync as ee,mkdirSync as X}from"fs";import{fileURLToPath as M}from"url";function P(){return typeof __dirname<"u"?__dirname:w(M(import.meta.url))}var F=P(),c=process.env.CLAUDE_MEM_DATA_DIR||p(O(),".claude-mem"),_=process.env.CLAUDE_CONFIG_DIR||p(O(),".claude"),te=p(c,"archives"),re=p(c,"logs"),ne=p(c,"trash"),oe=p(c,"backups"),ie=p(c,"settings.json"),I=p(c,"claude-mem.db"),ae=p(_,"settings.json"),de=p(_,"commands"),pe=p(_,"CLAUDE.md");function L(o){X(o,{recursive:!0})}function A(){return p(F,"..","..")}var T=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.SILENT=4]="SILENT",n))(T||{}),g=class{level;useColor;constructor(){let e=process.env.CLAUDE_MEM_LOG_LEVEL?.toUpperCase()||"INFO";this.level=T[e]??1,this.useColor=process.stdout.isTTY??!1}correlationId(e,s){return`obs-${e}-${s}`}sessionId(e){return`session-${e}`}formatData(e){if(e==null)return"";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean")return e.toString();if(typeof e=="object"){if(e instanceof Error)return this.level===0?`${e.message}
|
||||
${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Object.keys(e);return s.length===0?"{}":s.length<=3?JSON.stringify(e):`{${s.length} keys: ${s.slice(0,3).join(", ")}...}`}return String(e)}formatTool(e,s){if(!s)return e;try{let t=typeof s=="string"?JSON.parse(s):s;if(e==="Bash"&&t.command){let r=t.command.length>50?t.command.substring(0,50)+"...":t.command;return`${e}(${r})`}if(e==="Read"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Edit"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}if(e==="Write"&&t.file_path){let r=t.file_path.split("/").pop()||t.file_path;return`${e}(${r})`}return e}catch{return e}}log(e,s,t,r,n){if(e<this.level)return;let a=new Date().toISOString().replace("T"," ").substring(0,23),i=T[e].padEnd(5),d=s.padEnd(6),m="";r?.correlationId?m=`[${r.correlationId}] `:r?.sessionId&&(m=`[session-${r.sessionId}] `);let l="";n!=null&&(this.level===0&&typeof n=="object"?l=`
|
||||
`+JSON.stringify(n,null,2):l=" "+this.formatData(n));let R="";if(r){let{sessionId:G,sdkSessionId:K,correlationId:Y,...h}=r;Object.keys(h).length>0&&(R=` {${Object.entries(h).map(([x,U])=>`${x}=${U}`).join(", ")}}`)}let N=`[${a}] [${i}] [${d}] ${m}${t}${R}${l}`;e===3?console.error(N):console.log(N)}debug(e,s,t,r){this.log(0,e,s,t,r)}info(e,s,t,r){this.log(1,e,s,t,r)}warn(e,s,t,r){this.log(2,e,s,t,r)}error(e,s,t,r){this.log(3,e,s,t,r)}dataIn(e,s,t,r){this.info(e,`\u2192 ${s}`,t,r)}dataOut(e,s,t,r){this.info(e,`\u2190 ${s}`,t,r)}success(e,s,t,r){this.info(e,`\u2713 ${s}`,t,r)}failure(e,s,t,r){this.error(e,`\u2717 ${s}`,t,r)}timing(e,s,t,r){this.info(e,`\u23F1 ${s}`,r,{duration:`${t}ms`})}},u=new g;var E=class{db;constructor(){L(c),this.db=new H(I),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),this.initializeSchema(),this.ensureWorkerPortColumn(),this.ensurePromptTrackingColumns(),this.removeSessionSummariesUniqueConstraint(),this.addObservationHierarchicalFields(),this.makeObservationsTextNullable()}initializeSchema(){try{this.db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_versions (
|
||||
id INTEGER PRIMARY KEY,
|
||||
version INTEGER UNIQUE NOT NULL,
|
||||
@@ -63,7 +63,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_sdk_session ON session_summaries(sdk_session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_project ON session_summaries(project);
|
||||
CREATE INDEX IF NOT EXISTS idx_session_summaries_created ON session_summaries(created_at_epoch DESC);
|
||||
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(p=>p.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(p=>p.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
`),this.db.prepare("INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)").run(4,new Date().toISOString()),console.error("[SessionStore] Migration004 applied successfully"))}catch(e){throw console.error("[SessionStore] Schema initialization error:",e.message),e}}ensureWorkerPortColumn(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(5))return;this.db.pragma("table_info(sdk_sessions)").some(r=>r.name==="worker_port")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN worker_port INTEGER"),console.error("[SessionStore] Added worker_port column to sdk_sessions table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(5,new Date().toISOString())}catch(e){console.error("[SessionStore] Migration error:",e.message)}}ensurePromptTrackingColumns(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(6))return;this.db.pragma("table_info(sdk_sessions)").some(d=>d.name==="prompt_counter")||(this.db.exec("ALTER TABLE sdk_sessions ADD COLUMN prompt_counter INTEGER DEFAULT 0"),console.error("[SessionStore] Added prompt_counter column to sdk_sessions table")),this.db.pragma("table_info(observations)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE observations ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to observations table")),this.db.pragma("table_info(session_summaries)").some(d=>d.name==="prompt_number")||(this.db.exec("ALTER TABLE session_summaries ADD COLUMN prompt_number INTEGER"),console.error("[SessionStore] Added prompt_number column to session_summaries table")),this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(6,new Date().toISOString())}catch(e){console.error("[SessionStore] Prompt tracking migration error:",e.message)}}removeSessionSummariesUniqueConstraint(){try{if(this.db.prepare("SELECT version FROM schema_versions WHERE version = ?").get(7))return;if(!this.db.pragma("index_list(session_summaries)").some(r=>r.unique===1)){this.db.prepare("INSERT OR IGNORE INTO schema_versions (version, applied_at) VALUES (?, ?)").run(7,new Date().toISOString());return}console.error("[SessionStore] Removing UNIQUE constraint from session_summaries.sdk_session_id..."),this.db.exec("BEGIN TRANSACTION");try{this.db.exec(`
|
||||
CREATE TABLE session_summaries_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sdk_session_id TEXT NOT NULL,
|
||||
@@ -185,7 +185,7 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT files_read, files_modified
|
||||
FROM observations
|
||||
WHERE sdk_session_id = ?
|
||||
`).all(e),r=new Set,n=new Set;for(let i of t){if(i.files_read)try{let a=JSON.parse(i.files_read);Array.isArray(a)&&a.forEach(p=>r.add(p))}catch{}if(i.files_modified)try{let a=JSON.parse(i.files_modified);Array.isArray(a)&&a.forEach(p=>n.add(p))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
|
||||
`).all(e),r=new Set,n=new Set;for(let a of t){if(a.files_read)try{let i=JSON.parse(a.files_read);Array.isArray(i)&&i.forEach(d=>r.add(d))}catch{}if(a.files_modified)try{let i=JSON.parse(a.files_modified);Array.isArray(i)&&i.forEach(d=>n.add(d))}catch{}}return{filesRead:Array.from(r),filesModified:Array.from(n)}}getSessionById(e){return this.db.prepare(`
|
||||
SELECT id, sdk_session_id, project, user_prompt
|
||||
FROM sdk_sessions
|
||||
WHERE id = ?
|
||||
@@ -212,11 +212,13 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||1}getPromptCounter(e){return this.db.prepare(`
|
||||
SELECT prompt_counter FROM sdk_sessions WHERE id = ?
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime();return this.db.prepare(`
|
||||
INSERT INTO sdk_sessions
|
||||
`).get(e)?.prompt_counter||0}createSDKSession(e,s,t){let r=new Date,n=r.getTime(),i=this.db.prepare(`
|
||||
INSERT OR IGNORE INTO sdk_sessions
|
||||
(claude_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'active')
|
||||
`).run(e,s,t,r.toISOString(),n).lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
`).run(e,s,t,r.toISOString(),n);return i.lastInsertRowid===0||i.changes===0?this.db.prepare(`
|
||||
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
|
||||
`).get(e).id:i.lastInsertRowid}updateSDKSessionId(e,s){return this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET sdk_session_id = ?
|
||||
WHERE id = ? AND sdk_session_id IS NULL
|
||||
@@ -229,17 +231,17 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
FROM sdk_sessions
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(`
|
||||
`).get(e)?.worker_port||null}storeObservation(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(`
|
||||
INSERT INTO observations
|
||||
(sdk_session_id, project, type, title, subtitle, facts, narrative, concepts,
|
||||
files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),i)}storeSummary(e,s,t,r){let n=new Date,i=n.getTime();this.db.prepare(`
|
||||
`).run(e,s,t.type,t.title,t.subtitle,JSON.stringify(t.facts),t.narrative,JSON.stringify(t.concepts),JSON.stringify(t.files_read),JSON.stringify(t.files_modified),r||null,n.toISOString(),a)}storeSummary(e,s,t,r){let n=new Date,a=n.getTime();this.db.prepare(`
|
||||
INSERT INTO session_summaries
|
||||
(sdk_session_id, project, request, investigated, learned, completed,
|
||||
next_steps, notes, prompt_number, created_at, created_at_epoch)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),i)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
|
||||
`).run(e,s,t.request,t.investigated,t.learned,t.completed,t.next_steps,t.notes,r||null,n.toISOString(),a)}markSessionCompleted(e){let s=new Date,t=s.getTime();this.db.prepare(`
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'completed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE id = ?
|
||||
@@ -251,4 +253,4 @@ ${e.stack}`:e.message;if(Array.isArray(e))return`[${e.length} items]`;let s=Obje
|
||||
UPDATE sdk_sessions
|
||||
SET status = 'failed', completed_at = ?, completed_at_epoch = ?
|
||||
WHERE status = 'active'
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function S(o,e,s={}){let t=W(o,e,s);return JSON.stringify(t)}import f from"path";import{existsSync as b}from"fs";import{spawn as j}from"child_process";var $=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${$}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=v(),e=f.join(o,"plugin","scripts","worker-service.cjs");if(!b(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=f.join(o,"ecosystem.config.cjs"),t=f.join(o,"node_modules",".bin","pm2");if(!b(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!b(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=j(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(i=>setTimeout(i,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}async function y(o){if(!o)throw new Error("summaryHook requires input");let{session_id:e}=o;if(!await C())throw new Error("Worker service failed to start or become healthy");let t=new m,r=t.findActiveSDKSession(e);if(!r){t.close(),console.log(S("Stop",!0));return}if(!r.worker_port)throw t.close(),u.error("HOOK","No worker port for session",{sessionId:r.id}),new Error("No worker port for session - session may not be properly initialized");let n=t.getPromptCounter(r.id);t.close(),u.dataIn("HOOK","Stop: Requesting summary",{sessionId:r.id,workerPort:r.worker_port,promptNumber:n});let i=await fetch(`http://127.0.0.1:${r.worker_port}/sessions/${r.id}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:n}),signal:AbortSignal.timeout(2e3)});if(!i.ok){let a=await i.text();throw u.failure("HOOK","Failed to generate summary",{sessionId:r.id,status:i.status},a),new Error(`Failed to request summary from worker: ${i.status} ${a}`)}u.debug("HOOK","Summary request sent successfully",{sessionId:r.id}),console.log(S("Stop",!0))}import{stdin as D}from"process";var R="";D.on("data",o=>R+=o);D.on("end",async()=>{let o=R.trim()?JSON.parse(R):void 0;await y(o),process.exit(0)});
|
||||
`).run(e.toISOString(),s).changes}close(){this.db.close()}};function W(o,e,s){return o==="PreCompact"?e?{continue:!0,suppressOutput:!0}:{continue:!1,stopReason:s.reason||"Pre-compact operation failed",suppressOutput:!0}:o==="SessionStart"?e&&s.context?{continue:!0,suppressOutput:!0,hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:s.context}}:{continue:!0,suppressOutput:!0}:o==="UserPromptSubmit"||o==="PostToolUse"?{continue:!0,suppressOutput:!0}:o==="Stop"?{continue:!0,suppressOutput:!0}:{continue:e,suppressOutput:!0,...s.reason&&!e?{stopReason:s.reason}:{}}}function v(o,e,s={}){let t=W(o,e,s);return JSON.stringify(t)}import S from"path";import{existsSync as f}from"fs";import{spawn as j}from"child_process";var $=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10),B=`http://127.0.0.1:${$}/health`;async function k(){try{return(await fetch(B,{signal:AbortSignal.timeout(500)})).ok}catch{return!1}}async function C(){try{if(await k())return!0;console.error("[claude-mem] Worker not responding, starting...");let o=A(),e=S.join(o,"plugin","scripts","worker-service.cjs");if(!f(e))return console.error(`[claude-mem] Worker service not found at ${e}`),!1;let s=S.join(o,"ecosystem.config.cjs"),t=S.join(o,"node_modules",".bin","pm2");if(!f(t))throw new Error(`PM2 binary not found at ${t}. This is a bundled dependency - try running: npm install`);if(!f(s))throw new Error(`PM2 ecosystem config not found at ${s}. Plugin installation may be corrupted.`);let r=j(t,["start",s],{detached:!0,stdio:"ignore",cwd:o});r.on("error",n=>{throw new Error(`Failed to spawn PM2: ${n.message}`)}),r.unref(),console.error("[claude-mem] Worker started with PM2");for(let n=0;n<3;n++)if(await new Promise(a=>setTimeout(a,500)),await k())return console.error("[claude-mem] Worker is healthy"),!0;return console.error("[claude-mem] Worker failed to become healthy after startup"),!1}catch(o){return console.error(`[claude-mem] Failed to start worker: ${o.message}`),!1}}async function y(o){if(!o)throw new Error("summaryHook requires input");let{session_id:e}=o;if(!await C())throw new Error("Worker service failed to start or become healthy");let t=new E,r=t.createSDKSession(e,"",""),n=t.getPromptCounter(r);t.close();let a=parseInt(process.env.CLAUDE_MEM_WORKER_PORT||"37777",10);u.dataIn("HOOK","Stop: Requesting summary",{sessionId:r,workerPort:a,promptNumber:n});let i=await fetch(`http://127.0.0.1:${a}/sessions/${r}/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt_number:n}),signal:AbortSignal.timeout(2e3)});if(!i.ok){let d=await i.text();throw u.failure("HOOK","Failed to generate summary",{sessionId:r,status:i.status},d),new Error(`Failed to request summary from worker: ${i.status} ${d}`)}u.debug("HOOK","Summary request sent successfully",{sessionId:r}),console.log(v("Stop",!0))}import{stdin as D}from"process";var b="";D.on("data",o=>b+=o);D.on("end",async()=>{let o=b.trim()?JSON.parse(b):void 0;await y(o),process.exit(0)});
|
||||
|
||||
File diff suppressed because one or more lines are too long
+14
-43
@@ -31,54 +31,25 @@ export async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
const db = new SessionStore();
|
||||
|
||||
try {
|
||||
|
||||
// Check for any existing session (active, failed, or completed)
|
||||
let existing = db.findActiveSDKSession(session_id);
|
||||
let sessionDbId: number;
|
||||
let isNewSession = false;
|
||||
|
||||
if (existing) {
|
||||
// Session already active, increment prompt counter
|
||||
sessionDbId = existing.id;
|
||||
const promptNumber = db.incrementPromptCounter(sessionDbId);
|
||||
console.error(`[new-hook] Continuing session ${sessionDbId}, prompt #${promptNumber}`);
|
||||
} else {
|
||||
// Check for inactive sessions we can reuse
|
||||
const inactive = db.findAnySDKSession(session_id);
|
||||
|
||||
if (inactive) {
|
||||
// Reactivate the existing session
|
||||
sessionDbId = inactive.id;
|
||||
db.reactivateSession(sessionDbId, prompt);
|
||||
const promptNumber = db.incrementPromptCounter(sessionDbId);
|
||||
isNewSession = true;
|
||||
console.error(`[new-hook] Reactivated session ${sessionDbId}, prompt #${promptNumber}`);
|
||||
} else {
|
||||
// Create new session
|
||||
sessionDbId = db.createSDKSession(session_id, project, prompt);
|
||||
const promptNumber = db.incrementPromptCounter(sessionDbId);
|
||||
isNewSession = true;
|
||||
console.error(`[new-hook] Created new session ${sessionDbId}, prompt #${promptNumber}`);
|
||||
}
|
||||
}
|
||||
// Just save session_id for indexing - no validation, no state management
|
||||
const sessionDbId = db.createSDKSession(session_id, project, prompt);
|
||||
const promptNumber = db.incrementPromptCounter(sessionDbId);
|
||||
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber}`);
|
||||
|
||||
// Get fixed port
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Only initialize worker on new sessions
|
||||
if (isNewSession) {
|
||||
// Initialize session via HTTP
|
||||
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ project, userPrompt: prompt }),
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
// Initialize session via HTTP
|
||||
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ project, userPrompt: prompt }),
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to initialize session: ${response.status} ${errorText}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to initialize session: ${response.status} ${errorText}`);
|
||||
}
|
||||
|
||||
console.log(createHookResponse('UserPromptSubmit', true));
|
||||
|
||||
+11
-20
@@ -40,32 +40,23 @@ export async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
}
|
||||
|
||||
const db = new SessionStore();
|
||||
const session = db.findActiveSDKSession(session_id);
|
||||
|
||||
if (!session) {
|
||||
db.close();
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.worker_port) {
|
||||
db.close();
|
||||
logger.error('HOOK', 'No worker port for session', { sessionId: session.id });
|
||||
throw new Error('No worker port for session - session may not be properly initialized');
|
||||
}
|
||||
|
||||
// Get current prompt number for this session
|
||||
const promptNumber = db.getPromptCounter(session.id);
|
||||
// Get or create session - no validation, just use the session_id from hook
|
||||
const sessionDbId = db.createSDKSession(session_id, '', ''); // project and prompt not needed for observations
|
||||
const promptNumber = db.getPromptCounter(sessionDbId);
|
||||
db.close();
|
||||
|
||||
const toolStr = logger.formatTool(tool_name, tool_input);
|
||||
|
||||
// Use fixed worker port - no session.worker_port validation needed
|
||||
const FIXED_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || '37777', 10);
|
||||
|
||||
logger.dataIn('HOOK', `PostToolUse: ${toolStr}`, {
|
||||
sessionId: session.id,
|
||||
workerPort: session.worker_port
|
||||
sessionId: sessionDbId,
|
||||
workerPort: FIXED_PORT
|
||||
});
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${session.worker_port}/sessions/${session.id}/observations`, {
|
||||
const response = await fetch(`http://127.0.0.1:${FIXED_PORT}/sessions/${sessionDbId}/observations`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -80,12 +71,12 @@ export async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
logger.failure('HOOK', 'Failed to send observation', {
|
||||
sessionId: session.id,
|
||||
sessionId: sessionDbId,
|
||||
status: response.status
|
||||
}, errorText);
|
||||
throw new Error(`Failed to send observation to worker: ${response.status} ${errorText}`);
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Observation sent successfully', { sessionId: session.id, toolName: tool_name });
|
||||
logger.debug('HOOK', 'Observation sent successfully', { sessionId: sessionDbId, toolName: tool_name });
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
}
|
||||
|
||||
+11
-20
@@ -27,31 +27,22 @@ export async function summaryHook(input?: StopInput): Promise<void> {
|
||||
}
|
||||
|
||||
const db = new SessionStore();
|
||||
const session = db.findActiveSDKSession(session_id);
|
||||
|
||||
if (!session) {
|
||||
db.close();
|
||||
console.log(createHookResponse('Stop', true));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session.worker_port) {
|
||||
db.close();
|
||||
logger.error('HOOK', 'No worker port for session', { sessionId: session.id });
|
||||
throw new Error('No worker port for session - session may not be properly initialized');
|
||||
}
|
||||
|
||||
// Get current prompt number
|
||||
const promptNumber = db.getPromptCounter(session.id);
|
||||
// Get or create session - no validation, just use the session_id from hook
|
||||
const sessionDbId = db.createSDKSession(session_id, '', '');
|
||||
const promptNumber = db.getPromptCounter(sessionDbId);
|
||||
db.close();
|
||||
|
||||
// Use fixed worker port - no session.worker_port validation needed
|
||||
const FIXED_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || '37777', 10);
|
||||
|
||||
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
||||
sessionId: session.id,
|
||||
workerPort: session.worker_port,
|
||||
sessionId: sessionDbId,
|
||||
workerPort: FIXED_PORT,
|
||||
promptNumber
|
||||
});
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${session.worker_port}/sessions/${session.id}/summarize`, {
|
||||
const response = await fetch(`http://127.0.0.1:${FIXED_PORT}/sessions/${sessionDbId}/summarize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prompt_number: promptNumber }),
|
||||
@@ -61,12 +52,12 @@ export async function summaryHook(input?: StopInput): Promise<void> {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
logger.failure('HOOK', 'Failed to generate summary', {
|
||||
sessionId: session.id,
|
||||
sessionId: sessionDbId,
|
||||
status: response.status
|
||||
}, errorText);
|
||||
throw new Error(`Failed to request summary from worker: ${response.status} ${errorText}`);
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Summary request sent successfully', { sessionId: session.id });
|
||||
logger.debug('HOOK', 'Summary request sent successfully', { sessionId: sessionDbId });
|
||||
console.log(createHookResponse('Stop', true));
|
||||
}
|
||||
|
||||
@@ -706,19 +706,30 @@ export class SessionStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SDK session
|
||||
* Create a new SDK session (idempotent - returns existing session ID if already exists)
|
||||
*/
|
||||
createSDKSession(claudeSessionId: string, project: string, userPrompt: string): number {
|
||||
const now = new Date();
|
||||
const nowEpoch = now.getTime();
|
||||
|
||||
// Try to insert - will be ignored if session already exists
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO sdk_sessions
|
||||
INSERT OR IGNORE INTO sdk_sessions
|
||||
(claude_session_id, project, user_prompt, started_at, started_at_epoch, status)
|
||||
VALUES (?, ?, ?, ?, ?, 'active')
|
||||
`);
|
||||
|
||||
const result = stmt.run(claudeSessionId, project, userPrompt, now.toISOString(), nowEpoch);
|
||||
|
||||
// If lastInsertRowid is 0, insert was ignored (session exists), so fetch existing ID
|
||||
if (result.lastInsertRowid === 0 || result.changes === 0) {
|
||||
const selectStmt = this.db.prepare(`
|
||||
SELECT id FROM sdk_sessions WHERE claude_session_id = ? LIMIT 1
|
||||
`);
|
||||
const existing = selectStmt.get(claudeSessionId) as { id: number } | undefined;
|
||||
return existing!.id;
|
||||
}
|
||||
|
||||
return result.lastInsertRowid as number;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,11 +118,6 @@ class WorkerService {
|
||||
const correlationId = logger.sessionId(sessionDbId);
|
||||
logger.info('WORKER', 'Session init', { correlationId, project });
|
||||
|
||||
if (this.sessions.has(sessionDbId)) {
|
||||
res.status(409).json({ error: 'Session already exists' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Create session state
|
||||
const session: ActiveSession = {
|
||||
sessionDbId,
|
||||
|
||||
Reference in New Issue
Block a user