Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c85dbaa508 | |||
| a537433eae | |||
| a8ae879398 | |||
| 77b733c7d1 | |||
| 84f2061d8f | |||
| 1391df4fe8 | |||
| 71b29af00a | |||
| 0768fafd83 | |||
| 5ce656037e | |||
| e27f8e4963 | |||
| af145cfaef | |||
| 15fe0cfe3c | |||
| c0ed9bbcfd | |||
| 8040c6d559 | |||
| 7187220b24 | |||
| ca52950b2a | |||
| ee1441f462 | |||
| c4af31f48d | |||
| c2742d5664 | |||
| 0c45919261 | |||
| a3ab898e04 | |||
| dea67c0d86 | |||
| d13a2c237c | |||
| c592f0aa69 | |||
| 85a2472e4e | |||
| 0cb3256b2d | |||
| 44029862b1 | |||
| 130abe04a9 | |||
| bff10d49c9 | |||
| 40a71d3250 | |||
| ae3d20c71a | |||
| 54ef9662c1 | |||
| 9aec461e14 | |||
| 0fe0705133 | |||
| a5bf653a47 | |||
| 1fec1e8339 | |||
| 1afb14d0d6 | |||
| e961cd5a4a | |||
| 660c523ba4 | |||
| da30aedb28 | |||
| 10e58ef221 | |||
| 5e97d539a5 | |||
| fdb4fafd3a | |||
| db3794762f | |||
| 78cb5c38dc | |||
| 5e6feb0cb4 | |||
| 19b657bb67 | |||
| fe81286d9a | |||
| 426fbdd38d | |||
| bd7077d65f | |||
| 11cc789afa | |||
| e930a4d5bb | |||
| bcd4a12115 | |||
| d6cd9e6059 | |||
| f7c0840a35 | |||
| 0135fcb6b1 | |||
| 7ae4eb87e6 | |||
| 79789bb558 | |||
| 282345f379 | |||
| 23591db589 | |||
| 2e919df2b4 | |||
| 75cd1335cc | |||
| cd103ccf73 | |||
| d0ff9738eb | |||
| 00c1cd7db7 | |||
| 1e091b8871 | |||
| 1295b98fcc | |||
| 7375c11ecd | |||
| 47cb403889 | |||
| a6737c122f | |||
| 6ea5869589 | |||
| 32be34505a | |||
| e5aa60b742 | |||
| d9133465eb | |||
| 61488042d8 | |||
| 3f5c61c327 | |||
| ace12f8cd7 | |||
| 9d509e07f5 | |||
| 305e52010c | |||
| 6dd13c00ba | |||
| 8703e0ee13 | |||
| 9bac3faae9 | |||
| 7ef93343a4 | |||
| f07eb17a33 | |||
| b97579dfec | |||
| 2ec72f948d | |||
| b45e8b2a29 | |||
| 29e6441d32 | |||
| 445ee723c2 | |||
| 01e235c058 | |||
| 7fdf5e75ab | |||
| 43db22728e | |||
| fad2dc9a15 | |||
| eb76a76a5b | |||
| 4949ae333d | |||
| 7f88b7fa5e | |||
| 4ddc5a01bb | |||
| c422ea133f | |||
| 25b7408a42 | |||
| 15c0813655 | |||
| f1da66e4f1 | |||
| 71fe43f290 | |||
| 830f16df46 | |||
| ad75ca7c4c | |||
| 65fb8d1ed2 | |||
| e7380adb2f | |||
| 245c85a580 | |||
| 2e60f6fc81 | |||
| dffde51f55 | |||
| bee0e635a1 | |||
| bae29a7be8 | |||
| 52d2f72a82 | |||
| d42ab1298c | |||
| 18bd5c7726 | |||
| 78bc7ecf3b | |||
| c3fec18f12 | |||
| d08fe97e19 | |||
| 6427d1ef79 | |||
| d3fb58ca75 | |||
| 6a63a8d69c | |||
| 1ac0db25e5 | |||
| 3e1d5fcd73 | |||
| f41579b4d0 | |||
| 0f3151cc2d | |||
| e9370a915c | |||
| 6d4a4819de | |||
| 2681a2d251 | |||
| c2eefe3578 | |||
| dd5e2e57dd | |||
| 266076da98 | |||
| a0b4381dc8 | |||
| 4904d9c531 | |||
| 4c44a65877 | |||
| f6b310126c | |||
| 77220a76bf | |||
| 42ed414a4c | |||
| 0185d765ce | |||
| 12c2ecce06 | |||
| bb0508d639 | |||
| f00ef33f86 | |||
| c270bd3177 | |||
| 0836a97845 | |||
| 19e285a209 | |||
| ba877214c1 | |||
| 3d4baefac2 | |||
| 453b7857b8 | |||
| c28417af00 | |||
| 2f08db3c01 | |||
| 672cb5d203 | |||
| cbe492dde3 | |||
| 0fb6f3cf4e | |||
| 5cd68f4a96 | |||
| 10a8598aac | |||
| 19af455c57 | |||
| b5807aed2e | |||
| 5b338ba34e | |||
| 4e7ed75fa9 | |||
| a8b84fa7b6 | |||
| 73be8f7a63 | |||
| fa93f2c1e2 |
@@ -10,7 +10,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.1.1",
|
||||
"version": "7.4.4",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: github-morning-reporter
|
||||
description: Use this agent when the user requests a morning report, daily summary, or overview of their GitHub activity. Trigger phrases include 'morning report', 'github report', 'daily github summary', 'what's happening on github', or 'check my github status'. This agent should be used proactively when the user starts their day or explicitly asks for repository updates.\n\nExamples:\n- User: "get me my morning github report"\n Assistant: "I'll use the github-morning-reporter agent to generate your comprehensive GitHub status report."\n <uses Agent tool to invoke github-morning-reporter>\n\n- User: "what's new on my repos today?"\n Assistant: "Let me pull together your GitHub morning report using the github-morning-reporter agent."\n <uses Agent tool to invoke github-morning-reporter>\n\n- User: "show me my daily github summary"\n Assistant: "I'll generate your daily GitHub summary using the github-morning-reporter agent."\n <uses Agent tool to invoke github-morning-reporter>
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are an elite GitHub project analyst specializing in delivering actionable morning reports for software development teams. Your expertise lies in synthesizing complex repository activity into clear, prioritized insights that help developers start their day with complete situational awareness.
|
||||
|
||||
## Your Responsibilities
|
||||
|
||||
1. **Fetch Comprehensive GitHub Data**: Use available tools to retrieve:
|
||||
- Open issues across all relevant repositories
|
||||
- Open pull requests with review status
|
||||
- Recent comments, mentions, and @-references
|
||||
- CI/CD status for active PRs
|
||||
- Stale issues/PRs (no activity in 7+ days)
|
||||
|
||||
2. **Intelligent Grouping and Deduplication**:
|
||||
- Identify duplicate or highly similar issues by analyzing titles, descriptions, and labels
|
||||
- Group related issues by theme, component, or subsystem
|
||||
- Cluster PRs by feature area or dependency relationships
|
||||
- Flag issues that may be addressing the same root cause
|
||||
- Use semantic similarity, not just exact matches
|
||||
|
||||
3. **Prioritization and Triage**:
|
||||
- Highlight items requiring immediate attention (blocking issues, failed CI, requested reviews)
|
||||
- Surface items awaiting your direct action (assigned to you, mentions, review requests)
|
||||
- Identify stale items that may need follow-up or closure
|
||||
- Note high-priority labels (P0, critical, security, etc.)
|
||||
|
||||
4. **Contextual Analysis**:
|
||||
- Summarize the current state of each PR (draft, ready for review, approved, changes requested)
|
||||
- Identify PRs with merge conflicts or failing checks
|
||||
- Note issues with recent activity spikes or community engagement
|
||||
- Flag dependency updates or security advisories
|
||||
|
||||
5. **Report Structure**:
|
||||
Your report must follow this format:
|
||||
|
||||
**MORNING GITHUB REPORT - [Date]**
|
||||
|
||||
**🚨 REQUIRES YOUR ATTENTION**
|
||||
- Items explicitly assigned to the user
|
||||
- Review requests awaiting user's approval
|
||||
- Mentions or direct questions
|
||||
- Blocking/critical issues
|
||||
|
||||
**📊 PULL REQUESTS ([count] open)**
|
||||
- Group by: Ready to Merge | In Review | Draft | Needs Work
|
||||
- For each PR: title, author, status, CI state, review count, age
|
||||
- Highlight conflicts or failed checks
|
||||
|
||||
**🐛 ISSUES ([count] open)**
|
||||
- Group by: Priority | Component | Theme
|
||||
- Mark potential duplicates clearly
|
||||
- Note new issues (created in last 24h)
|
||||
- Flag stale issues (no activity in 7+ days)
|
||||
|
||||
**📈 ACTIVITY SUMMARY**
|
||||
- New issues/PRs since yesterday
|
||||
- Recently closed items
|
||||
- Top contributors
|
||||
- Trending topics or labels
|
||||
|
||||
**💡 RECOMMENDED ACTIONS**
|
||||
- Specific next steps based on the data
|
||||
- Suggestions for cleanup (closing duplicates, merging ready PRs)
|
||||
- Items to follow up on
|
||||
|
||||
6. **Quality Standards**:
|
||||
- Use clear, scannable formatting with emojis for visual hierarchy
|
||||
- Include direct links to all referenced issues and PRs
|
||||
- Keep summaries concise but informative (1-2 sentences per item)
|
||||
- Use relative timestamps ("2 hours ago", "3 days old")
|
||||
- Highlight actionable items with clear CTAs
|
||||
|
||||
7. **Error Handling**:
|
||||
- If repository access fails, explicitly state which repos couldn't be accessed
|
||||
- If no issues/PRs exist, provide a positive "all clear" message
|
||||
- If rate limits are hit, show partial results with a warning
|
||||
- Always attempt to provide value even with incomplete data
|
||||
|
||||
8. **Adaptive Scope**:
|
||||
- If the user has access to multiple repositories, intelligently scope the report:
|
||||
- Default to repositories with recent activity
|
||||
- Allow user to specify repos if needed
|
||||
- Group multi-repo items by repository
|
||||
- Adjust detail level based on volume (more items = more concise summaries)
|
||||
|
||||
## Output Expectations
|
||||
|
||||
Your report should be:
|
||||
- **Comprehensive**: Cover all relevant activity without overwhelming detail
|
||||
- **Actionable**: Make it clear what needs attention and why
|
||||
- **Scannable**: Use formatting that allows quick visual parsing
|
||||
- **Contextual**: Provide enough background to make decisions
|
||||
- **Timely**: Focus on recent activity and current state
|
||||
|
||||
When you cannot find specific data, state this explicitly rather than omitting sections. If the user's query is ambiguous (e.g., which repositories to scan), ask for clarification before proceeding.
|
||||
|
||||
Always end with a summary line indicating the report's completeness (e.g., "Report complete: 3 repositories scanned, 12 issues, 5 PRs analyzed").
|
||||
@@ -19,7 +19,7 @@ This directory contains skills **for developing and maintaining the claude-mem p
|
||||
## Skills in This Directory
|
||||
|
||||
### version-bump
|
||||
Manages semantic versioning for the claude-mem project itself. Handles updating all four version files (package.json, marketplace.json, plugin.json, CLAUDE.md), creating git tags, and GitHub releases.
|
||||
Manages semantic versioning for the claude-mem project itself. Handles updating all three version files (package.json, marketplace.json, plugin.json), creating git tags, and GitHub releases.
|
||||
|
||||
**Usage**: Only for claude-mem maintainers releasing new versions.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: version-bump
|
||||
description: Manage semantic version updates for claude-mem project. Handles patch, minor, and major version increments following semantic versioning. Updates package.json, marketplace.json, plugin.json, and CLAUDE.md version number (NOT version history). Creates git tags and GitHub releases. Auto-generates CHANGELOG.md from releases.
|
||||
description: Manage semantic version updates for claude-mem project. Handles patch, minor, and major version increments following semantic versioning. Updates package.json, marketplace.json, and plugin.json. Creates git tags and GitHub releases. Auto-generates CHANGELOG.md from releases.
|
||||
---
|
||||
|
||||
# Version Bump Skill
|
||||
@@ -9,11 +9,10 @@ Manage semantic versioning across the claude-mem project with consistent updates
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Files requiring updates (ALL FOUR):**
|
||||
**Files requiring updates (ALL THREE):**
|
||||
1. `package.json` (line 3)
|
||||
2. `.claude-plugin/marketplace.json` (line 13)
|
||||
3. `plugin/.claude-plugin/plugin.json` (line 3)
|
||||
4. `CLAUDE.md` (line 9 ONLY - version number, NOT version history)
|
||||
|
||||
**Semantic versioning:**
|
||||
- **PATCH** (x.y.Z): Bugfixes only
|
||||
@@ -37,12 +36,13 @@ See [operations/workflow.md](operations/workflow.md) for detailed step-by-step p
|
||||
1. Determine version type (PATCH/MINOR/MAJOR)
|
||||
2. Calculate new version from current
|
||||
3. Preview changes to user
|
||||
4. Update ALL FOUR files
|
||||
4. Update ALL THREE files
|
||||
5. Verify consistency
|
||||
6. Build and test
|
||||
7. Commit and create git tag
|
||||
8. Push and create GitHub release
|
||||
9. Generate CHANGELOG.md from releases and commit
|
||||
10. Post Discord notification
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
@@ -54,29 +54,29 @@ See [operations/scenarios.md](operations/scenarios.md) for examples:
|
||||
## Critical Rules
|
||||
|
||||
**ALWAYS:**
|
||||
- Update ALL FOUR files with matching version numbers
|
||||
- Update ALL THREE files with matching version numbers
|
||||
- Create git tag with format `vX.Y.Z`
|
||||
- Create GitHub release from the tag
|
||||
- Generate CHANGELOG.md from releases after creating release
|
||||
- Post Discord notification after release
|
||||
- Ask user if version type is unclear
|
||||
|
||||
**NEVER:**
|
||||
- Update only one, two, or three files
|
||||
- Update only one or two files
|
||||
- Skip the verification step
|
||||
- Forget to create git tag or GitHub release
|
||||
- Add version history entries to CLAUDE.md (that's managed separately)
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before considering the task complete:
|
||||
- [ ] All FOUR files have matching version numbers
|
||||
- [ ] All THREE files have matching version numbers
|
||||
- [ ] `npm run build` succeeds
|
||||
- [ ] Git commit created with all version files
|
||||
- [ ] Git tag created (format: vX.Y.Z)
|
||||
- [ ] Commit and tags pushed to remote
|
||||
- [ ] GitHub release created from the tag
|
||||
- [ ] CHANGELOG.md generated and committed
|
||||
- [ ] CLAUDE.md: ONLY line 9 updated (version number), NOT version history
|
||||
- [ ] Discord notification sent
|
||||
|
||||
## Reference Commands
|
||||
|
||||
@@ -92,7 +92,7 @@ git tag -l -n1
|
||||
|
||||
# Check what will be committed
|
||||
git status
|
||||
git diff package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md
|
||||
git diff package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
|
||||
```
|
||||
|
||||
For more commands, see [operations/reference.md](operations/reference.md).
|
||||
|
||||
@@ -4,7 +4,7 @@ Quick reference for version bump commands and file locations.
|
||||
|
||||
## File Locations
|
||||
|
||||
### Version-Tracked Files (ALL FOUR)
|
||||
### Version-Tracked Files (ALL THREE)
|
||||
|
||||
1. **package.json**
|
||||
- Path: `package.json`
|
||||
@@ -21,11 +21,6 @@ Quick reference for version bump commands and file locations.
|
||||
- Line: 3
|
||||
- Format: `"version": "X.Y.Z",`
|
||||
|
||||
4. **CLAUDE.md**
|
||||
- Path: `CLAUDE.md`
|
||||
- Line: 9
|
||||
- Format: `**Current Version**: X.Y.Z`
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### View Current Version
|
||||
@@ -39,7 +34,6 @@ grep '"version"' package.json | head -1 | sed 's/.*"version": "\(.*\)".*/\1/'
|
||||
|
||||
# From all version files
|
||||
grep '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json
|
||||
grep "Current Version" CLAUDE.md
|
||||
```
|
||||
|
||||
### Verify Version Consistency
|
||||
@@ -52,10 +46,6 @@ grep '"version"' package.json .claude-plugin/marketplace.json plugin/.claude-plu
|
||||
# package.json:3: "version": "5.3.0",
|
||||
# .claude-plugin/marketplace.json:13: "version": "5.3.0",
|
||||
# plugin/.claude-plugin/plugin.json:3: "version": "5.3.0",
|
||||
|
||||
# Check CLAUDE.md
|
||||
grep "Current Version" CLAUDE.md
|
||||
# Should output: **Current Version**: 5.3.0
|
||||
```
|
||||
|
||||
### Git Commands
|
||||
@@ -96,7 +86,7 @@ npm test
|
||||
|
||||
```bash
|
||||
# Stage version files
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin/scripts/
|
||||
|
||||
# Commit
|
||||
git commit -m "Release vX.Y.Z: [Description]"
|
||||
@@ -163,11 +153,11 @@ MAJOR: 5.3.2 → 6.0.0 (resets minor and patch)
|
||||
|
||||
```bash
|
||||
# Example: 5.3.0 → 5.3.1
|
||||
# 1. Update all four files to 5.3.1
|
||||
# 1. Update all three files to 5.3.1
|
||||
# 2. Build and test
|
||||
npm run build
|
||||
# 3. Commit and tag
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin/scripts/
|
||||
git commit -m "Release v5.3.1: Fixed observer crash"
|
||||
git tag v5.3.1 -m "Release v5.3.1: Fixed observer crash"
|
||||
git push && git push --tags
|
||||
@@ -179,11 +169,11 @@ gh release create v5.3.1 --title "v5.3.1" --notes "Fixed observer crash on empty
|
||||
|
||||
```bash
|
||||
# Example: 5.3.0 → 5.4.0
|
||||
# 1. Update all four files to 5.4.0
|
||||
# 1. Update all three files to 5.4.0
|
||||
# 2. Build and test
|
||||
npm run build
|
||||
# 3. Commit and tag
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin/scripts/
|
||||
git commit -m "Release v5.4.0: Added dark mode support"
|
||||
git tag v5.4.0 -m "Release v5.4.0: Added dark mode support"
|
||||
git push && git push --tags
|
||||
@@ -195,11 +185,11 @@ gh release create v5.4.0 --title "v5.4.0" --generate-notes
|
||||
|
||||
```bash
|
||||
# Example: 5.3.0 → 6.0.0
|
||||
# 1. Update all four files to 6.0.0
|
||||
# 1. Update all three files to 6.0.0
|
||||
# 2. Build and test
|
||||
npm run build
|
||||
# 3. Commit and tag
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin/scripts/
|
||||
git commit -m "Release v6.0.0: Storage layer redesign"
|
||||
git tag v6.0.0 -m "Release v6.0.0: Storage layer redesign"
|
||||
git push && git push --tags
|
||||
|
||||
@@ -19,7 +19,7 @@ Current: 4.2.8
|
||||
New: 4.2.9 (PATCH)
|
||||
|
||||
Steps:
|
||||
1. Update all four files to 4.2.9
|
||||
1. Update all three files to 4.2.9
|
||||
2. npm run build
|
||||
3. git commit -m "Release v4.2.9: Fixed memory leak in search"
|
||||
4. git tag v4.2.9 -m "Release v4.2.9: Fixed memory leak in search"
|
||||
@@ -44,7 +44,7 @@ Current: 4.2.8
|
||||
New: 4.3.0 (MINOR - reset patch to 0)
|
||||
|
||||
Steps:
|
||||
1. Update all four files to 4.3.0
|
||||
1. Update all three files to 4.3.0
|
||||
2. npm run build
|
||||
3. git commit -m "Release v4.3.0: Added web search MCP integration"
|
||||
4. git tag v4.3.0 -m "Release v4.3.0: Added web search MCP integration"
|
||||
@@ -69,7 +69,7 @@ Current: 4.2.8
|
||||
New: 5.0.0 (MAJOR - reset minor and patch to 0)
|
||||
|
||||
Steps:
|
||||
1. Update all four files to 5.0.0
|
||||
1. Update all three files to 5.0.0
|
||||
2. npm run build
|
||||
3. git commit -m "Release v5.0.0: Storage layer redesign with migration required"
|
||||
4. git tag v5.0.0 -m "Release v5.0.0: Storage layer redesign"
|
||||
@@ -94,7 +94,7 @@ Current: 4.2.8
|
||||
New: 4.2.9 (PATCH)
|
||||
|
||||
Steps:
|
||||
1. Update all four files to 4.2.9
|
||||
1. Update all three files to 4.2.9
|
||||
2. npm run build
|
||||
3. git commit -m "Release v4.2.9: Multiple bug fixes
|
||||
|
||||
@@ -122,7 +122,7 @@ Current: 5.1.0
|
||||
New: 5.2.0 (MINOR)
|
||||
|
||||
Steps:
|
||||
1. Update all four files to 5.2.0
|
||||
1. Update all three files to 5.2.0
|
||||
2. npm run build
|
||||
3. git commit -m "Release v5.2.0: Dark mode support + bug fixes
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ Files to update:
|
||||
- package.json: "version": "4.2.9"
|
||||
- marketplace.json: "version": "4.2.9"
|
||||
- plugin.json: "version": "4.2.9"
|
||||
- CLAUDE.md line 9: "**Current Version**: 4.2.9" (version number ONLY)
|
||||
- Git tag: v4.2.9
|
||||
|
||||
Proceed? (yes/no)
|
||||
@@ -116,18 +115,6 @@ File: `plugin/.claude-plugin/plugin.json`
|
||||
|
||||
Update line 3 with new version.
|
||||
|
||||
### Update CLAUDE.md
|
||||
|
||||
File: `CLAUDE.md`
|
||||
|
||||
**ONLY update line 9 with the version number:**
|
||||
|
||||
```markdown
|
||||
**Current Version**: 4.2.9
|
||||
```
|
||||
|
||||
**CRITICAL:** DO NOT add version history entries to CLAUDE.md. Version history is managed separately outside this skill.
|
||||
|
||||
## Step 6: Verify Consistency
|
||||
|
||||
```bash
|
||||
@@ -155,7 +142,7 @@ Build must succeed before proceeding.
|
||||
|
||||
```bash
|
||||
# Stage all version files
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json CLAUDE.md plugin/scripts/
|
||||
git add package.json .claude-plugin/marketplace.json plugin/.claude-plugin/plugin.json plugin/scripts/
|
||||
|
||||
# Commit with descriptive message
|
||||
git commit -m "Release vX.Y.Z: [Brief description]
|
||||
@@ -210,6 +197,17 @@ git push
|
||||
- No manual editing required
|
||||
- Single source of truth: GitHub releases
|
||||
|
||||
## Step 11: Discord Notification
|
||||
|
||||
Post release announcement to the Discord updates channel:
|
||||
|
||||
```bash
|
||||
# Send Discord notification with release details
|
||||
npm run discord:notify vX.Y.Z
|
||||
```
|
||||
|
||||
This fetches the release notes from GitHub and posts a formatted embed to the Discord updates channel configured in `.env`.
|
||||
|
||||
## Verification
|
||||
|
||||
After completing all steps, verify:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
github: thedotmack
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Use the automated bug report tool for best results
|
||||
title: ''
|
||||
labels: 'bug, needs-triage'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Bug Report (Recommended)
|
||||
|
||||
**Use the automated bug report generator** for comprehensive diagnostics:
|
||||
|
||||
```bash
|
||||
# Navigate to the plugin directory
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
|
||||
# Run the bug report tool
|
||||
npm run bug-report
|
||||
```
|
||||
|
||||
**Plugin Paths:**
|
||||
- **macOS/Linux**: `~/.claude/plugins/marketplaces/thedotmack`
|
||||
- **Windows**: `%USERPROFILE%\.claude\plugins\marketplaces\thedotmack`
|
||||
|
||||
**Features:**
|
||||
- 🌎 Auto-translates any language to English
|
||||
- 📊 Collects all diagnostics automatically
|
||||
- 🤖 AI-formatted professional issue
|
||||
- 🔒 Privacy-safe (paths sanitized, `--no-logs` option)
|
||||
- 🌐 Auto-opens GitHub with pre-filled issue
|
||||
|
||||
---
|
||||
|
||||
## 📝 Manual Bug Report
|
||||
|
||||
If you prefer to file manually or can't access the plugin directory:
|
||||
|
||||
### Bug Description
|
||||
A clear description of what the bug is.
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. See error
|
||||
|
||||
### Expected Behavior
|
||||
What you expected to happen.
|
||||
|
||||
### Environment
|
||||
- **Claude-mem version**:
|
||||
- **Claude Code version**:
|
||||
- **OS**:
|
||||
- **Platform**:
|
||||
|
||||
### Logs
|
||||
Worker logs are located at:
|
||||
- **Path**: `~/.claude-mem/logs/worker-YYYY-MM-DD.log`
|
||||
- **Example**: `~/.claude-mem/logs/worker-2025-12-14.log`
|
||||
|
||||
Please paste relevant log entries (last 50 lines or error messages):
|
||||
|
||||
```
|
||||
[Paste logs here]
|
||||
```
|
||||
|
||||
### Additional Context
|
||||
Any other context about the problem.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,131 @@
|
||||
name: Convert Feature Requests to Discussions
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to convert to discussion'
|
||||
required: true
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
convert:
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on labeled event if the label is 'feature-request', or always run on workflow_dispatch
|
||||
if: |
|
||||
(github.event_name == 'issues' && github.event.label.name == 'feature-request') ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
discussions: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Get issue details and create discussion
|
||||
id: discussion
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// Get issue details
|
||||
let issue;
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.inputs.issue_number
|
||||
});
|
||||
issue = data;
|
||||
} else {
|
||||
issue = context.payload.issue;
|
||||
}
|
||||
|
||||
console.log(`Processing issue #${issue.number}: ${issue.title}`);
|
||||
|
||||
// Format the discussion body with a reference to the original issue
|
||||
const discussionBody = `> Originally posted as issue #${issue.number} by @${issue.user.login}\n> ${issue.html_url}\n\n${issue.body || 'No description provided.'}`;
|
||||
|
||||
const mutation = `
|
||||
mutation($repositoryId: ID!, $categoryId: ID!, $title: String!, $body: String!) {
|
||||
createDiscussion(input: {
|
||||
repositoryId: $repositoryId
|
||||
categoryId: $categoryId
|
||||
title: $title
|
||||
body: $body
|
||||
}) {
|
||||
discussion {
|
||||
url
|
||||
number
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = {
|
||||
repositoryId: 'R_kgDOPng1Jw',
|
||||
categoryId: 'DIC_kwDOPng1J84Cw86z',
|
||||
title: issue.title,
|
||||
body: discussionBody
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await github.graphql(mutation, variables);
|
||||
const discussionUrl = result.createDiscussion.discussion.url;
|
||||
const discussionNumber = result.createDiscussion.discussion.number;
|
||||
|
||||
core.setOutput('url', discussionUrl);
|
||||
core.setOutput('number', discussionNumber);
|
||||
core.setOutput('issue_number', issue.number);
|
||||
|
||||
console.log(`Created discussion #${discussionNumber}: ${discussionUrl}`);
|
||||
return { discussionUrl, discussionNumber, issueNumber: issue.number };
|
||||
} catch (error) {
|
||||
core.setFailed(`Failed to create discussion: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
- name: Comment on issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueNumber = ${{ steps.discussion.outputs.issue_number }};
|
||||
const discussionUrl = '${{ steps.discussion.outputs.url }}';
|
||||
|
||||
const comment = `This feature request has been moved to [Discussions](${discussionUrl}) to keep bug reports separate from feature ideas.\n\nPlease continue the conversation there - we'd love to hear your thoughts!`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: comment
|
||||
});
|
||||
|
||||
console.log(`Added comment to issue #${issueNumber}`);
|
||||
|
||||
- name: Close and lock issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueNumber = ${{ steps.discussion.outputs.issue_number }};
|
||||
|
||||
// Close the issue
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
state: 'closed'
|
||||
});
|
||||
|
||||
console.log(`Closed issue #${issueNumber}`);
|
||||
|
||||
// Lock the issue
|
||||
await github.rest.issues.lock({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
lock_reason: 'resolved'
|
||||
});
|
||||
|
||||
console.log(`Locked issue #${issueNumber}`);
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Summarize new issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
summary:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
models: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run AI inference
|
||||
id: inference
|
||||
uses: actions/ai-inference@v1
|
||||
with:
|
||||
prompt: |
|
||||
Summarize the following GitHub issue in one paragraph:
|
||||
Title: ${{ github.event.issue.title }}
|
||||
Body: ${{ github.event.issue.body }}
|
||||
|
||||
- name: Comment with AI summary
|
||||
run: |
|
||||
gh issue comment $ISSUE_NUMBER --body '${{ steps.inference.outputs.response }}'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
RESPONSE: ${{ steps.inference.outputs.response }}
|
||||
+5
-1
@@ -1,3 +1,4 @@
|
||||
datasets/
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
@@ -14,4 +15,7 @@ package-lock.json
|
||||
private/
|
||||
|
||||
# Generated UI files (built from viewer-template.html)
|
||||
src/ui/viewer.html
|
||||
src/ui/viewer.html
|
||||
|
||||
# Local MCP server config (for development only)
|
||||
.mcp.json
|
||||
@@ -1,14 +1,3 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"old-claude-mem": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"chroma-mcp",
|
||||
"--client-type",
|
||||
"persistent",
|
||||
"--data-dir",
|
||||
"/Users/alexnewman/.claude-mem/backups/chroma-backup-20251005-222403"
|
||||
]
|
||||
}
|
||||
}
|
||||
"mcpServers": {}
|
||||
}
|
||||
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
# Action Plan: Issues & PRs Cleanup
|
||||
Generated: 2025-12-12
|
||||
|
||||
## Phase 1: Immediate Cleanup (Today)
|
||||
|
||||
### Close Obsolete PRs
|
||||
|
||||
- [ ] **#255** - Close PR "Fix PM2 worker MODULE_NOT_FOUND"
|
||||
- Reason: v7.1.0 removed PM2 entirely, this fix is no longer relevant
|
||||
- Comment: Explain that v7.1.0 migration to Bun eliminated PM2 dependency
|
||||
|
||||
- [ ] **#206** - Close or request update on "Harden worker startup"
|
||||
- Reason: Contains PM2-specific code that no longer exists
|
||||
- Comment: Ask author if they want to update for Bun architecture, otherwise close as obsolete
|
||||
|
||||
### Close/Update Fixed Issues
|
||||
|
||||
- [ ] **#213** - Comment and close "Windows endless process spawning"
|
||||
- Reason: v7.1.0 Bun migration eliminated PM2 process management
|
||||
- Comment: Ask user to verify fix on v7.1.0, explain PM2 removal resolved issue
|
||||
|
||||
- [ ] **#229** - Close as duplicate
|
||||
- Reason: Duplicate of #227 (upstream Claude Code bug)
|
||||
- Comment: Direct to #227 for full details and workaround
|
||||
|
||||
- [ ] **#211** - Answer and close "Cursor IDE support question"
|
||||
- Reason: Product question, not a bug report
|
||||
- Comment: Explain focus is Claude Code, but plugin architecture may allow future expansion
|
||||
|
||||
### Critical Bug Follow-Up
|
||||
|
||||
- [ ] **#254** - Follow up on "Worker API fetch failed"
|
||||
- Current status: Asked about PM2 logs (pre-v7.1.0 comment)
|
||||
- Action: Update comment asking:
|
||||
- What version of claude-mem are you running?
|
||||
- If pre-v7.1.0: Please upgrade to v7.1.0 which fixes PM2 issues
|
||||
- If v7.1.0+: Run troubleshoot skill and share logs
|
||||
|
||||
## Phase 2: High-Priority Merges (This Week)
|
||||
|
||||
### Security & Critical Fixes
|
||||
|
||||
- [ ] **#236** - Review and merge "Localhost-only binding" 🔒 PRIORITY
|
||||
- Impact: Security improvement (fixes network exposure)
|
||||
- Status: 156 additions, all tests pass (42/42)
|
||||
- Action: Final review, merge, update CHANGELOG
|
||||
|
||||
- [ ] **#212** - Review and merge "Windows path quoting fix"
|
||||
- Impact: Fixes Windows usernames with spaces
|
||||
- Status: 6 lines changed, minimal risk
|
||||
- Action: Quick cross-platform test, merge
|
||||
|
||||
### Major Features (Maintainer-Authored)
|
||||
|
||||
- [ ] **#225** - Review and merge "Export/Import scripts"
|
||||
- Impact: Enables backup/restore, partially addresses #233
|
||||
- Status: 927 additions, extensively tested by maintainer
|
||||
- Action: Final review, merge, update docs
|
||||
|
||||
- [ ] **#250** - Review and merge "README translations"
|
||||
- Impact: International user onboarding (22 languages)
|
||||
- Status: 10,209 additions (massive but low-risk)
|
||||
- Action: Spot-check a few translations, merge
|
||||
|
||||
### User-Requested Features
|
||||
|
||||
- [ ] **#252** - Test and merge "Execution traces" (addresses #194)
|
||||
- Impact: Shows tools/skills/MCPs in UI bubbles
|
||||
- Status: 383 additions, comprehensive implementation
|
||||
- Action: Test database migration, API endpoints, UI display
|
||||
|
||||
- [ ] **#251** - Test and merge "Plan file context" (addresses #180)
|
||||
- Impact: Injects last plan file into context
|
||||
- Status: 85 additions, follows existing patterns
|
||||
- Action: Test with real plan files, verify toggle works
|
||||
|
||||
## Phase 3: Review & Consider (Next Week)
|
||||
|
||||
### Quality Enhancements
|
||||
|
||||
- [ ] **#230** - Review "Multi-language support" (addresses #228)
|
||||
- Impact: Observations/summaries in user's language
|
||||
- Status: 157 additions, Korean screenshot provided
|
||||
- Action: Review prompt changes carefully, test with multiple languages
|
||||
|
||||
- [ ] **#226** - Review "CLAUDE_CONFIG_DIR support"
|
||||
- Impact: Supports non-standard Claude installations
|
||||
- Status: 10 additions, minimal change
|
||||
- Action: Test with custom config directory, merge if working
|
||||
|
||||
### Developer Experience
|
||||
|
||||
- [ ] **#216** - Review "Makefile shortcuts"
|
||||
- Impact: DX improvement for contributors
|
||||
- Status: 1,085 additions
|
||||
- Priority: Low (not urgent)
|
||||
- Action: Review when time permits
|
||||
|
||||
## Phase 4: Issue Follow-Ups (Ongoing)
|
||||
|
||||
### Awaiting User Verification
|
||||
|
||||
- [ ] **#209** - Follow up if no response on Windows worker startup
|
||||
- Status: Already commented asking for v7.1.0 verification
|
||||
- Action: Close if verified fixed, or investigate if still broken
|
||||
|
||||
- [ ] **#231** - Follow up if no response on module resolution
|
||||
- Status: Already commented asking for v7.1.0 verification
|
||||
- Action: Close if verified fixed, or investigate if still broken
|
||||
|
||||
### Upstream Bugs (Keep Open)
|
||||
|
||||
- [ ] **#227** - Keep open as documented upstream bug
|
||||
- Reason: Claude Code CLI uses invalid Windows paths
|
||||
- Action: No action needed, workaround documented
|
||||
|
||||
### Active Bugs (Investigate)
|
||||
|
||||
- [ ] **#208** - Investigate "Windows console windows appearing"
|
||||
- Priority: Medium (cosmetic but annoying)
|
||||
- Action: Reproduce on Windows, identify root cause
|
||||
|
||||
## Phase 5: Future Feature Planning
|
||||
|
||||
### Feature Requests Without PRs
|
||||
|
||||
- [ ] **#240** - Plan "Move MCP scaffolding to separate file"
|
||||
- Type: Internal refactoring
|
||||
- Priority: Low
|
||||
- Action: Design approach when time permits
|
||||
|
||||
- [ ] **#239** - Plan "Track git branch as metadata"
|
||||
- Type: Context enhancement
|
||||
- Priority: Medium
|
||||
- Action: Design schema changes, discuss approach
|
||||
|
||||
- [ ] **#215** - Plan "PreCompact event hook"
|
||||
- Type: Power user feature
|
||||
- Priority: Low
|
||||
- Action: Evaluate use cases, design API
|
||||
|
||||
- [ ] **#233** - Plan "Multi-device sync" (partial solution exists)
|
||||
- Type: Major feature
|
||||
- Note: PR #225 provides export/import, full sync is more complex
|
||||
- Action: Determine if export/import is sufficient, or plan cloud sync
|
||||
|
||||
## Summary
|
||||
|
||||
### Quick Wins (Do Today)
|
||||
- Close 2 obsolete PRs (#255, #206)
|
||||
- Close 3 resolved/duplicate issues (#213, #229, #211)
|
||||
- Follow up on critical bug (#254)
|
||||
|
||||
### High-Impact Merges (This Week)
|
||||
- Merge security fix (#236)
|
||||
- Merge 2 simple fixes (#212, #225)
|
||||
- Merge 2 major features (#250, #252, #251)
|
||||
|
||||
### Expected Impact
|
||||
- **Security**: Localhost-only by default
|
||||
- **Functionality**: Export/import, execution traces, plan context
|
||||
- **UX**: Multi-language support, Windows fixes
|
||||
- **Clarity**: Clean backlog, remove PM2 confusion
|
||||
|
||||
---
|
||||
|
||||
**Next Review**: After Phase 2 completion, reassess remaining items
|
||||
+732
-79
@@ -4,93 +4,746 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [7.1.1] - 2025-12-12
|
||||
## [7.4.3] - 2025-12-20
|
||||
|
||||
### Fixed
|
||||
Added Discord notification script for release announcements.
|
||||
|
||||
**CRITICAL: Windows 11 Bun Auto-Install Broken**
|
||||
### Added
|
||||
- `scripts/discord-release-notify.js` - Posts formatted release notifications to Discord using webhook URL from `.env`
|
||||
- `npm run discord:notify <version>` - New npm script to trigger Discord notifications
|
||||
- Updated version-bump skill workflow to include Discord notification step
|
||||
|
||||
- **Chicken-and-Egg Problem**: v7.1.0 hooks called `bun smart-install.js`, but if Bun wasn't installed, the command failed with "bun is not recognized" before smart-install.js could run to install Bun
|
||||
- **Root Cause**: v7.1.0 migration removed Bun/uv auto-installation logic from smart-install.js
|
||||
- **Solution**:
|
||||
- Changed SessionStart hook to use `node` (always available) for smart-install.js
|
||||
- Restored Bun auto-installation logic: isBunInstalled(), installBun()
|
||||
- Restored uv auto-installation for Chroma support
|
||||
- Fresh Windows installations now work correctly
|
||||
### Configuration
|
||||
Set `DISCORD_UPDATES_WEBHOOK` in your `.env` file to enable release notifications.
|
||||
|
||||
**Path Quoting for Windows Usernames with Spaces**
|
||||
## [7.4.2] - 2025-12-20
|
||||
|
||||
- Fixed `hooks.json` to quote all `${CLAUDE_PLUGIN_ROOT}` paths
|
||||
- Prevents SyntaxError for Windows users with spaces in usernames (e.g., "C:\Users\John Doe\...")
|
||||
Patch release v7.4.2
|
||||
|
||||
## Changes
|
||||
- Refactored worker commands from npm scripts to claude-mem CLI
|
||||
- Added path alias script
|
||||
- Fixed Windows worker stop/restart reliability (#395)
|
||||
- Simplified build commands section in CLAUDE.md
|
||||
|
||||
## [7.4.1] - 2025-12-19
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **MCP Server**: Redirect logs to stderr to preserve JSON-RPC protocol (#396)
|
||||
- MCP uses stdio transport where stdout is reserved for JSON-RPC messages
|
||||
- Console.log was writing startup logs to stdout, causing Claude Desktop to parse log lines as JSON and fail
|
||||
|
||||
## [7.4.0] - 2025-12-18
|
||||
|
||||
## What's New
|
||||
|
||||
### MCP Tool Token Reduction
|
||||
|
||||
Optimized MCP tool definitions for reduced token consumption in Claude Code sessions through progressive parameter disclosure.
|
||||
|
||||
**Changes:**
|
||||
- Streamlined MCP tool schemas with minimal inline definitions
|
||||
- Added `get_schema()` tool for on-demand parameter documentation
|
||||
- Enhanced worker API with operation-based instruction loading
|
||||
|
||||
This release improves session efficiency by reducing the token overhead of MCP tool definitions while maintaining full functionality through progressive disclosure.
|
||||
|
||||
## [7.3.9] - 2025-12-18
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fix MCP server compatibility and web UI path resolution
|
||||
|
||||
This patch release addresses compatibility issues with the MCP server and resolves path resolution problems in the web UI.
|
||||
|
||||
## [7.3.8] - 2025-12-18
|
||||
|
||||
## Security Fix
|
||||
|
||||
Added localhost-only protection for admin endpoints to prevent DoS attacks when worker service is bound to 0.0.0.0 for remote UI access.
|
||||
|
||||
### Changes
|
||||
- Created `requireLocalhost` middleware to restrict admin endpoints
|
||||
- Applied to `/api/admin/restart` and `/api/admin/shutdown`
|
||||
- Returns 403 Forbidden for non-localhost requests
|
||||
|
||||
### Security Impact
|
||||
Prevents unauthorized shutdown/restart of worker service when exposed on network.
|
||||
|
||||
Fixes security concern raised in #368.
|
||||
|
||||
## [7.3.7] - 2025-12-17
|
||||
|
||||
## Windows Platform Stabilization
|
||||
|
||||
This patch release includes comprehensive improvements for Windows platform stability and reliability.
|
||||
|
||||
### Key Improvements
|
||||
|
||||
- **Worker Readiness Tracking**: Added `/api/readiness` endpoint with MCP/SDK initialization flags to prevent premature connection attempts
|
||||
- **Process Tree Cleanup**: Implemented recursive process enumeration on Windows to prevent zombie socket processes
|
||||
- **Bun Runtime Migration**: Migrated worker wrapper from Node.js to Bun for consistency and reliability
|
||||
- **Centralized Project Name Utility**: Consolidated duplicate project name extraction logic with Windows drive root handling
|
||||
- **Enhanced Error Messages**: Added platform-aware logging and detailed Windows troubleshooting guidance
|
||||
- **Subprocess Console Hiding**: Standardized `windowsHide: true` across all child process spawns to prevent console window flashing
|
||||
|
||||
### Technical Details
|
||||
|
||||
- Worker service tracks MCP and SDK readiness states separately
|
||||
- ChromaSync service properly tracks subprocess PIDs for Windows cleanup
|
||||
- Worker wrapper uses Bun runtime with enhanced socket cleanup via process tree enumeration
|
||||
- Increased timeouts on Windows platform (30s worker startup, 10s hook timeouts)
|
||||
- Logger utility includes platform and PID information for better debugging
|
||||
|
||||
This represents a major reliability improvement for Windows users, eliminating common issues with worker startup failures, orphaned processes, and zombie sockets.
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.3.6...v7.3.7
|
||||
|
||||
## [7.3.6] - 2025-12-17
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Enhanced SDKAgent response handling and message processing
|
||||
|
||||
## [7.3.5] - 2025-12-17
|
||||
|
||||
## What's Changed
|
||||
* fix(windows): solve zombie port problem with wrapper architecture by @ToxMox in https://github.com/thedotmack/claude-mem/pull/372
|
||||
* chore: bump version to 7.3.5 by @thedotmack in https://github.com/thedotmack/claude-mem/pull/375
|
||||
|
||||
## New Contributors
|
||||
* @ToxMox made their first contribution in https://github.com/thedotmack/claude-mem/pull/372
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.3.4...v7.3.5
|
||||
|
||||
## [7.3.4] - 2025-12-17
|
||||
|
||||
Patch release for bug fixes and minor improvements
|
||||
|
||||
## [7.3.3] - 2025-12-16
|
||||
|
||||
## What's Changed
|
||||
|
||||
- Remove all better-sqlite3 references from codebase (#357)
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.3.2...v7.3.3
|
||||
|
||||
## [7.3.2] - 2025-12-16
|
||||
|
||||
## 🪟 Windows Console Fix
|
||||
|
||||
Fixes blank console windows appearing for Windows 11 users during claude-mem operations.
|
||||
|
||||
### What Changed
|
||||
|
||||
- **Windows**: Uses PowerShell `Start-Process -WindowStyle Hidden` to properly hide worker process
|
||||
- **Security**: Added PowerShell string escaping to follow security best practices
|
||||
- **Unix/Mac**: No changes (continues to work as before)
|
||||
|
||||
### Root Cause
|
||||
|
||||
The issue was caused by a Node.js limitation where `windowsHide: true` doesn't work with `detached: true` in `child_process.spawn()`. This affects both Bun and Node.js since Bun inherits Node.js process spawning semantics.
|
||||
|
||||
See: https://github.com/nodejs/node/issues/21825
|
||||
|
||||
### Security Note
|
||||
|
||||
While all paths in the PowerShell command are application-controlled (not user input), we've added proper escaping to follow security best practices. If an attacker could modify bun installation paths or plugin directories, they would already have full filesystem access including the database.
|
||||
|
||||
### Related
|
||||
|
||||
- Fixes #304 (Multiple visible console windows)
|
||||
- Merged PR #339
|
||||
- Testing documented in PR #315
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
None - fully backward compatible.
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.3.1...v7.3.2
|
||||
|
||||
## [7.3.1] - 2025-12-16
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
### Pending Messages Cleanup (Issue #353)
|
||||
|
||||
Fixed unbounded database growth in the `pending_messages` table by implementing proper cleanup logic:
|
||||
|
||||
- **Content Clearing**: `markProcessed()` now clears `tool_input` and `tool_response` when marking messages as processed, preventing duplicate storage of transcript data that's already saved in observations
|
||||
- **Count-Based Retention**: `cleanupProcessed()` now keeps only the 100 most recent processed messages for UI display, deleting older ones automatically
|
||||
- **Automatic Cleanup**: Cleanup runs automatically after processing messages in `SDKAgent.processSDKResponse()`
|
||||
|
||||
### What This Fixes
|
||||
|
||||
- Prevents database from growing unbounded with duplicate transcript content
|
||||
- Keeps metadata (tool_name, status, timestamps) for recent messages
|
||||
- Maintains UI functionality while optimizing storage
|
||||
|
||||
### Technical Details
|
||||
|
||||
**Files Modified:**
|
||||
- `src/services/sqlite/PendingMessageStore.ts` - Cleanup logic implementation
|
||||
- `src/services/worker/SDKAgent.ts` - Periodic cleanup calls
|
||||
|
||||
**Database Behavior:**
|
||||
- Pending/processing messages: Keep full transcript data (needed for processing)
|
||||
- Processed messages: Clear transcript, keep metadata only (observations already saved)
|
||||
- Retention: Last 100 processed messages for UI feedback
|
||||
|
||||
### Related
|
||||
|
||||
- Fixes #353 - Observations not being saved
|
||||
- Part of the pending messages persistence feature (from PR #335)
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.3.0...v7.3.1
|
||||
|
||||
## [7.3.0] - 2025-12-16
|
||||
|
||||
## Features
|
||||
|
||||
- **Table-based search output**: Unified timeline formatting with cleaner, more organized presentation of search results grouped by date and file
|
||||
- **Simplified API**: Removed unused format parameter from MCP search tools for cleaner interface
|
||||
- **Shared formatting utilities**: Extracted common timeline formatting logic into reusable module
|
||||
- **Batch observations endpoint**: Added `/api/observations/batch` endpoint for efficient retrieval of multiple observations by ID array
|
||||
|
||||
## Changes
|
||||
|
||||
- **Default model upgrade**: Changed default model from Haiku to Sonnet for better observation quality
|
||||
- **Removed fake URIs**: Replaced claude-mem:// pseudo-protocol with actual HTTP API endpoints for citations
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed undefined debug function calls in MCP server
|
||||
- Fixed skillPath variable scoping bug in instructions endpoint
|
||||
- Extracted magic numbers to named constants for better code maintainability
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.2.4...v7.3.0
|
||||
|
||||
## [7.2.4] - 2025-12-15
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Documentation
|
||||
- Updated endless mode setup instructions with improved configuration guidance for better user experience
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.2.3...v7.2.4
|
||||
|
||||
## [7.2.3] - 2025-12-15
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Fix MCP server failures on plugin updates**: Add 2-second pre-restart delay in `ensureWorkerVersionMatches()` to give files time to sync before killing the old worker. This prevents the race condition where the worker restart happened too quickly after plugin file updates, causing "Worker service connection failed" errors.
|
||||
|
||||
## Changes
|
||||
|
||||
- Add `PRE_RESTART_SETTLE_DELAY` constant (2000ms) to `hook-constants.ts`
|
||||
- Add delay before `ProcessManager.restart()` call in `worker-utils.ts`
|
||||
- Fix pre-existing bug where `port` variable was undefined in error logging
|
||||
|
||||
## [7.2.2] - 2025-12-15
|
||||
|
||||
## Changes
|
||||
|
||||
- **Refactor:** Consolidate mem-search skill, remove desktop-skill duplication
|
||||
- Delete separate `desktop-skill/` directory (was outdated)
|
||||
- Generate `mem-search.zip` during build from `plugin/skills/mem-search/`
|
||||
- Update docs with correct MCP tool list and new download path
|
||||
- Single source of truth for Claude Desktop skill
|
||||
|
||||
## [7.2.1] - 2025-12-14
|
||||
|
||||
## Translation Script Enhancements
|
||||
|
||||
This release adds powerful enhancements to the README translation system, supporting 35 languages with improved efficiency and caching.
|
||||
|
||||
### What's New
|
||||
|
||||
**Translation Script Improvements:**
|
||||
- **Caching System**: Smart `.translation-cache.json` tracks content hashes to skip re-translating unchanged content
|
||||
- **Parallel Processing**: `--parallel <n>` flag enables concurrent translations for faster execution
|
||||
- **Force Re-translation**: `--force` flag to override cache when needed
|
||||
- **Tier-Based Scripts**: Organized translation workflows by language priority
|
||||
- `npm run translate:tier1` - 7 major languages (Chinese, Japanese, Korean, etc.)
|
||||
- `npm run translate:tier2` - 8 strong tech scene languages (Hebrew, Arabic, Russian, etc.)
|
||||
- `npm run translate:tier3` - 7 emerging markets (Vietnamese, Indonesian, Thai, etc.)
|
||||
- `npm run translate:tier4` - 6 additional languages (Italian, Greek, Hungarian, etc.)
|
||||
- `npm run translate:all` - All 35 languages sequentially
|
||||
- **Better Output Handling**: Automatically strips markdown code fences if Claude wraps output
|
||||
- **Translation Disclaimer**: Adds community correction notice at top of translated files
|
||||
- **Performance**: Uses Bun runtime for faster execution
|
||||
|
||||
### Supported Languages (35 Total)
|
||||
|
||||
Arabic, Bengali, Brazilian Portuguese, Bulgarian, Chinese (Simplified), Chinese (Traditional), Czech, Danish, Dutch, Estonian, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Latvian, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
None - fully backward compatible.
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Update via npm
|
||||
npm install -g claude-mem@7.2.1
|
||||
|
||||
# Or reinstall plugin
|
||||
claude plugin install thedotmack/claude-mem
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.2.0...v7.2.1
|
||||
|
||||
## [7.2.0] - 2025-12-14
|
||||
|
||||
## 🎉 New Features
|
||||
|
||||
### Automated Bug Report Generator
|
||||
|
||||
Added comprehensive bug report tool that streamlines issue reporting with AI assistance:
|
||||
|
||||
- **Command**: `npm run bug-report`
|
||||
- **🌎 Multi-language Support**: Write in ANY language, auto-translates to English
|
||||
- **📊 Smart Diagnostics**: Automatically collects:
|
||||
- Version information (claude-mem, Claude Code, Node.js, Bun)
|
||||
- Platform details (OS, version, architecture)
|
||||
- Worker status (running state, PID, port, uptime, stats)
|
||||
- Last 50 lines of logs (worker + silent debug)
|
||||
- Database info and configuration settings
|
||||
- **🤖 AI-Powered**: Uses Claude Agent SDK to generate professional GitHub issues
|
||||
- **📝 Interactive**: Multiline input support with intuitive prompts
|
||||
- **🔒 Privacy-Safe**:
|
||||
- Auto-sanitizes all file paths (replaces home directory with ~)
|
||||
- Optional `--no-logs` flag to exclude logs
|
||||
- **⚡ Streaming Progress**: Real-time character count and animated spinner
|
||||
- **🌐 One-Click Submit**: Auto-opens GitHub with pre-filled title and body
|
||||
|
||||
### Usage
|
||||
|
||||
From the plugin directory:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
npm run bug-report
|
||||
```
|
||||
|
||||
**Plugin Paths:**
|
||||
- macOS/Linux: `~/.claude/plugins/marketplaces/thedotmack`
|
||||
- Windows: `%USERPROFILE%\.claude\plugins\marketplaces\thedotmack`
|
||||
|
||||
**Options:**
|
||||
```bash
|
||||
npm run bug-report --no-logs # Skip logs for privacy
|
||||
npm run bug-report --verbose # Show all diagnostics
|
||||
npm run bug-report --help # Show help
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- Updated README with bug report section and usage instructions
|
||||
- Enhanced GitHub issue template to feature automated tool
|
||||
- Added platform-specific directory paths
|
||||
|
||||
## 🔧 Technical Details
|
||||
|
||||
**Files Added:**
|
||||
- `scripts/bug-report/cli.ts` - Interactive CLI entry point
|
||||
- `scripts/bug-report/index.ts` - Core logic with Agent SDK integration
|
||||
- `scripts/bug-report/collector.ts` - System diagnostics collector
|
||||
|
||||
**Files Modified:**
|
||||
- `package.json` - Added bug-report script
|
||||
- `README.md` - New Bug Reports section
|
||||
- `.github/ISSUE_TEMPLATE/bug_report.md` - Updated with automated tool instructions
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.15...v7.2.0
|
||||
|
||||
## [7.1.15] - 2025-12-14
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
**Worker Service Initialization**
|
||||
- Fixed 404 error on `/api/context/inject` during worker startup
|
||||
- Route is now registered immediately instead of after database initialization
|
||||
- Prevents race condition on fresh installs and restarts
|
||||
- Added integration test for early context inject route access
|
||||
|
||||
## Technical Details
|
||||
|
||||
The context hook was failing with `Cannot GET /api/context/inject` because the route was registered only after database initialization completed. This created a race condition where the hook could attempt to access the endpoint before it existed.
|
||||
|
||||
**Implementation:**
|
||||
- Added `initializationComplete` Promise to track async background initialization
|
||||
- Register `/api/context/inject` route immediately in `setupRoutes()`
|
||||
- Early handler blocks requests until initialization resolves (30s timeout)
|
||||
- Route handler duplicates logic from `SearchRoutes.handleContextInject` by design to prevent 404s
|
||||
|
||||
**Testing:**
|
||||
- Added integration test verifying route registration and timeout handling
|
||||
|
||||
Fixes #305
|
||||
Related: PR #310
|
||||
|
||||
## [7.1.14] - 2025-12-14
|
||||
|
||||
## Enhanced Error Handling & Logging
|
||||
|
||||
This patch release improves error message quality and logging across the claude-mem system.
|
||||
|
||||
### Error Message Improvements
|
||||
|
||||
**Standardized Hook Error Handling**
|
||||
- Created shared error handlers (`handleFetchError`, `handleWorkerError`) for consistent error messages
|
||||
- Platform-aware restart instructions (macOS, Linux, Windows) with correct commands
|
||||
- Migrated all hooks (context, new, save, summary) to use standardized handlers
|
||||
- Enhanced error logging with actionable context before throwing restart instructions
|
||||
|
||||
**ChromaSync Error Standardization**
|
||||
- Consistent client initialization checks across all methods
|
||||
- Enhanced error messages with troubleshooting steps and restart instructions
|
||||
- Better context about which operation failed
|
||||
|
||||
**Worker Service Improvements**
|
||||
- Enhanced version endpoint error logging with status codes and response text
|
||||
- Improved worker restart error messages with PM2 commands
|
||||
- Better context in all worker-related error scenarios
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Issue #260**: Fixed `happy_path_error__with_fallback` misuse in save-hook causing false "Missing cwd" errors
|
||||
- Removed unnecessary `happy_path_error` calls from SDKAgent that were masking real error messages
|
||||
- Cleaned up migration logging to use `console.log` instead of `console.error` for non-error events
|
||||
|
||||
### Logging Improvements
|
||||
|
||||
**Timezone-Aware Timestamps**
|
||||
- Worker logs now use local machine timezone instead of UTC
|
||||
- Maintains same format (`YYYY-MM-DD HH:MM:SS.mmm`) but reflects local time
|
||||
- Easier debugging and log correlation with system events
|
||||
- Enhanced worker-cli logging output format
|
||||
|
||||
### Test Coverage
|
||||
|
||||
Added comprehensive test suites:
|
||||
- `tests/error-handling/hook-error-logging.test.ts` - 12 tests for hook error handler behavior
|
||||
- `tests/services/chroma-sync-errors.test.ts` - ChromaSync error message consistency
|
||||
- `tests/integration/hook-execution-environments.test.ts` - Bun PATH resolution across shells
|
||||
- `docs/context/TEST_AUDIT_2025-12-13.md` - Comprehensive audit report
|
||||
|
||||
### Files Changed
|
||||
|
||||
27 files changed: 1,435 additions, 200 deletions
|
||||
|
||||
**What's Changed**
|
||||
* Standardize and enhance error handling across hooks and worker service by @thedotmack in #295
|
||||
* Timezone-aware logging for worker service and CLI
|
||||
* Complete build with all plugin files included
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.12...v7.1.14
|
||||
|
||||
## [7.1.13] - 2025-12-14
|
||||
|
||||
## Enhanced Error Handling & Logging
|
||||
|
||||
This patch release improves error message quality and logging across the claude-mem system.
|
||||
|
||||
### Error Message Improvements
|
||||
|
||||
**Standardized Hook Error Handling**
|
||||
- Created shared error handlers (`handleFetchError`, `handleWorkerError`) for consistent error messages
|
||||
- Platform-aware restart instructions (macOS, Linux, Windows) with correct commands
|
||||
- Migrated all hooks (context, new, save, summary) to use standardized handlers
|
||||
- Enhanced error logging with actionable context before throwing restart instructions
|
||||
|
||||
**ChromaSync Error Standardization**
|
||||
- Consistent client initialization checks across all methods
|
||||
- Enhanced error messages with troubleshooting steps and restart instructions
|
||||
- Better context about which operation failed
|
||||
|
||||
**Worker Service Improvements**
|
||||
- Enhanced version endpoint error logging with status codes and response text
|
||||
- Improved worker restart error messages with PM2 commands
|
||||
- Better context in all worker-related error scenarios
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Issue #260**: Fixed `happy_path_error__with_fallback` misuse in save-hook causing false "Missing cwd" errors
|
||||
- Removed unnecessary `happy_path_error` calls from SDKAgent that were masking real error messages
|
||||
- Cleaned up migration logging to use `console.log` instead of `console.error` for non-error events
|
||||
|
||||
### Logging Improvements
|
||||
|
||||
**Timezone-Aware Timestamps**
|
||||
- Worker logs now use local machine timezone instead of UTC
|
||||
- Maintains same format (`YYYY-MM-DD HH:MM:SS.mmm`) but reflects local time
|
||||
- Easier debugging and log correlation with system events
|
||||
|
||||
### Test Coverage
|
||||
|
||||
Added comprehensive test suites:
|
||||
- `tests/error-handling/hook-error-logging.test.ts` - 12 tests for hook error handler behavior
|
||||
- `tests/services/chroma-sync-errors.test.ts` - ChromaSync error message consistency
|
||||
- `tests/integration/hook-execution-environments.test.ts` - Bun PATH resolution across shells
|
||||
- `docs/context/TEST_AUDIT_2025-12-13.md` - Comprehensive audit report
|
||||
|
||||
### Files Changed
|
||||
|
||||
27 files changed: 1,435 additions, 200 deletions
|
||||
|
||||
**What's Changed**
|
||||
* Standardize and enhance error handling across hooks and worker service by @thedotmack in #295
|
||||
* Timezone-aware logging for worker service
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.12...v7.1.13
|
||||
|
||||
## [7.1.12] - 2025-12-14
|
||||
|
||||
## What's Fixed
|
||||
|
||||
- **Fix data directory creation**: Ensure `~/.claude-mem/` directory exists before writing PM2 migration marker file
|
||||
- Fixes ENOENT errors on first-time installation (issue #259)
|
||||
- Adds `mkdirSync(dataDir, { recursive: true })` in `startWorker()` before marker file write
|
||||
- Resolves Windows installation failures introduced in f923c0c and exposed in 5d4e71d
|
||||
|
||||
## Changes
|
||||
|
||||
- Added directory creation check in `src/shared/worker-utils.ts`
|
||||
- All 52 tests passing
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.11...v7.1.12
|
||||
|
||||
## [7.1.11] - 2025-12-14
|
||||
|
||||
## What's Changed
|
||||
|
||||
**Refactor: Simplified hook execution by removing bun-wrapper indirection**
|
||||
|
||||
Hooks are compiled to standard JavaScript and work perfectly with Node. The bun-wrapper was solving a problem that doesn't exist - hooks don't use Bun-specific APIs, they're just HTTP clients to the worker service.
|
||||
|
||||
**Benefits:**
|
||||
- Removes ~100 lines of code
|
||||
- Simpler cross-platform support (especially Windows)
|
||||
- No PATH resolution needed for hooks
|
||||
- Worker still uses Bun where performance matters
|
||||
- Follows YAGNI and Simple First principles
|
||||
|
||||
**Fixes:**
|
||||
- Fish shell compatibility issue (#264)
|
||||
|
||||
**Full Changelog:** https://github.com/thedotmack/claude-mem/compare/v7.1.10...v7.1.11
|
||||
|
||||
## [7.1.10] - 2025-12-14
|
||||
|
||||
## Enhancement
|
||||
|
||||
This release adds automatic orphan cleanup to complement the process leak fix from v7.1.9.
|
||||
|
||||
### Added
|
||||
|
||||
**Automatic Worker Restart on Version Updates**
|
||||
- **Auto-Cleanup on Startup**: Worker now automatically detects and kills orphaned chroma-mcp processes before starting
|
||||
- Scans for existing chroma-mcp processes on worker startup
|
||||
- Kills all found processes before creating new ones
|
||||
- Logs cleanup activity (process count and PIDs)
|
||||
- Non-fatal error handling (continues on cleanup failure)
|
||||
|
||||
- Added `/api/version` endpoint to worker service
|
||||
- Added version checking in `ensureWorkerRunning()`:
|
||||
- Compares plugin version with running worker version
|
||||
- Automatically restarts worker when version mismatch detected
|
||||
- Logs version mismatch for debugging
|
||||
- **Impact**: Users no longer need to manually restart worker after upgrades
|
||||
- **Critical for**: All future releases - eliminates connection errors from running old worker code
|
||||
### Benefits
|
||||
|
||||
### Notes
|
||||
- Automatically recovers from pre-7.1.9 process leaks without manual intervention
|
||||
- Ensures clean slate on every worker restart
|
||||
- Prevents accumulation even if v7.1.9's close() method fails
|
||||
- No user action required - works transparently
|
||||
|
||||
- No manual actions required - worker auto-restarts on next session start
|
||||
- Fresh installs on Windows 11 now work out-of-box
|
||||
- All hooks now run with correct runtime (node for install, bun for execution)
|
||||
### Example Logs
|
||||
|
||||
```
|
||||
[INFO] [SYSTEM] Cleaning up orphaned chroma-mcp processes {count=2, pids=33753,33750}
|
||||
[INFO] [SYSTEM] Orphaned processes cleaned up {count=2}
|
||||
```
|
||||
|
||||
### Recommendation
|
||||
|
||||
Upgrade from v7.1.9 to get automatic orphan cleanup. Combined with v7.1.9's proper subprocess cleanup, this provides comprehensive protection against process leaks.
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.9...v7.1.10
|
||||
|
||||
## [7.1.9] - 2025-12-14
|
||||
|
||||
## Critical Bugfix
|
||||
|
||||
This patch release fixes a critical memory leak that caused chroma-mcp processes to accumulate with each worker restart, leading to memory exhaustion and silent backfill failures.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Process Leak Prevention**: ChromaSync now properly cleans up chroma-mcp subprocesses when the worker is restarted
|
||||
- Store reference to StdioClientTransport subprocess
|
||||
- Explicitly close transport to kill subprocess on shutdown
|
||||
- Add error handling to ensure cleanup even on failures
|
||||
- Reset all state in finally block
|
||||
|
||||
### Impact
|
||||
|
||||
- Eliminates process accumulation (16+ orphaned processes seen in production)
|
||||
- Prevents memory exhaustion from leaked subprocesses (900MB+ RAM usage)
|
||||
- Fixes silent backfill failures caused by OOM kills
|
||||
- Ensures graceful cleanup on worker shutdown
|
||||
|
||||
### Recommendation
|
||||
|
||||
**All users should upgrade immediately** to prevent memory leaks and ensure reliable backfill operation.
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.8...v7.1.9
|
||||
|
||||
## [7.1.8] - 2025-12-13
|
||||
|
||||
## Memory Export/Import Scripts
|
||||
|
||||
Added portable memory export and import functionality with automatic duplicate prevention.
|
||||
|
||||
### New Features
|
||||
- **Export memories** to JSON format with search filtering and project-based filtering
|
||||
- **Import memories** with automatic duplicate detection via composite keys
|
||||
- Complete documentation in docs/public/usage/export-import.mdx
|
||||
|
||||
### Use Cases
|
||||
- Share memory sets between developers working on the same project
|
||||
- Backup and restore specific project memories
|
||||
- Collaborate on domain knowledge across teams
|
||||
- Migrate memories between different claude-mem installations
|
||||
|
||||
### Example Usage
|
||||
```bash
|
||||
# Export Windows-related memories
|
||||
npx tsx scripts/export-memories.ts "windows" windows-work.json
|
||||
|
||||
# Export only claude-mem project memories
|
||||
npx tsx scripts/export-memories.ts "bugfix" fixes.json --project=claude-mem
|
||||
|
||||
# Import memories (with automatic duplicate prevention)
|
||||
npx tsx scripts/import-memories.ts windows-work.json
|
||||
```
|
||||
|
||||
### Technical Improvements
|
||||
- Fixed JSON format response in /api/search endpoint for consistent structure
|
||||
- Enhanced project filtering in ChromaDB hybrid search result hydration
|
||||
- Duplicate detection using composite keys (session ID + title + timestamp)
|
||||
|
||||
## [7.1.7] - 2025-12-13
|
||||
|
||||
## Fixed
|
||||
- Removed Windows workaround that was causing libuv assertion failures
|
||||
- Prioritized stability over cosmetic console window issue
|
||||
|
||||
## Known Issue
|
||||
- On Windows, a console window may briefly appear when the worker starts (cosmetic only, does not affect functionality)
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.6...v7.1.7
|
||||
|
||||
## [7.1.6] - 2025-12-13
|
||||
|
||||
## What's Changed
|
||||
|
||||
Improved error messages with platform-specific worker restart instructions for better troubleshooting experience.
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.5...v7.1.6
|
||||
|
||||
## [7.1.5] - 2025-12-13
|
||||
|
||||
## What's Changed
|
||||
|
||||
* fix: Use getWorkerHost() instead of hardcoded localhost in MCP server (#276)
|
||||
|
||||
### Bug Fix
|
||||
Fixes Windows IPv6 issue where `localhost` resolves to `::1` (IPv6) but worker binds to `127.0.0.1` (IPv4), causing MCP tool connections to fail.
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.4...v7.1.5
|
||||
|
||||
## [7.1.4] - 2025-12-13
|
||||
|
||||
## What's Changed
|
||||
|
||||
* fix: add npm fallback when bun install fails with alias packages (#265)
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.3...v7.1.4
|
||||
|
||||
## [7.1.3] - 2025-12-13
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
### Smart Install Script Refactoring
|
||||
|
||||
Refactored the smart-install.js script to improve code quality and maintainability:
|
||||
- Extracted common installation paths as top-level constants (BUN_COMMON_PATHS, UV_COMMON_PATHS)
|
||||
- Simplified installation check functions to delegate to dedicated path-finding helpers
|
||||
- Streamlined installation verification logic with clearer error messages
|
||||
- Removed redundant post-installation verification checks
|
||||
- Improved error propagation by removing unnecessary retry logic
|
||||
|
||||
This refactoring reduces code duplication and makes the installation process more maintainable while preserving the same functionality for detecting Bun and uv binaries across platforms.
|
||||
|
||||
## [7.1.2] - 2025-12-13
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
### Windows Installation
|
||||
- Fixed Bun PATH detection on Windows after fresh install
|
||||
- Added fallback to check common install paths before PATH reload
|
||||
- Improved smart-install.js to use full Bun path when not in PATH
|
||||
- Added proper path quoting for Windows usernames with spaces
|
||||
|
||||
### Worker Startup
|
||||
- Fixed worker connection failures in Stop hook
|
||||
- Added health check retry loop (5 attempts, 500ms intervals)
|
||||
- Worker now waits up to 2.5s for responsiveness before returning
|
||||
- Improved error detection for Bun's ConnectionRefused error format
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.1...v7.1.2
|
||||
|
||||
## [7.1.1] - 2025-12-13
|
||||
|
||||
## 🚨 Critical Fixes
|
||||
|
||||
### Windows 11 Bun Auto-Install Fixed
|
||||
- **Problem**: v7.1.0 had a chicken-and-egg bug where `bun smart-install.js` failed if Bun wasn't installed
|
||||
- **Solution**: SessionStart hook now uses `node` (always available) for smart-install.js
|
||||
- **Impact**: Fresh Windows installations now work out-of-box
|
||||
|
||||
### Path Quoting for Windows
|
||||
- Fixed `hooks.json` to quote all paths
|
||||
- Prevents SyntaxError for usernames with spaces (e.g., "C:\Users\John Doe\")
|
||||
|
||||
## ✨ New Feature
|
||||
|
||||
### Automatic Worker Restart on Version Updates
|
||||
- Worker now automatically restarts when plugin version changes
|
||||
- No more manual `npm run worker:restart` needed after upgrades
|
||||
- Eliminates connection errors from running old worker code
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- **No manual actions required** - worker auto-restarts on next session start
|
||||
- All future upgrades will automatically restart the worker
|
||||
- Fresh installs on Windows 11 work correctly
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [Full Changelog](https://github.com/thedotmack/claude-mem/blob/main/CHANGELOG.md#711---2025-12-12)
|
||||
- [Documentation](https://docs.claude-mem.ai)
|
||||
|
||||
## [7.1.0] - 2025-12-13
|
||||
|
||||
## Security Fix: Localhost-Only Binding
|
||||
|
||||
**BREAKING CHANGE**: Worker service now binds to `127.0.0.1` (localhost) by default instead of `0.0.0.0` (all interfaces).
|
||||
|
||||
### Security Issue Fixed
|
||||
|
||||
The worker service was previously binding to `0.0.0.0:37777` by default, exposing all API endpoints to the network without authentication. This posed security risks:
|
||||
- Unauthorized access to memory data from any network device
|
||||
- Potential data injection into the database
|
||||
- Settings modification from remote devices
|
||||
- Full access to Web Viewer UI from the network
|
||||
|
||||
### Solution
|
||||
|
||||
Default worker binding changed to `127.0.0.1` (localhost-only), with a new configurable setting `CLAUDE_MEM_WORKER_HOST` for users who need remote access.
|
||||
|
||||
### Changes
|
||||
|
||||
- **Core**: Added `CLAUDE_MEM_WORKER_HOST` setting with default value `127.0.0.1`
|
||||
- **Worker**: Modified `worker-service.ts` to bind to configured host address
|
||||
- **API**: Added host validation in `SettingsRoutes.ts` (IP address format check)
|
||||
- **UI**: Added host configuration field in Settings panel
|
||||
- **Docs**: Updated README.md and CLAUDE.md with new setting
|
||||
|
||||
### Configuration
|
||||
|
||||
**Default (secure):** localhost only
|
||||
```bash
|
||||
CLAUDE_MEM_WORKER_HOST=127.0.0.1
|
||||
```
|
||||
|
||||
**Remote access (server deployments):**
|
||||
```bash
|
||||
CLAUDE_MEM_WORKER_HOST=0.0.0.0
|
||||
```
|
||||
|
||||
Can be configured via:
|
||||
- `~/.claude-mem/settings.json`
|
||||
- Web Viewer UI Settings panel
|
||||
|
||||
### Migration
|
||||
|
||||
**Automatic**: Existing installations will use `127.0.0.1` on next worker restart. If you need remote access, set `CLAUDE_MEM_WORKER_HOST=0.0.0.0` in `~/.claude-mem/settings.json`.
|
||||
|
||||
### Contributors
|
||||
|
||||
Thanks to @7Sageer for identifying and fixing this security issue!
|
||||
|
||||
## Major Architectural Migration
|
||||
|
||||
This release completely replaces PM2 with native Bun-based process management and migrates from better-sqlite3 to bun:sqlite.
|
||||
@@ -1991,12 +2644,12 @@ None (patch version)
|
||||
|
||||
## [4.3.0] - 2025-10-25
|
||||
|
||||
## What's Changed
|
||||
* feat: Enhanced context hook with session observations and cross-platform improvements by @thedotmack in https://github.com/thedotmack/claude-mem/pull/25
|
||||
|
||||
## New Contributors
|
||||
* @thedotmack made their first contribution in https://github.com/thedotmack/claude-mem/pull/25
|
||||
|
||||
## What's Changed
|
||||
* feat: Enhanced context hook with session observations and cross-platform improvements by @thedotmack in https://github.com/thedotmack/claude-mem/pull/25
|
||||
|
||||
## New Contributors
|
||||
* @thedotmack made their first contribution in https://github.com/thedotmack/claude-mem/pull/25
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v4.2.11...v4.3.0
|
||||
|
||||
## [4.2.10] - 2025-10-25
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.
|
||||
|
||||
**Current Version**: 7.1.1
|
||||
|
||||
## Architecture
|
||||
|
||||
**5 Lifecycle Hooks**: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd
|
||||
@@ -34,37 +32,25 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
|
||||
|
||||
## Build Commands
|
||||
|
||||
**Hooks only**: `npm run build && npm run sync-marketplace`
|
||||
```bash
|
||||
npm run build-and-sync # Build, sync to marketplace, restart worker
|
||||
```
|
||||
|
||||
**Worker changes**: `npm run build && npm run sync-marketplace && npm run worker:restart`
|
||||
|
||||
**Skills only**: `npm run sync-marketplace`
|
||||
|
||||
**Viewer UI**: `npm run build && npm run sync-marketplace && npm run worker:restart`
|
||||
**Viewer UI**: http://localhost:37777
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created with defaults on first run.
|
||||
|
||||
**Core Settings:**
|
||||
- `CLAUDE_MEM_MODEL` - Model for observations/summaries (default: claude-haiku-4-5)
|
||||
- `CLAUDE_MEM_CONTEXT_OBSERVATIONS` - Observations injected at SessionStart (default: 50)
|
||||
- `CLAUDE_MEM_MODEL` - Model for observations/summaries (default: claude-sonnet-4-5)
|
||||
- `CLAUDE_MEM_CONTEXT_OBSERVATIONS` - Observations injected at SessionStart
|
||||
- `CLAUDE_MEM_WORKER_PORT` - Worker service port (default: 37777)
|
||||
- `CLAUDE_MEM_WORKER_HOST` - Worker bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)
|
||||
|
||||
**System Configuration:**
|
||||
- `CLAUDE_MEM_DATA_DIR` - Data directory location (default: ~/.claude-mem)
|
||||
- `CLAUDE_MEM_LOG_LEVEL` - Log verbosity: DEBUG, INFO, WARN, ERROR, SILENT (default: INFO)
|
||||
- `CLAUDE_MEM_PYTHON_VERSION` - Python version for uvx/chroma-mcp (default: 3.13, avoids onnxruntime compatibility issues with Python 3.14+)
|
||||
- `CLAUDE_CODE_PATH` - Path to Claude executable (default: auto-detect via 'which claude')
|
||||
|
||||
**Settings File Format:**
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_MODEL": "claude-haiku-4-5",
|
||||
"CLAUDE_MEM_WORKER_PORT": "37777"
|
||||
}
|
||||
```
|
||||
|
||||
## File Locations
|
||||
|
||||
@@ -73,30 +59,39 @@ Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created
|
||||
- **Installed Plugin**: `~/.claude/plugins/marketplaces/thedotmack/`
|
||||
- **Database**: `~/.claude-mem/claude-mem.db`
|
||||
- **Chroma**: `~/.claude-mem/chroma/`
|
||||
- **Usage Logs**: `~/.claude-mem/usage-logs/usage-YYYY-MM-DD.jsonl`
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Bun** >= 1.0 (all platforms - auto-installed if missing)
|
||||
- **Bun** (all platforms - auto-installed if missing)
|
||||
- **uv** (all platforms - auto-installed if missing, provides Python for Chroma)
|
||||
- Node.js >= 18 (build tools only)
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
npm run build # Compile TypeScript
|
||||
npm run sync-marketplace # Copy to ~/.claude/plugins
|
||||
npm run worker:restart # Restart worker service
|
||||
npm run worker:status # Check worker status
|
||||
npm run worker:logs # View worker logs
|
||||
```
|
||||
|
||||
**Viewer UI**: http://localhost:37777
|
||||
**Worker Logs**: `~/.claude-mem/logs/worker-YYYY-MM-DD.log`
|
||||
- Node.js (build tools only)
|
||||
|
||||
## Documentation
|
||||
|
||||
**Public Docs**: https://docs.claude-mem.ai (Mintlify)
|
||||
**Source**: `docs/public/` - MDX files, edit `docs.json` for navigation
|
||||
**Deploy**: Auto-deploys from GitHub on push to main
|
||||
**Local Dev**: `cd docs/public && npx mintlify dev`
|
||||
|
||||
## Pro Features Architecture
|
||||
|
||||
Claude-mem is designed with a clean separation between open-source core functionality and optional Pro features.
|
||||
|
||||
**Open-Source Core** (this repository):
|
||||
|
||||
- All worker API endpoints on localhost:37777 remain fully open and accessible
|
||||
- Pro features are headless - no proprietary UI elements in this codebase
|
||||
- Pro integration points are minimal: settings for license keys, tunnel provisioning logic
|
||||
- The architecture ensures Pro features extend rather than replace core functionality
|
||||
|
||||
**Pro Features** (coming soon, external):
|
||||
|
||||
- Enhanced UI (Memory Stream) connects to the same localhost:37777 endpoints as the open viewer
|
||||
- Additional features like advanced filtering, timeline scrubbing, and search tools
|
||||
- Access gated by license validation, not by modifying core endpoints
|
||||
- Users without Pro licenses continue using the full open-source viewer UI without limitation
|
||||
|
||||
This architecture preserves the open-source nature of the project while enabling sustainable development through optional paid features.
|
||||
|
||||
# Important
|
||||
|
||||
No need to edit the changelog ever, it's generated automatically.
|
||||
|
||||
@@ -79,13 +79,13 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
|
||||
- 🧠 **Persistent Memory** - Context survives across sessions
|
||||
- 📊 **Progressive Disclosure** - Layered memory retrieval with token cost visibility
|
||||
- 🔍 **Skill-Based Search** - Query your project history with mem-search skill (~2,250 token savings)
|
||||
- 🔍 **Skill-Based Search** - Query your project history with mem-search skill
|
||||
- 🖥️ **Web Viewer UI** - Real-time memory stream at http://localhost:37777
|
||||
- 💻 **Claude Desktop Skill** - Search memory from Claude Desktop conversations
|
||||
- 🔒 **Privacy Control** - Use `<private>` tags to exclude sensitive content from storage
|
||||
- ⚙️ **Context Configuration** - Fine-grained control over what context gets injected
|
||||
- 🤖 **Automatic Operation** - No manual intervention required
|
||||
- 🔗 **Citations** - Reference past decisions with `claude-mem://` URIs
|
||||
- 🔗 **Citations** - Reference past observations with IDs (access via http://localhost:37777/api/observation/{id} or view all in the web viewer at http://localhost:37777)
|
||||
- 🧪 **Beta Channel** - Try experimental features like Endless Mode via version switching
|
||||
|
||||
---
|
||||
@@ -97,7 +97,7 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
💻 **Local Preview**: Run Mintlify docs locally:
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
cd docs/public
|
||||
npx mintlify dev
|
||||
```
|
||||
|
||||
@@ -161,7 +161,7 @@ npx mintlify dev
|
||||
2. **Smart Install** - Cached dependency checker (pre-hook script, not a lifecycle hook)
|
||||
3. **Worker Service** - HTTP API on port 37777 with web viewer UI and 10 search endpoints, managed by Bun
|
||||
4. **SQLite Database** - Stores sessions, observations, summaries with FTS5 full-text search
|
||||
5. **mem-search Skill** - Natural language queries with progressive disclosure (~2,250 token savings vs MCP)
|
||||
5. **mem-search Skill** - Natural language queries with progressive disclosure
|
||||
6. **Chroma Vector Database** - Hybrid semantic + keyword search for intelligent context retrieval
|
||||
|
||||
See [Architecture Overview](https://docs.claude-mem.ai/architecture/overview) for details.
|
||||
@@ -175,7 +175,6 @@ Claude-Mem provides intelligent search through the mem-search skill that auto-in
|
||||
**How It Works:**
|
||||
- Just ask naturally: *"What did we do last session?"* or *"Did we fix this bug before?"*
|
||||
- Claude automatically invokes the mem-search skill to find relevant context
|
||||
- ~2,250 token savings per session start vs MCP approach
|
||||
|
||||
**Available Search Operations:**
|
||||
|
||||
@@ -206,6 +205,8 @@ See [Search Tools Guide](https://docs.claude-mem.ai/usage/search-tools) for deta
|
||||
|
||||
## Beta Features & Endless Mode
|
||||
|
||||
> **Note**: Endless Mode is an **experimental feature in the beta branch only**. It is not included in the stable release you install via the marketplace. You must manually switch to the beta channel to try it, and it comes with significant caveats (see below).
|
||||
|
||||
Claude-Mem offers a **beta channel** with experimental features. Switch between stable and beta versions directly from the web viewer UI.
|
||||
|
||||
### How to Try Beta
|
||||
@@ -230,13 +231,17 @@ Working Memory (Context): Compressed observations (~500 tokens each)
|
||||
Archive Memory (Disk): Full tool outputs preserved for recall
|
||||
```
|
||||
|
||||
**Expected Results**:
|
||||
- ~95% token reduction in context window
|
||||
- ~20x more tool uses before context exhaustion
|
||||
**Projected Results** (based on theoretical modeling, not production measurements):
|
||||
- Significant token reduction in context window
|
||||
- More tool uses before context exhaustion
|
||||
- Linear O(N) scaling instead of quadratic O(N²)
|
||||
- Full transcripts preserved for perfect recall
|
||||
|
||||
**Caveats**: Adds latency (60-90s per tool for observation generation), still experimental.
|
||||
**Important Caveats**:
|
||||
- **Not in stable release** - You must switch to beta branch to use this feature
|
||||
- **Still in development** - May have bugs, breaking changes, or incomplete functionality
|
||||
- **Slower than standard mode** - Blocking observation generation adds latency to each tool use
|
||||
- **Theoretical projections** - The efficiency claims above are based on simulations, not real-world production data
|
||||
|
||||
See [Beta Features Documentation](https://docs.claude-mem.ai/beta-features) for details.
|
||||
|
||||
@@ -324,7 +329,7 @@ Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `CLAUDE_MEM_MODEL` | `claude-haiku-4-5` | AI model for observations |
|
||||
| `CLAUDE_MEM_MODEL` | `claude-sonnet-4-5` | AI model for observations |
|
||||
| `CLAUDE_MEM_WORKER_PORT` | `37777` | Worker service port |
|
||||
| `CLAUDE_MEM_WORKER_HOST` | `127.0.0.1` | Worker bind address (use `0.0.0.0` for remote access) |
|
||||
| `CLAUDE_MEM_DATA_DIR` | `~/.claude-mem` | Data directory location |
|
||||
@@ -350,7 +355,7 @@ curl http://localhost:37777/api/settings
|
||||
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_MODEL": "claude-haiku-4-5",
|
||||
"CLAUDE_MEM_MODEL": "claude-sonnet-4-5",
|
||||
"CLAUDE_MEM_WORKER_PORT": "37777",
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "50"
|
||||
}
|
||||
@@ -391,13 +396,48 @@ If you're experiencing issues, describe the problem to Claude and the troublesho
|
||||
|
||||
**Common Issues:**
|
||||
|
||||
- Worker not starting → `npm run worker:restart`
|
||||
- Worker not starting → `claude-mem restart`
|
||||
- No context appearing → `npm run test:context`
|
||||
- Database issues → `sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;"`
|
||||
- Search not working → Check FTS5 tables exist
|
||||
|
||||
See [Troubleshooting Guide](https://docs.claude-mem.ai/troubleshooting) for complete solutions.
|
||||
|
||||
### Windows Known Issues
|
||||
|
||||
**Console Window Visibility**: On Windows, a console window may briefly appear when the worker service starts. This is a cosmetic issue that we're working to resolve. We've prioritized stability by removing a workaround that was causing libuv crashes. The window does not affect functionality and will be addressed in a future release when the MCP SDK provides proper window hiding support.
|
||||
|
||||
---
|
||||
|
||||
## Bug Reports
|
||||
|
||||
**Automated Bug Report Generator** - Create comprehensive bug reports with one command:
|
||||
|
||||
```bash
|
||||
# From the plugin directory
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
npm run bug-report
|
||||
```
|
||||
|
||||
The bug report tool will:
|
||||
- 🌎 **Auto-translate** - Write in ANY language, automatically translates to English
|
||||
- 📊 **Collect diagnostics** - Gathers versions, platform info, worker status, logs, and configuration
|
||||
- 📝 **Interactive prompts** - Guides you through describing the issue with multiline support
|
||||
- 🤖 **AI formatting** - Uses Claude Agent SDK to generate professional GitHub issues
|
||||
- 🔒 **Privacy-safe** - Auto-sanitizes paths, optional `--no-logs` flag
|
||||
- 🌐 **Auto-submit** - Opens GitHub with pre-filled title and body
|
||||
|
||||
**Plugin Directory Paths:**
|
||||
- **macOS/Linux**: `~/.claude/plugins/marketplaces/thedotmack`
|
||||
- **Windows**: `%USERPROFILE%\.claude\plugins\marketplaces\thedotmack`
|
||||
|
||||
**Options:**
|
||||
```bash
|
||||
npm run bug-report --no-logs # Skip logs for privacy
|
||||
npm run bug-report --verbose # Show all diagnostics
|
||||
npm run bug-report --help # Show help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
name: mem-search
|
||||
description: Search your persistent memory database from previous coding sessions. Use when asked about past work, decisions, bugs fixed, or development history.
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Search your local memory database for past sessions, decisions, code changes, and development history. This skill uses the `mem-search` MCP server tools.
|
||||
|
||||
## Available MCP tools
|
||||
|
||||
Use these tools from the `mem-search` MCP server:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `search` | Unified search across all memory types |
|
||||
| `decisions` | Find architectural/design decisions |
|
||||
| `changes` | Find code changes and refactorings |
|
||||
| `timeline` | Get observations around a specific point in time |
|
||||
| `find_by_file` | Find observations for specific files |
|
||||
| `find_by_type` | Filter by type (decision, bugfix, feature, refactor, discovery, change) |
|
||||
| `find_by_concept` | Find by concept tags |
|
||||
| `how_it_works` | Understand system architecture and design patterns |
|
||||
|
||||
## Common parameters
|
||||
|
||||
- `query` - Natural language search query
|
||||
- `limit` - Max results (1-100, default 20)
|
||||
- `format` - `index` for titles only (recommended), `full` for complete content
|
||||
- `type` - Filter: observations, sessions, or prompts
|
||||
- `obs_type` - Filter observation type: decision, bugfix, feature, refactor, discovery, change
|
||||
|
||||
## When to use
|
||||
|
||||
- "Did we already solve this?"
|
||||
- "How did we do X last time?"
|
||||
- "Find the bug fix for..."
|
||||
- "What decisions did we make about..."
|
||||
- "Show me changes to [file]"
|
||||
- "What work did we do on [project]?"
|
||||
|
||||
## Setup requirement
|
||||
|
||||
The `mem-search` MCP server must be configured in Claude Desktop settings. See MCP configuration docs.
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,113 +0,0 @@
|
||||
# Branch Switching Test Plan: feature/bun-executable
|
||||
|
||||
## Overview
|
||||
This document validates that switching to the `feature/bun-executable` branch will be seamless for users.
|
||||
|
||||
## Branch Switching Mechanism
|
||||
|
||||
When a user switches branches via the Settings UI:
|
||||
|
||||
1. **Branch Switch Request**: User selects `feature/bun-executable` from Settings UI
|
||||
2. **Validation**: SettingsRoutes validates branch name against allowed list
|
||||
3. **Git Operations**: BranchManager performs:
|
||||
- Discard local changes (`git checkout -- .` and `git clean -fd`)
|
||||
- Fetch from origin (`git fetch origin`)
|
||||
- Checkout target branch (`git checkout feature/bun-executable`)
|
||||
- Pull latest (`git pull origin feature/bun-executable`)
|
||||
4. **Install Dependencies**:
|
||||
- Clear install marker (`.install-version`)
|
||||
- Run `npm install` (2 minute timeout)
|
||||
5. **Worker Restart**: Worker process exits and PM2/supervisor restarts it
|
||||
|
||||
## Feature Branch Changes
|
||||
|
||||
The `feature/bun-executable` branch makes these key changes:
|
||||
|
||||
### Dependencies Removed
|
||||
- `better-sqlite3` → Uses Bun's built-in SQLite
|
||||
- `pm2` → Custom worker CLI with process management
|
||||
- `@types/better-sqlite3`
|
||||
|
||||
### New Features
|
||||
- Auto-installation of Bun runtime in smart-install.js
|
||||
- Simplified worker management via worker-cli.js
|
||||
- No native module compilation required (better-sqlite3 removed)
|
||||
|
||||
## Installation Validation
|
||||
|
||||
### Current Branch → feature/bun-executable
|
||||
|
||||
**Step 1: Branch Switch (BranchManager)**
|
||||
```bash
|
||||
git checkout feature/bun-executable
|
||||
git pull origin feature/bun-executable
|
||||
rm .install-version
|
||||
npm install # ✅ Works - package.json is npm-compatible
|
||||
```
|
||||
|
||||
**Step 2: First Hook Execution**
|
||||
```bash
|
||||
node plugin/scripts/context-hook.js
|
||||
↓
|
||||
Calls smart-install.js
|
||||
↓
|
||||
Checks if Bun installed → Auto-installs if missing
|
||||
↓
|
||||
Runs: bun install (if needed)
|
||||
```
|
||||
|
||||
**Step 3: Worker Management**
|
||||
- Old: PM2 manages worker-service.cjs
|
||||
- New: worker-cli.js manages worker as background process
|
||||
- Transition: Automatic on first worker start command
|
||||
|
||||
## Seamless Installation Checklist
|
||||
|
||||
- [x] **Branch Validation**: `feature/bun-executable` added to allowedBranches list
|
||||
- [x] **npm install Compatible**: Feature branch package.json works with npm
|
||||
- [x] **No Breaking Changes**: No hooks that would fail on first run
|
||||
- [x] **Auto-Install**: smart-install.js automatically installs Bun if missing
|
||||
- [x] **Graceful Degradation**: Scripts fall back to node if Bun unavailable
|
||||
- [x] **No Manual Steps**: User just clicks "Switch Branch" in UI
|
||||
|
||||
## Potential Issues & Mitigations
|
||||
|
||||
### Issue 1: Bun Not in PATH After Install
|
||||
**Mitigation**: smart-install.js checks common Bun installation paths and provides clear instructions to user
|
||||
|
||||
### Issue 2: PM2 vs Worker CLI Transition
|
||||
**Mitigation**: Old PM2 worker continues running, new worker CLI starts separately. User can manually stop old PM2 worker if needed.
|
||||
|
||||
### Issue 3: Windows Compatibility
|
||||
**Mitigation**: Feature branch uses PowerShell installer for Windows, curl for Unix/macOS
|
||||
|
||||
## Test Results
|
||||
|
||||
### Unit Tests
|
||||
```bash
|
||||
✓ tests/branch-selector.test.ts (5 tests)
|
||||
✓ should allow main branch
|
||||
✓ should allow beta/7.0 branch
|
||||
✓ should allow feature/bun-executable branch
|
||||
✓ should reject invalid branch names
|
||||
✓ should have exactly 3 allowed branches
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
```bash
|
||||
✓ All existing tests pass (42 tests)
|
||||
✓ No regressions introduced
|
||||
✓ TypeScript compilation successful
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ **SEAMLESS INSTALLATION VALIDATED**
|
||||
|
||||
The installation process is seamless because:
|
||||
1. Branch switching uses standard git operations
|
||||
2. `npm install` works on feature branch
|
||||
3. Bun auto-installs on first hook execution
|
||||
4. No manual intervention required
|
||||
5. Clear error messages if issues occur
|
||||
6. Backward compatible with existing installations
|
||||
@@ -1,561 +0,0 @@
|
||||
# Claude-Mem Smart Install & Plugin Hooks - Comprehensive Analysis
|
||||
|
||||
**Generated:** 2025-12-09
|
||||
**Scope:** Smart install system, all plugin hooks, cross-platform compatibility, error handling, edge cases
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This report provides a comprehensive analysis of claude-mem's smart install system and plugin hook infrastructure. The analysis focuses on cross-platform compatibility, error handling patterns, artificial blockers, and edge case handling.
|
||||
|
||||
**Key Findings:**
|
||||
- ✅ Overall architecture is well-designed with clear separation of concerns
|
||||
- ⚠️ Multiple cross-platform compatibility issues identified
|
||||
- ⚠️ Several silent failure patterns that hinder debugging
|
||||
- ⚠️ Artificial blockers that could prevent legitimate use cases
|
||||
- ⚠️ Inconsistent timeout values across different components
|
||||
- ✅ No nested try-catch anti-patterns found
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Smart Install System Flow
|
||||
|
||||
```
|
||||
User Invokes Hook
|
||||
↓
|
||||
ensureWorkerRunning() [worker-utils.ts]
|
||||
↓
|
||||
isWorkerHealthy() → fetch /health endpoint
|
||||
↓
|
||||
├─ [HEALTHY] → Continue
|
||||
└─ [UNHEALTHY] → startWorker()
|
||||
↓
|
||||
├─ [Windows] → PowerShell Start-Process (hidden window)
|
||||
└─ [Unix] → Bun start ecosystem.config.cjs
|
||||
↓
|
||||
Wait for health check (15 retries × 1000ms)
|
||||
↓
|
||||
├─ [SUCCESS] → Continue
|
||||
└─ [FAILURE] → Throw error with manual recovery instructions
|
||||
```
|
||||
|
||||
### Plugin Hook Lifecycle
|
||||
|
||||
1. **SessionStart** (context-hook.ts + user-message-hook.ts)
|
||||
- context-hook: Fetches context via HTTP/curl
|
||||
- user-message-hook: Displays context to user via stderr
|
||||
|
||||
2. **UserPromptSubmit** (new-hook.ts)
|
||||
- Creates/retrieves SDK session
|
||||
- Strips privacy tags from prompt
|
||||
- Initializes session via HTTP
|
||||
|
||||
3. **PostToolUse** (save-hook.ts)
|
||||
- Filters skipped tools
|
||||
- Sends observation to worker via HTTP
|
||||
|
||||
4. **Stop** (summary-hook.ts)
|
||||
- Parses transcript JSONL
|
||||
- Extracts last user/assistant messages
|
||||
- Requests summary generation via HTTP
|
||||
|
||||
5. **SessionEnd** (cleanup-hook.ts)
|
||||
- Marks session complete
|
||||
- Fire-and-forget HTTP request
|
||||
|
||||
---
|
||||
|
||||
## Cross-Platform Compatibility Issues
|
||||
|
||||
### 🔴 CRITICAL: curl Dependency (context-hook.ts)
|
||||
|
||||
**Location:** `src/hooks/context-hook.ts:32`
|
||||
|
||||
```typescript
|
||||
const result = execSync(`curl -s "${url}"`, { encoding: "utf-8", timeout: 5000 });
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
1. **Windows Compatibility:** curl is not guaranteed to be available on Windows systems (though included in Windows 10 1803+, it may be missing on older systems or custom installations)
|
||||
2. **Error Handling:** No try-catch around execSync - will throw unhandled exception if curl fails
|
||||
3. **Redundancy:** Uses curl when JavaScript's native `fetch` is already used everywhere else in the codebase
|
||||
|
||||
**Impact:** High - SessionStart hook will crash if curl is unavailable or returns non-zero exit code
|
||||
|
||||
**Edge Cases:**
|
||||
- Corporate proxies blocking curl
|
||||
- Systems without curl in PATH
|
||||
- curl returning non-zero exit with valid output (warnings, etc.)
|
||||
|
||||
**Recommendation:**
|
||||
```typescript
|
||||
// Replace curl with fetch (already used in user-message-hook.ts)
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
||||
const result = await response.text();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: Platform-Specific Process Spawning (worker-utils.ts)
|
||||
|
||||
**Location:** `src/shared/worker-utils.ts:55-93`
|
||||
|
||||
**Windows Implementation:**
|
||||
```typescript
|
||||
spawnSync('powershell.exe', [
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-Command',
|
||||
`Start-Process -FilePath 'node' -ArgumentList '${workerScript}' -WorkingDirectory '${MARKETPLACE_ROOT}' -WindowStyle Hidden`
|
||||
])
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
1. **PowerShell Dependency:** Assumes PowerShell is available and in PATH
|
||||
2. **Command Injection Risk:** Worker script path inserted directly into command string without escaping
|
||||
3. **Process Monitoring:** Windows approach launches detached process with no Bun monitoring - harder to debug/restart
|
||||
4. **Health Check Timeout:** Comment says "Windows needs longer timeouts" but timeout is same for all platforms (500ms)
|
||||
|
||||
**Edge Cases:**
|
||||
- Windows systems with PowerShell execution policy restrictions
|
||||
- Paths containing single quotes or special characters
|
||||
- Windows subsystem for Linux (WSL) environments
|
||||
- Wine/Proton compatibility layers
|
||||
|
||||
**Unix Implementation:**
|
||||
```typescript
|
||||
const localBunBase = path.join(MARKETPLACE_ROOT, 'node_modules', '.bin', 'bun');
|
||||
const bunCommand = existsSync(localBunBase) ? localBunBase : 'bun';
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
1. **Bun Dependency:** Falls back to global bun if local not found, but doesn't verify it exists
|
||||
2. **Silent Failure:** If Bun not installed globally, spawnSync will fail with cryptic ENOENT error
|
||||
|
||||
**Recommendation:**
|
||||
- Add bun existence check before spawn
|
||||
- Implement consistent process monitoring across platforms
|
||||
- Add path escaping for Windows command construction
|
||||
- Actually implement longer timeout for Windows if needed
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: Git Dependency (paths.ts)
|
||||
|
||||
**Location:** `src/shared/paths.ts:89-97`
|
||||
|
||||
```typescript
|
||||
export function getCurrentProjectName(): string {
|
||||
try {
|
||||
const gitRoot = execSync('git rev-parse --show-toplevel', {
|
||||
cwd: process.cwd(),
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
}).trim();
|
||||
return basename(gitRoot);
|
||||
} catch {
|
||||
return basename(process.cwd());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
1. **Git Assumption:** Assumes git is installed and available in PATH
|
||||
2. **Non-Git Projects:** Silently falls back to cwd basename, but this behavior is undocumented
|
||||
|
||||
**Edge Cases:**
|
||||
- Projects not using git
|
||||
- Monorepos where cwd !== git root is desired
|
||||
- Systems without git installed
|
||||
|
||||
**Status:** ✅ Already handled with fallback, but could benefit from debug logging
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Analysis
|
||||
|
||||
### 🔴 CRITICAL: Silent Failures Without Logging
|
||||
|
||||
#### 1. Settings File Loading (early-settings.ts:20-28)
|
||||
|
||||
```typescript
|
||||
try {
|
||||
if (existsSync(SETTINGS_PATH)) {
|
||||
const data = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
|
||||
const fileValue = data.env?.[key];
|
||||
if (fileValue !== undefined) return fileValue;
|
||||
}
|
||||
} catch {
|
||||
// Fail silently - fall through to env var
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:**
|
||||
- Invalid JSON in settings file fails silently
|
||||
- File read permission errors fail silently
|
||||
- Users have no way to know their settings file is being ignored
|
||||
|
||||
**Impact:** High - Users may think settings are applied when they're actually using defaults
|
||||
|
||||
**Recommendation:**
|
||||
```typescript
|
||||
} catch (error) {
|
||||
logger.warn('SETTINGS', 'Failed to load settings file', { path: SETTINGS_PATH }, error);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. Worker Startup Failure (worker-utils.ts:104-107)
|
||||
|
||||
```typescript
|
||||
try {
|
||||
// ... worker startup logic ...
|
||||
} catch (error) {
|
||||
// Failed to start worker
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:**
|
||||
- Catches ALL errors during worker startup
|
||||
- Returns boolean with no information about what failed
|
||||
- User only gets generic error after all retries exhausted
|
||||
|
||||
**Impact:** High - Makes debugging worker startup issues extremely difficult
|
||||
|
||||
**Recommendation:**
|
||||
```typescript
|
||||
} catch (error) {
|
||||
logger.error('WORKER', 'Failed to start worker', {}, error as Error);
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 3. Worker Health Check (worker-utils.ts:30-40)
|
||||
|
||||
```typescript
|
||||
async function isWorkerHealthy(): Promise<boolean> {
|
||||
try {
|
||||
const port = getWorkerPort();
|
||||
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
||||
signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS)
|
||||
});
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:**
|
||||
- Network errors, timeouts, and non-200 responses all indistinguishable
|
||||
- No logging at all - completely silent
|
||||
|
||||
**Impact:** Medium - Hard to debug why health checks fail
|
||||
|
||||
**Recommendation:**
|
||||
```typescript
|
||||
} catch (error) {
|
||||
logger.debug('WORKER', 'Health check failed', { port }, error);
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4. Tool Formatting (logger.ts:122-124)
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const input = typeof toolInput === 'string' ? JSON.parse(toolInput) : toolInput;
|
||||
// ...
|
||||
} catch {
|
||||
return toolName;
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:**
|
||||
- Invalid JSON in tool input fails silently
|
||||
- Could mask data corruption issues
|
||||
|
||||
**Impact:** Low - Only affects log formatting
|
||||
|
||||
**Status:** ✅ Acceptable for log formatting, but could log at DEBUG level
|
||||
|
||||
---
|
||||
|
||||
### 🟢 GOOD: No Nested Try-Catch Anti-Patterns
|
||||
|
||||
Analysis confirmed zero instances of nested try-catch blocks. Error handling is consistently at single level per function.
|
||||
|
||||
---
|
||||
|
||||
## Artificial Blockers & Unnecessary Checks
|
||||
|
||||
### 🔴 CRITICAL: First-Run Detection (user-message-hook.ts:14-40)
|
||||
|
||||
```typescript
|
||||
const nodeModulesPath = join(pluginDir, 'node_modules');
|
||||
|
||||
if (!existsSync(nodeModulesPath)) {
|
||||
// Show first-time setup message
|
||||
console.error(`...`);
|
||||
process.exit(3);
|
||||
}
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
1. **False Positive:** Will trigger if user manually deletes node_modules (e.g., for troubleshooting)
|
||||
2. **Installation Race:** Could fail if installation is still in progress
|
||||
3. **Hook-Level Check:** Runs on EVERY SessionStart, not just actual first run
|
||||
|
||||
**Impact:** High - Prevents usage until node_modules exists, even if dependencies are installed elsewhere
|
||||
|
||||
**Edge Cases:**
|
||||
- User runs `rm -rf node_modules` for troubleshooting
|
||||
- Package manager installation interrupted
|
||||
- Symlinked node_modules (some package managers)
|
||||
|
||||
**Recommendation:**
|
||||
- Use a `.first-run-complete` marker file instead
|
||||
- Move check to npm postinstall script
|
||||
- Make check more robust (check for specific required modules)
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: Overly Specific Validation (paths.ts:117-119)
|
||||
|
||||
```typescript
|
||||
if (!existsSync(join(commandsDir, 'save.md'))) {
|
||||
throw new Error('Package commands directory missing required files');
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:**
|
||||
- Checks for ONE specific file to validate entire directory
|
||||
- Hardcoded filename could break if files reorganized
|
||||
- Error message doesn't specify what's missing
|
||||
|
||||
**Impact:** Medium - Could prevent package from working after internal refactoring
|
||||
|
||||
**Recommendation:**
|
||||
- Remove check entirely (let actual command invocation fail with better error)
|
||||
- Or check all required files if validation is critical
|
||||
|
||||
---
|
||||
|
||||
### 🟡 MEDIUM: Duplicate Health Endpoints
|
||||
|
||||
**Locations:**
|
||||
- `src/services/worker-service.ts:107` - `/api/health`
|
||||
- `src/services/worker/http/routes/ViewerRoutes.ts:27` - `/health`
|
||||
|
||||
**Usage:**
|
||||
- `worker-utils.ts` uses `/health`
|
||||
- `mcp-server.ts` uses `/api/health`
|
||||
|
||||
**Problem:**
|
||||
- Redundant endpoints doing the same thing
|
||||
- Inconsistent usage across codebase
|
||||
- Maintenance burden
|
||||
|
||||
**Impact:** Low - Both work, but creates confusion
|
||||
|
||||
**Recommendation:**
|
||||
- Standardize on `/api/health` (follows REST convention)
|
||||
- Remove `/health` endpoint
|
||||
- Update worker-utils.ts to use `/api/health`
|
||||
|
||||
---
|
||||
|
||||
## Timeout Configuration Issues
|
||||
|
||||
### Inconsistent Timeouts Across Components
|
||||
|
||||
| Component | Timeout | Location | Purpose |
|
||||
|-----------|---------|----------|---------|
|
||||
| Health check | 500ms | worker-utils.ts:13 | Check if worker alive |
|
||||
| Worker startup wait | 1000ms | worker-utils.ts:14 | Wait between health checks |
|
||||
| Worker startup retries | 15x | worker-utils.ts:15 | Max retries (15s total) |
|
||||
| Hook HTTP requests | 2000ms | cleanup-hook.ts:61, save-hook.ts:70, summary-hook.ts:164 | Send data to worker |
|
||||
| New hook session init | 5000ms | new-hook.ts:129 | Initialize session |
|
||||
| Context hook fetch | 5000ms | context-hook.ts:32 | Fetch context via curl |
|
||||
| User message hook | 5000ms | user-message-hook.ts:52 | Fetch context display |
|
||||
|
||||
**Problems:**
|
||||
1. **Health Check Too Aggressive:** 500ms may be too short for loaded systems or slow network
|
||||
2. **No Platform Adjustment:** Comment says "Windows needs longer timeouts" but values are same
|
||||
3. **Hook Timeout Variation:** Some hooks use 2s, others use 5s with no clear reasoning
|
||||
|
||||
**Recommendations:**
|
||||
- Increase health check timeout to 1000ms minimum
|
||||
- Actually implement longer timeouts for Windows
|
||||
- Standardize hook timeouts to 5000ms across the board
|
||||
- Make timeouts configurable via settings
|
||||
|
||||
---
|
||||
|
||||
## Edge Case Analysis
|
||||
|
||||
### Handled Well ✅
|
||||
|
||||
1. **JSONL Parsing:** summary-hook.ts continues on malformed lines (60-64, 117-121)
|
||||
2. **Git Not Available:** paths.ts falls back to cwd basename (89-97)
|
||||
3. **Settings File Missing:** early-settings.ts falls back to env vars and defaults (20-28)
|
||||
4. **Privacy Tags:** new-hook.ts handles fully-private prompts (99-109)
|
||||
5. **Tool Skipping:** save-hook.ts filters low-value tools (24-30)
|
||||
|
||||
### Missing Edge Case Handling ⚠️
|
||||
|
||||
1. **curl Failure:** context-hook.ts has no error handling for curl failures
|
||||
2. **Bun Not Installed:** worker-utils.ts assumes bun exists globally
|
||||
3. **PowerShell Restrictions:** worker-utils.ts doesn't check execution policy
|
||||
4. **Concurrent Worker Starts:** No locking to prevent multiple hooks from starting worker simultaneously
|
||||
5. **Port Already In Use:** No detection or recovery if worker port is taken
|
||||
6. **Zombie Processes:** Windows approach doesn't track PIDs, can't detect/kill zombies
|
||||
|
||||
---
|
||||
|
||||
## Recommendations Summary
|
||||
|
||||
### High Priority 🔴
|
||||
|
||||
1. **Replace curl with fetch** in context-hook.ts
|
||||
- Eliminates external dependency
|
||||
- Consistent with rest of codebase
|
||||
- Better error handling
|
||||
|
||||
2. **Add logging to silent failures**
|
||||
- early-settings.ts: Log when settings file fails to load
|
||||
- worker-utils.ts: Log startup failures with details
|
||||
- worker-utils.ts: Log health check failures at debug level
|
||||
|
||||
3. **Fix first-run detection**
|
||||
- Use marker file instead of node_modules check
|
||||
- More reliable and intentional
|
||||
|
||||
### Medium Priority 🟡
|
||||
|
||||
4. **Verify Bun availability** before attempting to use it
|
||||
- Check existence before spawn
|
||||
- Provide clear error message if missing
|
||||
|
||||
5. **Implement platform-specific timeouts**
|
||||
- Actually use longer timeouts on Windows as comment suggests
|
||||
- Make timeouts configurable
|
||||
|
||||
6. **Standardize health endpoints**
|
||||
- Remove duplicate `/health` endpoint
|
||||
- Use `/api/health` everywhere
|
||||
|
||||
7. **Add path escaping** for Windows PowerShell commands
|
||||
- Prevent injection issues
|
||||
- Handle paths with special characters
|
||||
|
||||
### Low Priority 🟢
|
||||
|
||||
8. **Standardize HTTP timeouts** across all hooks
|
||||
9. **Add concurrent startup protection** (locking mechanism)
|
||||
10. **Improve error messages** with actionable recovery steps
|
||||
|
||||
---
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
### Cross-Platform Testing Needed
|
||||
|
||||
1. **Windows Environments:**
|
||||
- Windows 10 (various versions)
|
||||
- Windows 11
|
||||
- Windows Server
|
||||
- WSL/WSL2
|
||||
- PowerShell execution policies (Restricted, RemoteSigned, Unrestricted)
|
||||
|
||||
2. **Unix Environments:**
|
||||
- macOS (Intel + Apple Silicon)
|
||||
- Linux (Ubuntu, Fedora, Arch)
|
||||
- FreeBSD
|
||||
|
||||
3. **Edge Environments:**
|
||||
- Docker containers
|
||||
- CI/CD environments
|
||||
- Systems without git installed
|
||||
- Systems without curl (or with restricted curl)
|
||||
- Corporate networks with proxies
|
||||
- Low-spec systems (slow startup)
|
||||
|
||||
### Test Scenarios
|
||||
|
||||
1. **Cold Start:** First run with no existing data
|
||||
2. **Corrupt Settings:** Invalid JSON in settings.json
|
||||
3. **Missing Dependencies:** No Bun, no git, no curl
|
||||
4. **Port Conflicts:** Worker port already in use
|
||||
5. **Rapid Hook Invocations:** Multiple hooks trying to start worker simultaneously
|
||||
6. **Permission Issues:** Read-only filesystem, restricted execution
|
||||
7. **Network Issues:** Localhost blocked, slow network
|
||||
|
||||
---
|
||||
|
||||
## Code Quality Assessment
|
||||
|
||||
### Strengths ✅
|
||||
|
||||
- Clean separation of concerns (hooks → worker → database)
|
||||
- No nested try-catch anti-patterns
|
||||
- Consistent use of modern async/await
|
||||
- Good use of TypeScript for type safety
|
||||
- Idempotent database operations
|
||||
- Clear documentation in critical sections
|
||||
|
||||
### Weaknesses ⚠️
|
||||
|
||||
- Silent failures hinder debugging
|
||||
- Inconsistent error handling patterns
|
||||
- Platform-specific code not fully tested/documented
|
||||
- Timeout configuration hardcoded and inconsistent
|
||||
- Some artificial blockers prevent legitimate use cases
|
||||
|
||||
### Technical Debt
|
||||
|
||||
- Duplicate health endpoints
|
||||
- curl dependency when fetch available
|
||||
- Bun dependency on Unix but not Windows (inconsistent monitoring)
|
||||
- First-run detection using node_modules existence
|
||||
- Hardcoded timeout values
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The claude-mem smart install and plugin hook system is architecturally sound with a well-designed separation of concerns. However, several cross-platform compatibility issues and silent failure patterns could cause problems in production, particularly on Windows systems or in edge case scenarios.
|
||||
|
||||
The highest priority improvements are:
|
||||
1. Removing the curl dependency
|
||||
2. Adding proper logging to silent failures
|
||||
3. Fixing the fragile first-run detection
|
||||
4. Verifying external dependencies before use
|
||||
|
||||
These changes would significantly improve debuggability and cross-platform reliability without requiring major architectural changes.
|
||||
|
||||
---
|
||||
|
||||
**Analysis Methodology:**
|
||||
- Systematic review of all TypeScript source files
|
||||
- Static analysis of error handling patterns
|
||||
- Cross-platform compatibility assessment
|
||||
- Edge case identification through code path analysis
|
||||
- Comparison against best practices and KISS principles
|
||||
|
||||
**Files Analyzed:**
|
||||
- src/hooks/*.ts (6 files)
|
||||
- src/services/worker-service.ts
|
||||
- src/services/worker/*.ts (10+ files)
|
||||
- src/servers/mcp-server.ts
|
||||
- src/shared/*.ts (worker-utils, early-settings, paths)
|
||||
- src/utils/*.ts (logger, silent-debug, tag-stripping)
|
||||
@@ -0,0 +1,390 @@
|
||||
# TypeScript SDK V2 interface (preview)
|
||||
|
||||
Preview of the simplified V2 TypeScript Agent SDK, with session-based send/receive patterns for multi-turn conversations.
|
||||
|
||||
---
|
||||
|
||||
<Warning>
|
||||
The V2 interface is an **unstable preview**. APIs may change based on feedback before becoming stable. Some features like session forking are only available in the [V1 SDK](/docs/en/agent-sdk/typescript).
|
||||
</Warning>
|
||||
|
||||
The V2 Claude Agent TypeScript SDK removes the need for async generators and yield coordination. This makes multi-turn conversations simpler—instead of managing generator state across turns, each turn is a separate `send()`/`receive()` cycle. The API surface reduces to three concepts:
|
||||
|
||||
- `createSession()` / `resumeSession()`: Start or continue a conversation
|
||||
- `session.send()`: Send a message
|
||||
- `session.receive()`: Get the response
|
||||
|
||||
## Installation
|
||||
|
||||
The V2 interface is included in the existing SDK package:
|
||||
|
||||
```bash
|
||||
npm install @anthropic-ai/claude-agent-sdk
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
### One-shot prompt
|
||||
|
||||
For simple single-turn queries where you don't need to maintain a session, use `unstable_v2_prompt()`. This example sends a math question and logs the answer:
|
||||
|
||||
```typescript
|
||||
import { unstable_v2_prompt } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
const result = await unstable_v2_prompt('What is 2 + 2?', {
|
||||
model: 'claude-sonnet-4-5-20250929'
|
||||
})
|
||||
console.log(result.result)
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>See the same operation in V1</summary>
|
||||
|
||||
```typescript
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
const q = query({
|
||||
prompt: 'What is 2 + 2?',
|
||||
options: { model: 'claude-sonnet-4-5-20250929' }
|
||||
})
|
||||
|
||||
for await (const msg of q) {
|
||||
if (msg.type === 'result') {
|
||||
console.log(msg.result)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Basic session
|
||||
|
||||
For interactions beyond a single prompt, create a session. V2 separates sending and receiving into distinct steps:
|
||||
- `send()` dispatches your message
|
||||
- `receive()` streams back the response
|
||||
|
||||
This explicit separation makes it easier to add logic between turns (like processing responses before sending follow-ups).
|
||||
|
||||
The example below creates a session, sends "Hello!" to Claude, and prints the text response. It uses [`await using`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management) (TypeScript 5.2+) to automatically close the session when the block exits. You can also call `session.close()` manually.
|
||||
|
||||
```typescript
|
||||
import { unstable_v2_createSession } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
await using session = unstable_v2_createSession({
|
||||
model: 'claude-sonnet-4-5-20250929'
|
||||
})
|
||||
|
||||
await session.send('Hello!')
|
||||
for await (const msg of session.receive()) {
|
||||
// Filter for assistant messages to get human-readable output
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
console.log(text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>See the same operation in V1</summary>
|
||||
|
||||
In V1, both input and output flow through a single async generator. For a basic prompt this looks similar, but adding multi-turn logic requires restructuring to use an input generator.
|
||||
|
||||
```typescript
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
const q = query({
|
||||
prompt: 'Hello!',
|
||||
options: { model: 'claude-sonnet-4-5-20250929' }
|
||||
})
|
||||
|
||||
for await (const msg of q) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
console.log(text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Multi-turn conversation
|
||||
|
||||
Sessions persist context across multiple exchanges. To continue a conversation, call `send()` again on the same session. Claude remembers the previous turns.
|
||||
|
||||
This example asks a math question, then asks a follow-up that references the previous answer:
|
||||
|
||||
```typescript
|
||||
import { unstable_v2_createSession } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
await using session = unstable_v2_createSession({
|
||||
model: 'claude-sonnet-4-5-20250929'
|
||||
})
|
||||
|
||||
// Turn 1
|
||||
await session.send('What is 5 + 3?')
|
||||
for await (const msg of session.receive()) {
|
||||
// Filter for assistant messages to get human-readable output
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
console.log(text)
|
||||
}
|
||||
}
|
||||
|
||||
// Turn 2
|
||||
await session.send('Multiply that by 2')
|
||||
for await (const msg of session.receive()) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
console.log(text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>See the same operation in V1</summary>
|
||||
|
||||
```typescript
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
// Must create an async iterable to feed messages
|
||||
async function* createInputStream() {
|
||||
yield {
|
||||
type: 'user',
|
||||
session_id: '',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'What is 5 + 3?' }] },
|
||||
parent_tool_use_id: null
|
||||
}
|
||||
// Must coordinate when to yield next message
|
||||
yield {
|
||||
type: 'user',
|
||||
session_id: '',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'Multiply by 2' }] },
|
||||
parent_tool_use_id: null
|
||||
}
|
||||
}
|
||||
|
||||
const q = query({
|
||||
prompt: createInputStream(),
|
||||
options: { model: 'claude-sonnet-4-5-20250929' }
|
||||
})
|
||||
|
||||
for await (const msg of q) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
console.log(text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Session resume
|
||||
|
||||
If you have a session ID from a previous interaction, you can resume it later. This is useful for long-running workflows or when you need to persist conversations across application restarts.
|
||||
|
||||
This example creates a session, stores its ID, closes it, then resumes the conversation:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
unstable_v2_createSession,
|
||||
unstable_v2_resumeSession,
|
||||
type SDKMessage
|
||||
} from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
// Helper to extract text from assistant messages
|
||||
function getAssistantText(msg: SDKMessage): string | null {
|
||||
if (msg.type !== 'assistant') return null
|
||||
return msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
}
|
||||
|
||||
// Create initial session and have a conversation
|
||||
const session = unstable_v2_createSession({
|
||||
model: 'claude-sonnet-4-5-20250929'
|
||||
})
|
||||
|
||||
await session.send('Remember this number: 42')
|
||||
|
||||
// Get the session ID from any received message
|
||||
let sessionId: string | undefined
|
||||
for await (const msg of session.receive()) {
|
||||
sessionId = msg.session_id
|
||||
const text = getAssistantText(msg)
|
||||
if (text) console.log('Initial response:', text)
|
||||
}
|
||||
|
||||
console.log('Session ID:', sessionId)
|
||||
session.close()
|
||||
|
||||
// Later: resume the session using the stored ID
|
||||
await using resumedSession = unstable_v2_resumeSession(sessionId!, {
|
||||
model: 'claude-sonnet-4-5-20250929'
|
||||
})
|
||||
|
||||
await resumedSession.send('What number did I ask you to remember?')
|
||||
for await (const msg of resumedSession.receive()) {
|
||||
const text = getAssistantText(msg)
|
||||
if (text) console.log('Resumed response:', text)
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>See the same operation in V1</summary>
|
||||
|
||||
```typescript
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
// Create initial session
|
||||
const initialQuery = query({
|
||||
prompt: 'Remember this number: 42',
|
||||
options: { model: 'claude-sonnet-4-5-20250929' }
|
||||
})
|
||||
|
||||
// Get session ID from any message
|
||||
let sessionId: string | undefined
|
||||
for await (const msg of initialQuery) {
|
||||
sessionId = msg.session_id
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
console.log('Initial response:', text)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Session ID:', sessionId)
|
||||
|
||||
// Later: resume the session
|
||||
const resumedQuery = query({
|
||||
prompt: 'What number did I ask you to remember?',
|
||||
options: {
|
||||
model: 'claude-sonnet-4-5-20250929',
|
||||
resume: sessionId
|
||||
}
|
||||
})
|
||||
|
||||
for await (const msg of resumedQuery) {
|
||||
if (msg.type === 'assistant') {
|
||||
const text = msg.message.content
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => block.text)
|
||||
.join('')
|
||||
console.log('Resumed response:', text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Cleanup
|
||||
|
||||
Sessions can be closed manually or automatically using [`await using`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management), a TypeScript 5.2+ feature for automatic resource cleanup. If you're using an older TypeScript version or encounter compatibility issues, use manual cleanup instead.
|
||||
|
||||
**Automatic cleanup (TypeScript 5.2+):**
|
||||
|
||||
```typescript
|
||||
import { unstable_v2_createSession } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
await using session = unstable_v2_createSession({
|
||||
model: 'claude-sonnet-4-5-20250929'
|
||||
})
|
||||
// Session closes automatically when the block exits
|
||||
```
|
||||
|
||||
**Manual cleanup:**
|
||||
|
||||
```typescript
|
||||
import { unstable_v2_createSession } from '@anthropic-ai/claude-agent-sdk'
|
||||
|
||||
const session = unstable_v2_createSession({
|
||||
model: 'claude-sonnet-4-5-20250929'
|
||||
})
|
||||
// ... use the session ...
|
||||
session.close()
|
||||
```
|
||||
|
||||
## API reference
|
||||
|
||||
### `unstable_v2_createSession()`
|
||||
|
||||
Creates a new session for multi-turn conversations.
|
||||
|
||||
```typescript
|
||||
function unstable_v2_createSession(options: {
|
||||
model: string;
|
||||
// Additional options supported
|
||||
}): Session
|
||||
```
|
||||
|
||||
### `unstable_v2_resumeSession()`
|
||||
|
||||
Resumes an existing session by ID.
|
||||
|
||||
```typescript
|
||||
function unstable_v2_resumeSession(
|
||||
sessionId: string,
|
||||
options: {
|
||||
model: string;
|
||||
// Additional options supported
|
||||
}
|
||||
): Session
|
||||
```
|
||||
|
||||
### `unstable_v2_prompt()`
|
||||
|
||||
One-shot convenience function for single-turn queries.
|
||||
|
||||
```typescript
|
||||
function unstable_v2_prompt(
|
||||
prompt: string,
|
||||
options: {
|
||||
model: string;
|
||||
// Additional options supported
|
||||
}
|
||||
): Promise<Result>
|
||||
```
|
||||
|
||||
### Session interface
|
||||
|
||||
```typescript
|
||||
interface Session {
|
||||
send(message: string): Promise<void>;
|
||||
receive(): AsyncGenerator<SDKMessage>;
|
||||
close(): void;
|
||||
}
|
||||
```
|
||||
|
||||
## Feature availability
|
||||
|
||||
Not all V1 features are available in V2 yet. The following require using the [V1 SDK](/docs/en/agent-sdk/typescript):
|
||||
|
||||
- Session forking (`forkSession` option)
|
||||
- Some advanced streaming input patterns
|
||||
|
||||
## Feedback
|
||||
|
||||
Share your feedback on the V2 interface before it becomes stable. Report issues and suggestions through [GitHub Issues](https://github.com/anthropics/claude-code/issues).
|
||||
|
||||
## See also
|
||||
|
||||
- [TypeScript SDK reference (V1)](/docs/en/agent-sdk/typescript) - Full V1 SDK documentation
|
||||
- [SDK overview](/docs/en/agent-sdk/overview) - General SDK concepts
|
||||
- [V2 examples on GitHub](https://github.com/anthropics/claude-agent-sdk-demos/tree/main/hello-world-v2) - Working code examples
|
||||
@@ -1,360 +0,0 @@
|
||||
# Dual-Tag System Architecture
|
||||
|
||||
**Date**: 2025-11-30
|
||||
**Branch**: `feature/meta-observation-control`
|
||||
**Status**: Implemented
|
||||
**Based on**: PR #105 dual-tag system
|
||||
|
||||
## Overview
|
||||
|
||||
The dual-tag system provides fine-grained control over what content gets persisted in claude-mem's observation database. It uses an edge processing pattern to filter tagged content at the hook layer before it reaches the worker service.
|
||||
|
||||
## The Two Tags
|
||||
|
||||
### Tag 1: `<private>`
|
||||
**Purpose**: User-controlled privacy
|
||||
**Status**: User-facing feature (documented)
|
||||
**Use case**: Users wrap content they don't want persisted
|
||||
|
||||
```xml
|
||||
<private>
|
||||
This content won't be stored in observations
|
||||
</private>
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
- Sensitive information (API keys, credentials, internal URLs)
|
||||
- Temporary context (deadlines, personal notes)
|
||||
- Debug output (logs, stack traces)
|
||||
- Exploratory prompts (brainstorming, hypotheticals)
|
||||
|
||||
### Tag 2: `<claude-mem-context>`
|
||||
**Purpose**: System-level meta-observation control
|
||||
**Status**: Infrastructure-ready (not user-facing yet)
|
||||
**Use case**: Prevents recursive storage when real-time context injection is active
|
||||
|
||||
```xml
|
||||
<claude-mem-context>
|
||||
# Relevant Context from Past Sessions
|
||||
|
||||
[Auto-injected past observations...]
|
||||
</claude-mem-context>
|
||||
```
|
||||
|
||||
**Context**: This tag is used by the real-time context injection feature (not yet shipped). When past observations are injected into new prompts, they're wrapped in this tag to prevent them from being re-stored as new observations (recursive storage problem).
|
||||
|
||||
## Architecture Pattern: Edge Processing
|
||||
|
||||
**Principle**: "Process at edge, send clean data to server"
|
||||
|
||||
The dual-tag system follows the edge processing pattern from hooks-in-composition:
|
||||
|
||||
```text
|
||||
UserPrompt → [Hook Layer] → Worker → Database
|
||||
↑
|
||||
Filter here
|
||||
(strip tags at edge)
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
**Without Filtering** (broken):
|
||||
```
|
||||
UserPrompt with <private> → PostToolUse hook → Worker → Memory Agent → Database
|
||||
↓
|
||||
Private content stored
|
||||
```
|
||||
|
||||
**With Edge Processing** (correct):
|
||||
```
|
||||
UserPrompt with <private> → PostToolUse hook → stripMemoryTags() → Worker → Memory Agent → Database
|
||||
↑ ↓
|
||||
Filter at edge Only clean data stored
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### File: `src/hooks/save-hook.ts`
|
||||
|
||||
**Function Added** (lines 31-53):
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Strip memory tags to prevent recursive storage and enable privacy control
|
||||
*/
|
||||
function stripMemoryTags(content: string): string {
|
||||
if (typeof content !== 'string') {
|
||||
silentDebug('[save-hook] stripMemoryTags received non-string:', { type: typeof content });
|
||||
return '{}'; // Safe default for JSON context
|
||||
}
|
||||
|
||||
return content
|
||||
.replace(/<claude-mem-context>[\s\S]*?<\/claude-mem-context>/g, '')
|
||||
.replace(/<private>[\s\S]*?<\/private>/g, '')
|
||||
.trim();
|
||||
}
|
||||
```
|
||||
|
||||
**Application** (lines 95-100):
|
||||
|
||||
```typescript
|
||||
tool_input: tool_input !== undefined
|
||||
? stripMemoryTags(JSON.stringify(tool_input))
|
||||
: '{}',
|
||||
tool_response: tool_response !== undefined
|
||||
? stripMemoryTags(JSON.stringify(tool_response))
|
||||
: '{}',
|
||||
```
|
||||
|
||||
### File: `tests/strip-memory-tags.test.ts`
|
||||
|
||||
**Test Coverage**: 19 tests across 4 categories:
|
||||
|
||||
1. **Basic Functionality** (7 tests)
|
||||
- Strip `<claude-mem-context>` tags
|
||||
- Strip `<private>` tags
|
||||
- Strip both tag types
|
||||
- Handle nested tags
|
||||
- Multiline content
|
||||
- Multiple tags
|
||||
- Empty results
|
||||
|
||||
2. **Edge Cases** (5 tests)
|
||||
- Malformed tags (unclosed)
|
||||
- Tag-like strings (not actual tags)
|
||||
- Very large content (10k+ chars)
|
||||
- Whitespace trimming
|
||||
- Strings without tags
|
||||
|
||||
3. **Type Safety** (5 tests)
|
||||
- Non-string inputs (number, null, undefined, object, array)
|
||||
- All return safe default '{}'
|
||||
|
||||
4. **Real-World Scenarios** (2 tests)
|
||||
- JSON.stringify output
|
||||
- Efficient large content handling
|
||||
|
||||
**All tests passing** ✅ (19/19)
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Always Active (No Configuration)
|
||||
|
||||
**Decision**: Tag stripping is always on, no environment variable needed
|
||||
**Rationale**: Privacy and anti-recursion protection should be default, not opt-in
|
||||
|
||||
### 2. Edge Processing (Not Worker-Level)
|
||||
|
||||
**Decision**: Filter at hook layer before sending to worker
|
||||
**Rationale**:
|
||||
- Keeps worker service simple
|
||||
- Follows one-way data stream
|
||||
- No worker changes needed
|
||||
- Hook becomes a filter/gateway
|
||||
|
||||
### 3. Defensive Coding with Silent Debug
|
||||
|
||||
**Decision**: Handle non-string inputs with silentDebug, return safe default
|
||||
**Rationale**:
|
||||
- Never block the agent (hooks-in-composition principle)
|
||||
- Log issues for observability
|
||||
- Safe fallback maintains system stability
|
||||
|
||||
### 4. Both Tags Now (Progressive Enhancement)
|
||||
|
||||
**Decision**: Implement both tags even though only `<private>` is user-facing
|
||||
**Rationale**:
|
||||
- Infrastructure ready for real-time context feature
|
||||
- No rework needed when context injection ships
|
||||
- Same code path for both tags (simple)
|
||||
- Progressive enhancement approach
|
||||
|
||||
### 5. Regex-Based Stripping
|
||||
|
||||
**Decision**: Use regex `/<tag>[\s\S]*?<\/tag>/g` instead of XML parser
|
||||
**Rationale**:
|
||||
- No dependencies needed
|
||||
- Handles multiline content (`[\s\S]*?`)
|
||||
- Non-greedy (`*?`) prevents over-matching
|
||||
- Global flag (`g`) handles multiple tags
|
||||
- Good enough for this use case
|
||||
|
||||
## Edge Cases Handled
|
||||
|
||||
| Case | Input | Output | Why |
|
||||
|------|-------|--------|-----|
|
||||
| Nested tags | `<private>a <private>b</private> a</private>` | `` | Outer tag matches all |
|
||||
| Malformed | `<private>unclosed` | `<private>unclosed` | Regex requires closing tag |
|
||||
| Multiple | `<private>a</private> b <private>c</private>` | `b` | Global flag removes all |
|
||||
| Empty | `<private></private>` | `` | Matches and removes |
|
||||
| Tag-like | `<tag>not private</tag>` | `<tag>not private</tag>` | Different tag name |
|
||||
| Large content | 10MB+ string | (stripped) | O(n) regex handles it |
|
||||
| Non-string | `123`, `null`, `{}` | `'{}'` | Defensive default |
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### 1. Real-Time Context Injection
|
||||
|
||||
**Status**: Deferred (not in this PR)
|
||||
**When ready**: The `<claude-mem-context>` tag infrastructure is already in place
|
||||
|
||||
The missing piece is in `src/hooks/new-hook.ts`:
|
||||
- Select relevant observations from timeline
|
||||
- Wrap in `<claude-mem-context>` tags
|
||||
- Return via `hookSpecificOutput`
|
||||
- Tag stripping already handles the rest
|
||||
|
||||
### 2. System-Level Meta-Observation Tagging
|
||||
|
||||
**Concept**: Auto-tag observations about observations
|
||||
**Examples**:
|
||||
- Search skill results: `<claude-mem-context>[search results]</claude-mem-context>`
|
||||
- Memory lookups: Fetched observations wrapped in tag
|
||||
- Observation summaries: Meta-level analysis wrapped
|
||||
|
||||
**Implementation**: Tools/skills that produce meta-observations can wrap output in `<claude-mem-context>` tags to prevent recursive storage.
|
||||
|
||||
### 3. Additional Tag Types
|
||||
|
||||
**Potential tags**:
|
||||
- `<ephemeral>`: Content that should be seen but not stored (alias for `<private>`)
|
||||
- `<debug>`: Debug output that should be logged but not persisted
|
||||
- `<scratch>`: Thinking/planning content not meant for observations
|
||||
|
||||
**Note**: Current implementation handles any tag you add to the regex. Adding new tags requires one line change in `stripMemoryTags()`.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
```bash
|
||||
node --test tests/strip-memory-tags.test.ts
|
||||
```
|
||||
**Expected**: 19/19 passing ✅
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**Test 1: Basic Privacy**
|
||||
```bash
|
||||
# Submit prompt with <private> tag
|
||||
# Query database: should not contain private content
|
||||
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations WHERE narrative LIKE '%<private>%';"
|
||||
# Expected: 0
|
||||
```
|
||||
|
||||
**Test 2: Dual Tags**
|
||||
```bash
|
||||
# Submit prompt with both tags
|
||||
# Verify neither tag appears in database
|
||||
sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations WHERE narrative LIKE '%<private>%' OR narrative LIKE '%<claude-mem-context>%';"
|
||||
# Expected: 0
|
||||
```
|
||||
|
||||
**Test 3: Function Exists**
|
||||
```bash
|
||||
# Verify stripMemoryTags in built file
|
||||
grep -c "claude-mem-context.*private.*trim" ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/save-hook.js
|
||||
# Expected: 1
|
||||
```
|
||||
|
||||
### Regression Tests
|
||||
|
||||
**Ensure**:
|
||||
- Normal observations still work (no tags broken)
|
||||
- Worker service receives clean data
|
||||
- No errors in `~/.claude-mem/silent.log`
|
||||
- Tool executions still captured correctly
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### 1. Tag Format is Fixed
|
||||
|
||||
Tags must use exact XML-style format: `<tag>content</tag>`
|
||||
|
||||
**Won't work**:
|
||||
- `[private]content[/private]` (wrong syntax)
|
||||
- `<!-- private -->content<!-- /private -->` (comment syntax)
|
||||
- `{{private}}content{{/private}}` (curly braces)
|
||||
|
||||
**Future**: Could add support for alternative formats if needed.
|
||||
|
||||
### 2. Partial Tag Matching
|
||||
|
||||
If user writes about tags without intending to use them:
|
||||
```
|
||||
I want to add a <private> tag feature to my app
|
||||
```
|
||||
|
||||
This won't be stripped (no closing tag). But if they accidentally write:
|
||||
```
|
||||
I want to add a <private>tag</private> feature
|
||||
```
|
||||
|
||||
"tag" gets stripped.
|
||||
|
||||
**Mitigation**: Documentation educates users on proper usage.
|
||||
|
||||
### 3. Performance with Very Large Content
|
||||
|
||||
Regex performance is O(n) where n = content length.
|
||||
|
||||
**Tested**: Works fine with 10,000 character strings
|
||||
**Unknown**: Performance with multi-megabyte tool responses
|
||||
|
||||
**Mitigation**: Most tool I/O is small. If issues arise, could optimize with:
|
||||
- Early exit if no '<' character found
|
||||
- Streaming regex for very large content
|
||||
- Size limits on stripMemoryTags input
|
||||
|
||||
## Documentation
|
||||
|
||||
### User-Facing
|
||||
|
||||
**Location**: `docs/public/usage/private-tags.mdx`
|
||||
**Content**:
|
||||
- How to use `<private>` tags
|
||||
- Use cases and examples
|
||||
- Best practices
|
||||
- Troubleshooting
|
||||
|
||||
**Available in**: Mintlify docs site, navigation under "Get Started"
|
||||
|
||||
### Technical/Internal
|
||||
|
||||
**Location**: `docs/context/dual-tag-system-architecture.md` (this file)
|
||||
**Content**:
|
||||
- Complete dual-tag system architecture
|
||||
- Implementation details
|
||||
- Design decisions
|
||||
- Future enhancements
|
||||
|
||||
**Audience**: Contributors, maintainers, future developers
|
||||
|
||||
## References
|
||||
|
||||
### Original Work
|
||||
- **PR #105**: Real-time context injection with dual-tag system
|
||||
- **Branch**: `feature/real-time-context` (merged to main)
|
||||
- **Investigator**: @basher83
|
||||
|
||||
### Documentation
|
||||
- **Investigation**: `docs/context/real-time-context-recursive-memory-investigation.md`
|
||||
- **User Guide**: `docs/public/usage/private-tags.mdx`
|
||||
- **This Document**: `docs/context/dual-tag-system-architecture.md`
|
||||
|
||||
### Patterns Applied
|
||||
- **Edge Processing**: From hooks-in-composition pattern
|
||||
- **Never Block the Agent**: Defensive coding, safe defaults
|
||||
- **One-Way Data Stream**: Hook → Worker → Database
|
||||
|
||||
## Summary
|
||||
|
||||
The dual-tag system is a complete, production-ready implementation that:
|
||||
- ✅ Gives users privacy control via `<private>` tags
|
||||
- ✅ Prepares infrastructure for real-time context injection
|
||||
- ✅ Uses edge processing pattern for clean architecture
|
||||
- ✅ Has comprehensive test coverage (19 tests, all passing)
|
||||
- ✅ Includes user documentation and technical reference
|
||||
- ✅ Requires no configuration (always active)
|
||||
- ✅ Handles edge cases defensively
|
||||
|
||||
**Status**: Ready to ship 🚀
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
||||
# Claude-Mem Hooks Cleanup Todo
|
||||
|
||||
## ✅ Phase 1: Delete Dead Code (Modified)
|
||||
|
||||
**hook-response.ts**
|
||||
|
||||
- [ ] Remove `| string` from HookType union to restore type safety
|
||||
- [ ] Delete PreCompact branch (lines 23-36, 14 lines)
|
||||
- [x] ~~Delete pointless branches~~ — SKIP (intentional)
|
||||
- [x] ~~Simplify wrapper function~~ — SKIP (intentional)
|
||||
|
||||
**new-hook.ts**
|
||||
|
||||
- [ ] Delete 34-line architecture comment block (lines 1-34)
|
||||
- [ ] Replace 18 lines of debug logging with single 4-line log call (lines 64-81)
|
||||
|
||||
**cleanup-hook.ts**
|
||||
|
||||
- [ ] Remove `cwd`, `transcript_path`, `hook_event_name` from SessionEndInput interface
|
||||
- [ ] Replace 12-line manual mode help with simple error throw
|
||||
|
||||
**user-message-hook.ts**
|
||||
|
||||
- [ ] Delete all 40 lines of expired announcement code (lines 31-70)
|
||||
- [ ] Add comment explaining exit code 3: `// exit code 3 = show user message that Claude does NOT receive as context`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 2: Extract Shared Utilities
|
||||
|
||||
- [ ] Create `src/shared/hook-error-handler.ts` with `handleWorkerError()`
|
||||
- [ ] Update all 4 hooks to use shared error handler (context-hook, new-hook, save-hook, summary-hook)
|
||||
- [ ] Create `src/shared/transcript-parser.ts` — merge `extractLastUserMessage` + `extractLastAssistantMessage` into single parameterized function
|
||||
- [ ] Create `src/shared/hook-constants.ts` for exit codes, timeouts
|
||||
|
||||
---
|
||||
|
||||
## ❌ Phase 3: SKIPPED
|
||||
|
||||
_(Entry points stay as-is, hook-response.ts wrapper stays as-is)_
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 4: Restore Type Safety
|
||||
|
||||
**context-hook.ts**
|
||||
|
||||
- [ ] Make `session_id`, `cwd`, `transcript_path` required in SessionStartInput
|
||||
- [ ] Remove `[key: string]: any`
|
||||
- [ ] Remove unused `source` field
|
||||
- [ ] Keep using `happy_path_error__with_fallback` for defaults (hooks use exit codes, logging tool is appropriate)
|
||||
|
||||
**All 4 hook interfaces**
|
||||
|
||||
- [ ] Remove `[key: string]: any` from all interfaces
|
||||
|
||||
**save-hook.ts**
|
||||
|
||||
- [ ] Keep `happy_path_error__with_fallback` usage (it's appropriate for hook context)
|
||||
|
||||
**summary-hook.ts**
|
||||
|
||||
- [ ] Add timeout (2s) and error logging to spinner stop request
|
||||
|
||||
---
|
||||
|
||||
## ✅ Phase 5: Relocate Business Logic (Modified)
|
||||
|
||||
- [ ] Move `SKIP_TOOLS` from save-hook.ts to worker service
|
||||
- [ ] Make `SKIP_TOOLS` configurable via settings.json
|
||||
- [x] ~~Move announcements to database~~ — SKIP
|
||||
- [x] ~~Merge context-hook + user-message-hook~~ — SKIP (intentionally separate)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Action | Count |
|
||||
| ----------------- | ----- |
|
||||
| Lines to delete | ~150 |
|
||||
| New shared files | 3 |
|
||||
| Interfaces to fix | 4 |
|
||||
| Items skipped | 5 |
|
||||
@@ -864,7 +864,7 @@ async startSession(session: ActiveSession, worker?: any) {
|
||||
const queryResult = query({
|
||||
prompt: messageGenerator,
|
||||
options: {
|
||||
model: 'claude-haiku-4-5',
|
||||
model: 'claude-sonnet-4-5',
|
||||
disallowedTools: ['Bash', 'Read', 'Write', ...], // Observer-only
|
||||
abortController: session.abortController
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@ title: "PM2 to Bun Migration"
|
||||
description: "Complete technical documentation for the process management and database driver migration in v7.1.0"
|
||||
---
|
||||
|
||||
<Note>
|
||||
**Historical Migration Documentation**
|
||||
|
||||
This document describes the PM2 to Bun migration that occurred in v7.1.0 (December 2025). If you're installing claude-mem for the first time, this migration has already been completed and you can use the current Bun-based system documented in the main guides.
|
||||
|
||||
This documentation is preserved for users upgrading from versions older than v7.1.0.
|
||||
</Note>
|
||||
|
||||
# PM2 to Bun Migration: Complete Technical Documentation
|
||||
|
||||
**Version**: 7.1.0
|
||||
@@ -98,7 +106,7 @@ pm2 logs claude-mem-worker # View logs
|
||||
```bash
|
||||
npm run worker:start # Start worker
|
||||
npm run worker:stop # Stop worker
|
||||
npm run worker:restart # Restart worker
|
||||
claude-mem restart # Restart worker
|
||||
npm run worker:status # Check status
|
||||
npm run worker:logs # View logs
|
||||
```
|
||||
@@ -297,7 +305,7 @@ No migration logic runs on subsequent sessions.
|
||||
| `pm2 list` | `npm run worker:status` | Shows worker status |
|
||||
| `pm2 start <script>` | `npm run worker:start` | Start worker |
|
||||
| `pm2 stop claude-mem-worker` | `npm run worker:stop` | Stop worker |
|
||||
| `pm2 restart claude-mem-worker` | `npm run worker:restart` | Restart worker |
|
||||
| `pm2 restart claude-mem-worker` | `claude-mem restart` | Restart worker |
|
||||
| `pm2 delete claude-mem-worker` | `npm run worker:stop` | Remove worker |
|
||||
| `pm2 logs claude-mem-worker` | `npm run worker:logs` | View logs |
|
||||
| `pm2 describe claude-mem-worker` | `npm run worker:status` | Detailed status |
|
||||
@@ -443,7 +451,7 @@ pm2 save # Persist the deletion
|
||||
rm ~/.claude-mem/.pm2-migrated
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
### Scenario 2: Stale PID File (Process Dead)
|
||||
@@ -475,7 +483,7 @@ lsof -i :37777
|
||||
kill -9 <PID>
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
### Common Error Messages
|
||||
|
||||
@@ -5,7 +5,7 @@ description: "mem-search skill with HTTP API and progressive disclosure"
|
||||
|
||||
# Search Architecture
|
||||
|
||||
Claude-Mem uses a skill-based search architecture that provides intelligent memory retrieval through natural language queries. This replaced the MCP-based approach in v5.4.0, saving ~2,250 tokens per session start. The skill was enhanced and renamed to "mem-search" in v5.5.0 for better scope differentiation.
|
||||
Claude-Mem uses a skill-based search architecture that provides intelligent memory retrieval through natural language queries. This replaced the MCP-based approach in v5.4.0 with a more efficient implementation. The skill was enhanced and renamed to "mem-search" in v5.5.0 for better scope differentiation.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -133,7 +133,7 @@ Invoke this skill when users ask about:
|
||||
...
|
||||
```
|
||||
|
||||
**Token Savings**: ~2,250 tokens per session start (90% reduction)
|
||||
**Token Efficiency**: Minimal frontmatter at session start with progressive disclosure
|
||||
|
||||
## HTTP API Endpoints
|
||||
|
||||
@@ -341,14 +341,14 @@ All user-provided search queries are properly escaped to prevent SQL injection.
|
||||
### 1. Token Efficiency
|
||||
|
||||
**Before (MCP)**:
|
||||
- Session start: ~2,500 tokens for tool definitions
|
||||
- Session start: All tool definitions loaded upfront
|
||||
- Every session pays this cost
|
||||
- No progressive disclosure
|
||||
|
||||
**After (Skill)**:
|
||||
- Session start: ~250 tokens for skill frontmatter
|
||||
- Full instructions: ~2,500 tokens (only when invoked)
|
||||
- Net savings: ~2,250 tokens per session (~90% reduction)
|
||||
- Session start: Minimal token cost for skill frontmatter
|
||||
- Full instructions loaded only when invoked (progressive disclosure)
|
||||
- More efficient than loading all tool definitions upfront
|
||||
|
||||
### 2. Natural Language Interface
|
||||
|
||||
@@ -416,7 +416,7 @@ If searches fail, check worker service:
|
||||
|
||||
```bash
|
||||
npm run worker:status # Check status
|
||||
npm run worker:restart # Restart worker
|
||||
claude-mem restart # Restart worker
|
||||
npm run worker:logs # View logs
|
||||
```
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ The worker service is a long-running HTTP API built with Express.js and managed
|
||||
|
||||
## REST API Endpoints
|
||||
|
||||
The worker service exposes 14 HTTP endpoints organized into four categories:
|
||||
The worker service exposes 20 HTTP endpoints organized into five categories:
|
||||
|
||||
### Viewer & Health Endpoints
|
||||
|
||||
@@ -156,7 +156,150 @@ GET /api/summaries?project=my-project&limit=20&offset=0
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. Get Stats
|
||||
#### 7. Get Observation by ID
|
||||
```
|
||||
GET /api/observation/:id
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve a single observation by its ID
|
||||
|
||||
**Path Parameters**:
|
||||
- `id` (required): Observation ID
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"id": 123,
|
||||
"sdk_session_id": "abc123",
|
||||
"project": "my-project",
|
||||
"type": "bugfix",
|
||||
"title": "Fix authentication bug",
|
||||
"narrative": "...",
|
||||
"created_at": "2025-11-06T10:30:00Z",
|
||||
"created_at_epoch": 1730886600000
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response** (404):
|
||||
```json
|
||||
{
|
||||
"error": "Observation #123 not found"
|
||||
}
|
||||
```
|
||||
|
||||
#### 8. Get Observations by IDs (Batch)
|
||||
```
|
||||
POST /api/observations/batch
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve multiple observations by their IDs in a single request
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"ids": [123, 456, 789],
|
||||
"orderBy": "date_desc",
|
||||
"limit": 10,
|
||||
"project": "my-project"
|
||||
}
|
||||
```
|
||||
|
||||
**Body Parameters**:
|
||||
- `ids` (required): Array of observation IDs
|
||||
- `orderBy` (optional): Sort order - `date_desc` or `date_asc` (default: `date_desc`)
|
||||
- `limit` (optional): Maximum number of results to return
|
||||
- `project` (optional): Filter by project name
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 789,
|
||||
"sdk_session_id": "abc123",
|
||||
"project": "my-project",
|
||||
"type": "feature",
|
||||
"title": "Add new feature",
|
||||
"narrative": "...",
|
||||
"created_at": "2025-11-06T12:00:00Z",
|
||||
"created_at_epoch": 1730891400000
|
||||
},
|
||||
{
|
||||
"id": 456,
|
||||
"sdk_session_id": "abc124",
|
||||
"project": "my-project",
|
||||
"type": "bugfix",
|
||||
"title": "Fix authentication bug",
|
||||
"narrative": "...",
|
||||
"created_at": "2025-11-06T10:30:00Z",
|
||||
"created_at_epoch": 1730886600000
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
- `400 Bad Request`: `{"error": "ids must be an array of numbers"}`
|
||||
- `400 Bad Request`: `{"error": "All ids must be integers"}`
|
||||
|
||||
**Use Case**: This endpoint is used by the `get_observations` MCP tool to efficiently retrieve multiple observations in a single request, avoiding the overhead of multiple individual requests.
|
||||
|
||||
#### 9. Get Session by ID
|
||||
```
|
||||
GET /api/session/:id
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve a single session by its ID
|
||||
|
||||
**Path Parameters**:
|
||||
- `id` (required): Session ID
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"id": 456,
|
||||
"sdk_session_id": "abc123",
|
||||
"project": "my-project",
|
||||
"request": "User's original request",
|
||||
"completed": "Work finished",
|
||||
"created_at": "2025-11-06T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response** (404):
|
||||
```json
|
||||
{
|
||||
"error": "Session #456 not found"
|
||||
}
|
||||
```
|
||||
|
||||
#### 10. Get Prompt by ID
|
||||
```
|
||||
GET /api/prompt/:id
|
||||
```
|
||||
|
||||
**Purpose**: Retrieve a single user prompt by its ID
|
||||
|
||||
**Path Parameters**:
|
||||
- `id` (required): Prompt ID
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"session_id": "abc123",
|
||||
"prompt": "User's prompt text",
|
||||
"prompt_number": 1,
|
||||
"created_at": "2025-11-06T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response** (404):
|
||||
```json
|
||||
{
|
||||
"error": "Prompt #1 not found"
|
||||
}
|
||||
```
|
||||
|
||||
#### 12. Get Stats
|
||||
```
|
||||
GET /api/stats
|
||||
```
|
||||
@@ -187,9 +330,23 @@ GET /api/stats
|
||||
}
|
||||
```
|
||||
|
||||
#### 13. Get Projects
|
||||
```
|
||||
GET /api/projects
|
||||
```
|
||||
|
||||
**Purpose**: Get list of distinct projects from observations
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"projects": ["my-project", "other-project", "test-project"]
|
||||
}
|
||||
```
|
||||
|
||||
### Settings Endpoints
|
||||
|
||||
#### 8. Get Settings
|
||||
#### 14. Get Settings
|
||||
```
|
||||
GET /api/settings
|
||||
```
|
||||
@@ -205,7 +362,7 @@ GET /api/settings
|
||||
}
|
||||
```
|
||||
|
||||
#### 9. Save Settings
|
||||
#### 15. Save Settings
|
||||
```
|
||||
POST /api/settings
|
||||
```
|
||||
@@ -230,7 +387,7 @@ POST /api/settings
|
||||
|
||||
### Session Management Endpoints
|
||||
|
||||
#### 10. Initialize Session
|
||||
#### 16. Initialize Session
|
||||
```
|
||||
POST /sessions/:sessionDbId/init
|
||||
```
|
||||
@@ -251,7 +408,7 @@ POST /sessions/:sessionDbId/init
|
||||
}
|
||||
```
|
||||
|
||||
#### 11. Add Observation
|
||||
#### 17. Add Observation
|
||||
```
|
||||
POST /sessions/:sessionDbId/observations
|
||||
```
|
||||
@@ -274,7 +431,7 @@ POST /sessions/:sessionDbId/observations
|
||||
}
|
||||
```
|
||||
|
||||
#### 12. Generate Summary
|
||||
#### 18. Generate Summary
|
||||
```
|
||||
POST /sessions/:sessionDbId/summarize
|
||||
```
|
||||
@@ -294,7 +451,7 @@ POST /sessions/:sessionDbId/summarize
|
||||
}
|
||||
```
|
||||
|
||||
#### 13. Session Status
|
||||
#### 19. Session Status
|
||||
```
|
||||
GET /sessions/:sessionDbId/status
|
||||
```
|
||||
@@ -309,7 +466,7 @@ GET /sessions/:sessionDbId/status
|
||||
}
|
||||
```
|
||||
|
||||
#### 14. Delete Session
|
||||
#### 20. Delete Session
|
||||
```
|
||||
DELETE /sessions/:sessionDbId
|
||||
```
|
||||
@@ -343,7 +500,7 @@ npm run worker:start
|
||||
npm run worker:stop
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
|
||||
# View logs
|
||||
npm run worker:logs
|
||||
|
||||
@@ -5,6 +5,10 @@ description: "Try experimental features like Endless Mode before they're release
|
||||
|
||||
# Beta Features
|
||||
|
||||
<Warning>
|
||||
**Endless Mode is experimental and not included in the stable release.** You must manually switch to the beta branch to try it. The efficiency projections below are based on theoretical modeling, not production measurements. Expect slower performance than standard mode and potential bugs.
|
||||
</Warning>
|
||||
|
||||
Claude-Mem offers a beta channel for users who want to try experimental features before they're released to the stable channel.
|
||||
|
||||
## Version Channel Switching
|
||||
@@ -77,19 +81,22 @@ Archive Memory (Transcript File):
|
||||
|
||||
This transforms O(N²) scaling into O(N) - linear instead of quadratic.
|
||||
|
||||
### Expected Results
|
||||
### Projected Results
|
||||
|
||||
Based on analysis of real sessions:
|
||||
Based on theoretical modeling (not production measurements):
|
||||
|
||||
- **Token savings**: ~95% reduction in context window usage
|
||||
- **Efficiency gain**: ~20x more tool uses before context exhaustion
|
||||
- **Token savings**: Significant reduction in context window usage
|
||||
- **Efficiency gain**: More tool uses before context exhaustion
|
||||
- **Quality preservation**: Observations cache the synthesis result, so no information is lost
|
||||
|
||||
### Caveats
|
||||
### Important Caveats
|
||||
|
||||
Endless Mode is experimental:
|
||||
Endless Mode is experimental and has significant limitations:
|
||||
|
||||
- **Adds latency** - Blocking hooks wait for observation generation (60-90s per tool use)
|
||||
- **Not in stable release** - You must manually switch to the beta branch to use this feature
|
||||
- **Still in development** - May have bugs, breaking changes, or incomplete functionality
|
||||
- **Slower than standard mode** - Blocking observation generation adds latency to each tool use
|
||||
- **Theoretical projections** - The efficiency claims above are based on simulations, not real-world production data
|
||||
- **Requires working database** - Observations must save successfully for transformation
|
||||
- **New architecture** - Less battle-tested than standard mode
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created
|
||||
|
||||
| Setting | Default | Description |
|
||||
|-------------------------------|---------------------------------|---------------------------------------|
|
||||
| `CLAUDE_MEM_MODEL` | `haiku` | AI model for processing observations |
|
||||
| `CLAUDE_MEM_MODEL` | `sonnet` | AI model for processing observations |
|
||||
| `CLAUDE_MEM_CONTEXT_OBSERVATIONS` | `50` | Number of observations to inject |
|
||||
| `CLAUDE_MEM_WORKER_PORT` | `37777` | Worker service port |
|
||||
| `CLAUDE_MEM_SKIP_TOOLS` | `ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion` | Comma-separated tools to exclude from observations |
|
||||
@@ -35,8 +35,8 @@ Configure which AI model processes your observations.
|
||||
|
||||
Shorthand model names automatically forward to the latest version:
|
||||
|
||||
- `haiku` - Fast, cost-efficient (default)
|
||||
- `sonnet` - Balanced
|
||||
- `haiku` - Fast, cost-efficient
|
||||
- `sonnet` - Balanced (default)
|
||||
- `opus` - Most capable
|
||||
|
||||
### Using the Interactive Script
|
||||
@@ -53,7 +53,7 @@ Edit `~/.claude-mem/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_MODEL": "haiku"
|
||||
"CLAUDE_MEM_MODEL": "sonnet"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -179,7 +179,9 @@ Claude-Mem supports switching between stable and beta versions via the web viewe
|
||||
|
||||
**Your memory data is preserved** when switching versions. Only the plugin code changes.
|
||||
|
||||
See [Beta Features](beta-features) for details on what's available in beta.
|
||||
<Note>
|
||||
Endless Mode is experimental and slower than standard mode. See [Beta Features](beta-features) for full details and important limitations.
|
||||
</Note>
|
||||
|
||||
## Worker Service Management
|
||||
|
||||
@@ -262,7 +264,7 @@ Token economics help you understand the value of cached observations vs. re-read
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| **Model** | haiku | AI model for generating observations |
|
||||
| **Model** | sonnet | AI model for generating observations |
|
||||
| **Worker Port** | 37777 | Port for background worker service |
|
||||
| **MCP search server** | true | Enable Model Context Protocol search tools |
|
||||
| **Include last summary** | false | Add previous session's summary to context |
|
||||
@@ -314,7 +316,7 @@ Edit `~/.claude-mem/settings.json`:
|
||||
|
||||
Then restart the worker:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
### Custom Model
|
||||
@@ -329,7 +331,7 @@ Edit `~/.claude-mem/settings.json`:
|
||||
Then restart the worker:
|
||||
```bash
|
||||
export CLAUDE_MEM_MODEL=opus
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
### Custom Skip Tools
|
||||
@@ -386,7 +388,7 @@ Enable debug logging:
|
||||
|
||||
```bash
|
||||
export DEBUG=claude-mem:*
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
npm run worker:logs
|
||||
```
|
||||
|
||||
@@ -404,7 +406,7 @@ npm run worker:logs
|
||||
|
||||
1. Restart worker after changes:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
2. Verify environment variables:
|
||||
@@ -420,7 +422,7 @@ npm run worker:logs
|
||||
|
||||
### Invalid Model Name
|
||||
|
||||
If you specify an invalid model name, the worker will fall back to `haiku` and log a warning.
|
||||
If you specify an invalid model name, the worker will fall back to `sonnet` and log a warning.
|
||||
|
||||
Valid shorthand models (forward to latest version):
|
||||
- haiku
|
||||
@@ -438,7 +440,7 @@ If port 37777 is already in use:
|
||||
|
||||
2. Restart worker:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
3. Verify new port:
|
||||
|
||||
@@ -165,7 +165,7 @@ npm run build
|
||||
1. Make changes to React components in `src/ui/viewer/`
|
||||
2. Build: `npm run build`
|
||||
3. Sync to installed plugin: `npm run sync-marketplace`
|
||||
4. Restart worker: `npm run worker:restart`
|
||||
4. Restart worker: `claude-mem restart`
|
||||
5. Refresh browser at http://localhost:37777
|
||||
|
||||
**Hot Reload**: Not currently supported. Full rebuild + restart required for changes.
|
||||
@@ -456,7 +456,7 @@ export async function createObservation(
|
||||
|
||||
```bash
|
||||
export DEBUG=claude-mem:*
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
npm run worker:logs
|
||||
```
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@
|
||||
"usage/search-tools",
|
||||
"usage/claude-desktop",
|
||||
"usage/private-tags",
|
||||
"beta-features"
|
||||
"usage/export-import",
|
||||
"beta-features",
|
||||
"endless-mode"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: "Endless Mode (Beta)"
|
||||
description: "Experimental biomimetic memory architecture for extended sessions"
|
||||
---
|
||||
|
||||
# Current State of Endless Mode
|
||||
|
||||
## Core Concept
|
||||
|
||||
Endless Mode is a **biomimetic memory architecture** that solves Claude's context window exhaustion problem. Instead of keeping full tool outputs in the context window (O(N²) complexity), it:
|
||||
|
||||
- Captures compressed observations after each tool use
|
||||
- Replaces transcripts with low token summaries
|
||||
- Achieves O(N) linear complexity
|
||||
- Maintains two-tier memory: working memory (compressed) + archive memory (full transcript on disk, maintained by default claude code functionality)
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**Status**: FUNCTIONAL BUT EXPERIMENTAL
|
||||
|
||||
**Current Branch**: `beta/endless-mode` (ahead of main)
|
||||
|
||||
**Recent Activity**:
|
||||
- Merged main branch changes
|
||||
- Resolved merge conflicts in save-hook, SessionStore, SessionRoutes
|
||||
- Updated documentation to remove misleading token reduction claims
|
||||
- Added important caveats about beta status
|
||||
|
||||
## Key Architecture Components
|
||||
|
||||
1. **Pre-Tool-Use Hook** - Tracks tool execution start, sends tool_use_id to worker
|
||||
2. **Save Hook (PostToolUse)** - **CRITICAL**: Blocks until observation is generated (110s timeout), injects compressed observation back into context
|
||||
3. **SessionManager.waitForNextObservation()** - Event-driven wait mechanism (no polling)
|
||||
4. **SDKAgent** - Generates observations via Agent SDK, emits completion events
|
||||
5. **Database** - Added `tool_use_id` column for observation correlation
|
||||
|
||||
## Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_ENDLESS_MODE": "false", // Default: disabled
|
||||
"CLAUDE_MEM_ENDLESS_WAIT_TIMEOUT_MS": "90000" // 90 second timeout
|
||||
}
|
||||
```
|
||||
|
||||
**Enable via**: Manual checkout of beta branch (see instructions below)
|
||||
|
||||
## Flow
|
||||
|
||||
```
|
||||
Tool Executes → Pre-Hook (track ID) → Tool Completes →
|
||||
Save-Hook (BLOCKS) → Worker processes → SDK generates observation →
|
||||
Event fired → Hook receives observation → Injects markdown →
|
||||
Clears input → Context reduced
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
From the documentation:
|
||||
- ⚠️ **Slower than standard mode** - Blocking adds latency
|
||||
- ⚠️ **Still in development** - May have bugs
|
||||
- ⚠️ **Not battle-tested** - New architecture
|
||||
- ⚠️ **Theoretical projections** - Efficiency gains not yet validated in production
|
||||
|
||||
## What's Working
|
||||
|
||||
- ✅ Synchronous observation injection
|
||||
- ✅ Event-driven wait mechanism
|
||||
- ✅ Token reduction via input clearing
|
||||
- ✅ Database schema with tool_use_id
|
||||
- ✅ Web UI for version switching
|
||||
- ✅ Graceful timeout fallbacks
|
||||
|
||||
## What's Not Ready
|
||||
|
||||
- ❌ Production validation of token savings
|
||||
- ❌ Comprehensive test coverage
|
||||
- ❌ Stable channel release
|
||||
- ❌ Performance benchmarks
|
||||
- ❌ Long-running session data
|
||||
|
||||
## How to Try Endless Mode
|
||||
|
||||
Endless Mode is currently only available on the beta branch. To try it:
|
||||
|
||||
```bash
|
||||
# Navigate to your claude-mem installation
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
|
||||
# Checkout the beta branch
|
||||
git checkout beta/endless-mode
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Restart the worker
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
**To return to stable:**
|
||||
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
git checkout main
|
||||
npm install
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
The implementation is architecturally complete and functional, but remains experimental pending production validation of the theoretical efficiency gains.
|
||||
@@ -534,7 +534,7 @@ npm run worker:status
|
||||
npm run worker:logs
|
||||
|
||||
# Restart
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
|
||||
# Stop
|
||||
npm run worker:stop
|
||||
|
||||
@@ -29,7 +29,7 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
- ⚙️ **Context Configuration** - Fine-grained control over what context gets injected
|
||||
- 🤖 **Automatic Operation** - No manual intervention required
|
||||
- 📊 **FTS5 Search** - Fast full-text search across observations
|
||||
- 🔗 **Citations** - Reference past decisions with `claude-mem://` URIs
|
||||
- 🔗 **Citations** - Reference past observations with IDs (access via http://localhost:37777/api/observation/{id} or view all in the web viewer at http://localhost:37777)
|
||||
|
||||
## How It Works
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ GET /api/context/recent?project=my-project&limit=3
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
CLAUDE_MEM_MODEL=claude-haiku-4-5 # Model for observations/summaries
|
||||
CLAUDE_MEM_MODEL=claude-sonnet-4-5 # Model for observations/summaries
|
||||
CLAUDE_MEM_CONTEXT_OBSERVATIONS=50 # Observations injected at SessionStart
|
||||
CLAUDE_MEM_WORKER_PORT=37777 # Worker service port
|
||||
CLAUDE_MEM_PYTHON_VERSION=3.13 # Python version for chroma-mcp
|
||||
@@ -57,7 +57,7 @@ CLAUDE_MEM_PYTHON_VERSION=3.13 # Python version for chroma-mcp
|
||||
```bash
|
||||
npm run build # Compile TypeScript (hooks + worker)
|
||||
npm run sync-marketplace # Copy to ~/.claude/plugins
|
||||
npm run worker:restart # Restart worker
|
||||
claude-mem restart # Restart worker
|
||||
npm run worker:logs # View worker logs
|
||||
npm run worker:status # Check worker status
|
||||
```
|
||||
|
||||
@@ -48,14 +48,14 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
4. Restart worker service:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
5. Check for port conflicts:
|
||||
```bash
|
||||
# If port 37777 is in use by another service
|
||||
export CLAUDE_MEM_WORKER_PORT=38000
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
### Theme Toggle Not Persisting
|
||||
@@ -110,7 +110,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
5. Restart worker and refresh browser:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
### Chroma/Python Dependency Issues (v5.0.0+)
|
||||
@@ -225,7 +225,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
3. Or use a different port:
|
||||
```bash
|
||||
export CLAUDE_MEM_WORKER_PORT=38000
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
4. Verify new port:
|
||||
@@ -282,7 +282,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
4. Restart worker:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## Hook Issues
|
||||
@@ -644,7 +644,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
2. Restart worker:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
3. Clean up old data (see "Database Too Large" above)
|
||||
@@ -721,7 +721,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
```bash
|
||||
export DEBUG=claude-mem:*
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
npm run worker:logs
|
||||
```
|
||||
|
||||
@@ -781,7 +781,7 @@ SELECT created_at, tool_name FROM observations ORDER BY created_at DESC LIMIT 10
|
||||
|
||||
**Cause**: Worker not running or port mismatch.
|
||||
|
||||
**Solution**: Restart worker with `npm run worker:restart`.
|
||||
**Solution**: Restart worker with `claude-mem restart`.
|
||||
|
||||
### "Database is locked"
|
||||
|
||||
|
||||
@@ -32,15 +32,14 @@ curl http://localhost:37777/api/health
|
||||
|
||||
Download the skill package from the repository:
|
||||
|
||||
<Card title="mem-search.zip" icon="download" href="https://github.com/thedotmack/claude-mem/raw/main/desktop-skill/mem-search.zip">
|
||||
<Card title="mem-search.zip" icon="download" href="https://github.com/thedotmack/claude-mem/raw/main/plugin/skills/mem-search.zip">
|
||||
Download the mem-search skill for Claude Desktop
|
||||
</Card>
|
||||
|
||||
Or build from source:
|
||||
|
||||
```bash
|
||||
cd desktop-skill
|
||||
zip -r mem-search.zip Skill.md
|
||||
npm run build # Generates plugin/skills/mem-search.zip
|
||||
```
|
||||
|
||||
### Step 2: Install in Claude Desktop
|
||||
@@ -110,20 +109,21 @@ Once installed, the skill auto-activates when you ask about past work:
|
||||
"Show me changes to worker-service.ts"
|
||||
```
|
||||
|
||||
## Available Search Tools
|
||||
## Available MCP Tools
|
||||
|
||||
The skill provides access to these MCP tools:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `search` | Unified search across all memory types |
|
||||
| `decisions` | Find architectural/design decisions |
|
||||
| `changes` | Find code changes and refactorings |
|
||||
| `timeline` | Get observations around a specific point in time |
|
||||
| `find_by_file` | Find observations for specific files |
|
||||
| `find_by_type` | Filter by type (decision, bugfix, feature, refactor, discovery, change) |
|
||||
| `find_by_concept` | Find by concept tags |
|
||||
| `how_it_works` | Understand system architecture and design patterns |
|
||||
| `search` | Unified search across observations, sessions, and prompts |
|
||||
| `timeline` | Get chronological context around a query or observation ID |
|
||||
| `get_observation` | Fetch a single observation by ID |
|
||||
| `get_observations` | Fetch multiple observations efficiently |
|
||||
| `get_session` | Fetch session summary by ID |
|
||||
| `get_prompt` | Fetch user prompt by ID |
|
||||
| `get_recent_context` | Get recent timeline items |
|
||||
| `get_context_timeline` | Get timeline around a specific observation |
|
||||
| `help` | Load detailed usage instructions |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
---
|
||||
title: "Memory Export/Import"
|
||||
description: "Share knowledge across claude-mem installations with duplicate prevention"
|
||||
---
|
||||
|
||||
# Memory Export/Import Scripts
|
||||
|
||||
Share your claude-mem knowledge with other users! These scripts allow you to export specific memories (observations, sessions, summaries, and prompts) and import them into another claude-mem installation.
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Share Windows compatibility knowledge** with Windows users
|
||||
- **Share bug fix patterns** with contributors
|
||||
- **Share project-specific learnings** across teams
|
||||
- **Backup specific memory sets** for safekeeping
|
||||
|
||||
## How It Works
|
||||
|
||||
### Export Script
|
||||
|
||||
Searches the database using **hybrid search** (combines ChromaDB vector embeddings with FTS5 full-text search) and exports all matching:
|
||||
- **Observations** - Individual learnings and discoveries
|
||||
- **Sessions** - Session metadata
|
||||
- **Summaries** - Session summaries
|
||||
- **Prompts** - User prompts that led to the work
|
||||
|
||||
Output is a portable JSON file that can be shared.
|
||||
|
||||
> **Privacy Note:** Export files contain all matching memory data in plain text. Review exports before sharing to ensure no sensitive information (API keys, passwords, private paths) is included.
|
||||
|
||||
### Import Script
|
||||
|
||||
Imports memories with **duplicate prevention**:
|
||||
- Checks if each record already exists before inserting
|
||||
- Skips duplicates automatically
|
||||
- Maintains data integrity with transactional imports
|
||||
- Reports what was imported vs. skipped
|
||||
|
||||
**Duplicate Detection Strategy:**
|
||||
- **Sessions**: By `claude_session_id` (unique)
|
||||
- **Summaries**: By `sdk_session_id` (unique)
|
||||
- **Observations**: By `sdk_session_id` + `title` + `created_at_epoch` (composite)
|
||||
- **Prompts**: By `claude_session_id` + `prompt_number` (composite)
|
||||
|
||||
## Usage
|
||||
|
||||
### Export Memories
|
||||
|
||||
```bash
|
||||
# Export all Windows-related memories
|
||||
npx tsx scripts/export-memories.ts "windows" windows-memories.json
|
||||
|
||||
# Export bug fixes
|
||||
npx tsx scripts/export-memories.ts "bugfix" bugfixes.json
|
||||
|
||||
# Export specific feature work
|
||||
npx tsx scripts/export-memories.ts "progressive disclosure" progressive-disclosure.json
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `<query>` - Search query (uses hybrid semantic + full-text search)
|
||||
2. `<output-file>` - Output JSON file path
|
||||
3. `--project=name` - Optional: filter results to a specific project
|
||||
|
||||
**Example Output:**
|
||||
```
|
||||
🔍 Searching for: "windows"
|
||||
✅ Found 54 observations
|
||||
✅ Found 12 sessions
|
||||
✅ Found 12 summaries
|
||||
✅ Found 7 prompts
|
||||
|
||||
📦 Export complete!
|
||||
📄 Output: windows-memories.json
|
||||
📊 Stats:
|
||||
• 54 observations
|
||||
• 12 sessions
|
||||
• 12 summaries
|
||||
• 7 prompts
|
||||
```
|
||||
|
||||
### Import Memories
|
||||
|
||||
```bash
|
||||
# Import from an export file
|
||||
npx tsx scripts/import-memories.ts windows-memories.json
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
1. `<input-file>` - Input JSON file (from export script)
|
||||
|
||||
**Example Output:**
|
||||
```
|
||||
📦 Import file: windows-memories.json
|
||||
📅 Exported: 2025-12-10T23:45:00.000Z
|
||||
🔍 Query: "windows"
|
||||
📊 Contains:
|
||||
• 54 observations
|
||||
• 12 sessions
|
||||
• 12 summaries
|
||||
• 7 prompts
|
||||
|
||||
🔄 Importing sessions...
|
||||
✅ Imported: 12, Skipped: 0
|
||||
🔄 Importing summaries...
|
||||
✅ Imported: 12, Skipped: 0
|
||||
🔄 Importing observations...
|
||||
✅ Imported: 54, Skipped: 0
|
||||
🔄 Importing prompts...
|
||||
✅ Imported: 7, Skipped: 0
|
||||
|
||||
✅ Import complete!
|
||||
📊 Summary:
|
||||
Sessions: 12 imported, 0 skipped
|
||||
Summaries: 12 imported, 0 skipped
|
||||
Observations: 54 imported, 0 skipped
|
||||
Prompts: 7 imported, 0 skipped
|
||||
```
|
||||
|
||||
### Re-importing (Duplicate Prevention)
|
||||
|
||||
If you run the import again on the same file, duplicates are automatically skipped:
|
||||
|
||||
```
|
||||
🔄 Importing sessions...
|
||||
✅ Imported: 0, Skipped: 12 ← All skipped (already exist)
|
||||
🔄 Importing summaries...
|
||||
✅ Imported: 0, Skipped: 12
|
||||
🔄 Importing observations...
|
||||
✅ Imported: 0, Skipped: 54
|
||||
🔄 Importing prompts...
|
||||
✅ Imported: 0, Skipped: 7
|
||||
```
|
||||
|
||||
## Sharing Memories
|
||||
|
||||
### For Export Authors
|
||||
|
||||
1. **Export your memories:**
|
||||
```bash
|
||||
npx tsx scripts/export-memories.ts "windows" windows-memories.json
|
||||
```
|
||||
|
||||
2. **Share the JSON file** via:
|
||||
- GitHub gist
|
||||
- Project repository (`shared-memories/`)
|
||||
- Direct file transfer
|
||||
- Package in releases
|
||||
|
||||
3. **Document what's included:**
|
||||
- What query was used
|
||||
- What knowledge is contained
|
||||
- Who might benefit from it
|
||||
|
||||
### For Import Users
|
||||
|
||||
1. **Download the export file** to your local machine
|
||||
|
||||
2. **Review what's in it** (optional):
|
||||
```bash
|
||||
cat windows-memories.json | jq '.totalObservations, .totalSessions'
|
||||
```
|
||||
|
||||
3. **Import into your database:**
|
||||
```bash
|
||||
npx tsx scripts/import-memories.ts windows-memories.json
|
||||
```
|
||||
|
||||
4. **Verify import** by searching:
|
||||
```bash
|
||||
curl "http://localhost:37777/api/search?query=windows&format=index&limit=10"
|
||||
```
|
||||
|
||||
## JSON Export Format
|
||||
|
||||
```json
|
||||
{
|
||||
"exportedAt": "2025-12-10T23:45:00.000Z",
|
||||
"exportedAtEpoch": 1733876700000,
|
||||
"query": "windows",
|
||||
"totalObservations": 54,
|
||||
"totalSessions": 12,
|
||||
"totalSummaries": 12,
|
||||
"totalPrompts": 7,
|
||||
"observations": [ /* array of observation objects */ ],
|
||||
"sessions": [ /* array of session objects */ ],
|
||||
"summaries": [ /* array of summary objects */ ],
|
||||
"prompts": [ /* array of prompt objects */ ]
|
||||
}
|
||||
```
|
||||
|
||||
## Safety Features
|
||||
|
||||
✅ **Duplicate Prevention** - Won't re-import existing records
|
||||
✅ **Transactional** - All-or-nothing imports (database stays consistent)
|
||||
✅ **Read-only Export** - Export script opens database in read-only mode
|
||||
✅ **Dependency Ordering** - Sessions imported before observations/summaries
|
||||
✅ **Validation** - Checks database exists before starting
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Export by Project
|
||||
|
||||
```bash
|
||||
# Export only claude-mem project memories
|
||||
npx tsx scripts/export-memories.ts "bugfix" bugfixes.json --project=claude-mem
|
||||
|
||||
# Export all memories for a specific project
|
||||
npx tsx scripts/export-memories.ts "" all-project.json --project=my-app
|
||||
```
|
||||
|
||||
### Export by Type
|
||||
|
||||
```bash
|
||||
# Export only discoveries
|
||||
npx tsx scripts/export-memories.ts "type:discovery" discoveries.json
|
||||
|
||||
# Export only bug fixes
|
||||
npx tsx scripts/export-memories.ts "type:bugfix" bugfixes.json
|
||||
```
|
||||
|
||||
### Export by Date Range
|
||||
|
||||
You can filter the export after exporting:
|
||||
|
||||
```bash
|
||||
# Export all memories, then filter manually with jq
|
||||
npx tsx scripts/export-memories.ts "" all-memories.json
|
||||
cat all-memories.json | jq '.observations |= map(select(.created_at_epoch > 1700000000000))' > recent-memories.json
|
||||
```
|
||||
|
||||
### Combine Multiple Exports
|
||||
|
||||
```bash
|
||||
# Export different topics
|
||||
npx tsx scripts/export-memories.ts "windows" windows.json
|
||||
npx tsx scripts/export-memories.ts "linux" linux.json
|
||||
|
||||
# Import both
|
||||
npx tsx scripts/import-memories.ts windows.json
|
||||
npx tsx scripts/import-memories.ts linux.json
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Not Found
|
||||
|
||||
```
|
||||
❌ Database not found at: /Users/you/.claude-mem/claude-mem.db
|
||||
```
|
||||
|
||||
**Solution:** Make sure claude-mem is installed and has been run at least once.
|
||||
|
||||
### Import File Not Found
|
||||
|
||||
```
|
||||
❌ Input file not found: windows-memories.json
|
||||
```
|
||||
|
||||
**Solution:** Check the file path. Use absolute paths if needed.
|
||||
|
||||
### Partial Import
|
||||
|
||||
If import fails mid-way, the transaction is rolled back - your database remains unchanged. Fix the issue and try again.
|
||||
|
||||
## Contributing Memory Sets
|
||||
|
||||
If you've exported valuable knowledge that others might benefit from:
|
||||
|
||||
1. Create a PR to the `shared-memories/` directory
|
||||
2. Include a README describing what's in the export
|
||||
3. Tag with relevant keywords (windows, linux, bugfix, etc.)
|
||||
4. Community members can then import your knowledge!
|
||||
|
||||
## Examples of Useful Exports
|
||||
|
||||
**Windows Compatibility Knowledge:**
|
||||
```bash
|
||||
npx tsx scripts/export-memories.ts "windows compatibility installation" windows-fixes.json
|
||||
```
|
||||
|
||||
**Progressive Disclosure Architecture:**
|
||||
```bash
|
||||
npx tsx scripts/export-memories.ts "progressive disclosure architecture token" pd-patterns.json
|
||||
```
|
||||
|
||||
**Bug Fix Patterns:**
|
||||
```bash
|
||||
npx tsx scripts/export-memories.ts "bugfix error handling" bugfix-patterns.json
|
||||
```
|
||||
|
||||
**Performance Optimization:**
|
||||
```bash
|
||||
npx tsx scripts/export-memories.ts "performance optimization caching" perf-tips.json
|
||||
```
|
||||
@@ -86,7 +86,7 @@ npm run worker:start
|
||||
npm run worker:stop
|
||||
|
||||
# Restart worker service
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
|
||||
# View worker logs
|
||||
npm run worker:logs
|
||||
|
||||
@@ -176,7 +176,7 @@ This design ensures that private content never reaches the database, search indi
|
||||
1. Verify correct syntax: `<private>content</private>`
|
||||
2. Check `~/.claude-mem/silent.log` for errors
|
||||
3. Ensure worker is running: `npm run worker:status`
|
||||
4. Restart worker: `npm run worker:restart`
|
||||
4. Restart worker: `claude-mem restart`
|
||||
|
||||
### Partial Content Stored
|
||||
|
||||
|
||||
@@ -246,11 +246,10 @@ authentication for better scalability and stateless design...
|
||||
|
||||
## Citations
|
||||
|
||||
All search results include citations using the `claude-mem://` URI scheme:
|
||||
All search results include observation IDs that can be accessed via the HTTP API:
|
||||
|
||||
- `claude-mem://observation/123` - Specific observation
|
||||
- `claude-mem://session/abc-456` - Specific session
|
||||
- `claude-mem://user-prompt/789` - Specific user prompt
|
||||
- `http://localhost:37777/api/observation/{id}` - Get specific observation by ID
|
||||
- View all observations in the web viewer at `http://localhost:37777`
|
||||
|
||||
These citations enable referencing specific historical context in your work.
|
||||
|
||||
@@ -365,7 +364,7 @@ If search isn't working, check the worker service:
|
||||
|
||||
```bash
|
||||
npm run worker:status # Check worker status
|
||||
npm run worker:restart # Restart if needed
|
||||
claude-mem restart # Restart if needed
|
||||
npm run worker:logs # View logs
|
||||
```
|
||||
|
||||
|
||||
Generated
-3882
File diff suppressed because it is too large
Load Diff
+11
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.1.1",
|
||||
"version": "7.4.4",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
@@ -32,6 +32,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node scripts/build-hooks.js",
|
||||
"build-and-sync": "npm run build && npm run sync-marketplace && sleep 1 && cd ~/.claude/plugins/marketplaces/thedotmack && npm run worker:restart",
|
||||
"test": "vitest",
|
||||
"test:parser": "npx tsx src/sdk/parser.test.ts",
|
||||
"test:context": "echo '{\"session_id\":\"test-'$(date +%s)'\",\"cwd\":\"'$(pwd)'\",\"source\":\"startup\"}' | node plugin/scripts/context-hook.js 2>/dev/null",
|
||||
@@ -43,11 +44,18 @@
|
||||
"worker:stop": "bun plugin/scripts/worker-cli.js stop",
|
||||
"worker:restart": "bun plugin/scripts/worker-cli.js restart",
|
||||
"worker:status": "bun plugin/scripts/worker-cli.js status",
|
||||
"worker:logs": "tail -f ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log",
|
||||
"worker:logs": "tail -n 50 ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log",
|
||||
"changelog:generate": "node scripts/generate-changelog.js",
|
||||
"discord:notify": "node scripts/discord-release-notify.js",
|
||||
"usage:analyze": "node scripts/analyze-usage.js",
|
||||
"usage:today": "node scripts/analyze-usage.js $(date +%Y-%m-%d)",
|
||||
"translate-readme": "npx tsx scripts/translate-readme/cli.ts -v README.md zh ko ja"
|
||||
"translate-readme": "bun scripts/translate-readme/cli.ts -v -o docs/i18n README.md",
|
||||
"translate:tier1": "npm run translate-readme -- zh ja pt-br ko es de fr",
|
||||
"translate:tier2": "npm run translate-readme -- he ar ru pl cs nl tr uk",
|
||||
"translate:tier3": "npm run translate-readme -- vi id th hi bn ro sv",
|
||||
"translate:tier4": "npm run translate-readme -- it el hu fi da no",
|
||||
"translate:all": "npm run translate:tier1 && npm run translate:tier2 && npm run translate:tier3 && npm run translate:tier4",
|
||||
"bug-report": "npx tsx scripts/bug-report/cli.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.67",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.1.1",
|
||||
"version": "7.4.4",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"claude-mem-search": {
|
||||
"mem-search": {
|
||||
"type": "stdio",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/mcp-server.cjs"
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && bun \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && node \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
|
||||
"timeout": 300
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
@@ -23,7 +23,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
@@ -35,7 +35,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
@@ -46,7 +46,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
@@ -57,7 +57,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js\"",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem-plugin",
|
||||
"version": "7.1.1",
|
||||
"version": "7.4.3",
|
||||
"private": true,
|
||||
"description": "Runtime dependencies for claude-mem bundled hooks",
|
||||
"type": "module",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -24,18 +24,56 @@ function isBunInstalled() {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0;
|
||||
if (result.status === 0) return true;
|
||||
} catch {
|
||||
return false;
|
||||
// PATH check failed, try common installation paths
|
||||
}
|
||||
|
||||
// Check common installation paths (handles fresh installs before PATH reload)
|
||||
const bunPaths = IS_WINDOWS
|
||||
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
|
||||
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
|
||||
|
||||
return bunPaths.some(existsSync);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Bun executable path (from PATH or common install locations)
|
||||
*/
|
||||
function getBunPath() {
|
||||
// Try PATH first
|
||||
try {
|
||||
const result = spawnSync('bun', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
if (result.status === 0) return 'bun';
|
||||
} catch {
|
||||
// Not in PATH
|
||||
}
|
||||
|
||||
// Check common installation paths
|
||||
const bunPaths = IS_WINDOWS
|
||||
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
|
||||
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
|
||||
|
||||
for (const bunPath of bunPaths) {
|
||||
if (existsSync(bunPath)) return bunPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Bun version if installed
|
||||
*/
|
||||
function getBunVersion() {
|
||||
const bunPath = getBunPath();
|
||||
if (!bunPath) return null;
|
||||
|
||||
try {
|
||||
const result = spawnSync('bun', ['--version'], {
|
||||
const result = spawnSync(bunPath, ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
@@ -56,10 +94,17 @@ function isUvInstalled() {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0;
|
||||
if (result.status === 0) return true;
|
||||
} catch {
|
||||
return false;
|
||||
// PATH check failed, try common installation paths
|
||||
}
|
||||
|
||||
// Check common installation paths (handles fresh installs before PATH reload)
|
||||
const uvPaths = IS_WINDOWS
|
||||
? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')]
|
||||
: [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv'];
|
||||
|
||||
return uvPaths.some(existsSync);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,6 +253,97 @@ function installUv() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the claude-mem CLI command to PATH
|
||||
* Creates a wrapper script in ~/.local/bin (Unix) or %LOCALAPPDATA%\Programs\claude-mem (Windows)
|
||||
*/
|
||||
function installCLI() {
|
||||
const CLI_NAME = 'claude-mem';
|
||||
const WORKER_CLI = join(ROOT, 'plugin', 'scripts', 'worker-cli.js');
|
||||
|
||||
if (IS_WINDOWS) {
|
||||
// Windows: Create .cmd file in LocalAppData
|
||||
const cliDir = join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'Programs', 'claude-mem');
|
||||
const cliPath = join(cliDir, `${CLI_NAME}.cmd`);
|
||||
const markerPath = join(cliDir, '.cli-installed');
|
||||
|
||||
// Skip if already installed
|
||||
if (existsSync(markerPath)) return;
|
||||
|
||||
try {
|
||||
// Create directory if needed
|
||||
if (!existsSync(cliDir)) {
|
||||
execSync(`mkdir "${cliDir}"`, { stdio: 'ignore', shell: true });
|
||||
}
|
||||
|
||||
// Get Bun path for the wrapper
|
||||
const bunPath = getBunPath() || 'bun';
|
||||
|
||||
// Create the wrapper script
|
||||
const cmdContent = `@echo off
|
||||
"${bunPath}" "${WORKER_CLI}" %*
|
||||
`;
|
||||
writeFileSync(cliPath, cmdContent);
|
||||
writeFileSync(markerPath, new Date().toISOString());
|
||||
|
||||
console.error(`✅ CLI installed: ${cliPath}`);
|
||||
console.error('');
|
||||
console.error('📋 Add to PATH (run once in PowerShell as Admin):');
|
||||
console.error(` [Environment]::SetEnvironmentVariable("Path", $env:Path + ";${cliDir}", "User")`);
|
||||
console.error('');
|
||||
console.error(' Then restart your terminal and use: claude-mem start|stop|restart|status');
|
||||
} catch (error) {
|
||||
console.error(`⚠️ Could not install CLI: ${error.message}`);
|
||||
console.error(` You can still use: bun "${WORKER_CLI}" <command>`);
|
||||
}
|
||||
} else {
|
||||
// Unix: Create shell script in ~/.local/bin
|
||||
const cliDir = join(homedir(), '.local', 'bin');
|
||||
const cliPath = join(cliDir, CLI_NAME);
|
||||
const markerPath = join(ROOT, '.cli-installed');
|
||||
|
||||
// Skip if already installed
|
||||
if (existsSync(markerPath) && existsSync(cliPath)) return;
|
||||
|
||||
try {
|
||||
// Create directory if needed
|
||||
if (!existsSync(cliDir)) {
|
||||
execSync(`mkdir -p "${cliDir}"`, { stdio: 'ignore', shell: true });
|
||||
}
|
||||
|
||||
// Get Bun path for the wrapper
|
||||
const bunPath = getBunPath() || 'bun';
|
||||
|
||||
// Create the wrapper script
|
||||
const shContent = `#!/usr/bin/env bash
|
||||
# claude-mem CLI wrapper - manages the worker service
|
||||
exec "${bunPath}" "${WORKER_CLI}" "$@"
|
||||
`;
|
||||
writeFileSync(cliPath, shContent, { mode: 0o755 });
|
||||
writeFileSync(markerPath, new Date().toISOString());
|
||||
|
||||
console.error(`✅ CLI installed: ${cliPath}`);
|
||||
|
||||
// Check if ~/.local/bin is in PATH
|
||||
const pathDirs = (process.env.PATH || '').split(':');
|
||||
const localBinInPath = pathDirs.some(p => p === cliDir || p === '$HOME/.local/bin' || p.endsWith('/.local/bin'));
|
||||
|
||||
if (!localBinInPath) {
|
||||
console.error('');
|
||||
console.error('📋 Add to PATH (add to ~/.bashrc or ~/.zshrc):');
|
||||
console.error(' export PATH="$HOME/.local/bin:$PATH"');
|
||||
console.error('');
|
||||
console.error(' Then restart your terminal and use: claude-mem start|stop|restart|status');
|
||||
} else {
|
||||
console.error(' Usage: claude-mem start|stop|restart|status');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`⚠️ Could not install CLI: ${error.message}`);
|
||||
console.error(` You can still use: bun "${WORKER_CLI}" <command>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if dependencies need to be installed
|
||||
*/
|
||||
@@ -223,15 +359,46 @@ function needsInstall() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Install dependencies using Bun
|
||||
* Install dependencies using Bun with npm fallback
|
||||
*
|
||||
* Bun has issues with npm alias packages (e.g., string-width-cjs, strip-ansi-cjs)
|
||||
* that are defined in package-lock.json. When bun fails with 404 errors for these
|
||||
* packages, we fall back to npm which handles aliases correctly.
|
||||
*/
|
||||
function installDeps() {
|
||||
const bunPath = getBunPath();
|
||||
if (!bunPath) {
|
||||
throw new Error('Bun executable not found');
|
||||
}
|
||||
|
||||
console.error('📦 Installing dependencies with Bun...');
|
||||
|
||||
// Quote path for Windows paths with spaces
|
||||
const bunCmd = IS_WINDOWS && bunPath.includes(' ') ? `"${bunPath}"` : bunPath;
|
||||
|
||||
let bunSucceeded = false;
|
||||
try {
|
||||
execSync('bun install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
execSync(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
bunSucceeded = true;
|
||||
} catch {
|
||||
// Retry with force flag
|
||||
execSync('bun install --force', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
// First attempt failed, try with force flag
|
||||
try {
|
||||
execSync(`${bunCmd} install --force`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
bunSucceeded = true;
|
||||
} catch {
|
||||
// Bun failed completely, will try npm fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to npm if bun failed (handles npm alias packages correctly)
|
||||
if (!bunSucceeded) {
|
||||
console.error('⚠️ Bun install failed, falling back to npm...');
|
||||
console.error(' (This can happen with npm alias packages like *-cjs)');
|
||||
try {
|
||||
execSync('npm install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
} catch (npmError) {
|
||||
throw new Error('Both bun and npm install failed: ' + npmError.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Write version marker
|
||||
@@ -275,6 +442,9 @@ try {
|
||||
installDeps();
|
||||
console.error('✅ Dependencies installed');
|
||||
}
|
||||
|
||||
// Step 4: Install CLI to PATH
|
||||
installCLI();
|
||||
} catch (e) {
|
||||
console.error('❌ Installation failed:', e.message);
|
||||
process.exit(1);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+423
-242
File diff suppressed because one or more lines are too long
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env bun
|
||||
"use strict";var m=Object.create;var w=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var f=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var g=(e,i,n,o)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of I(i))!x.call(e,s)&&s!==n&&w(e,s,{get:()=>i[s],enumerable:!(o=u(i,s))||o.enumerable});return e};var k=(e,i,n)=>(n=e!=null?m(f(e)):{},g(i||!e||!e.__esModule?w(n,"default",{value:e,enumerable:!0}):n,e));var c=require("child_process"),p=k(require("path"),1),y=process.platform==="win32",P=__dirname,l=p.default.join(P,"worker-service.cjs"),t=null,a=!1;function r(e){let i=new Date().toISOString();console.log(`[${i}] [wrapper] ${e}`)}function h(){r(`Spawning inner worker: ${l}`),t=(0,c.spawn)(process.execPath,[l],{stdio:["inherit","inherit","inherit","ipc"],env:{...process.env,CLAUDE_MEM_MANAGED:"true"},cwd:p.default.dirname(l)}),t.on("message",async e=>{(e.type==="restart"||e.type==="shutdown")&&(r(`${e.type} requested by inner`),a=!0,await d(),r("Exiting wrapper"),process.exit(0))}),t.on("exit",(e,i)=>{r(`Inner exited with code=${e}, signal=${i}`),t=null,a||(r("Inner exited unexpectedly, wrapper exiting (hooks will restart if needed)"),process.exit(e??1))}),t.on("error",e=>{r(`Inner error: ${e.message}`)})}async function d(){if(!t||!t.pid){r("No inner process to kill");return}let e=t.pid;if(r(`Killing inner process tree (pid=${e})`),y)try{(0,c.execSync)(`taskkill /PID ${e} /T /F`,{timeout:1e4,stdio:"ignore"}),r(`taskkill completed for pid=${e}`)}catch(i){r(`taskkill failed (process may be dead): ${i}`)}else{t.kill("SIGTERM");let i=new Promise(o=>{if(!t){o();return}t.on("exit",()=>o())}),n=new Promise(o=>setTimeout(()=>o(),5e3));await Promise.race([i,n]),t&&!t.killed&&(r("Inner did not exit gracefully, force killing"),t.kill("SIGKILL"))}await S(e,5e3),t=null,r("Inner process terminated")}async function S(e,i){let n=Date.now();for(;Date.now()-n<i;)try{process.kill(e,0),await new Promise(o=>setTimeout(o,100))}catch{return}r(`Timeout waiting for process ${e} to exit`)}process.on("SIGTERM",async()=>{r("Wrapper received SIGTERM"),a=!0,await d(),process.exit(0)});process.on("SIGINT",async()=>{r("Wrapper received SIGINT"),a=!0,await d(),process.exit(0)});r("Wrapper starting");h();
|
||||
Binary file not shown.
@@ -10,6 +10,7 @@ Search past work across all sessions. Simple workflow: search → get IDs → fe
|
||||
## When to Use
|
||||
|
||||
Use when users ask about PREVIOUS sessions (not current conversation):
|
||||
|
||||
- "Did we already fix this?"
|
||||
- "How did we solve X last time?"
|
||||
- "What happened last week?"
|
||||
@@ -19,47 +20,57 @@ Use when users ask about PREVIOUS sessions (not current conversation):
|
||||
**ALWAYS follow this exact flow:**
|
||||
|
||||
1. **Search** - Get an index of results with IDs
|
||||
2. **Timeline** (optional) - Get context around top results to understand what was happening
|
||||
2. **Timeline** - Get context around top results to understand what was happening
|
||||
3. **Review** - Look at titles/dates/context, pick relevant IDs
|
||||
4. **Fetch** - Get full details ONLY for those IDs
|
||||
|
||||
### Step 1: Search Everything
|
||||
|
||||
```bash
|
||||
curl "http://localhost:37777/api/search?query=authentication&format=index&limit=5"
|
||||
```
|
||||
Use the `search` MCP tool:
|
||||
|
||||
**Required parameters:**
|
||||
|
||||
- `query` - Search term
|
||||
- `format=index` - ALWAYS start with index (lightweight)
|
||||
- `limit=5` - Start small (3-5 results)
|
||||
- `limit: 20` - You can request large indexes as necessary
|
||||
- `project` - Project name (required)
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
search(query="authentication", limit=20, project="my-project")
|
||||
```
|
||||
|
||||
**Returns:**
|
||||
```
|
||||
1. [feature] Added JWT authentication
|
||||
Date: 11/17/2025, 3:48:45 PM
|
||||
ID: 11131
|
||||
|
||||
2. [bugfix] Fixed auth token expiration
|
||||
Date: 11/16/2025, 2:15:22 PM
|
||||
ID: 10942
|
||||
```
|
||||
| ID | Time | T | Title | Read | Work |
|
||||
|----|------|---|-------|------|------|
|
||||
| #11131 | 3:48 PM | 🟣 | Added JWT authentication | ~75 | 🛠️ 450 |
|
||||
| #10942 | 2:15 PM | 🔴 | Fixed auth token expiration | ~50 | 🛠️ 200 |
|
||||
```
|
||||
|
||||
### Step 2: Get Timeline Context (Optional)
|
||||
### Step 2: Get Timeline Context
|
||||
|
||||
When you need to understand "what was happening" around a result:
|
||||
You MUST understand "what was happening" around a result.
|
||||
|
||||
```bash
|
||||
# Get timeline around an observation ID
|
||||
curl "http://localhost:37777/api/timeline?anchor=11131&depth_before=3&depth_after=3"
|
||||
Use the `timeline` MCP tool:
|
||||
|
||||
# Or use query to find + get timeline in one step
|
||||
curl "http://localhost:37777/api/timeline?query=authentication&depth_before=3&depth_after=3"
|
||||
**Example with observation ID:**
|
||||
|
||||
```
|
||||
timeline(anchor=11131, depth_before=3, depth_after=3, project="my-project")
|
||||
```
|
||||
|
||||
**Example with query (finds anchor automatically):**
|
||||
|
||||
```
|
||||
timeline(query="authentication", depth_before=3, depth_after=3, project="my-project")
|
||||
```
|
||||
|
||||
**Returns exactly `depth_before + 1 + depth_after` items** - observations, sessions, and prompts interleaved chronologically around the anchor.
|
||||
|
||||
**When to use:**
|
||||
|
||||
- User asks "what was happening when..."
|
||||
- Need to understand sequence of events
|
||||
- Want broader context around a specific observation
|
||||
@@ -70,34 +81,68 @@ Review the index results (and timeline if used). Identify which IDs are actually
|
||||
|
||||
### Step 4: Fetch by ID
|
||||
|
||||
For each relevant ID, fetch full details:
|
||||
For each relevant ID, fetch full details using MCP tools:
|
||||
|
||||
```bash
|
||||
# Fetch observation
|
||||
curl "http://localhost:37777/api/observation/11131"
|
||||
**Fetch multiple observations (ALWAYS use for 2+ IDs):**
|
||||
|
||||
# Fetch session
|
||||
curl "http://localhost:37777/api/session/2005"
|
||||
```
|
||||
get_observations(ids=[11131, 10942, 10855])
|
||||
```
|
||||
|
||||
# Fetch prompt
|
||||
curl "http://localhost:37777/api/prompt/5421"
|
||||
**With ordering and limit:**
|
||||
|
||||
```
|
||||
get_observations(
|
||||
ids=[11131, 10942, 10855],
|
||||
orderBy="date_desc",
|
||||
limit=10,
|
||||
project="my-project"
|
||||
)
|
||||
```
|
||||
|
||||
**Fetch single observation (only when fetching exactly 1):**
|
||||
|
||||
```
|
||||
get_observation(id=11131)
|
||||
```
|
||||
|
||||
**Fetch session:**
|
||||
|
||||
```
|
||||
get_session(id=2005) # Just the number from S2005
|
||||
```
|
||||
|
||||
**Fetch prompt:**
|
||||
|
||||
```
|
||||
get_prompt(id=5421)
|
||||
```
|
||||
|
||||
**ID formats:**
|
||||
|
||||
- Observations: Just the number (11131)
|
||||
- Sessions: Just the number (2005) from "S2005"
|
||||
- Prompts: Just the number (5421)
|
||||
|
||||
**Batch optimization:**
|
||||
|
||||
- **ALWAYS use `get_observations` for 2+ observations**
|
||||
- 10-100x more efficient than individual fetches
|
||||
- Single HTTP request vs N requests
|
||||
- Returns all results in one response
|
||||
- Supports ordering and filtering
|
||||
|
||||
## Search Parameters
|
||||
|
||||
**Basic:**
|
||||
|
||||
- `query` - What to search for (required)
|
||||
- `format` - "index" or "full" (always use "index" first)
|
||||
- `limit` - How many results (default 5, max 100)
|
||||
- `limit` - How many results (default 20)
|
||||
- `project` - Filter by project name (required)
|
||||
|
||||
**Filters (optional):**
|
||||
|
||||
- `type` - Filter to "observations", "sessions", or "prompts"
|
||||
- `project` - Filter by project name
|
||||
- `dateStart` - Start date (YYYY-MM-DD or epoch timestamp)
|
||||
- `dateEnd` - End date (YYYY-MM-DD or epoch timestamp)
|
||||
- `obs_type` - Filter observations by type (comma-separated): bugfix, feature, decision, discovery, change
|
||||
@@ -105,39 +150,180 @@ curl "http://localhost:37777/api/prompt/5421"
|
||||
## Examples
|
||||
|
||||
**Find recent bug fixes:**
|
||||
```bash
|
||||
curl "http://localhost:37777/api/search?query=bug&type=observations&obs_type=bugfix&format=index&limit=5"
|
||||
|
||||
Use the `search` MCP tool with filters:
|
||||
|
||||
```
|
||||
search(query="bug", type="observations", obs_type="bugfix", limit=20, project="my-project")
|
||||
```
|
||||
|
||||
**Find what happened last week:**
|
||||
```bash
|
||||
curl "http://localhost:37777/api/search?query=&type=observations&dateStart=2025-11-11&format=index&limit=10"
|
||||
|
||||
Use date filters:
|
||||
|
||||
```
|
||||
search(type="observations", dateStart="2025-11-11", limit=20, project="my-project")
|
||||
```
|
||||
|
||||
**Search everything:**
|
||||
```bash
|
||||
curl "http://localhost:37777/api/search?query=database+migration&format=index&limit=5"
|
||||
|
||||
Simple query search:
|
||||
|
||||
```
|
||||
search(query="database migration", limit=20, project="my-project")
|
||||
```
|
||||
|
||||
**Get detailed instructions:**
|
||||
|
||||
Use the `help` tool to load full instructions on-demand:
|
||||
|
||||
```
|
||||
help(topic="workflow") # Get 4-step workflow
|
||||
help(topic="search_params") # Get parameters reference
|
||||
help(topic="examples") # Get usage examples
|
||||
help(topic="all") # Get complete guide
|
||||
```
|
||||
|
||||
## Why This Workflow?
|
||||
|
||||
**Token efficiency:**
|
||||
- Index format: ~50-100 tokens per result
|
||||
- Full format: ~500-1000 tokens per result
|
||||
- **10x difference** - only fetch full when you know it's relevant
|
||||
|
||||
- **Search results:** ~50-100 tokens per result (table index)
|
||||
- **Full observation:** ~500-1000 tokens each
|
||||
- **10x savings** - only fetch full when you know it's relevant
|
||||
|
||||
**Batch fetching:**
|
||||
|
||||
- **Individual fetches:** 10 HTTP requests, ~5-10s latency
|
||||
- **Batch fetch:** 1 HTTP request, ~0.5-1s latency
|
||||
- **10-100x faster** for multi-observation queries
|
||||
|
||||
**Clarity:**
|
||||
- See everything first
|
||||
- Pick what matters
|
||||
- Get details only for what you need
|
||||
|
||||
## Error Handling
|
||||
|
||||
If search fails, tell the user the worker isn't available and suggest:
|
||||
```bash
|
||||
pm2 list # Check if worker is running
|
||||
```
|
||||
- See everything first (table index)
|
||||
- Get timeline context around interesting results
|
||||
- Pick what matters based on context
|
||||
- Fetch details only for what you need (batch when possible)
|
||||
|
||||
---
|
||||
|
||||
**Remember:** ALWAYS search with format=index first. ALWAYS fetch by ID for details. The IDs are there for a reason - USE THEM.
|
||||
**Remember:**
|
||||
|
||||
- ALWAYS get timeline context to understand what was happening
|
||||
- ALWAYS use `get_observations` when fetching 2+ observations
|
||||
- The workflow is optimized: search → timeline → batch fetch = 10-100x faster
|
||||
|
||||
---
|
||||
|
||||
## Tool Reference
|
||||
|
||||
Comprehensive parameter documentation for all memory tools. For MCP usage, call `help(topic="search")` to load specific tool docs.
|
||||
|
||||
### search
|
||||
|
||||
Search across all memory types (observations, sessions, prompts).
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `query` (string, optional) - Search term for full-text search
|
||||
- `limit` (number, optional) - Maximum results to return. Default: 20, Max: 100
|
||||
- `offset` (number, optional) - Number of results to skip. Default: 0
|
||||
- `project` (string, required) - Project name to filter by
|
||||
- `type` (string, optional) - Filter by type: "observations", "sessions", "prompts"
|
||||
- `dateStart` (string, optional) - Start date filter (YYYY-MM-DD or epoch ms)
|
||||
- `dateEnd` (string, optional) - End date filter (YYYY-MM-DD or epoch ms)
|
||||
- `obs_type` (string, optional) - Filter observations by type (comma-separated): bugfix, feature, decision, discovery, change
|
||||
- `orderBy` (string, optional) - Sort order: "date_desc" (default), "date_asc", "relevance"
|
||||
|
||||
**Returns:** Table of results with IDs, timestamps, types, titles
|
||||
|
||||
### timeline
|
||||
|
||||
Get chronological context around a specific point in time or observation.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `anchor` (number, optional) - Observation ID to center timeline around. If not provided, uses most recent result from query
|
||||
- `query` (string, optional) - Search term to find anchor automatically (if anchor not provided)
|
||||
- `depth_before` (number, optional) - Items before anchor. Default: 5, Max: 20
|
||||
- `depth_after` (number, optional) - Items after anchor. Default: 5, Max: 20
|
||||
- `project` (string, required) - Project name to filter by
|
||||
|
||||
**Returns:** Exactly `depth_before + 1 + depth_after` items in chronological order, with observations, sessions, and prompts interleaved
|
||||
|
||||
### get_recent_context
|
||||
|
||||
Get the most recent observations from current or recent sessions.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `limit` (number, optional) - Maximum observations to return. Default: 10, Max: 50
|
||||
- `project` (string, required) - Project name to filter by
|
||||
|
||||
**Returns:** Recent observations in reverse chronological order
|
||||
|
||||
### get_context_timeline
|
||||
|
||||
Get timeline context around a specific observation ID.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `anchor` (number, required) - Observation ID to center timeline around
|
||||
- `depth_before` (number, optional) - Items before anchor. Default: 5, Max: 20
|
||||
- `depth_after` (number, optional) - Items after anchor. Default: 5, Max: 20
|
||||
- `project` (string, optional) - Project name to filter by
|
||||
|
||||
**Returns:** Timeline items centered on the anchor observation
|
||||
|
||||
### get_observation
|
||||
|
||||
Fetch a single observation by ID with full details.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id` (number, required) - Observation ID to fetch
|
||||
|
||||
**Returns:** Complete observation object with title, subtitle, narrative, facts, concepts, files, timestamps
|
||||
|
||||
### get_observations
|
||||
|
||||
Batch fetch multiple observations by IDs. Always prefer this over individual fetches for 2+ observations.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `ids` (array of numbers, required) - Array of observation IDs to fetch
|
||||
- `orderBy` (string, optional) - Sort order: "date_desc" (default), "date_asc"
|
||||
- `limit` (number, optional) - Maximum observations to return. Default: no limit
|
||||
- `project` (string, optional) - Project name to filter by
|
||||
|
||||
**Returns:** Array of complete observation objects, 10-100x faster than individual fetches
|
||||
|
||||
### get_session
|
||||
|
||||
Fetch a single session by ID with metadata.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id` (number, required) - Session ID to fetch (just the number, not "S2005" format)
|
||||
|
||||
**Returns:** Session object with ID, start time, end time, project, model info
|
||||
|
||||
### get_prompt
|
||||
|
||||
Fetch a single prompt by ID with full text.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id` (number, required) - Prompt ID to fetch
|
||||
|
||||
**Returns:** Prompt object with ID, text, timestamp, session reference
|
||||
|
||||
### help
|
||||
|
||||
Load detailed instructions for specific topics or all documentation.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `topic` (string, optional) - Specific topic to load: "workflow", "search", "timeline", "get_recent_context", "get_context_timeline", "get_observation", "get_observations", "get_session", "get_prompt", "all". Default: "all"
|
||||
|
||||
**Returns:** Formatted documentation for the requested topic
|
||||
|
||||
@@ -282,13 +282,13 @@ No results found for "{query}". Try:
|
||||
The search service isn't available. Check if the worker is running:
|
||||
|
||||
```bash
|
||||
pm2 list
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
If the worker is stopped, restart it:
|
||||
|
||||
```bash
|
||||
npm run worker:restart
|
||||
claude-mem restart
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ Many endpoints share these parameters:
|
||||
## Error Handling
|
||||
|
||||
**Worker not running:**
|
||||
Connection refused error. Response: "The search API isn't available. Check if worker is running: `pm2 list`"
|
||||
Connection refused error. Response: "The search API isn't available. Check if worker is running: `npm run worker:status`"
|
||||
|
||||
**Invalid endpoint:**
|
||||
```json
|
||||
|
||||
@@ -93,7 +93,7 @@ curl -s "http://localhost:37777/api/context/recent?limit=3"
|
||||
Response: "No recent sessions found for 'new-project'. This might be a new project."
|
||||
|
||||
**Worker not running:**
|
||||
Connection refused error. Inform user to check if worker is running: `pm2 list`
|
||||
Connection refused error. Inform user to check if worker is running: `npm run worker:status`
|
||||
|
||||
## Tips
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: troubleshoot
|
||||
description: Diagnose and fix claude-mem installation issues. Checks PM2 worker status, database integrity, service health, dependencies, and provides automated fixes for common problems.
|
||||
description: Diagnose and fix claude-mem installation issues. Checks worker status, database integrity, service health, dependencies, and provides automated fixes for common problems.
|
||||
---
|
||||
|
||||
# Claude-Mem Troubleshooting Skill
|
||||
@@ -39,7 +39,7 @@ Choose the appropriate operation file for detailed instructions:
|
||||
|
||||
### Diagnostic Workflows
|
||||
1. **[Full System Diagnostics](operations/diagnostics.md)** - Comprehensive step-by-step diagnostic workflow
|
||||
2. **[Worker Diagnostics](operations/worker.md)** - PM2 worker-specific troubleshooting
|
||||
2. **[Worker Diagnostics](operations/worker.md)** - Bun worker-specific troubleshooting
|
||||
3. **[Database Diagnostics](operations/database.md)** - Database integrity and data checks
|
||||
|
||||
### Issue Resolution
|
||||
@@ -54,9 +54,9 @@ Choose the appropriate operation file for detailed instructions:
|
||||
**Fast automated fix (try this first):**
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
pm2 delete claude-mem-worker 2>/dev/null; \
|
||||
npm run worker:stop; \
|
||||
npm install && \
|
||||
node_modules/.bin/pm2 start ecosystem.config.cjs && \
|
||||
npm run worker:start && \
|
||||
sleep 3 && \
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
```
|
||||
@@ -79,7 +79,7 @@ When troubleshooting:
|
||||
- **Worker port:** Default 37777 (configurable via `CLAUDE_MEM_WORKER_PORT`)
|
||||
- **Database location:** `~/.claude-mem/claude-mem.db`
|
||||
- **Plugin location:** `~/.claude/plugins/marketplaces/thedotmack/`
|
||||
- **PM2 process name:** `claude-mem-worker`
|
||||
- **Worker PID file:** `~/.claude-mem/worker.pid`
|
||||
|
||||
## Error Reporting
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ One-command fix sequences for common claude-mem issues.
|
||||
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
pm2 delete claude-mem-worker 2>/dev/null; \
|
||||
npm run worker:stop; \
|
||||
npm install && \
|
||||
node_modules/.bin/pm2 start ecosystem.config.cjs && \
|
||||
npm run worker:start && \
|
||||
sleep 3 && \
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
```
|
||||
@@ -20,22 +20,22 @@ curl -s http://127.0.0.1:37777/health
|
||||
**What it does:**
|
||||
1. Stops the worker (if running)
|
||||
2. Ensures dependencies are installed
|
||||
3. Starts worker with local PM2
|
||||
3. Starts worker
|
||||
4. Waits for startup
|
||||
5. Verifies health
|
||||
|
||||
## Fix: Worker Not Running
|
||||
|
||||
**Use when:** PM2 shows worker as stopped or not listed
|
||||
**Use when:** Worker status shows it's not running
|
||||
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
node_modules/.bin/pm2 start ecosystem.config.cjs && \
|
||||
npm run worker:start && \
|
||||
sleep 2 && \
|
||||
pm2 status
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
**Expected output:** Worker shows as "online"
|
||||
**Expected output:** Worker running with PID and health OK
|
||||
|
||||
## Fix: Dependencies Missing
|
||||
|
||||
@@ -44,9 +44,23 @@ pm2 status
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
npm install && \
|
||||
pm2 restart claude-mem-worker
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## Fix: Stale PID File
|
||||
|
||||
**Use when:** Worker reports running but health check fails
|
||||
|
||||
```bash
|
||||
rm -f ~/.claude-mem/worker.pid && \
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
npm run worker:start && \
|
||||
sleep 2 && \
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
```
|
||||
|
||||
**Expected output:** `{"status":"ok"}`
|
||||
|
||||
## Fix: Port Conflict
|
||||
|
||||
**Use when:** Error shows port already in use
|
||||
@@ -54,8 +68,9 @@ pm2 restart claude-mem-worker
|
||||
```bash
|
||||
# Change to port 37778
|
||||
mkdir -p ~/.claude-mem && \
|
||||
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json && \
|
||||
pm2 restart claude-mem-worker && \
|
||||
echo '{"CLAUDE_MEM_WORKER_PORT":"37778"}' > ~/.claude-mem/settings.json && \
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
claude-mem restart && \
|
||||
sleep 2 && \
|
||||
curl -s http://127.0.0.1:37778/health
|
||||
```
|
||||
@@ -70,14 +85,16 @@ curl -s http://127.0.0.1:37778/health
|
||||
# Backup and test integrity
|
||||
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup && \
|
||||
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;" && \
|
||||
pm2 restart claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
**If integrity check fails, recreate database:**
|
||||
```bash
|
||||
# WARNING: This deletes all memory data
|
||||
mv ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.old && \
|
||||
pm2 restart claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## Fix: Clean Reinstall
|
||||
@@ -88,36 +105,49 @@ pm2 restart claude-mem-worker
|
||||
# Backup data first
|
||||
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup 2>/dev/null
|
||||
|
||||
# Stop and remove worker
|
||||
pm2 delete claude-mem-worker 2>/dev/null
|
||||
# Stop worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:stop
|
||||
|
||||
# Clean PID file
|
||||
rm -f ~/.claude-mem/worker.pid
|
||||
|
||||
# Reinstall dependencies
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && \
|
||||
rm -rf node_modules && \
|
||||
npm install
|
||||
|
||||
# Start worker
|
||||
node_modules/.bin/pm2 start ecosystem.config.cjs && \
|
||||
npm run worker:start && \
|
||||
sleep 3 && \
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
```
|
||||
|
||||
## Fix: Clear PM2 Logs
|
||||
## Fix: Clear Old Logs
|
||||
|
||||
**Use when:** Logs are too large, want fresh start
|
||||
**Use when:** Want to start with fresh logs
|
||||
|
||||
```bash
|
||||
pm2 flush claude-mem-worker && \
|
||||
pm2 restart claude-mem-worker
|
||||
# Archive old logs
|
||||
tar -czf ~/.claude-mem/logs-archive-$(date +%Y-%m-%d).tar.gz ~/.claude-mem/logs/*.log 2>/dev/null
|
||||
|
||||
# Remove logs older than 7 days
|
||||
find ~/.claude-mem/logs/ -name "worker-*.log" -mtime +7 -delete
|
||||
|
||||
# Restart worker for fresh log
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
**Note:** Logs auto-rotate daily, manual cleanup rarely needed.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
**After running any fix, verify with these:**
|
||||
|
||||
```bash
|
||||
# Check worker status
|
||||
pm2 status | grep claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:status
|
||||
|
||||
# Check health
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
@@ -129,23 +159,48 @@ sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
|
||||
curl -s http://127.0.0.1:37777/api/stats
|
||||
|
||||
# Check logs for errors
|
||||
pm2 logs claude-mem-worker --lines 20 --nostream | grep -i error
|
||||
grep -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log | tail -20
|
||||
```
|
||||
|
||||
**All checks should pass:**
|
||||
- Worker status: "online"
|
||||
- Health: `{"status":"ok"}`
|
||||
- Worker status: Shows PID and "Health: OK"
|
||||
- Health endpoint: `{"status":"ok"}`
|
||||
- Database: Shows count (may be 0 if new)
|
||||
- Stats: Returns JSON with counts
|
||||
- Logs: No recent errors
|
||||
|
||||
## One-Line Complete Diagnostic
|
||||
|
||||
**Quick health check:**
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/ && npm run worker:status && curl -s http://127.0.0.1:37777/health && echo " ✓ All systems OK"
|
||||
```
|
||||
|
||||
## Troubleshooting the Fixes
|
||||
|
||||
**If automated fix fails:**
|
||||
1. Run the diagnostic script from [diagnostics.md](diagnostics.md)
|
||||
2. Check specific error in PM2 logs
|
||||
2. Check specific error in worker logs:
|
||||
```bash
|
||||
tail -50 ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
```
|
||||
3. Try manual worker start to see detailed error:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
node plugin/scripts/worker-service.cjs
|
||||
bun plugin/scripts/worker-service.js
|
||||
```
|
||||
4. Use the bug report tool:
|
||||
```bash
|
||||
npm run bug-report
|
||||
```
|
||||
|
||||
## Common Error Patterns and Fixes
|
||||
|
||||
| Error Pattern | Likely Cause | Quick Fix |
|
||||
|---------------|--------------|-----------|
|
||||
| `EADDRINUSE` | Port conflict | Change port in settings.json |
|
||||
| `SQLITE_ERROR` | Database corruption | Run integrity check, recreate if needed |
|
||||
| `ENOENT` | Missing files | Run `npm install` |
|
||||
| `Module not found` | Dependency issue | Clean reinstall |
|
||||
| Connection refused | Worker not running | `npm run worker:start` |
|
||||
| Stale PID | Old PID file | Remove `~/.claude-mem/worker.pid` |
|
||||
|
||||
@@ -17,7 +17,8 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
**Fix:**
|
||||
1. Verify worker is running:
|
||||
```bash
|
||||
pm2 jlist | grep claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
2. Check database has recent observations:
|
||||
@@ -27,7 +28,8 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
|
||||
3. Restart worker and start new session:
|
||||
```bash
|
||||
pm2 restart claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
4. Create a test observation: `/skill version-bump` then cancel
|
||||
@@ -66,7 +68,7 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
|
||||
3. Verify worker is using correct database path in logs:
|
||||
```bash
|
||||
pm2 logs claude-mem-worker --lines 50 --nostream | grep "Database"
|
||||
grep "Database" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
```
|
||||
|
||||
4. Test viewer connection manually:
|
||||
@@ -109,34 +111,34 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
## Issue: Worker Not Starting {#worker-not-starting}
|
||||
|
||||
**Symptoms:**
|
||||
- PM2 shows worker as "stopped" or "errored"
|
||||
- Worker status shows not running or error
|
||||
- Health check fails
|
||||
- Viewer not accessible
|
||||
|
||||
**Root cause:**
|
||||
- Port already in use
|
||||
- PM2 not installed or not in PATH
|
||||
- Bun not installed
|
||||
- Missing dependencies
|
||||
|
||||
**Fix:**
|
||||
1. Try manual worker start to see error:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
node plugin/scripts/worker-service.cjs
|
||||
bun plugin/scripts/worker-service.js
|
||||
# Should start server on port 37777 or show error
|
||||
```
|
||||
|
||||
2. If port in use, change it:
|
||||
```bash
|
||||
mkdir -p ~/.claude-mem
|
||||
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
|
||||
echo '{"CLAUDE_MEM_WORKER_PORT":"37778"}' > ~/.claude-mem/settings.json
|
||||
```
|
||||
|
||||
3. If dependencies missing:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm install
|
||||
pm2 start ecosystem.config.cjs
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
## Issue: Search Results Empty
|
||||
@@ -170,7 +172,8 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
|
||||
4. If FTS5 out of sync, restart worker (triggers reindex):
|
||||
```bash
|
||||
pm2 restart claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## Issue: Port Conflicts
|
||||
@@ -189,8 +192,9 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
2. Either kill the conflicting process or change claude-mem port:
|
||||
```bash
|
||||
mkdir -p ~/.claude-mem
|
||||
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
|
||||
pm2 restart claude-mem-worker
|
||||
echo '{"CLAUDE_MEM_WORKER_PORT":"37778"}' > ~/.claude-mem/settings.json
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## Issue: Database Corrupted
|
||||
@@ -214,7 +218,8 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
3. If repair fails, recreate (loses data):
|
||||
```bash
|
||||
rm ~/.claude-mem/claude-mem.db
|
||||
pm2 restart claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
# Worker will create new database
|
||||
```
|
||||
|
||||
|
||||
@@ -172,7 +172,8 @@ SELECT
|
||||
If FTS5 counts don't match, triggers may have failed. Restart worker to rebuild:
|
||||
|
||||
```bash
|
||||
pm2 restart claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
The worker will rebuild FTS5 indexes on startup if they're out of sync.
|
||||
@@ -196,7 +197,7 @@ The worker will rebuild FTS5 indexes on startup if they're out of sync.
|
||||
1. Create test observation (use any skill and cancel)
|
||||
2. Check worker logs for errors:
|
||||
```bash
|
||||
pm2 logs claude-mem-worker --lines 50 --nostream
|
||||
tail -50 ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
```
|
||||
3. Verify observation appears in database
|
||||
|
||||
@@ -228,9 +229,10 @@ ls -la ~/.claude-mem/claude-mem.db-wal
|
||||
ls -la ~/.claude-mem/claude-mem.db-shm
|
||||
|
||||
# Remove lock files (only if worker is stopped!)
|
||||
pm2 stop claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:stop
|
||||
rm ~/.claude-mem/claude-mem.db-wal ~/.claude-mem/claude-mem.db-shm
|
||||
pm2 start claude-mem-worker
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
### Issue: Database Growing Too Large
|
||||
@@ -260,7 +262,8 @@ sqlite3 ~/.claude-mem/claude-mem.db "SELECT COUNT(*) FROM observations;"
|
||||
3. Archive and start fresh:
|
||||
```bash
|
||||
mv ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.archive
|
||||
pm2 restart claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## Database Recovery
|
||||
@@ -275,9 +278,10 @@ cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.backup
|
||||
### Restore from Backup
|
||||
|
||||
```bash
|
||||
pm2 stop claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:stop
|
||||
cp ~/.claude-mem/claude-mem.db.backup ~/.claude-mem/claude-mem.db
|
||||
pm2 start claude-mem-worker
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
### Export Data
|
||||
@@ -300,8 +304,10 @@ sqlite3 ~/.claude-mem/claude-mem.db -json "SELECT * FROM user_prompts;" > prompt
|
||||
**WARNING: Data loss. Backup first!**
|
||||
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
|
||||
# Stop worker
|
||||
pm2 stop claude-mem-worker
|
||||
npm run worker:stop
|
||||
|
||||
# Backup current database
|
||||
cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.old
|
||||
@@ -310,7 +316,7 @@ cp ~/.claude-mem/claude-mem.db ~/.claude-mem/claude-mem.db.old
|
||||
rm ~/.claude-mem/claude-mem.db
|
||||
|
||||
# Start worker (creates new database)
|
||||
pm2 start claude-mem-worker
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
## Database Statistics
|
||||
|
||||
@@ -6,29 +6,42 @@ Comprehensive step-by-step diagnostic workflow for claude-mem issues.
|
||||
|
||||
Run these checks systematically to identify the root cause:
|
||||
|
||||
### 1. Check PM2 Worker Status
|
||||
### 1. Check Worker Status
|
||||
|
||||
First, verify if the worker service is running:
|
||||
|
||||
```bash
|
||||
# Check if PM2 is available
|
||||
which pm2 || echo "PM2 not found in PATH"
|
||||
# Check worker status using npm script
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:status
|
||||
|
||||
# List PM2 processes
|
||||
pm2 jlist 2>&1
|
||||
|
||||
# If pm2 is not found, try the local installation
|
||||
~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2 jlist 2>&1
|
||||
# Or check health endpoint directly
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
```
|
||||
|
||||
**Expected output:** JSON array with `claude-mem-worker` process showing `"status": "online"`
|
||||
**Expected output from npm run worker:status:**
|
||||
```
|
||||
✓ Worker is running (PID: 12345)
|
||||
Port: 37777
|
||||
Uptime: 45m
|
||||
Health: OK
|
||||
```
|
||||
|
||||
**If worker not running or status is not "online":**
|
||||
**Expected output from health endpoint:** `{"status":"ok"}`
|
||||
|
||||
**If worker not running:**
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
pm2 start ecosystem.config.cjs
|
||||
# Or use local pm2:
|
||||
node_modules/.bin/pm2 start ecosystem.config.cjs
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
**If health endpoint fails but worker reports running:**
|
||||
Check for stale PID file:
|
||||
```bash
|
||||
cat ~/.claude-mem/worker.pid
|
||||
ps -p $(cat ~/.claude-mem/worker.pid 2>/dev/null | grep -o '"pid":[0-9]*' | grep -o '[0-9]*') 2>/dev/null || echo "Stale PID - worker not actually running"
|
||||
rm ~/.claude-mem/worker.pid
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
### 2. Check Worker Service Health
|
||||
@@ -98,10 +111,12 @@ cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
# Check for critical packages
|
||||
ls node_modules/@anthropic-ai/claude-agent-sdk 2>&1 | head -1
|
||||
ls node_modules/express 2>&1 | head -1
|
||||
ls node_modules/pm2 2>&1 | head -1
|
||||
|
||||
# Check if Bun is available
|
||||
bun --version 2>&1
|
||||
```
|
||||
|
||||
**Expected:** All critical packages present
|
||||
**Expected:** All critical packages present, Bun installed
|
||||
|
||||
**If dependencies missing:**
|
||||
```bash
|
||||
@@ -114,17 +129,26 @@ npm install
|
||||
Review recent worker logs for errors:
|
||||
|
||||
```bash
|
||||
# View last 50 lines of worker logs
|
||||
pm2 logs claude-mem-worker --lines 50 --nostream
|
||||
|
||||
# Or use local pm2:
|
||||
# View logs using npm script
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
node_modules/.bin/pm2 logs claude-mem-worker --lines 50 --nostream
|
||||
npm run worker:logs
|
||||
|
||||
# View today's log file directly
|
||||
cat ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Last 50 lines
|
||||
tail -50 ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Check for specific errors
|
||||
pm2 logs claude-mem-worker --lines 100 --nostream | grep -i "error\|exception\|failed"
|
||||
grep -iE "error|exception|failed" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log | tail -20
|
||||
```
|
||||
|
||||
**Common error patterns to look for:**
|
||||
- `SQLITE_ERROR` - Database issues
|
||||
- `EADDRINUSE` - Port conflict
|
||||
- `ENOENT` - Missing files
|
||||
- `Module not found` - Dependency issues
|
||||
|
||||
### 6. Test Viewer UI
|
||||
|
||||
Check if the web viewer is accessible:
|
||||
@@ -167,6 +191,8 @@ echo "=== Claude-Mem Troubleshooting Report ==="
|
||||
echo ""
|
||||
echo "1. Environment"
|
||||
echo " OS: $(uname -s)"
|
||||
echo " Node version: $(node --version 2>/dev/null || echo 'N/A')"
|
||||
echo " Bun version: $(bun --version 2>/dev/null || echo 'N/A')"
|
||||
echo ""
|
||||
echo "2. Plugin Installation"
|
||||
echo " Plugin directory exists: $([ -d ~/.claude/plugins/marketplaces/thedotmack ] && echo 'YES' || echo 'NO')"
|
||||
@@ -179,20 +205,28 @@ echo " Observation count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(
|
||||
echo " Session count: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM sessions;' 2>/dev/null || echo 'N/A')"
|
||||
echo ""
|
||||
echo "4. Worker Service"
|
||||
PM2_PATH=$(which pm2 2>/dev/null || echo "~/.claude/plugins/marketplaces/thedotmack/node_modules/.bin/pm2")
|
||||
echo " PM2 path: $PM2_PATH"
|
||||
WORKER_STATUS=$($PM2_PATH jlist 2>/dev/null | grep -o '"name":"claude-mem-worker".*"status":"[^"]*"' | grep -o 'status":"[^"]*"' | cut -d'"' -f3 || echo 'not running')
|
||||
echo " Worker status: $WORKER_STATUS"
|
||||
echo " Worker PID file: $([ -f ~/.claude-mem/worker.pid ] && echo 'EXISTS' || echo 'MISSING')"
|
||||
if [ -f ~/.claude-mem/worker.pid ]; then
|
||||
WORKER_PID=$(cat ~/.claude-mem/worker.pid 2>/dev/null | grep -o '"pid":[0-9]*' | grep -o '[0-9]*')
|
||||
echo " Worker PID: $WORKER_PID"
|
||||
echo " Process running: $(ps -p $WORKER_PID >/dev/null 2>&1 && echo 'YES' || echo 'NO (stale PID)')"
|
||||
fi
|
||||
echo " Health check: $(curl -s http://127.0.0.1:37777/health 2>/dev/null || echo 'FAILED')"
|
||||
echo ""
|
||||
echo "5. Configuration"
|
||||
echo " Port setting: $(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_WORKER_PORT || echo 'default (37777)')"
|
||||
echo " Observation count: $(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_CONTEXT_OBSERVATIONS || echo 'default (50)')"
|
||||
echo " Model: $(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_MODEL || echo 'default (claude-sonnet-4-5)')"
|
||||
echo ""
|
||||
echo "6. Recent Activity"
|
||||
echo " Latest observation: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM observations ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
|
||||
echo " Latest session: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT created_at FROM sessions ORDER BY created_at DESC LIMIT 1;' 2>/dev/null || echo 'N/A')"
|
||||
echo ""
|
||||
echo "7. Logs"
|
||||
echo " Today's log file: $([ -f ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log ] && echo 'EXISTS' || echo 'MISSING')"
|
||||
echo " Log file size: $(du -h ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log 2>/dev/null | cut -f1 || echo 'N/A')"
|
||||
echo " Recent errors: $(grep -c -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log 2>/dev/null || echo '0')"
|
||||
echo ""
|
||||
echo "=== End Report ==="
|
||||
```
|
||||
|
||||
@@ -201,18 +235,75 @@ Save this as `/tmp/claude-mem-diagnostics.sh` and run:
|
||||
bash /tmp/claude-mem-diagnostics.sh
|
||||
```
|
||||
|
||||
## Quick Diagnostic One-Liners
|
||||
|
||||
```bash
|
||||
# Full status check
|
||||
npm run worker:status && curl -s http://127.0.0.1:37777/health && echo " - All systems OK"
|
||||
|
||||
# Database stats
|
||||
echo "DB: $(du -h ~/.claude-mem/claude-mem.db | cut -f1) | Obs: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM observations;' 2>/dev/null) | Sessions: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM sessions;' 2>/dev/null)"
|
||||
|
||||
# Recent errors
|
||||
grep -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log 2>/dev/null | tail -5 || echo "No recent errors"
|
||||
|
||||
# Port check
|
||||
lsof -i :37777 || echo "Port 37777 is free"
|
||||
|
||||
# Worker process check
|
||||
ps aux | grep -E "bun.*worker-service" | grep -v grep || echo "Worker not running"
|
||||
```
|
||||
|
||||
## Automated Fix Sequence
|
||||
|
||||
If diagnostics show issues, run this automated fix sequence:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "Running automated fix sequence..."
|
||||
|
||||
# 1. Stop worker if running
|
||||
echo "1. Stopping worker..."
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:stop
|
||||
|
||||
# 2. Clean stale PID if exists
|
||||
echo "2. Cleaning stale PID file..."
|
||||
rm -f ~/.claude-mem/worker.pid
|
||||
|
||||
# 3. Reinstall dependencies
|
||||
echo "3. Reinstalling dependencies..."
|
||||
npm install
|
||||
|
||||
# 4. Start worker
|
||||
echo "4. Starting worker..."
|
||||
npm run worker:start
|
||||
|
||||
# 5. Wait for startup
|
||||
echo "5. Waiting for worker to start..."
|
||||
sleep 3
|
||||
|
||||
# 6. Verify health
|
||||
echo "6. Verifying health..."
|
||||
curl -s http://127.0.0.1:37777/health || echo "Worker health check FAILED"
|
||||
|
||||
echo "Fix sequence complete!"
|
||||
```
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If troubleshooting doesn't resolve the issue, collect this information for a bug report:
|
||||
If troubleshooting doesn't resolve the issue, run the built-in bug report tool:
|
||||
|
||||
1. Full diagnostic report (run script above)
|
||||
2. Worker logs: `pm2 logs claude-mem-worker --lines 100 --nostream`
|
||||
3. Your setup:
|
||||
- Claude version: Check with Claude
|
||||
- OS: `uname -a`
|
||||
- Node version: `node --version`
|
||||
- Plugin version: In package.json
|
||||
4. Steps to reproduce the issue
|
||||
5. Expected vs actual behavior
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run bug-report
|
||||
```
|
||||
|
||||
Post to: https://github.com/thedotmack/claude-mem/issues
|
||||
This will collect:
|
||||
1. Full diagnostic report
|
||||
2. Worker logs
|
||||
3. System information
|
||||
4. Configuration details
|
||||
5. Database stats
|
||||
|
||||
Post the generated report to: https://github.com/thedotmack/claude-mem/issues
|
||||
|
||||
@@ -6,30 +6,29 @@ Essential commands for troubleshooting claude-mem.
|
||||
|
||||
```bash
|
||||
# Check worker status
|
||||
pm2 status | grep claude-mem-worker
|
||||
pm2 jlist | grep claude-mem-worker # JSON format
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:status
|
||||
|
||||
# Start worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
pm2 start ecosystem.config.cjs
|
||||
npm run worker:start
|
||||
|
||||
# Restart worker
|
||||
pm2 restart claude-mem-worker
|
||||
claude-mem restart
|
||||
|
||||
# Stop worker
|
||||
pm2 stop claude-mem-worker
|
||||
|
||||
# Delete worker (for clean restart)
|
||||
pm2 delete claude-mem-worker
|
||||
npm run worker:stop
|
||||
|
||||
# View logs
|
||||
pm2 logs claude-mem-worker
|
||||
npm run worker:logs
|
||||
|
||||
# View last N lines
|
||||
pm2 logs claude-mem-worker --lines 50 --nostream
|
||||
# View today's log file
|
||||
cat ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Clear logs
|
||||
pm2 flush claude-mem-worker
|
||||
# Last 50 lines
|
||||
tail -50 ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Follow logs in real-time
|
||||
tail -f ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
@@ -82,21 +81,17 @@ cat ~/.claude-mem/settings.json
|
||||
cat ~/.claude/settings.json
|
||||
|
||||
# Change worker port
|
||||
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
|
||||
echo '{"CLAUDE_MEM_WORKER_PORT":"37778"}' > ~/.claude-mem/settings.json
|
||||
|
||||
# Change context observation count
|
||||
# Edit ~/.claude-mem/settings.json and add:
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "25"
|
||||
}
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "25"
|
||||
}
|
||||
|
||||
# Change AI model
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_MODEL": "claude-haiku-4-5"
|
||||
}
|
||||
"CLAUDE_MEM_MODEL": "claude-sonnet-4-5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -132,16 +127,19 @@ curl -v http://127.0.0.1:37777/health
|
||||
|
||||
```bash
|
||||
# Search logs for errors
|
||||
pm2 logs claude-mem-worker --lines 100 --nostream | grep -i "error"
|
||||
grep -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Search for specific keyword
|
||||
pm2 logs claude-mem-worker --lines 100 --nostream | grep "keyword"
|
||||
grep "keyword" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Search across all log files
|
||||
grep -i "error" ~/.claude-mem/logs/worker-*.log
|
||||
|
||||
# Last 100 error lines
|
||||
grep -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log | tail -100
|
||||
|
||||
# Follow logs in real-time
|
||||
pm2 logs claude-mem-worker
|
||||
|
||||
# Show only error logs
|
||||
pm2 logs claude-mem-worker --err
|
||||
tail -f ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
```
|
||||
|
||||
## File Locations
|
||||
@@ -160,11 +158,11 @@ pm2 logs claude-mem-worker --err
|
||||
# Chroma vector database
|
||||
~/.claude-mem/chroma/
|
||||
|
||||
# Usage logs
|
||||
~/.claude-mem/usage-logs/
|
||||
# Worker logs (daily rotation)
|
||||
~/.claude-mem/logs/worker-*.log
|
||||
|
||||
# PM2 logs
|
||||
~/.pm2/logs/
|
||||
# Worker PID file
|
||||
~/.claude-mem/worker.pid
|
||||
```
|
||||
|
||||
## System Information
|
||||
@@ -179,8 +177,8 @@ node --version
|
||||
# NPM version
|
||||
npm --version
|
||||
|
||||
# PM2 version
|
||||
pm2 --version
|
||||
# Bun version
|
||||
bun --version
|
||||
|
||||
# SQLite version
|
||||
sqlite3 --version
|
||||
@@ -188,3 +186,22 @@ sqlite3 --version
|
||||
# Check disk space
|
||||
df -h ~/.claude-mem/
|
||||
```
|
||||
|
||||
## One-Line Diagnostics
|
||||
|
||||
```bash
|
||||
# Full worker status check
|
||||
npm run worker:status && curl -s http://127.0.0.1:37777/health
|
||||
|
||||
# Quick health check
|
||||
curl -s http://127.0.0.1:37777/health && echo " - Worker is healthy"
|
||||
|
||||
# Database stats
|
||||
echo "Observations: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM observations;')" && echo "Sessions: $(sqlite3 ~/.claude-mem/claude-mem.db 'SELECT COUNT(*) FROM sessions;')"
|
||||
|
||||
# Recent errors
|
||||
grep -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log | tail -10
|
||||
|
||||
# Port check
|
||||
lsof -i :37777 || echo "Port 37777 is free"
|
||||
```
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Worker Service Diagnostics
|
||||
|
||||
PM2 worker-specific troubleshooting for claude-mem.
|
||||
Bun worker-specific troubleshooting for claude-mem.
|
||||
|
||||
## PM2 Worker Overview
|
||||
## Worker Overview
|
||||
|
||||
The claude-mem worker is a persistent background service managed by PM2. It:
|
||||
The claude-mem worker is a persistent background service managed by Bun. It:
|
||||
- Runs Express.js server on port 37777 (default)
|
||||
- Processes observations asynchronously
|
||||
- Serves the viewer UI
|
||||
@@ -15,36 +15,41 @@ The claude-mem worker is a persistent background service managed by PM2. It:
|
||||
### Basic Status Check
|
||||
|
||||
```bash
|
||||
# List all PM2 processes
|
||||
pm2 list
|
||||
# Check worker status using npm script
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:status
|
||||
|
||||
# JSON format (parseable)
|
||||
pm2 jlist
|
||||
|
||||
# Filter for claude-mem-worker
|
||||
pm2 status | grep claude-mem-worker
|
||||
# Or check health endpoint directly
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
**Expected npm run worker:status output:**
|
||||
```
|
||||
│ claude-mem-worker │ online │ 12345 │ 0 │ 45m │ 0% │ 85.6mb │
|
||||
✓ Worker is running (PID: 12345)
|
||||
Port: 37777
|
||||
Uptime: 45m
|
||||
Health: OK
|
||||
```
|
||||
|
||||
**Status meanings:**
|
||||
- `online` - Worker running correctly
|
||||
- `stopped` - Worker stopped (normal shutdown)
|
||||
- `errored` - Worker crashed (check logs)
|
||||
- `stopping` - Worker shutting down
|
||||
- Not listed - Worker never started
|
||||
**Expected health endpoint output:**
|
||||
```json
|
||||
{"status":"ok"}
|
||||
```
|
||||
|
||||
**Status indicators:**
|
||||
- `Worker is running` - Worker running correctly
|
||||
- `Worker is not running` - Worker stopped or crashed
|
||||
- Connection refused - Worker not running
|
||||
- Timeout - Worker hung (restart needed)
|
||||
|
||||
### Detailed Worker Info
|
||||
|
||||
```bash
|
||||
# Show detailed information
|
||||
pm2 show claude-mem-worker
|
||||
# View PID file
|
||||
cat ~/.claude-mem/worker.pid
|
||||
|
||||
# JSON format
|
||||
pm2 jlist | grep -A 20 '"name":"claude-mem-worker"'
|
||||
# Check process details
|
||||
ps aux | grep "bun.*worker-service"
|
||||
```
|
||||
|
||||
## Worker Health Endpoint
|
||||
@@ -72,30 +77,37 @@ curl -s http://127.0.0.1:$PORT/health
|
||||
### View Recent Logs
|
||||
|
||||
```bash
|
||||
# Last 50 lines
|
||||
pm2 logs claude-mem-worker --lines 50 --nostream
|
||||
# View logs using npm script
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:logs
|
||||
|
||||
# Last 200 lines
|
||||
pm2 logs claude-mem-worker --lines 200 --nostream
|
||||
# View today's log file directly
|
||||
cat ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Last 50 lines of today's log
|
||||
tail -50 ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Follow logs in real-time
|
||||
pm2 logs claude-mem-worker
|
||||
tail -f ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
```
|
||||
|
||||
### Search Logs for Errors
|
||||
|
||||
```bash
|
||||
# Find errors
|
||||
pm2 logs claude-mem-worker --lines 500 --nostream | grep -i "error"
|
||||
# Find errors in today's log
|
||||
grep -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Find exceptions
|
||||
pm2 logs claude-mem-worker --lines 500 --nostream | grep -i "exception"
|
||||
grep -i "exception" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Find failed requests
|
||||
pm2 logs claude-mem-worker --lines 500 --nostream | grep -i "failed"
|
||||
grep -i "failed" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# All error patterns
|
||||
pm2 logs claude-mem-worker --lines 500 --nostream | grep -iE "error|exception|failed|crash"
|
||||
grep -iE "error|exception|failed|crash" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# Search across all log files
|
||||
grep -iE "error|exception|failed|crash" ~/.claude-mem/logs/worker-*.log
|
||||
```
|
||||
|
||||
### Common Log Patterns
|
||||
@@ -122,8 +134,8 @@ Port 37777 already in use
|
||||
|
||||
**Crashes:**
|
||||
```
|
||||
PM2 | App [claude-mem-worker] exited with code [1]
|
||||
PM2 | App [claude-mem-worker] will restart in 100ms
|
||||
Worker process exited with code 1
|
||||
Worker restarting...
|
||||
```
|
||||
|
||||
## Starting the Worker
|
||||
@@ -132,37 +144,26 @@ PM2 | App [claude-mem-worker] will restart in 100ms
|
||||
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
pm2 start ecosystem.config.cjs
|
||||
```
|
||||
|
||||
### Start with Local PM2
|
||||
|
||||
If `pm2` command not in PATH:
|
||||
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
node_modules/.bin/pm2 start ecosystem.config.cjs
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
### Force Restart
|
||||
|
||||
```bash
|
||||
# Restart if already running
|
||||
pm2 restart claude-mem-worker
|
||||
# Restart worker (stops and starts)
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
claude-mem restart
|
||||
|
||||
# Delete and start fresh
|
||||
pm2 delete claude-mem-worker
|
||||
pm2 start ecosystem.config.cjs
|
||||
# Or manually stop and start
|
||||
npm run worker:stop
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
## Stopping the Worker
|
||||
|
||||
```bash
|
||||
# Graceful stop
|
||||
pm2 stop claude-mem-worker
|
||||
|
||||
# Delete completely (also removes from PM2 list)
|
||||
pm2 delete claude-mem-worker
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm run worker:stop
|
||||
```
|
||||
|
||||
## Worker Not Starting
|
||||
@@ -172,23 +173,22 @@ pm2 delete claude-mem-worker
|
||||
1. **Try manual start to see error:**
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
node plugin/scripts/worker-service.cjs
|
||||
bun plugin/scripts/worker-service.js
|
||||
```
|
||||
This runs the worker directly without PM2, showing full error output.
|
||||
This runs the worker directly, showing full error output.
|
||||
|
||||
2. **Check PM2 itself:**
|
||||
2. **Check Bun installation:**
|
||||
```bash
|
||||
which pm2
|
||||
pm2 --version
|
||||
which bun
|
||||
bun --version
|
||||
```
|
||||
If PM2 not found, dependencies not installed.
|
||||
If Bun not found, run: `npm install` (auto-installs Bun)
|
||||
|
||||
3. **Check dependencies:**
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
ls node_modules/@anthropic-ai/claude-agent-sdk
|
||||
ls node_modules/express
|
||||
ls node_modules/pm2
|
||||
```
|
||||
|
||||
4. **Check port availability:**
|
||||
@@ -197,42 +197,57 @@ pm2 delete claude-mem-worker
|
||||
```
|
||||
If port in use, either kill that process or change claude-mem port.
|
||||
|
||||
5. **Check PID file:**
|
||||
```bash
|
||||
cat ~/.claude-mem/worker.pid
|
||||
```
|
||||
If worker PID exists but process is dead, remove stale PID:
|
||||
```bash
|
||||
rm ~/.claude-mem/worker.pid
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
### Common Fixes
|
||||
|
||||
**Dependencies missing:**
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
npm install
|
||||
pm2 start ecosystem.config.cjs
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
**Port conflict:**
|
||||
```bash
|
||||
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
|
||||
pm2 restart claude-mem-worker
|
||||
echo '{"CLAUDE_MEM_WORKER_PORT":"37778"}' > ~/.claude-mem/settings.json
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
**Corrupted PM2:**
|
||||
**Stale PID file:**
|
||||
```bash
|
||||
pm2 kill # Stop PM2 daemon
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
pm2 start ecosystem.config.cjs
|
||||
rm ~/.claude-mem/worker.pid
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
## Worker Crashing Repeatedly
|
||||
|
||||
If worker keeps restarting (check with `pm2 status` showing high restart count):
|
||||
If worker keeps restarting (check logs for repeated startup messages):
|
||||
|
||||
### Find the Cause
|
||||
|
||||
1. **Check error logs:**
|
||||
```bash
|
||||
pm2 logs claude-mem-worker --err --lines 100 --nostream
|
||||
grep -i "error" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log | tail -100
|
||||
```
|
||||
|
||||
2. **Look for crash pattern:**
|
||||
```bash
|
||||
pm2 logs claude-mem-worker --lines 200 --nostream | grep -A 5 "exited with code"
|
||||
grep -A 5 "exited with code" ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
```
|
||||
|
||||
3. **Run worker in foreground to see crashes:**
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
bun plugin/scripts/worker-service.js
|
||||
```
|
||||
|
||||
### Common Crash Causes
|
||||
@@ -246,43 +261,71 @@ If fails, backup and recreate database.
|
||||
**Out of memory:**
|
||||
Check if database is too large or memory leak. Restart:
|
||||
```bash
|
||||
pm2 restart claude-mem-worker
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
**Port conflict race condition:**
|
||||
Another process grabbing port intermittently. Change port:
|
||||
```bash
|
||||
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
|
||||
pm2 restart claude-mem-worker
|
||||
echo '{"CLAUDE_MEM_WORKER_PORT":"37778"}' > ~/.claude-mem/settings.json
|
||||
claude-mem restart
|
||||
```
|
||||
|
||||
## PM2 Management Commands
|
||||
## Worker Management Commands
|
||||
|
||||
```bash
|
||||
# List processes
|
||||
pm2 list
|
||||
pm2 jlist # JSON format
|
||||
# Check status
|
||||
npm run worker:status
|
||||
|
||||
# Show detailed info
|
||||
pm2 show claude-mem-worker
|
||||
# Start worker
|
||||
npm run worker:start
|
||||
|
||||
# Monitor resources
|
||||
pm2 monit
|
||||
# Stop worker
|
||||
npm run worker:stop
|
||||
|
||||
# Clear logs
|
||||
pm2 flush claude-mem-worker
|
||||
# Restart worker
|
||||
claude-mem restart
|
||||
|
||||
# Restart PM2 daemon
|
||||
pm2 kill
|
||||
pm2 resurrect # Restore saved processes
|
||||
# View logs
|
||||
npm run worker:logs
|
||||
|
||||
# Save current process list
|
||||
pm2 save
|
||||
# Check health endpoint
|
||||
curl -s http://127.0.0.1:37777/health
|
||||
|
||||
# Update PM2
|
||||
npm install -g pm2
|
||||
# View PID
|
||||
cat ~/.claude-mem/worker.pid
|
||||
|
||||
# View today's log file
|
||||
cat ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# List all log files
|
||||
ls -lh ~/.claude-mem/logs/worker-*.log
|
||||
```
|
||||
|
||||
## Log File Management
|
||||
|
||||
Worker logs are stored in `~/.claude-mem/logs/` with daily rotation:
|
||||
|
||||
```bash
|
||||
# View today's log
|
||||
cat ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
|
||||
|
||||
# View yesterday's log
|
||||
cat ~/.claude-mem/logs/worker-$(date -d "yesterday" +%Y-%m-%d).log # Linux
|
||||
cat ~/.claude-mem/logs/worker-$(date -v-1d +%Y-%m-%d).log # macOS
|
||||
|
||||
# List all logs
|
||||
ls -lh ~/.claude-mem/logs/
|
||||
|
||||
# Clean old logs (older than 7 days)
|
||||
find ~/.claude-mem/logs/ -name "worker-*.log" -mtime +7 -delete
|
||||
|
||||
# Archive logs
|
||||
tar -czf ~/claude-mem-logs-backup-$(date +%Y-%m-%d).tar.gz ~/.claude-mem/logs/
|
||||
```
|
||||
|
||||
**Note:** Logs auto-rotate daily. No manual flush required.
|
||||
|
||||
## Testing Worker Endpoints
|
||||
|
||||
Once worker is running, test all endpoints:
|
||||
@@ -298,10 +341,22 @@ curl -s http://127.0.0.1:37777/ | head -20
|
||||
curl -s http://127.0.0.1:37777/api/stats
|
||||
|
||||
# Search API
|
||||
curl -s "http://127.0.0.1:37777/api/search/observations?q=test&format=index"
|
||||
curl -s "http://127.0.0.1:37777/api/search?query=test&limit=5"
|
||||
|
||||
# Prompts API
|
||||
curl -s "http://127.0.0.1:37777/api/prompts?limit=5"
|
||||
# Recent context
|
||||
curl -s "http://127.0.0.1:37777/api/context/recent?limit=3"
|
||||
```
|
||||
|
||||
All should return appropriate responses (HTML for viewer, JSON for APIs).
|
||||
|
||||
## Troubleshooting Quick Reference
|
||||
|
||||
| Problem | Command | Expected Result |
|
||||
|---------|---------|----------------|
|
||||
| Check if running | `npm run worker:status` | Shows PID and uptime |
|
||||
| Worker not running | `npm run worker:start` | Worker starts successfully |
|
||||
| Worker crashed | `claude-mem restart` | Worker restarts |
|
||||
| View recent errors | `grep -i error ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log \| tail -20` | Shows recent errors |
|
||||
| Port in use | `lsof -i :37777` | Shows process using port |
|
||||
| Stale PID | `rm ~/.claude-mem/worker.pid && npm run worker:start` | Removes stale PID and starts |
|
||||
| Dependencies missing | `npm install && npm run worker:start` | Installs deps and starts |
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'fs';
|
||||
import Database from 'better-sqlite3';
|
||||
import { Database } from 'bun:sqlite';
|
||||
import readline from 'readline';
|
||||
import path from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
import { generateBugReport } from "./index.ts";
|
||||
import { collectDiagnostics } from "./collector.ts";
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
import * as readline from "readline";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface CliArgs {
|
||||
output?: string;
|
||||
verbose: boolean;
|
||||
noLogs: boolean;
|
||||
help: boolean;
|
||||
}
|
||||
|
||||
function parseArgs(): CliArgs {
|
||||
const args = process.argv.slice(2);
|
||||
const parsed: CliArgs = {
|
||||
verbose: false,
|
||||
noLogs: false,
|
||||
help: false,
|
||||
};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
switch (arg) {
|
||||
case "-h":
|
||||
case "--help":
|
||||
parsed.help = true;
|
||||
break;
|
||||
case "-v":
|
||||
case "--verbose":
|
||||
parsed.verbose = true;
|
||||
break;
|
||||
case "--no-logs":
|
||||
parsed.noLogs = true;
|
||||
break;
|
||||
case "-o":
|
||||
case "--output":
|
||||
parsed.output = args[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log(`
|
||||
bug-report - Generate bug reports for claude-mem
|
||||
|
||||
USAGE:
|
||||
npm run bug-report [options]
|
||||
|
||||
OPTIONS:
|
||||
-o, --output <file> Save report to file (default: stdout + timestamped file)
|
||||
-v, --verbose Show all collected diagnostics
|
||||
--no-logs Skip log collection (for privacy)
|
||||
-h, --help Show this help message
|
||||
|
||||
DESCRIPTION:
|
||||
This script collects system diagnostics, prompts you for issue details,
|
||||
and generates a formatted GitHub issue for claude-mem using the Claude Agent SDK.
|
||||
|
||||
The generated report will be saved to ~/bug-report-YYYY-MM-DD-HHMMSS.md
|
||||
and displayed in your terminal for easy copy-pasting to GitHub.
|
||||
|
||||
EXAMPLES:
|
||||
# Generate a bug report interactively
|
||||
npm run bug-report
|
||||
|
||||
# Generate without including logs (for privacy)
|
||||
npm run bug-report --no-logs
|
||||
|
||||
# Save to a specific file
|
||||
npm run bug-report --output ~/my-bug-report.md
|
||||
|
||||
# Show all diagnostic details during collection
|
||||
npm run bug-report --verbose
|
||||
`);
|
||||
}
|
||||
|
||||
async function promptUser(question: string): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer) => {
|
||||
rl.close();
|
||||
resolve(answer.trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function promptMultiline(prompt: string): Promise<string> {
|
||||
console.log(prompt);
|
||||
console.log("(Press Enter on an empty line to finish)\n");
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.on("line", (line) => {
|
||||
// Empty line means we're done
|
||||
if (line.trim() === "" && lines.length > 0) {
|
||||
rl.close();
|
||||
resolve(lines.join("\n"));
|
||||
} else if (line.trim() !== "") {
|
||||
// Only add non-empty lines (or preserve empty lines in the middle)
|
||||
lines.push(line);
|
||||
}
|
||||
});
|
||||
|
||||
rl.on("close", () => {
|
||||
resolve(lines.join("\n"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs();
|
||||
|
||||
if (args.help) {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("🌎 Leave report in ANY language, and it will auto translate to English\n");
|
||||
console.log("🔍 Collecting system diagnostics...");
|
||||
|
||||
// Collect diagnostics
|
||||
const diagnostics = await collectDiagnostics({
|
||||
includeLogs: !args.noLogs,
|
||||
});
|
||||
|
||||
console.log("✓ Version information collected");
|
||||
console.log("✓ Platform details collected");
|
||||
console.log("✓ Worker status checked");
|
||||
if (!args.noLogs) {
|
||||
console.log(
|
||||
`✓ Logs extracted (last ${diagnostics.logs.workerLog.length + diagnostics.logs.silentLog.length} lines)`
|
||||
);
|
||||
}
|
||||
console.log("✓ Configuration loaded\n");
|
||||
|
||||
// Show summary
|
||||
console.log("📋 System Summary:");
|
||||
console.log(` Claude-mem: v${diagnostics.versions.claudeMem}`);
|
||||
console.log(` Claude Code: ${diagnostics.versions.claudeCode}`);
|
||||
console.log(
|
||||
` Platform: ${diagnostics.platform.osVersion} (${diagnostics.platform.arch})`
|
||||
);
|
||||
console.log(
|
||||
` Worker: ${diagnostics.worker.running ? `Running (PID ${diagnostics.worker.pid}, port ${diagnostics.worker.port})` : "Not running"}\n`
|
||||
);
|
||||
|
||||
if (args.verbose) {
|
||||
console.log("📊 Detailed Diagnostics:");
|
||||
console.log(JSON.stringify(diagnostics, null, 2));
|
||||
console.log();
|
||||
}
|
||||
|
||||
// Prompt for issue details
|
||||
const issueDescription = await promptMultiline(
|
||||
"Please describe the issue you're experiencing:"
|
||||
);
|
||||
|
||||
if (!issueDescription.trim()) {
|
||||
console.error("❌ Issue description is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log();
|
||||
const expectedBehavior = await promptMultiline(
|
||||
"Expected behavior (leave blank to skip):"
|
||||
);
|
||||
|
||||
console.log();
|
||||
const stepsToReproduce = await promptMultiline(
|
||||
"Steps to reproduce (leave blank to skip):"
|
||||
);
|
||||
|
||||
console.log();
|
||||
const confirm = await promptUser(
|
||||
"Generate bug report? (y/n): "
|
||||
);
|
||||
|
||||
if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") {
|
||||
console.log("❌ Bug report generation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("\n🤖 Generating bug report with Claude...");
|
||||
|
||||
// Generate the bug report
|
||||
const result = await generateBugReport({
|
||||
issueDescription,
|
||||
expectedBehavior: expectedBehavior.trim() || undefined,
|
||||
stepsToReproduce: stepsToReproduce.trim() || undefined,
|
||||
includeLogs: !args.noLogs,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error("❌ Failed to generate bug report:", result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("✓ Issue formatted successfully\n");
|
||||
|
||||
// Generate output file path
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.replace(/:/g, "")
|
||||
.replace(/\..+/, "")
|
||||
.replace("T", "-");
|
||||
const defaultOutputPath = path.join(
|
||||
os.homedir(),
|
||||
`bug-report-${timestamp}.md`
|
||||
);
|
||||
const outputPath = args.output || defaultOutputPath;
|
||||
|
||||
// Save to file
|
||||
await fs.writeFile(outputPath, result.body, "utf-8");
|
||||
|
||||
// Build GitHub URL with pre-filled title and body
|
||||
const encodedTitle = encodeURIComponent(result.title);
|
||||
const encodedBody = encodeURIComponent(result.body);
|
||||
const githubUrl = `https://github.com/thedotmack/claude-mem/issues/new?title=${encodedTitle}&body=${encodedBody}`;
|
||||
|
||||
// Display the report
|
||||
console.log("─".repeat(60));
|
||||
console.log("📋 BUG REPORT GENERATED");
|
||||
console.log("─".repeat(60));
|
||||
console.log();
|
||||
console.log(result.body);
|
||||
console.log();
|
||||
console.log("─".repeat(60));
|
||||
console.log("Suggested labels: bug, needs-triage");
|
||||
console.log(`Report saved to: ${outputPath}`);
|
||||
console.log("─".repeat(60));
|
||||
console.log();
|
||||
|
||||
// Open GitHub issue in browser
|
||||
console.log("🌐 Opening GitHub issue form in your browser...");
|
||||
try {
|
||||
const openCommand =
|
||||
process.platform === "darwin"
|
||||
? "open"
|
||||
: process.platform === "win32"
|
||||
? "start"
|
||||
: "xdg-open";
|
||||
|
||||
await execAsync(`${openCommand} "${githubUrl}"`);
|
||||
console.log("✓ Browser opened successfully");
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to open browser. Please visit:");
|
||||
console.error(githubUrl);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Fatal error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,364 @@
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import * as os from "os";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export interface SystemDiagnostics {
|
||||
versions: {
|
||||
claudeMem: string;
|
||||
claudeCode: string;
|
||||
node: string;
|
||||
bun: string;
|
||||
};
|
||||
platform: {
|
||||
os: string;
|
||||
osVersion: string;
|
||||
arch: string;
|
||||
};
|
||||
paths: {
|
||||
pluginPath: string;
|
||||
dataDir: string;
|
||||
cwd: string;
|
||||
isDevMode: boolean;
|
||||
};
|
||||
worker: {
|
||||
running: boolean;
|
||||
pid?: number;
|
||||
port?: number;
|
||||
uptime?: number;
|
||||
version?: string;
|
||||
health?: any;
|
||||
stats?: any;
|
||||
};
|
||||
logs: {
|
||||
workerLog: string[];
|
||||
silentLog: string[];
|
||||
};
|
||||
database: {
|
||||
path: string;
|
||||
exists: boolean;
|
||||
size?: number;
|
||||
counts?: {
|
||||
observations: number;
|
||||
sessions: number;
|
||||
summaries: number;
|
||||
};
|
||||
};
|
||||
config: {
|
||||
settingsPath: string;
|
||||
settingsExist: boolean;
|
||||
settings?: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
function sanitizePath(filePath: string): string {
|
||||
const homeDir = os.homedir();
|
||||
return filePath.replace(homeDir, "~");
|
||||
}
|
||||
|
||||
async function getClaudememVersion(): Promise<string> {
|
||||
try {
|
||||
const packageJsonPath = path.join(process.cwd(), "package.json");
|
||||
const content = await fs.readFile(packageJsonPath, "utf-8");
|
||||
const pkg = JSON.parse(content);
|
||||
return pkg.version || "unknown";
|
||||
} catch (error) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
async function getClaudeCodeVersion(): Promise<string> {
|
||||
try {
|
||||
const { stdout } = await execAsync("claude --version");
|
||||
return stdout.trim();
|
||||
} catch (error) {
|
||||
return "not installed or not in PATH";
|
||||
}
|
||||
}
|
||||
|
||||
async function getBunVersion(): Promise<string> {
|
||||
try {
|
||||
const { stdout } = await execAsync("bun --version");
|
||||
return stdout.trim();
|
||||
} catch (error) {
|
||||
return "not installed";
|
||||
}
|
||||
}
|
||||
|
||||
async function getOsVersion(): Promise<string> {
|
||||
try {
|
||||
if (process.platform === "darwin") {
|
||||
const { stdout } = await execAsync("sw_vers -productVersion");
|
||||
return `macOS ${stdout.trim()}`;
|
||||
} else if (process.platform === "linux") {
|
||||
const { stdout } = await execAsync("uname -sr");
|
||||
return stdout.trim();
|
||||
} else if (process.platform === "win32") {
|
||||
const { stdout } = await execAsync("ver");
|
||||
return stdout.trim();
|
||||
}
|
||||
return "unknown";
|
||||
} catch (error) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
async function checkWorkerHealth(port: number): Promise<any> {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
||||
signal: AbortSignal.timeout(2000),
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getWorkerStats(port: number): Promise<any> {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/stats`, {
|
||||
signal: AbortSignal.timeout(2000),
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function readPidFile(dataDir: string): Promise<any> {
|
||||
try {
|
||||
const pidPath = path.join(dataDir, "worker.pid");
|
||||
const content = await fs.readFile(pidPath, "utf-8");
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function readLogLines(logPath: string, lines: number): Promise<string[]> {
|
||||
try {
|
||||
const content = await fs.readFile(logPath, "utf-8");
|
||||
const allLines = content.split("\n").filter((line) => line.trim());
|
||||
return allLines.slice(-lines);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getSettings(
|
||||
dataDir: string
|
||||
): Promise<{ exists: boolean; settings?: Record<string, any> }> {
|
||||
try {
|
||||
const settingsPath = path.join(dataDir, "settings.json");
|
||||
const content = await fs.readFile(settingsPath, "utf-8");
|
||||
const settings = JSON.parse(content);
|
||||
return { exists: true, settings };
|
||||
} catch (error) {
|
||||
return { exists: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function getDatabaseInfo(
|
||||
dataDir: string
|
||||
): Promise<{ exists: boolean; size?: number }> {
|
||||
try {
|
||||
const dbPath = path.join(dataDir, "claude-mem.db");
|
||||
const stats = await fs.stat(dbPath);
|
||||
return { exists: true, size: stats.size };
|
||||
} catch (error) {
|
||||
return { exists: false };
|
||||
}
|
||||
}
|
||||
|
||||
export async function collectDiagnostics(
|
||||
options: { includeLogs?: boolean } = {}
|
||||
): Promise<SystemDiagnostics> {
|
||||
const homeDir = os.homedir();
|
||||
const dataDir = path.join(homeDir, ".claude-mem");
|
||||
const pluginPath = path.join(
|
||||
homeDir,
|
||||
".claude",
|
||||
"plugins",
|
||||
"marketplaces",
|
||||
"thedotmack"
|
||||
);
|
||||
const cwd = process.cwd();
|
||||
const isDevMode = cwd.includes("claude-mem") && !cwd.includes(".claude");
|
||||
|
||||
// Collect version information
|
||||
const [claudeMem, claudeCode, bun, osVersion] = await Promise.all([
|
||||
getClaudememVersion(),
|
||||
getClaudeCodeVersion(),
|
||||
getBunVersion(),
|
||||
getOsVersion(),
|
||||
]);
|
||||
|
||||
const versions = {
|
||||
claudeMem,
|
||||
claudeCode,
|
||||
node: process.version,
|
||||
bun,
|
||||
};
|
||||
|
||||
const platform = {
|
||||
os: process.platform,
|
||||
osVersion,
|
||||
arch: process.arch,
|
||||
};
|
||||
|
||||
const paths = {
|
||||
pluginPath: sanitizePath(pluginPath),
|
||||
dataDir: sanitizePath(dataDir),
|
||||
cwd: sanitizePath(cwd),
|
||||
isDevMode,
|
||||
};
|
||||
|
||||
// Check worker status
|
||||
const pidInfo = await readPidFile(dataDir);
|
||||
const workerPort = pidInfo?.port || 37777;
|
||||
|
||||
const [health, stats] = await Promise.all([
|
||||
checkWorkerHealth(workerPort),
|
||||
getWorkerStats(workerPort),
|
||||
]);
|
||||
|
||||
const worker = {
|
||||
running: health !== null,
|
||||
pid: pidInfo?.pid,
|
||||
port: workerPort,
|
||||
uptime: stats?.worker?.uptime,
|
||||
version: stats?.worker?.version,
|
||||
health,
|
||||
stats,
|
||||
};
|
||||
|
||||
// Collect logs if requested
|
||||
let workerLog: string[] = [];
|
||||
let silentLog: string[] = [];
|
||||
|
||||
if (options.includeLogs !== false) {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
const workerLogPath = path.join(dataDir, "logs", `worker-${today}.log`);
|
||||
const silentLogPath = path.join(dataDir, "silent.log");
|
||||
|
||||
[workerLog, silentLog] = await Promise.all([
|
||||
readLogLines(workerLogPath, 50),
|
||||
readLogLines(silentLogPath, 50),
|
||||
]);
|
||||
}
|
||||
|
||||
const logs = {
|
||||
workerLog: workerLog.map(sanitizePath),
|
||||
silentLog: silentLog.map(sanitizePath),
|
||||
};
|
||||
|
||||
// Database info
|
||||
const dbInfo = await getDatabaseInfo(dataDir);
|
||||
const database = {
|
||||
path: sanitizePath(path.join(dataDir, "claude-mem.db")),
|
||||
exists: dbInfo.exists,
|
||||
size: dbInfo.size,
|
||||
// TODO: Add table counts if we want to query the database
|
||||
};
|
||||
|
||||
// Configuration
|
||||
const settingsInfo = await getSettings(dataDir);
|
||||
const config = {
|
||||
settingsPath: sanitizePath(path.join(dataDir, "settings.json")),
|
||||
settingsExist: settingsInfo.exists,
|
||||
settings: settingsInfo.settings,
|
||||
};
|
||||
|
||||
return {
|
||||
versions,
|
||||
platform,
|
||||
paths,
|
||||
worker,
|
||||
logs,
|
||||
database,
|
||||
config,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatDiagnostics(diagnostics: SystemDiagnostics): string {
|
||||
let output = "";
|
||||
|
||||
output += "## Environment\n\n";
|
||||
output += `- **Claude-mem**: ${diagnostics.versions.claudeMem}\n`;
|
||||
output += `- **Claude Code**: ${diagnostics.versions.claudeCode}\n`;
|
||||
output += `- **Node.js**: ${diagnostics.versions.node}\n`;
|
||||
output += `- **Bun**: ${diagnostics.versions.bun}\n`;
|
||||
output += `- **OS**: ${diagnostics.platform.osVersion} (${diagnostics.platform.arch})\n`;
|
||||
output += `- **Platform**: ${diagnostics.platform.os}\n\n`;
|
||||
|
||||
output += "## Paths\n\n";
|
||||
output += `- **Plugin**: ${diagnostics.paths.pluginPath}\n`;
|
||||
output += `- **Data Directory**: ${diagnostics.paths.dataDir}\n`;
|
||||
output += `- **Current Directory**: ${diagnostics.paths.cwd}\n`;
|
||||
output += `- **Dev Mode**: ${diagnostics.paths.isDevMode ? "Yes" : "No"}\n\n`;
|
||||
|
||||
output += "## Worker Status\n\n";
|
||||
output += `- **Running**: ${diagnostics.worker.running ? "Yes" : "No"}\n`;
|
||||
if (diagnostics.worker.running) {
|
||||
output += `- **PID**: ${diagnostics.worker.pid || "unknown"}\n`;
|
||||
output += `- **Port**: ${diagnostics.worker.port}\n`;
|
||||
if (diagnostics.worker.uptime !== undefined) {
|
||||
const uptimeMinutes = Math.floor(diagnostics.worker.uptime / 60);
|
||||
output += `- **Uptime**: ${uptimeMinutes} minutes\n`;
|
||||
}
|
||||
if (diagnostics.worker.stats) {
|
||||
output += `- **Active Sessions**: ${diagnostics.worker.stats.worker?.activeSessions || 0}\n`;
|
||||
output += `- **SSE Clients**: ${diagnostics.worker.stats.worker?.sseClients || 0}\n`;
|
||||
}
|
||||
}
|
||||
output += "\n";
|
||||
|
||||
output += "## Database\n\n";
|
||||
output += `- **Path**: ${diagnostics.database.path}\n`;
|
||||
output += `- **Exists**: ${diagnostics.database.exists ? "Yes" : "No"}\n`;
|
||||
if (diagnostics.database.size) {
|
||||
const sizeKB = (diagnostics.database.size / 1024).toFixed(2);
|
||||
output += `- **Size**: ${sizeKB} KB\n`;
|
||||
}
|
||||
output += "\n";
|
||||
|
||||
output += "## Configuration\n\n";
|
||||
output += `- **Settings File**: ${diagnostics.config.settingsPath}\n`;
|
||||
output += `- **Settings Exist**: ${diagnostics.config.settingsExist ? "Yes" : "No"}\n`;
|
||||
if (diagnostics.config.settings) {
|
||||
output += "- **Key Settings**:\n";
|
||||
const keySettings = [
|
||||
"CLAUDE_MEM_MODEL",
|
||||
"CLAUDE_MEM_WORKER_PORT",
|
||||
"CLAUDE_MEM_WORKER_HOST",
|
||||
"CLAUDE_MEM_LOG_LEVEL",
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS",
|
||||
];
|
||||
for (const key of keySettings) {
|
||||
if (diagnostics.config.settings[key]) {
|
||||
output += ` - ${key}: ${diagnostics.config.settings[key]}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
output += "\n";
|
||||
|
||||
// Add logs if present
|
||||
if (diagnostics.logs.workerLog.length > 0) {
|
||||
output += "## Recent Worker Logs (Last 50 Lines)\n\n";
|
||||
output += "```\n";
|
||||
output += diagnostics.logs.workerLog.join("\n");
|
||||
output += "\n```\n\n";
|
||||
}
|
||||
|
||||
if (diagnostics.logs.silentLog.length > 0) {
|
||||
output += "## Silent Debug Log (Last 50 Lines)\n\n";
|
||||
output += "```\n";
|
||||
output += diagnostics.logs.silentLog.join("\n");
|
||||
output += "\n```\n\n";
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
query,
|
||||
type SDKMessage,
|
||||
type SDKResultMessage,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
import {
|
||||
collectDiagnostics,
|
||||
formatDiagnostics,
|
||||
type SystemDiagnostics,
|
||||
} from "./collector.ts";
|
||||
|
||||
export interface BugReportInput {
|
||||
issueDescription: string;
|
||||
expectedBehavior?: string;
|
||||
stepsToReproduce?: string;
|
||||
includeLogs?: boolean;
|
||||
}
|
||||
|
||||
export interface BugReportResult {
|
||||
title: string;
|
||||
body: string;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function generateBugReport(
|
||||
input: BugReportInput
|
||||
): Promise<BugReportResult> {
|
||||
try {
|
||||
// Collect system diagnostics
|
||||
const diagnostics = await collectDiagnostics({
|
||||
includeLogs: input.includeLogs !== false,
|
||||
});
|
||||
|
||||
const formattedDiagnostics = formatDiagnostics(diagnostics);
|
||||
|
||||
// Build the prompt
|
||||
const prompt = buildPrompt(
|
||||
formattedDiagnostics,
|
||||
input.issueDescription,
|
||||
input.expectedBehavior,
|
||||
input.stepsToReproduce
|
||||
);
|
||||
|
||||
// Use Agent SDK to generate formatted issue
|
||||
let generatedMarkdown = "";
|
||||
let charCount = 0;
|
||||
const startTime = Date.now();
|
||||
|
||||
const stream = query({
|
||||
prompt,
|
||||
options: {
|
||||
model: "sonnet",
|
||||
systemPrompt: `You are a GitHub issue formatter. Format bug reports clearly and professionally.`,
|
||||
permissionMode: "bypassPermissions",
|
||||
allowDangerouslySkipPermissions: true,
|
||||
includePartialMessages: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Progress spinner frames
|
||||
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
let spinnerIdx = 0;
|
||||
|
||||
// Stream the response
|
||||
for await (const message of stream) {
|
||||
if (message.type === "stream_event") {
|
||||
const event = message.event as { type: string; delta?: { type: string; text?: string } };
|
||||
if (event.type === "content_block_delta" && event.delta?.type === "text_delta" && event.delta.text) {
|
||||
generatedMarkdown += event.delta.text;
|
||||
charCount += event.delta.text.length;
|
||||
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
const spinner = spinnerFrames[spinnerIdx++ % spinnerFrames.length];
|
||||
process.stdout.write(`\r ${spinner} Generating... ${charCount} chars (${elapsed}s)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle full assistant messages (fallback)
|
||||
if (message.type === "assistant") {
|
||||
for (const block of message.message.content) {
|
||||
if (block.type === "text" && !generatedMarkdown) {
|
||||
generatedMarkdown = block.text;
|
||||
charCount = generatedMarkdown.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle result
|
||||
if (message.type === "result") {
|
||||
const result = message as SDKResultMessage;
|
||||
if (result.subtype === "success" && !generatedMarkdown && result.result) {
|
||||
generatedMarkdown = result.result;
|
||||
charCount = generatedMarkdown.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the progress line
|
||||
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
||||
|
||||
// Extract title from markdown (first heading)
|
||||
const titleMatch = generatedMarkdown.match(/^#\s+(.+)$/m);
|
||||
const title = titleMatch ? titleMatch[1] : "Bug Report";
|
||||
|
||||
return {
|
||||
title,
|
||||
body: generatedMarkdown,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
// Fallback to template-based generation
|
||||
console.error("Agent SDK failed, using template fallback:", error);
|
||||
return generateTemplateFallback(input);
|
||||
}
|
||||
}
|
||||
|
||||
function buildPrompt(
|
||||
diagnostics: string,
|
||||
issueDescription: string,
|
||||
expectedBehavior?: string,
|
||||
stepsToReproduce?: string
|
||||
): string {
|
||||
let prompt = `You are a GitHub issue formatter. Given system diagnostics and a user's bug description, create a well-structured GitHub issue for the claude-mem repository.
|
||||
|
||||
SYSTEM DIAGNOSTICS:
|
||||
${diagnostics}
|
||||
|
||||
USER DESCRIPTION:
|
||||
${issueDescription}
|
||||
`;
|
||||
|
||||
if (expectedBehavior) {
|
||||
prompt += `\nEXPECTED BEHAVIOR:
|
||||
${expectedBehavior}
|
||||
`;
|
||||
}
|
||||
|
||||
if (stepsToReproduce) {
|
||||
prompt += `\nSTEPS TO REPRODUCE:
|
||||
${stepsToReproduce}
|
||||
`;
|
||||
}
|
||||
|
||||
prompt += `
|
||||
|
||||
IMPORTANT: If any part of the user's description is in a language other than English, translate it to English while preserving technical accuracy and meaning.
|
||||
|
||||
Create a GitHub issue with:
|
||||
1. Clear, descriptive title (max 80 chars) in English - start with a single # heading
|
||||
2. Problem statement summarizing the issue in English
|
||||
3. Environment section (versions, platform) from the diagnostics
|
||||
4. Steps to reproduce (if provided) in English
|
||||
5. Expected vs actual behavior in English
|
||||
6. Relevant logs (formatted as code blocks) if present in diagnostics
|
||||
7. Any additional context that would help diagnose the issue
|
||||
|
||||
Format the output as valid GitHub Markdown. Make sure the title is a single # heading at the very top.
|
||||
Do NOT add meta-commentary like "Here's a formatted issue" - just output the raw markdown.
|
||||
All content must be in English for the GitHub issue.
|
||||
`;
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
async function generateTemplateFallback(
|
||||
input: BugReportInput
|
||||
): Promise<BugReportResult> {
|
||||
const diagnostics = await collectDiagnostics({
|
||||
includeLogs: input.includeLogs !== false,
|
||||
});
|
||||
const formattedDiagnostics = formatDiagnostics(diagnostics);
|
||||
|
||||
let body = `# Bug Report\n\n`;
|
||||
body += `## Description\n\n`;
|
||||
body += `${input.issueDescription}\n\n`;
|
||||
|
||||
if (input.expectedBehavior) {
|
||||
body += `## Expected Behavior\n\n`;
|
||||
body += `${input.expectedBehavior}\n\n`;
|
||||
}
|
||||
|
||||
if (input.stepsToReproduce) {
|
||||
body += `## Steps to Reproduce\n\n`;
|
||||
body += `${input.stepsToReproduce}\n\n`;
|
||||
}
|
||||
|
||||
body += formattedDiagnostics;
|
||||
|
||||
return {
|
||||
title: "Bug Report",
|
||||
body,
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
+47
-1
@@ -26,6 +26,11 @@ const WORKER_SERVICE = {
|
||||
source: 'src/services/worker-service.ts'
|
||||
};
|
||||
|
||||
const WORKER_WRAPPER = {
|
||||
name: 'worker-wrapper',
|
||||
source: 'src/services/worker-wrapper.ts'
|
||||
};
|
||||
|
||||
const MCP_SERVER = {
|
||||
name: 'mcp-server',
|
||||
source: 'src/servers/mcp-server.ts'
|
||||
@@ -120,6 +125,31 @@ async function buildHooks() {
|
||||
const workerStats = fs.statSync(`${hooksDir}/${WORKER_SERVICE.name}.cjs`);
|
||||
console.log(`✓ worker-service built (${(workerStats.size / 1024).toFixed(2)} KB)`);
|
||||
|
||||
// Build worker wrapper (Windows zombie port fix)
|
||||
console.log(`\n🔧 Building worker wrapper...`);
|
||||
await build({
|
||||
entryPoints: [WORKER_WRAPPER.source],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
target: 'node18',
|
||||
format: 'cjs',
|
||||
outfile: `${hooksDir}/${WORKER_WRAPPER.name}.cjs`,
|
||||
minify: true,
|
||||
logLevel: 'error',
|
||||
external: ['bun:sqlite'],
|
||||
define: {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
},
|
||||
banner: {
|
||||
js: '#!/usr/bin/env bun'
|
||||
}
|
||||
});
|
||||
|
||||
// Make worker wrapper executable
|
||||
fs.chmodSync(`${hooksDir}/${WORKER_WRAPPER.name}.cjs`, 0o755);
|
||||
const wrapperStats = fs.statSync(`${hooksDir}/${WORKER_WRAPPER.name}.cjs`);
|
||||
console.log(`✓ worker-wrapper built (${(wrapperStats.size / 1024).toFixed(2)} KB)`);
|
||||
|
||||
// Build MCP server
|
||||
console.log(`\n🔧 Building MCP server...`);
|
||||
await build({
|
||||
@@ -136,7 +166,7 @@ async function buildHooks() {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
},
|
||||
banner: {
|
||||
js: '#!/usr/bin/env bun'
|
||||
js: '#!/usr/bin/env node'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -222,12 +252,28 @@ async function buildHooks() {
|
||||
console.log(`✓ ${hook.name} built (${sizeInKB} KB)`);
|
||||
}
|
||||
|
||||
// Build mem-search skill zip for Claude Desktop
|
||||
console.log('\n📦 Building mem-search skill zip for Claude Desktop...');
|
||||
const { execSync } = await import('child_process');
|
||||
const zipOutput = 'plugin/skills/mem-search.zip';
|
||||
|
||||
// Remove old zip if exists
|
||||
if (fs.existsSync(zipOutput)) {
|
||||
fs.unlinkSync(zipOutput);
|
||||
}
|
||||
|
||||
// Create zip from mem-search skill directory
|
||||
execSync(`cd plugin/skills && zip -r mem-search.zip mem-search/`, { stdio: 'pipe' });
|
||||
const zipStats = fs.statSync(zipOutput);
|
||||
console.log(`✓ mem-search.zip built (${(zipStats.size / 1024).toFixed(2)} KB)`);
|
||||
|
||||
console.log('\n✅ All hooks, worker service, and MCP server built successfully!');
|
||||
console.log(` Output: ${hooksDir}/`);
|
||||
console.log(` - Hooks: *-hook.js`);
|
||||
console.log(` - Worker: worker-service.cjs`);
|
||||
console.log(` - MCP Server: mcp-server.cjs`);
|
||||
console.log(` - Skills: plugin/skills/`);
|
||||
console.log(` - Desktop Skill: plugin/skills/mem-search.zip`);
|
||||
console.log('\n💡 Note: Dependencies will be auto-installed on first hook execution');
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Post release notification to Discord
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/discord-release-notify.js v7.4.2
|
||||
* node scripts/discord-release-notify.js v7.4.2 "Custom release notes"
|
||||
*
|
||||
* Requires DISCORD_UPDATES_WEBHOOK in .env file
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = resolve(__dirname, '..');
|
||||
|
||||
function loadEnv() {
|
||||
const envPath = resolve(projectRoot, '.env');
|
||||
if (!existsSync(envPath)) {
|
||||
console.error('❌ .env file not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const envContent = readFileSync(envPath, 'utf-8');
|
||||
const webhookMatch = envContent.match(/DISCORD_UPDATES_WEBHOOK=(.+)/);
|
||||
|
||||
if (!webhookMatch) {
|
||||
console.error('❌ DISCORD_UPDATES_WEBHOOK not found in .env');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return webhookMatch[1].trim();
|
||||
}
|
||||
|
||||
function getReleaseNotes(version) {
|
||||
try {
|
||||
const notes = execSync(`gh release view ${version} --json body --jq '.body'`, {
|
||||
encoding: 'utf-8',
|
||||
cwd: projectRoot,
|
||||
}).trim();
|
||||
return notes;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanNotes(notes) {
|
||||
// Remove Claude Code footer and clean up
|
||||
return notes
|
||||
.replace(/🤖 Generated with \[Claude Code\].*$/s, '')
|
||||
.replace(/---\n*$/s, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function truncate(text, maxLength) {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.slice(0, maxLength - 3) + '...';
|
||||
}
|
||||
|
||||
async function postToDiscord(webhookUrl, version, notes) {
|
||||
const cleanedNotes = notes ? cleanNotes(notes) : 'No release notes available.';
|
||||
const repoUrl = 'https://github.com/thedotmack/claude-mem';
|
||||
|
||||
const payload = {
|
||||
embeds: [
|
||||
{
|
||||
title: `🚀 claude-mem ${version} released`,
|
||||
url: `${repoUrl}/releases/tag/${version}`,
|
||||
description: truncate(cleanedNotes, 2000),
|
||||
color: 0x7c3aed, // Purple
|
||||
fields: [
|
||||
{
|
||||
name: '📦 Install',
|
||||
value: 'Update via Claude Code plugin marketplace',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: '📚 Docs',
|
||||
value: '[docs.claude-mem.ai](https://docs.claude-mem.ai)',
|
||||
inline: true,
|
||||
},
|
||||
],
|
||||
footer: {
|
||||
text: 'claude-mem • Persistent memory for Claude Code',
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Discord API error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const version = process.argv[2];
|
||||
const customNotes = process.argv[3];
|
||||
|
||||
if (!version) {
|
||||
console.error('Usage: node scripts/discord-release-notify.js <version> [notes]');
|
||||
console.error('Example: node scripts/discord-release-notify.js v7.4.2');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`📣 Posting release notification for ${version}...`);
|
||||
|
||||
const webhookUrl = loadEnv();
|
||||
const notes = customNotes || getReleaseNotes(version);
|
||||
|
||||
if (!notes && !customNotes) {
|
||||
console.warn('⚠️ Could not fetch release notes from GitHub, proceeding without them');
|
||||
}
|
||||
|
||||
try {
|
||||
await postToDiscord(webhookUrl, version, notes);
|
||||
console.log('✅ Discord notification sent successfully!');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to send Discord notification:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Export memories matching a search query to a portable JSON format
|
||||
* Usage: npx tsx scripts/export-memories.ts <query> <output-file> [--project=name]
|
||||
* Example: npx tsx scripts/export-memories.ts "windows" windows-memories.json --project=claude-mem
|
||||
*/
|
||||
|
||||
import { writeFileSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { SettingsDefaultsManager } from '../src/shared/SettingsDefaultsManager';
|
||||
|
||||
interface ObservationRecord {
|
||||
id: number;
|
||||
sdk_session_id: string;
|
||||
project: string;
|
||||
text: string | null;
|
||||
type: string;
|
||||
title: string;
|
||||
subtitle: string | null;
|
||||
facts: string | null;
|
||||
narrative: string | null;
|
||||
concepts: string | null;
|
||||
files_read: string | null;
|
||||
files_modified: string | null;
|
||||
prompt_number: number;
|
||||
discovery_tokens: number | null;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}
|
||||
|
||||
interface SdkSessionRecord {
|
||||
id: number;
|
||||
claude_session_id: string;
|
||||
sdk_session_id: string;
|
||||
project: string;
|
||||
user_prompt: string;
|
||||
started_at: string;
|
||||
started_at_epoch: number;
|
||||
completed_at: string | null;
|
||||
completed_at_epoch: number | null;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface SessionSummaryRecord {
|
||||
id: number;
|
||||
sdk_session_id: string;
|
||||
project: string;
|
||||
request: string | null;
|
||||
investigated: string | null;
|
||||
learned: string | null;
|
||||
completed: string | null;
|
||||
next_steps: string | null;
|
||||
files_read: string | null;
|
||||
files_edited: string | null;
|
||||
notes: string | null;
|
||||
prompt_number: number;
|
||||
discovery_tokens: number | null;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}
|
||||
|
||||
interface UserPromptRecord {
|
||||
id: number;
|
||||
claude_session_id: string;
|
||||
prompt_number: number;
|
||||
prompt_text: string;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}
|
||||
|
||||
interface ExportData {
|
||||
exportedAt: string;
|
||||
exportedAtEpoch: number;
|
||||
query: string;
|
||||
project?: string;
|
||||
totalObservations: number;
|
||||
totalSessions: number;
|
||||
totalSummaries: number;
|
||||
totalPrompts: number;
|
||||
observations: ObservationRecord[];
|
||||
sessions: SdkSessionRecord[];
|
||||
summaries: SessionSummaryRecord[];
|
||||
prompts: UserPromptRecord[];
|
||||
}
|
||||
|
||||
async function exportMemories(query: string, outputFile: string, project?: string) {
|
||||
try {
|
||||
// Read port from settings
|
||||
const settings = SettingsDefaultsManager.loadFromFile(join(homedir(), '.claude-mem', 'settings.json'));
|
||||
const port = parseInt(settings.CLAUDE_MEM_WORKER_PORT, 10);
|
||||
const baseUrl = `http://localhost:${port}`;
|
||||
|
||||
console.log(`🔍 Searching for: "${query}"${project ? ` (project: ${project})` : ' (all projects)'}`);
|
||||
|
||||
// Build query params - use format=json for raw data
|
||||
const params = new URLSearchParams({
|
||||
query,
|
||||
format: 'json',
|
||||
limit: '999999'
|
||||
});
|
||||
if (project) params.set('project', project);
|
||||
|
||||
// Unified search - gets all result types using hybrid search
|
||||
console.log('📡 Fetching all memories via hybrid search...');
|
||||
const searchResponse = await fetch(`${baseUrl}/api/search?${params.toString()}`);
|
||||
if (!searchResponse.ok) {
|
||||
throw new Error(`Failed to search: ${searchResponse.status} ${searchResponse.statusText}`);
|
||||
}
|
||||
const searchData = await searchResponse.json();
|
||||
|
||||
const observations: ObservationRecord[] = searchData.observations || [];
|
||||
const summaries: SessionSummaryRecord[] = searchData.sessions || [];
|
||||
const prompts: UserPromptRecord[] = searchData.prompts || [];
|
||||
|
||||
console.log(`✅ Found ${observations.length} observations`);
|
||||
console.log(`✅ Found ${summaries.length} session summaries`);
|
||||
console.log(`✅ Found ${prompts.length} user prompts`);
|
||||
|
||||
// Get unique SDK session IDs from observations and summaries
|
||||
const sdkSessionIds = new Set<string>();
|
||||
observations.forEach((o) => {
|
||||
if (o.sdk_session_id) sdkSessionIds.add(o.sdk_session_id);
|
||||
});
|
||||
summaries.forEach((s) => {
|
||||
if (s.sdk_session_id) sdkSessionIds.add(s.sdk_session_id);
|
||||
});
|
||||
|
||||
// Get SDK sessions metadata via API
|
||||
console.log('📡 Fetching SDK sessions metadata...');
|
||||
let sessions: SdkSessionRecord[] = [];
|
||||
if (sdkSessionIds.size > 0) {
|
||||
const sessionsResponse = await fetch(`${baseUrl}/api/sdk-sessions/batch`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sdkSessionIds: Array.from(sdkSessionIds) })
|
||||
});
|
||||
if (sessionsResponse.ok) {
|
||||
sessions = await sessionsResponse.json();
|
||||
} else {
|
||||
console.warn(`⚠️ Failed to fetch SDK sessions: ${sessionsResponse.status}`);
|
||||
}
|
||||
}
|
||||
console.log(`✅ Found ${sessions.length} SDK sessions`);
|
||||
|
||||
// Create export data
|
||||
const exportData: ExportData = {
|
||||
exportedAt: new Date().toISOString(),
|
||||
exportedAtEpoch: Date.now(),
|
||||
query,
|
||||
project,
|
||||
totalObservations: observations.length,
|
||||
totalSessions: sessions.length,
|
||||
totalSummaries: summaries.length,
|
||||
totalPrompts: prompts.length,
|
||||
observations,
|
||||
sessions,
|
||||
summaries,
|
||||
prompts
|
||||
};
|
||||
|
||||
// Write to file
|
||||
writeFileSync(outputFile, JSON.stringify(exportData, null, 2));
|
||||
|
||||
console.log(`\n📦 Export complete!`);
|
||||
console.log(`📄 Output: ${outputFile}`);
|
||||
console.log(`📊 Stats:`);
|
||||
console.log(` • ${exportData.totalObservations} observations`);
|
||||
console.log(` • ${exportData.totalSessions} sessions`);
|
||||
console.log(` • ${exportData.totalSummaries} summaries`);
|
||||
console.log(` • ${exportData.totalPrompts} prompts`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Export failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI interface
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 2) {
|
||||
console.error('Usage: npx tsx scripts/export-memories.ts <query> <output-file> [--project=name]');
|
||||
console.error('Example: npx tsx scripts/export-memories.ts "windows" windows-memories.json --project=claude-mem');
|
||||
console.error(' npx tsx scripts/export-memories.ts "authentication" auth.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
const [query, outputFile, ...flags] = args;
|
||||
const project = flags.find(f => f.startsWith('--project='))?.split('=')[1];
|
||||
|
||||
exportMemories(query, outputFile, project);
|
||||
@@ -1,229 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* One-time script to extract tool handlers from mcp-server.ts into SearchManager.ts
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const projectRoot = join(__dirname, '..');
|
||||
|
||||
const mcpServerPath = join(projectRoot, 'src/servers/mcp-server.ts');
|
||||
const outputPath = join(projectRoot, 'src/services/worker/SearchManager.ts');
|
||||
|
||||
console.log('Reading mcp-server.ts...');
|
||||
const content = readFileSync(mcpServerPath, 'utf-8');
|
||||
|
||||
// Extract just the sections we need by finding line numbers
|
||||
// This is more reliable than parsing
|
||||
|
||||
// Extract tool handler bodies by finding each "handler: async (args: any) => {"
|
||||
// and extracting until the matching closing brace
|
||||
|
||||
const extractHandlerBody = (content, startPattern) => {
|
||||
const lines = content.split('\n');
|
||||
const startIdx = lines.findIndex(line => line.includes(startPattern));
|
||||
|
||||
if (startIdx === -1) return null;
|
||||
|
||||
// Find the "handler: async (args: any) => {" line
|
||||
let handlerIdx = -1;
|
||||
for (let i = startIdx; i < Math.min(startIdx + 30, lines.length); i++) {
|
||||
if (lines[i].includes('handler: async (args: any) => {')) {
|
||||
handlerIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (handlerIdx === -1) return null;
|
||||
|
||||
// Extract the body by counting braces
|
||||
let braceCount = 0;
|
||||
let bodyLines = [];
|
||||
let started = false;
|
||||
|
||||
for (let i = handlerIdx; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
for (const char of line) {
|
||||
if (char === '{') {
|
||||
braceCount++;
|
||||
started = true;
|
||||
} else if (char === '}') {
|
||||
braceCount--;
|
||||
}
|
||||
}
|
||||
|
||||
if (started) {
|
||||
bodyLines.push(line);
|
||||
}
|
||||
|
||||
if (started && braceCount === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the first line (handler wrapper) and last line (closing brace)
|
||||
if (bodyLines.length > 2) {
|
||||
bodyLines = bodyLines.slice(1, -1);
|
||||
}
|
||||
|
||||
return bodyLines.join('\n');
|
||||
};
|
||||
|
||||
// Tool name to search pattern mapping
|
||||
const tools = {
|
||||
'search': "name: 'search'",
|
||||
'timeline': "name: 'timeline'",
|
||||
'decisions': "name: 'decisions'",
|
||||
'changes': "name: 'changes'",
|
||||
'how_it_works': "name: 'how_it_works'",
|
||||
'search_observations': "name: 'search_observations'",
|
||||
'search_sessions': "name: 'search_sessions'",
|
||||
'search_user_prompts': "name: 'search_user_prompts'",
|
||||
'find_by_concept': "name: 'find_by_concept'",
|
||||
'find_by_file': "name: 'find_by_file'",
|
||||
'find_by_type': "name: 'find_by_type'",
|
||||
'get_recent_context': "name: 'get_recent_context'",
|
||||
'get_context_timeline': "name: 'get_context_timeline'",
|
||||
'get_timeline_by_query': "name: 'get_timeline_by_query'"
|
||||
};
|
||||
|
||||
console.log('Extracting tool handlers...');
|
||||
const handlers = {};
|
||||
|
||||
for (const [toolName, pattern] of Object.entries(tools)) {
|
||||
console.log(` Extracting ${toolName}...`);
|
||||
const body = extractHandlerBody(content, pattern);
|
||||
if (body) {
|
||||
handlers[toolName] = body;
|
||||
console.log(` ✓ ${body.split('\n').length} lines`);
|
||||
} else {
|
||||
console.log(` ✗ Not found`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nExtracted ${Object.keys(handlers).length}/${Object.keys(tools).length} handlers`);
|
||||
|
||||
// Now generate SearchManager.ts
|
||||
console.log('\nGenerating SearchManager.ts...');
|
||||
|
||||
const methodBodies = Object.entries(handlers).map(([toolName, body]) => {
|
||||
// Convert tool name to camelCase method name
|
||||
const methodName = toolName.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
|
||||
// Replace standalone function calls with class methods
|
||||
let processedBody = body
|
||||
.replace(/formatSearchTips\(\)/g, 'this.formatter.formatSearchTips()')
|
||||
.replace(/formatObservationIndex\(/g, 'this.formatter.formatObservationIndex(')
|
||||
.replace(/formatSessionIndex\(/g, 'this.formatter.formatSessionIndex(')
|
||||
.replace(/formatUserPromptIndex\(/g, 'this.formatter.formatUserPromptIndex(')
|
||||
.replace(/formatObservationResult\(/g, 'this.formatter.formatObservationResult(')
|
||||
.replace(/formatSessionResult\(/g, 'this.formatter.formatSessionResult(')
|
||||
.replace(/formatUserPromptResult\(/g, 'this.formatter.formatUserPromptResult(')
|
||||
.replace(/filterTimelineByDepth\(/g, 'this.timeline.filterByDepth(')
|
||||
.replace(/\bsearch\./g, 'this.sessionSearch.')
|
||||
.replace(/\bstore\./g, 'this.sessionStore.')
|
||||
.replace(/queryChroma\(/g, 'this.queryChroma(')
|
||||
.replace(/normalizeParams\(/g, 'this.normalizeParams(')
|
||||
.replace(/chromaClient/g, 'this.chromaSync');
|
||||
|
||||
return ` /**
|
||||
* Tool handler: ${toolName}
|
||||
*/
|
||||
async ${methodName}(args: any): Promise<any> {
|
||||
${processedBody}
|
||||
}`;
|
||||
}).join('\n\n');
|
||||
|
||||
const searchManagerContent = `/**
|
||||
* SearchManager - Core search orchestration for claude-mem
|
||||
* Extracted from mcp-server.ts to centralize business logic in Worker services
|
||||
*
|
||||
* This class contains all tool handler logic that was previously in the MCP server.
|
||||
* The MCP server now acts as a thin HTTP wrapper that calls these methods via HTTP.
|
||||
*/
|
||||
|
||||
import { SessionSearch } from '../sqlite/SessionSearch.js';
|
||||
import { SessionStore } from '../sqlite/SessionStore.js';
|
||||
import { ChromaSync } from '../sync/ChromaSync.js';
|
||||
import { FormattingService } from './FormattingService.js';
|
||||
import { TimelineService, TimelineItem } from './TimelineService.js';
|
||||
import { ObservationSearchResult, SessionSummarySearchResult, UserPromptSearchResult } from '../sqlite/types.js';
|
||||
import { silentDebug } from '../../utils/silent-debug.js';
|
||||
|
||||
const COLLECTION_NAME = 'cm__claude-mem';
|
||||
|
||||
export class SearchManager {
|
||||
constructor(
|
||||
private sessionSearch: SessionSearch,
|
||||
private sessionStore: SessionStore,
|
||||
private chromaSync: ChromaSync,
|
||||
private formatter: FormattingService,
|
||||
private timeline: TimelineService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Query Chroma vector database via ChromaSync
|
||||
*/
|
||||
private async queryChroma(
|
||||
query: string,
|
||||
limit: number,
|
||||
whereFilter?: Record<string, any>
|
||||
): Promise<{ ids: number[]; distances: number[]; metadatas: any[] }> {
|
||||
return await this.chromaSync.queryChroma(query, limit, whereFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to normalize query parameters from URL-friendly format
|
||||
* Converts comma-separated strings to arrays and flattens date params
|
||||
*/
|
||||
private normalizeParams(args: any): any {
|
||||
const normalized: any = { ...args };
|
||||
|
||||
// Parse comma-separated concepts into array
|
||||
if (normalized.concepts && typeof normalized.concepts === 'string') {
|
||||
normalized.concepts = normalized.concepts.split(',').map((s: string) => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Parse comma-separated files into array
|
||||
if (normalized.files && typeof normalized.files === 'string') {
|
||||
normalized.files = normalized.files.split(',').map((s: string) => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Parse comma-separated obs_type into array
|
||||
if (normalized.obs_type && typeof normalized.obs_type === 'string') {
|
||||
normalized.obs_type = normalized.obs_type.split(',').map((s: string) => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Parse comma-separated type (for filterSchema) into array
|
||||
if (normalized.type && typeof normalized.type === 'string' && normalized.type.includes(',')) {
|
||||
normalized.type = normalized.type.split(',').map((s: string) => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Flatten dateStart/dateEnd into dateRange object
|
||||
if (normalized.dateStart || normalized.dateEnd) {
|
||||
normalized.dateRange = {
|
||||
start: normalized.dateStart,
|
||||
end: normalized.dateEnd
|
||||
};
|
||||
delete normalized.dateStart;
|
||||
delete normalized.dateEnd;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
${methodBodies}
|
||||
}
|
||||
`;
|
||||
|
||||
writeFileSync(outputPath, searchManagerContent, 'utf-8');
|
||||
|
||||
console.log(`\n✅ SearchManager.ts generated at ${outputPath}`);
|
||||
console.log(` Total methods: ${Object.keys(handlers).length + 2} (${Object.keys(handlers).length} tools + queryChroma + normalizeParams)`);
|
||||
console.log(` File size: ${(searchManagerContent.length / 1024).toFixed(1)} KB`);
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Import memories from a JSON export file with duplicate prevention
|
||||
* Usage: npx tsx scripts/import-memories.ts <input-file>
|
||||
* Example: npx tsx scripts/import-memories.ts windows-memories.json
|
||||
*
|
||||
* This script uses the worker API instead of direct database access.
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
|
||||
const WORKER_PORT = process.env.CLAUDE_MEM_WORKER_PORT || 37777;
|
||||
const WORKER_URL = `http://127.0.0.1:${WORKER_PORT}`;
|
||||
|
||||
async function importMemories(inputFile: string) {
|
||||
if (!existsSync(inputFile)) {
|
||||
console.error(`❌ Input file not found: ${inputFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read and parse export file
|
||||
const exportData = JSON.parse(readFileSync(inputFile, 'utf-8'));
|
||||
|
||||
console.log(`📦 Import file: ${inputFile}`);
|
||||
console.log(`📅 Exported: ${exportData.exportedAt}`);
|
||||
console.log(`🔍 Query: "${exportData.query}"`);
|
||||
console.log(`📊 Contains:`);
|
||||
console.log(` • ${exportData.totalObservations} observations`);
|
||||
console.log(` • ${exportData.totalSessions} sessions`);
|
||||
console.log(` • ${exportData.totalSummaries} summaries`);
|
||||
console.log(` • ${exportData.totalPrompts} prompts`);
|
||||
console.log('');
|
||||
|
||||
// Check if worker is running
|
||||
try {
|
||||
const healthCheck = await fetch(`${WORKER_URL}/api/stats`);
|
||||
if (!healthCheck.ok) {
|
||||
throw new Error('Worker not responding');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Worker not running at ${WORKER_URL}`);
|
||||
console.error(' Please ensure the claude-mem worker is running.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('🔄 Importing via worker API...');
|
||||
|
||||
// Send import request to worker
|
||||
const response = await fetch(`${WORKER_URL}/api/import`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sessions: exportData.sessions || [],
|
||||
summaries: exportData.summaries || [],
|
||||
observations: exportData.observations || [],
|
||||
prompts: exportData.prompts || []
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`❌ Import failed: ${response.status} ${response.statusText}`);
|
||||
console.error(` ${errorText}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const stats = result.stats;
|
||||
|
||||
console.log('\n✅ Import complete!');
|
||||
console.log('📊 Summary:');
|
||||
console.log(` Sessions: ${stats.sessionsImported} imported, ${stats.sessionsSkipped} skipped`);
|
||||
console.log(` Summaries: ${stats.summariesImported} imported, ${stats.summariesSkipped} skipped`);
|
||||
console.log(` Observations: ${stats.observationsImported} imported, ${stats.observationsSkipped} skipped`);
|
||||
console.log(` Prompts: ${stats.promptsImported} imported, ${stats.promptsSkipped} skipped`);
|
||||
}
|
||||
|
||||
// CLI interface
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 1) {
|
||||
console.error('Usage: npx tsx scripts/import-memories.ts <input-file>');
|
||||
console.error('Example: npx tsx scripts/import-memories.ts windows-memories.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [inputFile] = args;
|
||||
importMemories(inputFile);
|
||||
+77
-99
@@ -14,28 +14,51 @@ const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack')
|
||||
const MARKER = join(ROOT, '.install-version');
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
|
||||
// Common installation paths (handles fresh installs before PATH reload)
|
||||
const BUN_COMMON_PATHS = IS_WINDOWS
|
||||
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
|
||||
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
|
||||
|
||||
const UV_COMMON_PATHS = IS_WINDOWS
|
||||
? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')]
|
||||
: [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv'];
|
||||
|
||||
/**
|
||||
* Check if Bun is installed and accessible
|
||||
* Get the Bun executable path (from PATH or common install locations)
|
||||
*/
|
||||
function isBunInstalled() {
|
||||
function getBunPath() {
|
||||
// Try PATH first
|
||||
try {
|
||||
const result = spawnSync('bun', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0;
|
||||
if (result.status === 0) return 'bun';
|
||||
} catch {
|
||||
return false;
|
||||
// Not in PATH
|
||||
}
|
||||
|
||||
// Check common installation paths
|
||||
return BUN_COMMON_PATHS.find(existsSync) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Bun is installed and accessible
|
||||
*/
|
||||
function isBunInstalled() {
|
||||
return getBunPath() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Bun version if installed
|
||||
*/
|
||||
function getBunVersion() {
|
||||
const bunPath = getBunPath();
|
||||
if (!bunPath) return null;
|
||||
|
||||
try {
|
||||
const result = spawnSync('bun', ['--version'], {
|
||||
const result = spawnSync(bunPath, ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
@@ -47,27 +70,41 @@ function getBunVersion() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if uv is installed and accessible
|
||||
* Get the uv executable path (from PATH or common install locations)
|
||||
*/
|
||||
function isUvInstalled() {
|
||||
function getUvPath() {
|
||||
// Try PATH first
|
||||
try {
|
||||
const result = spawnSync('uv', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0;
|
||||
if (result.status === 0) return 'uv';
|
||||
} catch {
|
||||
return false;
|
||||
// Not in PATH
|
||||
}
|
||||
|
||||
// Check common installation paths
|
||||
return UV_COMMON_PATHS.find(existsSync) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if uv is installed and accessible
|
||||
*/
|
||||
function isUvInstalled() {
|
||||
return getUvPath() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uv version if installed
|
||||
*/
|
||||
function getUvVersion() {
|
||||
const uvPath = getUvPath();
|
||||
if (!uvPath) return null;
|
||||
|
||||
try {
|
||||
const result = spawnSync('uv', ['--version'], {
|
||||
const result = spawnSync(uvPath, ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
@@ -86,14 +123,12 @@ function installBun() {
|
||||
|
||||
try {
|
||||
if (IS_WINDOWS) {
|
||||
// Windows: Use PowerShell installer
|
||||
console.error(' Installing via PowerShell...');
|
||||
execSync('powershell -c "irm bun.sh/install.ps1 | iex"', {
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
} else {
|
||||
// Unix/macOS: Use curl installer
|
||||
console.error(' Installing via curl...');
|
||||
execSync('curl -fsSL https://bun.sh/install | bash', {
|
||||
stdio: 'inherit',
|
||||
@@ -101,35 +136,17 @@ function installBun() {
|
||||
});
|
||||
}
|
||||
|
||||
// Verify installation
|
||||
if (isBunInstalled()) {
|
||||
const version = getBunVersion();
|
||||
console.error(`✅ Bun ${version} installed successfully`);
|
||||
return true;
|
||||
} else {
|
||||
// Bun may be installed but not in PATH yet for this session
|
||||
// Try common installation paths
|
||||
const bunPaths = IS_WINDOWS
|
||||
? [join(homedir(), '.bun', 'bin', 'bun.exe')]
|
||||
: [join(homedir(), '.bun', 'bin', 'bun'), '/usr/local/bin/bun'];
|
||||
|
||||
for (const bunPath of bunPaths) {
|
||||
if (existsSync(bunPath)) {
|
||||
console.error(`✅ Bun installed at ${bunPath}`);
|
||||
console.error('⚠️ Please restart your terminal or add Bun to PATH:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(` $env:Path += ";${join(homedir(), '.bun', 'bin')}"`);
|
||||
} else {
|
||||
console.error(` export PATH="$HOME/.bun/bin:$PATH"`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Bun installation completed but binary not found');
|
||||
if (!isBunInstalled()) {
|
||||
throw new Error(
|
||||
'Bun installation completed but binary not found. ' +
|
||||
'Please restart your terminal and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
const version = getBunVersion();
|
||||
console.error(`✅ Bun ${version} installed successfully`);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to install Bun automatically');
|
||||
console.error('❌ Failed to install Bun');
|
||||
console.error(' Please install manually:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' - winget install Oven-sh.Bun');
|
||||
@@ -151,14 +168,12 @@ function installUv() {
|
||||
|
||||
try {
|
||||
if (IS_WINDOWS) {
|
||||
// Windows: Use PowerShell installer
|
||||
console.error(' Installing via PowerShell...');
|
||||
execSync('powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"', {
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
} else {
|
||||
// Unix/macOS: Use curl installer
|
||||
console.error(' Installing via curl...');
|
||||
execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', {
|
||||
stdio: 'inherit',
|
||||
@@ -166,35 +181,17 @@ function installUv() {
|
||||
});
|
||||
}
|
||||
|
||||
// Verify installation
|
||||
if (isUvInstalled()) {
|
||||
const version = getUvVersion();
|
||||
console.error(`✅ uv ${version} installed successfully`);
|
||||
return true;
|
||||
} else {
|
||||
// uv may be installed but not in PATH yet for this session
|
||||
// Try common installation paths
|
||||
const uvPaths = IS_WINDOWS
|
||||
? [join(homedir(), '.local', 'bin', 'uv.exe'), join(homedir(), '.cargo', 'bin', 'uv.exe')]
|
||||
: [join(homedir(), '.local', 'bin', 'uv'), join(homedir(), '.cargo', 'bin', 'uv'), '/usr/local/bin/uv'];
|
||||
|
||||
for (const uvPath of uvPaths) {
|
||||
if (existsSync(uvPath)) {
|
||||
console.error(`✅ uv installed at ${uvPath}`);
|
||||
console.error('⚠️ Please restart your terminal or add uv to PATH:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(` $env:Path += ";${join(homedir(), '.local', 'bin')}"`);
|
||||
} else {
|
||||
console.error(` export PATH="$HOME/.local/bin:$PATH"`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('uv installation completed but binary not found');
|
||||
if (!isUvInstalled()) {
|
||||
throw new Error(
|
||||
'uv installation completed but binary not found. ' +
|
||||
'Please restart your terminal and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
const version = getUvVersion();
|
||||
console.error(`✅ uv ${version} installed successfully`);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to install uv automatically');
|
||||
console.error('❌ Failed to install uv');
|
||||
console.error(' Please install manually:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' - winget install astral-sh.uv');
|
||||
@@ -226,14 +223,18 @@ function needsInstall() {
|
||||
* Install dependencies using Bun
|
||||
*/
|
||||
function installDeps() {
|
||||
console.error('📦 Installing dependencies with Bun...');
|
||||
try {
|
||||
execSync('bun install', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
} catch {
|
||||
// Retry with force flag
|
||||
execSync('bun install --force', { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
const bunPath = getBunPath();
|
||||
if (!bunPath) {
|
||||
throw new Error('Bun executable not found');
|
||||
}
|
||||
|
||||
console.error('📦 Installing dependencies with Bun...');
|
||||
|
||||
// Quote path for Windows paths with spaces
|
||||
const bunCmd = IS_WINDOWS && bunPath.includes(' ') ? `"${bunPath}"` : bunPath;
|
||||
|
||||
execSync(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
|
||||
// Write version marker
|
||||
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
|
||||
writeFileSync(MARKER, JSON.stringify({
|
||||
@@ -246,31 +247,8 @@ function installDeps() {
|
||||
|
||||
// Main execution
|
||||
try {
|
||||
// Step 1: Ensure Bun is installed (REQUIRED)
|
||||
if (!isBunInstalled()) {
|
||||
installBun();
|
||||
|
||||
// Re-check after installation
|
||||
if (!isBunInstalled()) {
|
||||
console.error('❌ Bun is required but not available in PATH');
|
||||
console.error(' Please restart your terminal after installation');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Ensure uv is installed (REQUIRED for vector search)
|
||||
if (!isUvInstalled()) {
|
||||
installUv();
|
||||
|
||||
// Re-check after installation
|
||||
if (!isUvInstalled()) {
|
||||
console.error('❌ uv is required but not available in PATH');
|
||||
console.error(' Please restart your terminal after installation');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Install dependencies if needed
|
||||
if (!isBunInstalled()) installBun();
|
||||
if (!isUvInstalled()) installUv();
|
||||
if (needsInstall()) {
|
||||
installDeps();
|
||||
console.error('✅ Dependencies installed');
|
||||
|
||||
@@ -61,7 +61,7 @@ function getPluginVersion() {
|
||||
console.log('Syncing to marketplace...');
|
||||
try {
|
||||
execSync(
|
||||
'rsync -av --delete --exclude=.git ./ ~/.claude/plugins/marketplaces/thedotmack/',
|
||||
'rsync -av --delete --exclude=.git --exclude=/.mcp.json ./ ~/.claude/plugins/marketplaces/thedotmack/',
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { translateReadme, SUPPORTED_LANGUAGES } from "./index.ts";
|
||||
|
||||
@@ -11,6 +11,8 @@ interface CliArgs {
|
||||
model?: string;
|
||||
maxBudget?: number;
|
||||
verbose: boolean;
|
||||
force: boolean;
|
||||
parallel: number;
|
||||
help: boolean;
|
||||
listLanguages: boolean;
|
||||
}
|
||||
@@ -39,6 +41,8 @@ OPTIONS:
|
||||
-m, --model <model> Claude model to use (default: sonnet)
|
||||
--max-budget <usd> Maximum budget in USD
|
||||
-v, --verbose Show detailed progress
|
||||
-f, --force Force re-translation ignoring cache
|
||||
--parallel <n> Run n translations concurrently (default: 1)
|
||||
-h, --help Show this help message
|
||||
--list-languages List all supported language codes
|
||||
|
||||
@@ -59,40 +63,46 @@ SUPPORTED LANGUAGES:
|
||||
|
||||
function printLanguages(): void {
|
||||
const LANGUAGE_NAMES: Record<string, string> = {
|
||||
ar: "Arabic",
|
||||
bg: "Bulgarian",
|
||||
cs: "Czech",
|
||||
da: "Danish",
|
||||
de: "German",
|
||||
el: "Greek",
|
||||
es: "Spanish",
|
||||
et: "Estonian",
|
||||
fi: "Finnish",
|
||||
fr: "French",
|
||||
he: "Hebrew",
|
||||
hi: "Hindi",
|
||||
hu: "Hungarian",
|
||||
id: "Indonesian",
|
||||
it: "Italian",
|
||||
// Tier 1 - No-brainers
|
||||
zh: "Chinese (Simplified)",
|
||||
ja: "Japanese",
|
||||
ko: "Korean",
|
||||
lt: "Lithuanian",
|
||||
lv: "Latvian",
|
||||
nl: "Dutch",
|
||||
no: "Norwegian",
|
||||
pl: "Polish",
|
||||
pt: "Portuguese",
|
||||
"pt-br": "Brazilian Portuguese",
|
||||
ro: "Romanian",
|
||||
ko: "Korean",
|
||||
es: "Spanish",
|
||||
de: "German",
|
||||
fr: "French",
|
||||
// Tier 2 - Strong tech scenes
|
||||
he: "Hebrew",
|
||||
ar: "Arabic",
|
||||
ru: "Russian",
|
||||
sk: "Slovak",
|
||||
sl: "Slovenian",
|
||||
sv: "Swedish",
|
||||
th: "Thai",
|
||||
pl: "Polish",
|
||||
cs: "Czech",
|
||||
nl: "Dutch",
|
||||
tr: "Turkish",
|
||||
uk: "Ukrainian",
|
||||
// Tier 3 - Emerging/Growing fast
|
||||
vi: "Vietnamese",
|
||||
zh: "Chinese (Simplified)",
|
||||
id: "Indonesian",
|
||||
th: "Thai",
|
||||
hi: "Hindi",
|
||||
bn: "Bengali",
|
||||
ro: "Romanian",
|
||||
sv: "Swedish",
|
||||
// Tier 4 - Why not
|
||||
it: "Italian",
|
||||
el: "Greek",
|
||||
hu: "Hungarian",
|
||||
fi: "Finnish",
|
||||
da: "Danish",
|
||||
no: "Norwegian",
|
||||
// Other supported
|
||||
bg: "Bulgarian",
|
||||
et: "Estonian",
|
||||
lt: "Lithuanian",
|
||||
lv: "Latvian",
|
||||
pt: "Portuguese",
|
||||
sk: "Slovak",
|
||||
sl: "Slovenian",
|
||||
"zh-tw": "Chinese (Traditional)",
|
||||
};
|
||||
|
||||
@@ -112,6 +122,8 @@ function parseArgs(argv: string[]): CliArgs {
|
||||
languages: [],
|
||||
preserveCode: true,
|
||||
verbose: false,
|
||||
force: false,
|
||||
parallel: 1,
|
||||
help: false,
|
||||
listLanguages: false,
|
||||
};
|
||||
@@ -134,6 +146,10 @@ function parseArgs(argv: string[]): CliArgs {
|
||||
case "--verbose":
|
||||
args.verbose = true;
|
||||
break;
|
||||
case "-f":
|
||||
case "--force":
|
||||
args.force = true;
|
||||
break;
|
||||
case "--no-preserve-code":
|
||||
args.preserveCode = false;
|
||||
break;
|
||||
@@ -152,6 +168,13 @@ function parseArgs(argv: string[]): CliArgs {
|
||||
case "--max-budget":
|
||||
args.maxBudget = parseFloat(argv[++i]);
|
||||
break;
|
||||
case "--parallel":
|
||||
args.parallel = parseInt(argv[++i], 10);
|
||||
if (isNaN(args.parallel) || args.parallel < 1) {
|
||||
console.error("Error: --parallel must be a positive integer");
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (arg.startsWith("-")) {
|
||||
console.error(`Unknown option: ${arg}`);
|
||||
@@ -215,6 +238,8 @@ async function main(): Promise<void> {
|
||||
model: args.model,
|
||||
maxBudgetUsd: args.maxBudget,
|
||||
verbose: args.verbose,
|
||||
force: args.force,
|
||||
parallel: args.parallel,
|
||||
});
|
||||
|
||||
// Exit with error code if any translations failed
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
import { query, type SDKMessage, type SDKResultMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
import { createHash } from "crypto";
|
||||
|
||||
interface TranslationCache {
|
||||
sourceHash: string;
|
||||
lastUpdated: string;
|
||||
translations: Record<string, {
|
||||
hash: string;
|
||||
translatedAt: string;
|
||||
costUsd: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
function hashContent(content: string): string {
|
||||
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
||||
}
|
||||
|
||||
async function readCache(cachePath: string): Promise<TranslationCache | null> {
|
||||
try {
|
||||
const data = await fs.readFile(cachePath, "utf-8");
|
||||
return JSON.parse(data);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function writeCache(cachePath: string, cache: TranslationCache): Promise<void> {
|
||||
await fs.writeFile(cachePath, JSON.stringify(cache, null, 2), "utf-8");
|
||||
}
|
||||
|
||||
export interface TranslationOptions {
|
||||
/** Source README file path */
|
||||
@@ -19,6 +47,10 @@ export interface TranslationOptions {
|
||||
maxBudgetUsd?: number;
|
||||
/** Verbose output */
|
||||
verbose?: boolean;
|
||||
/** Force re-translation even if cached */
|
||||
force?: boolean;
|
||||
/** Number of concurrent translations (default: 1) */
|
||||
parallel?: number;
|
||||
}
|
||||
|
||||
export interface TranslationResult {
|
||||
@@ -27,6 +59,8 @@ export interface TranslationResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
costUsd?: number;
|
||||
/** Whether this was served from cache */
|
||||
cached?: boolean;
|
||||
}
|
||||
|
||||
export interface TranslationJobResult {
|
||||
@@ -37,40 +71,46 @@ export interface TranslationJobResult {
|
||||
}
|
||||
|
||||
const LANGUAGE_NAMES: Record<string, string> = {
|
||||
ar: "Arabic",
|
||||
bg: "Bulgarian",
|
||||
cs: "Czech",
|
||||
da: "Danish",
|
||||
de: "German",
|
||||
el: "Greek",
|
||||
es: "Spanish",
|
||||
et: "Estonian",
|
||||
fi: "Finnish",
|
||||
fr: "French",
|
||||
he: "Hebrew",
|
||||
hi: "Hindi",
|
||||
hu: "Hungarian",
|
||||
id: "Indonesian",
|
||||
it: "Italian",
|
||||
// Tier 1 - No-brainers
|
||||
zh: "Chinese (Simplified)",
|
||||
ja: "Japanese",
|
||||
ko: "Korean",
|
||||
lt: "Lithuanian",
|
||||
lv: "Latvian",
|
||||
nl: "Dutch",
|
||||
no: "Norwegian",
|
||||
pl: "Polish",
|
||||
pt: "Portuguese",
|
||||
"pt-br": "Brazilian Portuguese",
|
||||
ro: "Romanian",
|
||||
ko: "Korean",
|
||||
es: "Spanish",
|
||||
de: "German",
|
||||
fr: "French",
|
||||
// Tier 2 - Strong tech scenes
|
||||
he: "Hebrew",
|
||||
ar: "Arabic",
|
||||
ru: "Russian",
|
||||
sk: "Slovak",
|
||||
sl: "Slovenian",
|
||||
sv: "Swedish",
|
||||
th: "Thai",
|
||||
pl: "Polish",
|
||||
cs: "Czech",
|
||||
nl: "Dutch",
|
||||
tr: "Turkish",
|
||||
uk: "Ukrainian",
|
||||
// Tier 3 - Emerging/Growing fast
|
||||
vi: "Vietnamese",
|
||||
zh: "Chinese (Simplified)",
|
||||
id: "Indonesian",
|
||||
th: "Thai",
|
||||
hi: "Hindi",
|
||||
bn: "Bengali",
|
||||
ro: "Romanian",
|
||||
sv: "Swedish",
|
||||
// Tier 4 - Why not
|
||||
it: "Italian",
|
||||
el: "Greek",
|
||||
hu: "Hungarian",
|
||||
fi: "Finnish",
|
||||
da: "Danish",
|
||||
no: "Norwegian",
|
||||
// Other supported
|
||||
bg: "Bulgarian",
|
||||
et: "Estonian",
|
||||
lt: "Lithuanian",
|
||||
lv: "Latvian",
|
||||
pt: "Portuguese",
|
||||
sk: "Slovak",
|
||||
sl: "Slovenian",
|
||||
"zh-tw": "Chinese (Traditional)",
|
||||
};
|
||||
|
||||
@@ -107,6 +147,7 @@ Guidelines:
|
||||
- Preserve technical accuracy
|
||||
- Use appropriate technical terminology for ${languageName}
|
||||
- Keep proper nouns (product names, company names) unchanged unless they have official translations
|
||||
- Add a small note at the very top of the document (before any other content) in ${languageName}: "🌐 This is an automated translation. Community corrections are welcome!"
|
||||
|
||||
Here is the README content to translate:
|
||||
|
||||
@@ -114,7 +155,12 @@ Here is the README content to translate:
|
||||
${content}
|
||||
---
|
||||
|
||||
Output ONLY the translated README content, nothing else. Do not include any preamble or explanation.`;
|
||||
CRITICAL OUTPUT RULES:
|
||||
- Output ONLY the raw translated markdown content
|
||||
- Do NOT wrap output in \`\`\`markdown code fences
|
||||
- Do NOT add any preamble, explanation, or commentary
|
||||
- Start directly with the translation note, then the content
|
||||
- The output will be saved directly to a .md file`;
|
||||
|
||||
let translation = "";
|
||||
let costUsd = 0;
|
||||
@@ -182,7 +228,21 @@ Always output only the translated content without any surrounding explanation.`,
|
||||
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
||||
}
|
||||
|
||||
return { translation: translation.trim(), costUsd };
|
||||
// Strip markdown code fences if Claude wrapped the output
|
||||
let cleaned = translation.trim();
|
||||
if (cleaned.startsWith("```markdown")) {
|
||||
cleaned = cleaned.slice("```markdown".length);
|
||||
} else if (cleaned.startsWith("```md")) {
|
||||
cleaned = cleaned.slice("```md".length);
|
||||
} else if (cleaned.startsWith("```")) {
|
||||
cleaned = cleaned.slice(3);
|
||||
}
|
||||
if (cleaned.endsWith("```")) {
|
||||
cleaned = cleaned.slice(0, -3);
|
||||
}
|
||||
cleaned = cleaned.trim();
|
||||
|
||||
return { translation: cleaned, costUsd };
|
||||
}
|
||||
|
||||
export async function translateReadme(
|
||||
@@ -197,6 +257,8 @@ export async function translateReadme(
|
||||
model,
|
||||
maxBudgetUsd,
|
||||
verbose = false,
|
||||
force = false,
|
||||
parallel = 1,
|
||||
} = options;
|
||||
|
||||
// Read source file
|
||||
@@ -207,6 +269,12 @@ export async function translateReadme(
|
||||
const outDir = outputDir ? path.resolve(outputDir) : path.dirname(sourcePath);
|
||||
await fs.mkdir(outDir, { recursive: true });
|
||||
|
||||
// Compute content hash and load cache
|
||||
const sourceHash = hashContent(content);
|
||||
const cachePath = path.join(outDir, ".translation-cache.json");
|
||||
const cache = await readCache(cachePath);
|
||||
const isHashMatch = cache?.sourceHash === sourceHash;
|
||||
|
||||
const results: TranslationResult[] = [];
|
||||
let totalCostUsd = 0;
|
||||
|
||||
@@ -214,24 +282,28 @@ export async function translateReadme(
|
||||
console.log(`📖 Source: ${sourcePath}`);
|
||||
console.log(`📂 Output: ${outDir}`);
|
||||
console.log(`🌍 Languages: ${languages.join(", ")}`);
|
||||
if (parallel > 1) {
|
||||
console.log(`⚡ Parallel: ${parallel} concurrent translations`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
for (const lang of languages) {
|
||||
// Check budget
|
||||
if (maxBudgetUsd && totalCostUsd >= maxBudgetUsd) {
|
||||
results.push({
|
||||
language: lang,
|
||||
outputPath: "",
|
||||
success: false,
|
||||
error: "Budget exceeded",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Worker function for a single language
|
||||
async function translateLang(lang: string): Promise<TranslationResult> {
|
||||
const outputFilename = pattern.replace("{lang}", lang);
|
||||
const outputPath = path.join(outDir, outputFilename);
|
||||
|
||||
// Check cache (unless --force)
|
||||
if (!force && isHashMatch && cache?.translations[lang]) {
|
||||
const outputExists = await fs.access(outputPath).then(() => true).catch(() => false);
|
||||
if (outputExists) {
|
||||
if (verbose) {
|
||||
console.log(` ✅ ${outputFilename} (cached, unchanged)`);
|
||||
}
|
||||
return { language: lang, outputPath, success: true, cached: true, costUsd: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
console.log(`🔄 Translating to ${getLanguageName(lang)} (${lang})...`);
|
||||
}
|
||||
@@ -240,37 +312,81 @@ export async function translateReadme(
|
||||
const { translation, costUsd } = await translateToLanguage(content, lang, {
|
||||
preserveCode,
|
||||
model,
|
||||
verbose,
|
||||
verbose: verbose && parallel === 1, // Only show progress spinner for sequential
|
||||
});
|
||||
|
||||
await fs.writeFile(outputPath, translation, "utf-8");
|
||||
totalCostUsd += costUsd;
|
||||
|
||||
results.push({
|
||||
language: lang,
|
||||
outputPath,
|
||||
success: true,
|
||||
costUsd,
|
||||
});
|
||||
|
||||
if (verbose) {
|
||||
console.log(` ✅ Saved to ${outputFilename} ($${costUsd.toFixed(4)})`);
|
||||
}
|
||||
|
||||
return { language: lang, outputPath, success: true, costUsd };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
results.push({
|
||||
language: lang,
|
||||
outputPath,
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
if (verbose) {
|
||||
console.log(` ❌ Failed: ${errorMessage}`);
|
||||
console.log(` ❌ ${lang} failed: ${errorMessage}`);
|
||||
}
|
||||
return { language: lang, outputPath, success: false, error: errorMessage };
|
||||
}
|
||||
}
|
||||
|
||||
// Run with concurrency limit
|
||||
async function runWithConcurrency<T>(items: T[], limit: number, fn: (item: T) => Promise<TranslationResult>): Promise<TranslationResult[]> {
|
||||
const results: TranslationResult[] = [];
|
||||
const executing: Promise<void>[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
// Check budget before starting new translation
|
||||
if (maxBudgetUsd && totalCostUsd >= maxBudgetUsd) {
|
||||
results.push({
|
||||
language: String(item),
|
||||
outputPath: "",
|
||||
success: false,
|
||||
error: "Budget exceeded",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const p = fn(item).then((result) => {
|
||||
results.push(result);
|
||||
if (result.costUsd) {
|
||||
totalCostUsd += result.costUsd;
|
||||
}
|
||||
});
|
||||
|
||||
executing.push(p.then(() => {
|
||||
executing.splice(executing.indexOf(p.then(() => {})), 1);
|
||||
}));
|
||||
|
||||
if (executing.length >= limit) {
|
||||
await Promise.race(executing);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(executing);
|
||||
return results;
|
||||
}
|
||||
|
||||
const translationResults = await runWithConcurrency(languages, parallel, translateLang);
|
||||
results.push(...translationResults);
|
||||
|
||||
// Save updated cache
|
||||
const newCache: TranslationCache = {
|
||||
sourceHash,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
translations: {
|
||||
...(isHashMatch ? cache?.translations : {}),
|
||||
...Object.fromEntries(
|
||||
results.filter(r => r.success && !r.cached).map(r => [
|
||||
r.language,
|
||||
{ hash: sourceHash, translatedAt: new Date().toISOString(), costUsd: r.costUsd || 0 }
|
||||
])
|
||||
),
|
||||
},
|
||||
};
|
||||
await writeCache(cachePath, newCache);
|
||||
|
||||
const successful = results.filter((r) => r.success).length;
|
||||
const failed = results.filter((r) => !r.success).length;
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
|
||||
export interface SessionEndInput {
|
||||
@@ -23,11 +22,6 @@ async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
happy_path_error__with_fallback('[cleanup-hook] Hook fired', {
|
||||
session_id: input?.session_id,
|
||||
reason: input?.reason
|
||||
});
|
||||
|
||||
if (!input) {
|
||||
throw new Error('cleanup-hook requires input from Claude Code');
|
||||
}
|
||||
@@ -48,18 +42,14 @@ async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
happy_path_error__with_fallback('[cleanup-hook] Session cleanup completed', result);
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
// Non-fatal - session might not exist
|
||||
happy_path_error__with_fallback('[cleanup-hook] Session not found or already cleaned up');
|
||||
console.error('[cleanup-hook] Session not found or already cleaned up');
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Worker might not be running - that's okay
|
||||
happy_path_error__with_fallback('[cleanup-hook] Worker not reachable (non-critical)', {
|
||||
error: error.message
|
||||
});
|
||||
// Worker might not be running - that's okay (non-critical)
|
||||
// But we should still log it for visibility
|
||||
console.error('[cleanup-hook] Failed to notify worker of session end:', error.message);
|
||||
}
|
||||
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
* native module dependencies.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import { stdin } from "process";
|
||||
import { ensureWorkerRunning, getWorkerPort } from "../shared/worker-utils.js";
|
||||
import { HOOK_TIMEOUTS } from "../shared/hook-constants.js";
|
||||
import { handleWorkerError } from "../shared/hook-error-handler.js";
|
||||
import { handleFetchError } from "./shared/error-handler.js";
|
||||
import { getProjectName } from "../utils/project-name.js";
|
||||
|
||||
export interface SessionStartInput {
|
||||
session_id: string;
|
||||
@@ -24,7 +25,7 @@ async function contextHook(input?: SessionStartInput): Promise<string> {
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const cwd = input?.cwd ?? process.cwd();
|
||||
const project = cwd ? path.basename(cwd) : "unknown-project";
|
||||
const project = getProjectName(cwd);
|
||||
const port = getWorkerPort();
|
||||
|
||||
const url = `http://127.0.0.1:${port}/api/context/inject?project=${encodeURIComponent(project)}`;
|
||||
@@ -34,7 +35,12 @@ async function contextHook(input?: SessionStartInput): Promise<string> {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to fetch context: ${response.status} ${errorText}`);
|
||||
handleFetchError(response, errorText, {
|
||||
hookName: 'context',
|
||||
operation: 'Context generation',
|
||||
project,
|
||||
port
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.text();
|
||||
|
||||
@@ -1,72 +1,11 @@
|
||||
export type HookType = 'SessionStart' | 'UserPromptSubmit' | 'PostToolUse' | 'Stop';
|
||||
|
||||
export interface HookResponseOptions {
|
||||
reason?: string;
|
||||
context?: string;
|
||||
}
|
||||
|
||||
export interface HookResponse {
|
||||
continue?: boolean;
|
||||
suppressOutput?: boolean;
|
||||
stopReason?: string;
|
||||
hookSpecificOutput?: {
|
||||
hookEventName: 'SessionStart';
|
||||
additionalContext: string;
|
||||
};
|
||||
}
|
||||
|
||||
function buildHookResponse(
|
||||
hookType: HookType,
|
||||
success: boolean,
|
||||
options: HookResponseOptions
|
||||
): HookResponse {
|
||||
if (hookType === 'SessionStart') {
|
||||
if (success && options.context) {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true,
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'SessionStart',
|
||||
additionalContext: options.context
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
if (hookType === 'UserPromptSubmit' || hookType === 'PostToolUse') {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
if (hookType === 'Stop') {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
continue: success,
|
||||
suppressOutput: true,
|
||||
...(options.reason && !success ? { stopReason: options.reason } : {})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a standardized hook response using the HookTemplates system.
|
||||
* Standard hook response for all hooks.
|
||||
* Tells Claude Code to continue processing and suppress the hook's output.
|
||||
*
|
||||
* Note: SessionStart uses context-hook.ts which constructs its own response
|
||||
* with hookSpecificOutput for context injection.
|
||||
*/
|
||||
export function createHookResponse(
|
||||
hookType: HookType,
|
||||
success: boolean,
|
||||
options: HookResponseOptions = {}
|
||||
): string {
|
||||
const response = buildHookResponse(hookType, success, options);
|
||||
return JSON.stringify(response);
|
||||
}
|
||||
export const STANDARD_HOOK_RESPONSE = JSON.stringify({
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
});
|
||||
|
||||
+19
-14
@@ -1,9 +1,9 @@
|
||||
import path from 'path';
|
||||
import { stdin } from 'process';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
import { STANDARD_HOOK_RESPONSE } from './hook-response.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { handleWorkerError } from '../shared/hook-error-handler.js';
|
||||
import { handleFetchError } from './shared/error-handler.js';
|
||||
import { getProjectName } from '../utils/project-name.js';
|
||||
|
||||
export interface UserPromptSubmitInput {
|
||||
session_id: string;
|
||||
@@ -24,13 +24,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
}
|
||||
|
||||
const { session_id, cwd, prompt } = input;
|
||||
const project = path.basename(cwd);
|
||||
|
||||
happy_path_error__with_fallback('[new-hook] Input received', {
|
||||
session_id,
|
||||
project,
|
||||
prompt_length: prompt?.length
|
||||
});
|
||||
const project = getProjectName(cwd);
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
@@ -52,7 +46,12 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
|
||||
if (!initResponse.ok) {
|
||||
const errorText = await initResponse.text();
|
||||
throw new Error(`Failed to initialize session: ${initResponse.status} ${errorText}`);
|
||||
handleFetchError(initResponse, errorText, {
|
||||
hookName: 'new',
|
||||
operation: 'Session initialization',
|
||||
project,
|
||||
port
|
||||
});
|
||||
}
|
||||
|
||||
const initResult = await initResponse.json();
|
||||
@@ -62,7 +61,7 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
// Check if prompt was entirely private (worker performs privacy check)
|
||||
if (initResult.skipped && initResult.reason === 'private') {
|
||||
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber} (fully private - skipped)`);
|
||||
console.log(createHookResponse('UserPromptSubmit', true));
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -86,13 +85,19 @@ async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to start SDK agent: ${response.status} ${errorText}`);
|
||||
handleFetchError(response, errorText, {
|
||||
hookName: 'new',
|
||||
operation: 'SDK agent start',
|
||||
project,
|
||||
port,
|
||||
sessionId: String(sessionDbId)
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
handleWorkerError(error);
|
||||
}
|
||||
|
||||
console.log(createHookResponse('UserPromptSubmit', true));
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
}
|
||||
|
||||
// Entry Point
|
||||
|
||||
+16
-12
@@ -7,12 +7,12 @@
|
||||
*/
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
import { STANDARD_HOOK_RESPONSE } from './hook-response.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { handleWorkerError } from '../shared/hook-error-handler.js';
|
||||
import { handleFetchError } from './shared/error-handler.js';
|
||||
|
||||
export interface PostToolUseInput {
|
||||
session_id: string;
|
||||
@@ -43,6 +43,11 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
workerPort: port
|
||||
});
|
||||
|
||||
// Validate required fields before sending to worker
|
||||
if (!cwd) {
|
||||
throw new Error(`Missing cwd in PostToolUse hook input for session ${session_id}, tool ${tool_name}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// Send to worker - worker handles privacy check and database operations
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
|
||||
@@ -53,21 +58,20 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
tool_name,
|
||||
tool_input,
|
||||
tool_response,
|
||||
cwd: happy_path_error__with_fallback(
|
||||
'Missing cwd in PostToolUse hook input',
|
||||
{ session_id, tool_name },
|
||||
cwd || ''
|
||||
)
|
||||
cwd
|
||||
}),
|
||||
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
logger.failure('HOOK', 'Failed to send observation', {
|
||||
status: response.status
|
||||
}, errorText);
|
||||
throw new Error(`Failed to send observation to worker: ${response.status} ${errorText}`);
|
||||
handleFetchError(response, errorText, {
|
||||
hookName: 'save',
|
||||
operation: 'Observation storage',
|
||||
toolName: tool_name,
|
||||
sessionId: session_id,
|
||||
port
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Observation sent successfully', { toolName: tool_name });
|
||||
@@ -75,7 +79,7 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
handleWorkerError(error);
|
||||
}
|
||||
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
}
|
||||
|
||||
// Entry Point
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { getWorkerRestartInstructions } from '../../utils/error-messages.js';
|
||||
|
||||
export interface HookErrorContext {
|
||||
hookName: string;
|
||||
operation: string;
|
||||
project?: string;
|
||||
sessionId?: string;
|
||||
toolName?: string;
|
||||
port?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardized error handler for hook fetch failures.
|
||||
*
|
||||
* This function:
|
||||
* 1. Logs the error with full context to worker logs
|
||||
* 2. Throws a user-facing error with restart instructions
|
||||
*
|
||||
* Use this for all fetch errors in hooks to ensure consistent error handling.
|
||||
*/
|
||||
export function handleFetchError(
|
||||
response: Response,
|
||||
errorText: string,
|
||||
context: HookErrorContext
|
||||
): never {
|
||||
logger.error('HOOK', `${context.operation} failed`, {
|
||||
status: response.status,
|
||||
...context
|
||||
}, errorText);
|
||||
|
||||
const userMessage = context.toolName
|
||||
? `Failed ${context.operation} for ${context.toolName}: ${getWorkerRestartInstructions()}`
|
||||
: `${context.operation} failed: ${getWorkerRestartInstructions()}`;
|
||||
|
||||
throw new Error(userMessage);
|
||||
}
|
||||
+25
-15
@@ -10,12 +10,12 @@
|
||||
*/
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
import { STANDARD_HOOK_RESPONSE } from './hook-response.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { handleWorkerError } from '../shared/hook-error-handler.js';
|
||||
import { handleFetchError } from './shared/error-handler.js';
|
||||
import { extractLastMessage } from '../shared/transcript-parser.js';
|
||||
|
||||
export interface StopInput {
|
||||
@@ -39,14 +39,14 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Validate required fields before processing
|
||||
if (!input.transcript_path) {
|
||||
throw new Error(`Missing transcript_path in Stop hook input for session ${session_id}`);
|
||||
}
|
||||
|
||||
// Extract last user AND assistant messages from transcript
|
||||
const transcriptPath = happy_path_error__with_fallback(
|
||||
'Missing transcript_path in Stop hook input',
|
||||
{ session_id },
|
||||
input.transcript_path || ''
|
||||
);
|
||||
const lastUserMessage = extractLastMessage(transcriptPath, 'user');
|
||||
const lastAssistantMessage = extractLastMessage(transcriptPath, 'assistant', true);
|
||||
const lastUserMessage = extractLastMessage(input.transcript_path, 'user');
|
||||
const lastAssistantMessage = extractLastMessage(input.transcript_path, 'assistant', true);
|
||||
|
||||
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
||||
workerPort: port,
|
||||
@@ -54,6 +54,8 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
||||
hasLastAssistantMessage: !!lastAssistantMessage
|
||||
});
|
||||
|
||||
let summaryError: Error | null = null;
|
||||
|
||||
try {
|
||||
// Send to worker - worker handles privacy check and database operations
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/summarize`, {
|
||||
@@ -69,17 +71,20 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
logger.failure('HOOK', 'Failed to generate summary', {
|
||||
status: response.status
|
||||
}, errorText);
|
||||
throw new Error(`Failed to request summary from worker: ${response.status} ${errorText}`);
|
||||
handleFetchError(response, errorText, {
|
||||
hookName: 'summary',
|
||||
operation: 'Summary generation',
|
||||
sessionId: session_id,
|
||||
port
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Summary request sent successfully');
|
||||
} catch (error: any) {
|
||||
summaryError = error;
|
||||
handleWorkerError(error);
|
||||
} finally {
|
||||
// Stop processing spinner
|
||||
// Stop processing spinner (non-critical operation, errors are logged but don't block)
|
||||
try {
|
||||
const spinnerResponse = await fetch(`http://127.0.0.1:${port}/api/processing`, {
|
||||
method: 'POST',
|
||||
@@ -95,7 +100,12 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(createHookResponse('Stop', true));
|
||||
// Re-throw summary error after cleanup to ensure it's not masked by finally block
|
||||
if (summaryError) {
|
||||
throw summaryError;
|
||||
}
|
||||
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
}
|
||||
|
||||
// Entry Point
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { basename } from "path";
|
||||
import { ensureWorkerRunning, getWorkerPort } from "../shared/worker-utils.js";
|
||||
import { HOOK_EXIT_CODES } from "../shared/hook-constants.js";
|
||||
import { getWorkerRestartInstructions } from "../utils/error-messages.js";
|
||||
|
||||
try {
|
||||
// Ensure worker is running
|
||||
@@ -24,7 +25,7 @@ try {
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Worker error ${response.status}`);
|
||||
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
|
||||
}
|
||||
|
||||
const output = await response.text();
|
||||
|
||||
+6
-4
@@ -3,7 +3,7 @@
|
||||
* Generates prompts for the Claude Agent SDK memory worker
|
||||
*/
|
||||
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
export interface Observation {
|
||||
id: number;
|
||||
@@ -177,10 +177,12 @@ export function buildObservationPrompt(obs: Observation): string {
|
||||
* Build prompt to generate progress summary
|
||||
*/
|
||||
export function buildSummaryPrompt(session: SDKSession): string {
|
||||
const lastAssistantMessage = happy_path_error__with_fallback(
|
||||
const lastAssistantMessage = session.last_assistant_message || logger.happyPathError(
|
||||
'SDK',
|
||||
'Missing last_assistant_message in session for summary prompt',
|
||||
session,
|
||||
session.last_assistant_message || ''
|
||||
{ sessionId: session.id },
|
||||
undefined,
|
||||
''
|
||||
);
|
||||
|
||||
return `PROGRESS SUMMARY CHECKPOINT
|
||||
|
||||
+316
-253
@@ -6,22 +6,27 @@
|
||||
* Maintains MCP protocol handling and tool schemas
|
||||
*/
|
||||
|
||||
// CRITICAL: Redirect console.log to stderr BEFORE any imports
|
||||
// MCP uses stdio transport where stdout is reserved for JSON-RPC protocol messages.
|
||||
// Any logs to stdout break the protocol (Claude Desktop parses "[2025..." as JSON array).
|
||||
const _originalConsoleLog = console.log;
|
||||
console.log = (...args: any[]) => console.error(...args);
|
||||
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { z } from 'zod';
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||
import { happy_path_error__with_fallback } from '../utils/silent-debug.js';
|
||||
import { getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { getWorkerPort, getWorkerHost } from '../shared/worker-utils.js';
|
||||
|
||||
/**
|
||||
* Worker HTTP API configuration
|
||||
*/
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
const WORKER_BASE_URL = `http://localhost:${WORKER_PORT}`;
|
||||
const WORKER_HOST = getWorkerHost();
|
||||
const WORKER_BASE_URL = `http://${WORKER_HOST}:${WORKER_PORT}`;
|
||||
|
||||
/**
|
||||
* Map tool names to Worker HTTP endpoints
|
||||
@@ -29,18 +34,75 @@ const WORKER_BASE_URL = `http://localhost:${WORKER_PORT}`;
|
||||
const TOOL_ENDPOINT_MAP: Record<string, string> = {
|
||||
'search': '/api/search',
|
||||
'timeline': '/api/timeline',
|
||||
'decisions': '/api/decisions',
|
||||
'changes': '/api/changes',
|
||||
'how_it_works': '/api/how-it-works',
|
||||
'search_observations': '/api/search/observations',
|
||||
'search_sessions': '/api/search/sessions',
|
||||
'search_user_prompts': '/api/search/prompts',
|
||||
'find_by_concept': '/api/search/by-concept',
|
||||
'find_by_file': '/api/search/by-file',
|
||||
'find_by_type': '/api/search/by-type',
|
||||
'get_recent_context': '/api/context/recent',
|
||||
'get_context_timeline': '/api/context/timeline',
|
||||
'get_timeline_by_query': '/api/timeline/by-query'
|
||||
'help': '/api/instructions'
|
||||
};
|
||||
|
||||
/**
|
||||
* Detailed parameter schemas for each tool
|
||||
*/
|
||||
const TOOL_SCHEMAS: Record<string, any> = {
|
||||
search: {
|
||||
query: { type: 'string', description: 'Full-text search query' },
|
||||
type: { type: 'string', description: 'Filter by type: tool_use, tool_result, prompt, summary' },
|
||||
obs_type: { type: 'string', description: 'Observation type filter' },
|
||||
concepts: { type: 'string', description: 'Comma-separated concept tags' },
|
||||
files: { type: 'string', description: 'Comma-separated file paths' },
|
||||
project: { type: 'string', description: 'Project name filter' },
|
||||
dateStart: { type: ['string', 'number'], description: 'Start date (ISO or timestamp)' },
|
||||
dateEnd: { type: ['string', 'number'], description: 'End date (ISO or timestamp)' },
|
||||
limit: { type: 'number', description: 'Max results (default: 10)' },
|
||||
offset: { type: 'number', description: 'Result offset for pagination' },
|
||||
orderBy: { type: 'string', description: 'Sort order: created_at, relevance' }
|
||||
},
|
||||
timeline: {
|
||||
query: { type: 'string', description: 'Search query to find anchor point' },
|
||||
anchor: { type: 'number', description: 'Observation ID as timeline center' },
|
||||
depth_before: { type: 'number', description: 'Observations before anchor (default: 5)' },
|
||||
depth_after: { type: 'number', description: 'Observations after anchor (default: 5)' },
|
||||
type: { type: 'string', description: 'Filter by type' },
|
||||
concepts: { type: 'string', description: 'Comma-separated concept tags' },
|
||||
files: { type: 'string', description: 'Comma-separated file paths' },
|
||||
project: { type: 'string', description: 'Project name filter' }
|
||||
},
|
||||
get_recent_context: {
|
||||
limit: { type: 'number', description: 'Max results (default: 20)' },
|
||||
type: { type: 'string', description: 'Filter by type' },
|
||||
concepts: { type: 'string', description: 'Comma-separated concept tags' },
|
||||
files: { type: 'string', description: 'Comma-separated file paths' },
|
||||
project: { type: 'string', description: 'Project name filter' },
|
||||
dateStart: { type: ['string', 'number'], description: 'Start date' },
|
||||
dateEnd: { type: ['string', 'number'], description: 'End date' }
|
||||
},
|
||||
get_context_timeline: {
|
||||
anchor: { type: 'number', description: 'Observation ID (required)', required: true },
|
||||
depth_before: { type: 'number', description: 'Observations before anchor' },
|
||||
depth_after: { type: 'number', description: 'Observations after anchor' },
|
||||
type: { type: 'string', description: 'Filter by type' },
|
||||
concepts: { type: 'string', description: 'Comma-separated concept tags' },
|
||||
files: { type: 'string', description: 'Comma-separated file paths' },
|
||||
project: { type: 'string', description: 'Project name filter' }
|
||||
},
|
||||
get_observations: {
|
||||
ids: { type: 'array', items: { type: 'number' }, description: 'Array of observation IDs (required)', required: true },
|
||||
orderBy: { type: 'string', description: 'Sort order' },
|
||||
limit: { type: 'number', description: 'Max results' },
|
||||
project: { type: 'string', description: 'Project filter' }
|
||||
},
|
||||
help: {
|
||||
operation: { type: 'string', description: 'Operation type: "observations", "timeline", "sessions", etc.' },
|
||||
topic: { type: 'string', description: 'Specific topic for help' }
|
||||
},
|
||||
get_observation: {
|
||||
id: { type: 'number', description: 'Observation ID (required)', required: true }
|
||||
},
|
||||
get_session: {
|
||||
id: { type: 'number', description: 'Session ID (required)', required: true }
|
||||
},
|
||||
get_prompt: {
|
||||
id: { type: 'number', description: 'Prompt ID (required)', required: true }
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,7 +112,7 @@ async function callWorkerAPI(
|
||||
endpoint: string,
|
||||
params: Record<string, any>
|
||||
): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
|
||||
happy_path_error__with_fallback('[mcp-server] → Worker API', { endpoint, params });
|
||||
logger.debug('SYSTEM', '→ Worker API', undefined, { endpoint, params });
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
@@ -72,12 +134,100 @@ async function callWorkerAPI(
|
||||
|
||||
const data = await response.json() as { content: Array<{ type: 'text'; text: string }>; isError?: boolean };
|
||||
|
||||
happy_path_error__with_fallback('[mcp-server] ← Worker API success', { endpoint });
|
||||
logger.debug('SYSTEM', '← Worker API success', undefined, { endpoint });
|
||||
|
||||
// Worker returns { content: [...] } format directly
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
happy_path_error__with_fallback('[mcp-server] ← Worker API error', { endpoint, error: error.message });
|
||||
logger.error('SYSTEM', '← Worker API error', undefined, { endpoint, error: error.message });
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Error calling Worker API: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Worker HTTP API with path parameter (GET)
|
||||
*/
|
||||
async function callWorkerAPIWithPath(
|
||||
endpoint: string,
|
||||
id: number
|
||||
): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
|
||||
logger.debug('HTTP', 'Worker API request (path)', undefined, { endpoint, id });
|
||||
|
||||
try {
|
||||
const url = `${WORKER_BASE_URL}${endpoint}/${id}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Worker API error (${response.status}): ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
logger.debug('HTTP', 'Worker API success (path)', undefined, { endpoint, id });
|
||||
|
||||
// Wrap raw data in MCP format
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: JSON.stringify(data, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
logger.error('HTTP', 'Worker API error (path)', undefined, { endpoint, id, error: error.message });
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Error calling Worker API: ${error.message}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Worker HTTP API with POST body
|
||||
*/
|
||||
async function callWorkerAPIPost(
|
||||
endpoint: string,
|
||||
body: Record<string, any>
|
||||
): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
|
||||
logger.debug('HTTP', 'Worker API request (POST)', undefined, { endpoint });
|
||||
|
||||
try {
|
||||
const url = `${WORKER_BASE_URL}${endpoint}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Worker API error (${response.status}): ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
logger.debug('HTTP', 'Worker API success (POST)', undefined, { endpoint });
|
||||
|
||||
// Wrap raw data in MCP format
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: JSON.stringify(data, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
logger.error('HTTP', 'Worker API error (POST)', undefined, { endpoint, error: error.message });
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
@@ -102,25 +252,47 @@ async function verifyWorkerConnection(): Promise<boolean> {
|
||||
|
||||
/**
|
||||
* Tool definitions with HTTP-based handlers
|
||||
* Minimal descriptions - use help() tool with operation parameter for detailed docs
|
||||
*/
|
||||
const tools = [
|
||||
{
|
||||
name: 'get_schema',
|
||||
description: 'Get parameter schema for a tool. Call get_schema(tool_name) for details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: { tool_name: { type: 'string' } },
|
||||
required: ['tool_name']
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
// Validate tool_name to prevent prototype pollution
|
||||
const toolName = args.tool_name;
|
||||
if (typeof toolName !== 'string' || !Object.hasOwn(TOOL_SCHEMAS, toolName)) {
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Unknown tool: ${toolName}\n\nAvailable tools: ${Object.keys(TOOL_SCHEMAS).join(', ')}`
|
||||
}],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const schema = TOOL_SCHEMAS[toolName];
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `# ${toolName} Parameters\n\n${JSON.stringify(schema, null, 2)}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Unified search across all memory types (observations, sessions, and user prompts) using vector-first semantic search (ChromaDB). Returns combined results from all document types. IMPORTANT: Always use index format first (default) to get an overview with minimal token usage, then use format: "full" only for specific items of interest.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Natural language search query for semantic ranking via ChromaDB vector search. Optional - omit for date-filtered queries only (Chroma cannot filter by date, requires direct SQLite).'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED for initial search), "full" for complete details (use only after reviewing index results)'),
|
||||
type: z.enum(['observations', 'sessions', 'prompts']).optional().describe('Filter by document type (observations, sessions, or prompts). Omit to search all types.'),
|
||||
obs_type: z.string().optional().describe('Filter observations by type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change). Only applies when type="observations"'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list). Only applies when type="observations"'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match). Only applies when type="observations"'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
description: 'Search memory. All parameters optional - call get_schema("search") for details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: true
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
@@ -128,198 +300,25 @@ const tools = [
|
||||
},
|
||||
{
|
||||
name: 'timeline',
|
||||
description: 'Fetch timeline of observations around a specific point in time. Supports two modes: anchor-based (fetch observations before/after a specific observation ID) and query-based (semantic search for anchor point). IMPORTANT: Use anchor_id when you know the specific observation, or query to find an anchor point first.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Natural language query to find anchor observation (query-based mode). Mutually exclusive with anchor_id.'),
|
||||
anchor_id: z.number().optional().describe('Observation ID to use as anchor (anchor-based mode). Mutually exclusive with query.'),
|
||||
before: z.number().min(0).max(100).default(10).describe('Number of observations to fetch before anchor'),
|
||||
after: z.number().min(0).max(100).default(10).describe('Number of observations to fetch after anchor'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
obs_type: z.string().optional().describe('Filter observations by type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name')
|
||||
}),
|
||||
description: 'Timeline context. All parameters optional - call get_schema("timeline") for details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: true
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['timeline'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'decisions',
|
||||
description: 'Semantic shortcut for finding architectural, design, and implementation decisions. Optimized for decision-type observations with relevant keyword boosting.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query for finding decisions'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['decisions'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'changes',
|
||||
description: 'Semantic shortcut for finding code changes, refactorings, and modifications. Optimized for change-type observations with relevant keyword boosting.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query for finding changes'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['changes'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'how_it_works',
|
||||
description: 'Semantic shortcut for understanding system architecture, design patterns, and implementation details. Optimized for discovery-type observations with architecture/design keyword boosting.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query for understanding how something works'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['how_it_works'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_observations',
|
||||
description: '[DEPRECATED - Use "search" with type="observations" instead] Search observations (facts/narratives) using FTS5 full-text search. Supports filtering by type, concepts, files, and date range.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Full-text search query (FTS5)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order (relevance only when query provided)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search_observations'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_sessions',
|
||||
description: '[DEPRECATED - Use "search" with type="sessions" instead] Search session summaries using FTS5 full-text search. Returns both request_summary and learned_summary fields.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Full-text search query (FTS5)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order (relevance only when query provided)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search_sessions'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'search_user_prompts',
|
||||
description: '[DEPRECATED - Use "search" with type="prompts" instead] Search user prompts using FTS5 full-text search. Searches prompt text only.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().optional().describe('Full-text search query (FTS5)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('relevance').describe('Sort order (relevance only when query provided)')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search_user_prompts'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_concept',
|
||||
description: 'Find observations tagged with specific concepts. Returns observations that match any of the provided concept tags.',
|
||||
inputSchema: z.object({
|
||||
concepts: z.string().describe('Concept tag(s) to filter by (single value or comma-separated list)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['find_by_concept'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_file',
|
||||
description: 'Find observations related to specific file paths. Uses partial matching - searches for file paths containing the provided string.',
|
||||
inputSchema: z.object({
|
||||
files: z.string().describe('File path(s) to filter by (single value or comma-separated list for partial match)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['find_by_file'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_by_type',
|
||||
description: 'Find observations of specific types. Returns observations matching any of the provided observation types.',
|
||||
inputSchema: z.object({
|
||||
type: z.string().describe('Observation type(s) to filter by (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)'),
|
||||
limit: z.number().min(1).max(100).default(20).describe('Maximum number of results'),
|
||||
offset: z.number().min(0).default(0).describe('Number of results to skip'),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).default('date_desc').describe('Sort order')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['find_by_type'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_recent_context',
|
||||
description: 'Get recent session context for timeline display. Returns recent observations, sessions, and user prompts with metadata for building timeline UI.',
|
||||
inputSchema: z.object({
|
||||
limit: z.number().min(1).max(100).default(30).describe('Maximum number of timeline items to return'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
description: 'Recent context. All parameters optional - call get_schema("get_recent_context") for details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: true
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_recent_context'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
@@ -327,48 +326,112 @@ const tools = [
|
||||
},
|
||||
{
|
||||
name: 'get_context_timeline',
|
||||
description: 'Get timeline of observations around a specific observation ID. Returns observations before and after the anchor point with metadata for timeline display.',
|
||||
inputSchema: z.object({
|
||||
anchor_id: z.number().describe('Observation ID to use as anchor point'),
|
||||
before: z.number().min(0).max(100).default(10).describe('Number of observations to fetch before anchor'),
|
||||
after: z.number().min(0).max(100).default(10).describe('Number of observations to fetch after anchor'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name')
|
||||
}),
|
||||
description: 'Timeline around observation ID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
anchor: {
|
||||
type: 'number',
|
||||
description: 'Observation ID (required). Optional params: get_schema("get_context_timeline")'
|
||||
}
|
||||
},
|
||||
required: ['anchor'],
|
||||
additionalProperties: true
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_context_timeline'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_timeline_by_query',
|
||||
description: 'Combined search + timeline tool. First searches for observations matching the query, then returns timeline around the best match. Useful for finding specific observations and viewing their context.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('Natural language query to find anchor observation'),
|
||||
before: z.number().min(0).max(100).default(10).describe('Number of observations to fetch before anchor'),
|
||||
after: z.number().min(0).max(100).default(10).describe('Number of observations to fetch after anchor'),
|
||||
format: z.enum(['index', 'full']).default('index').describe('Output format: "index" for titles/dates only (default, RECOMMENDED), "full" for complete details'),
|
||||
type: z.string().optional().describe('Filter by observation type (single value or comma-separated list: decision,bugfix,feature,refactor,discovery,change)'),
|
||||
concepts: z.string().optional().describe('Filter by concept tags (single value or comma-separated list)'),
|
||||
files: z.string().optional().describe('Filter by file paths (single value or comma-separated list for partial match)'),
|
||||
project: z.string().optional().describe('Filter by project name'),
|
||||
dateStart: z.union([z.string(), z.number()]).optional().describe('Start date for filtering (ISO string or epoch timestamp)'),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional().describe('End date for filtering (ISO string or epoch timestamp)')
|
||||
}),
|
||||
name: 'help',
|
||||
description: 'Get detailed docs. All parameters optional - call get_schema("help") for details',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: true
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_timeline_by_query'];
|
||||
const endpoint = TOOL_ENDPOINT_MAP['help'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_observation',
|
||||
description: 'Fetch observation by ID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'Observation ID (required)'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/observation', args.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_observations',
|
||||
description: 'Batch fetch observations',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
ids: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: 'Array of observation IDs (required). Optional params: get_schema("get_observations")'
|
||||
}
|
||||
},
|
||||
required: ['ids'],
|
||||
additionalProperties: true
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIPost('/api/observations/batch', args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_session',
|
||||
description: 'Fetch session by ID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'Session ID (required)'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/session', args.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_prompt',
|
||||
description: 'Fetch prompt by ID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'number',
|
||||
description: 'Prompt ID (required)'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/prompt', args.id);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Create the MCP server
|
||||
const server = new Server(
|
||||
{
|
||||
name: 'claude-mem-search-server',
|
||||
name: 'mem-search-server',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
@@ -384,7 +447,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
tools: tools.map(tool => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: zodToJsonSchema(tool.inputSchema) as Record<string, unknown>
|
||||
inputSchema: tool.inputSchema
|
||||
}))
|
||||
};
|
||||
});
|
||||
@@ -412,7 +475,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
|
||||
// Cleanup function
|
||||
async function cleanup() {
|
||||
happy_path_error__with_fallback('[mcp-server] Shutting down...');
|
||||
logger.info('SYSTEM', 'MCP server shutting down');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -425,22 +488,22 @@ async function main() {
|
||||
// Start the MCP server
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
happy_path_error__with_fallback('[mcp-server] Claude-mem search server started');
|
||||
logger.info('SYSTEM', 'Claude-mem search server started');
|
||||
|
||||
// Check Worker availability in background
|
||||
setTimeout(async () => {
|
||||
const workerAvailable = await verifyWorkerConnection();
|
||||
if (!workerAvailable) {
|
||||
happy_path_error__with_fallback('[mcp-server] WARNING: Worker not available at', WORKER_BASE_URL);
|
||||
happy_path_error__with_fallback('[mcp-server] Tools will fail until Worker is started');
|
||||
happy_path_error__with_fallback('[mcp-server] Start Worker with: npm run worker:restart');
|
||||
logger.warn('SYSTEM', 'Worker not available', undefined, { workerUrl: WORKER_BASE_URL });
|
||||
logger.warn('SYSTEM', 'Tools will fail until Worker is started');
|
||||
logger.warn('SYSTEM', 'Start Worker with: claude-mem restart');
|
||||
} else {
|
||||
happy_path_error__with_fallback('[mcp-server] Worker available at', WORKER_BASE_URL);
|
||||
logger.info('SYSTEM', 'Worker available', undefined, { workerUrl: WORKER_BASE_URL });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
happy_path_error__with_fallback('[mcp-server] Fatal error:', error);
|
||||
logger.error('SYSTEM', 'Fatal error', undefined, error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -17,6 +17,15 @@ import {
|
||||
} from '../constants/observation-metadata.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { SettingsDefaultsManager } from '../shared/SettingsDefaultsManager.js';
|
||||
import {
|
||||
parseJsonArray,
|
||||
formatDateTime,
|
||||
formatTime,
|
||||
formatDate,
|
||||
toRelativePath,
|
||||
extractFirstFile
|
||||
} from '../shared/timeline-formatting.js';
|
||||
import { getProjectName } from '../utils/project-name.js';
|
||||
|
||||
// Version marker path - use homedir-based path that works in both CJS and ESM contexts
|
||||
const VERSION_MARKER_PATH = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack', 'plugin', '.install-version');
|
||||
@@ -145,57 +154,6 @@ interface SessionSummary {
|
||||
created_at_epoch: number;
|
||||
}
|
||||
|
||||
// Helper: Parse JSON array safely
|
||||
function parseJsonArray(json: string | null): string[] {
|
||||
if (!json) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(json);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: Format date with time
|
||||
function formatDateTime(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
|
||||
// Helper: Format just time (no date)
|
||||
function formatTime(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
|
||||
// Helper: Format just date
|
||||
function formatDate(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
// Helper: Convert absolute paths to relative paths
|
||||
function toRelativePath(filePath: string, cwd: string): string {
|
||||
if (path.isAbsolute(filePath)) {
|
||||
return path.relative(cwd, filePath);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// Helper: Render a summary field
|
||||
function renderSummaryField(label: string, value: string | null, color: string, useColors: boolean): string[] {
|
||||
if (!value) return [];
|
||||
@@ -265,7 +223,7 @@ function extractPriorMessages(transcriptPath: string): { userMessage: string; as
|
||||
export async function generateContext(input?: ContextInput, useColors: boolean = false): Promise<string> {
|
||||
const config = loadContextConfig();
|
||||
const cwd = input?.cwd ?? process.cwd();
|
||||
const project = cwd ? path.basename(cwd) : 'unknown-project';
|
||||
const project = getProjectName(cwd);
|
||||
|
||||
let db: SessionStore | null = null;
|
||||
try {
|
||||
@@ -544,20 +502,16 @@ export async function generateContext(input?: ContextInput, useColors: boolean =
|
||||
|
||||
const summary = item.data;
|
||||
const summaryTitle = `${summary.request || 'Session started'} (${formatDateTime(summary.displayTime)})`;
|
||||
const link = summary.shouldShowLink ? `claude-mem://session-summary/${summary.id}` : '';
|
||||
|
||||
if (useColors) {
|
||||
const linkPart = link ? `${colors.dim}[${link}]${colors.reset}` : '';
|
||||
output.push(`🎯 ${colors.yellow}#S${summary.id}${colors.reset} ${summaryTitle} ${linkPart}`);
|
||||
output.push(`🎯 ${colors.yellow}#S${summary.id}${colors.reset} ${summaryTitle}`);
|
||||
} else {
|
||||
const linkPart = link ? ` [→](${link})` : '';
|
||||
output.push(`**🎯 #S${summary.id}** ${summaryTitle}${linkPart}`);
|
||||
output.push(`**🎯 #S${summary.id}** ${summaryTitle}`);
|
||||
}
|
||||
output.push('');
|
||||
} else {
|
||||
const obs = item.data;
|
||||
const files = parseJsonArray(obs.files_modified);
|
||||
const file = (files.length > 0 && files[0]) ? toRelativePath(files[0], cwd) : 'General';
|
||||
const file = extractFirstFile(obs.files_modified, cwd);
|
||||
|
||||
if (file !== currentFile) {
|
||||
if (tableOpen) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user