Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ea5869589 | |||
| 32be34505a | |||
| 61488042d8 | |||
| 7fdf5e75ab | |||
| 43db22728e | |||
| 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 | |||
| c6fd984cc1 | |||
| 490ba182d5 | |||
| 4baed97bd0 | |||
| f41824fa59 | |||
| 80ba7633e5 | |||
| d14266d70a | |||
| 1cd545c36c | |||
| 901af0b7f7 | |||
| 6815cc55b8 | |||
| 12603a1a5c | |||
| 15d05b5ac7 | |||
| bf4a20223a | |||
| a549d9fe47 | |||
| e896cfa0c5 | |||
| 5d4e71d2ff | |||
| f923c0cdd5 | |||
| 1491123706 | |||
| 9f1745bdec | |||
| d25b1d7394 | |||
| f6351434ae | |||
| ef9716eb5c | |||
| 6ebb678306 | |||
| 25684ea8f7 | |||
| 147b2c5aa5 | |||
| f154e32145 | |||
| 013fe9423e | |||
| f50a005cef | |||
| d24d5dda04 | |||
| 50c7603a37 | |||
| 807d1d6100 | |||
| ded9671a82 | |||
| b8a9f366e7 | |||
| 83b0f9551b | |||
| 8bf22b3dc5 | |||
| f8108047c4 | |||
| e4bd0ae461 | |||
| 5b338ba34e | |||
| 4e7ed75fa9 | |||
| a8b84fa7b6 | |||
| 73be8f7a63 | |||
| fa93f2c1e2 | |||
| b39cf84730 | |||
| 772e235e92 | |||
| 0986301e7a | |||
| 822cf796e1 | |||
| 53314d9c38 | |||
| 3a1ed0d299 | |||
| 8c589b6265 | |||
| 8bdec6abc0 | |||
| 41fbb87aa0 | |||
| fb9cccd350 | |||
| 9387418707 | |||
| eaba21329c | |||
| 84c4d62812 | |||
| 9e9ff20cba | |||
| bc28891bca | |||
| bafc86832c | |||
| b985579959 | |||
| 5f36d2bf9a | |||
| 65e5411c21 | |||
| 7a22144069 | |||
| 1360195390 | |||
| 6b38be29fb | |||
| f992251c32 | |||
| c2015c4dfc | |||
| 005a80c540 | |||
| c3761a2204 | |||
| d957bff495 | |||
| c5ee27f001 | |||
| d9f3798c90 | |||
| 1fb8df42b6 | |||
| e09e64ade5 | |||
| 7cab32151e | |||
| a2f7a4dc5a | |||
| fc5c2d5e07 | |||
| b22adcca05 | |||
| 2adc830c71 | |||
| e2c8f6b99e | |||
| 280608574b | |||
| 291f43d2c7 | |||
| d7dc29498c | |||
| 1f2e5f1a9c | |||
| 679a077f9b | |||
| f7a80e6abc | |||
| 4321add69c | |||
| 105b4ca70d | |||
| 06ba1cd92c | |||
| b003a43e73 | |||
| 5210bc74c7 | |||
| a00ca2b3ec | |||
| ba2c098ec1 | |||
| 5550ecf623 | |||
| 9a27f380c3 | |||
| 577cac8831 | |||
| 8da92c6569 | |||
| a18b43744c | |||
| 7c4979eba1 | |||
| ffe1e1622d |
@@ -10,7 +10,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.0.0",
|
||||
"version": "7.2.2",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
|
||||
@@ -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,7 +36,7 @@ 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
|
||||
@@ -54,29 +53,27 @@ 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
|
||||
- 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
|
||||
|
||||
## Reference Commands
|
||||
|
||||
@@ -92,7 +89,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]
|
||||
|
||||
@@ -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 }}
|
||||
+4
-1
@@ -14,4 +14,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": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"sourceHash": "9ab0d799179c66f9",
|
||||
"lastUpdated": "2025-12-12T07:42:03.489Z",
|
||||
"translations": {
|
||||
"zh": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:06:55.026Z",
|
||||
"costUsd": 0.12256679999999998
|
||||
},
|
||||
"ja": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:06:55.026Z",
|
||||
"costUsd": 0.12973164999999998
|
||||
},
|
||||
"pt-br": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:06:55.026Z",
|
||||
"costUsd": 0.11508539999999999
|
||||
},
|
||||
"ko": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:06:55.026Z",
|
||||
"costUsd": 0.13952664999999997
|
||||
},
|
||||
"es": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:06:55.026Z",
|
||||
"costUsd": 0.12530165
|
||||
},
|
||||
"de": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:06:55.026Z",
|
||||
"costUsd": 0.12232164999999998
|
||||
},
|
||||
"fr": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:06:55.026Z",
|
||||
"costUsd": 0.11906665
|
||||
},
|
||||
"he": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.151329
|
||||
},
|
||||
"ar": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.151952
|
||||
},
|
||||
"ru": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.13418499999999997
|
||||
},
|
||||
"pl": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.13196799999999997
|
||||
},
|
||||
"cs": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.12714599999999998
|
||||
},
|
||||
"nl": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.118389
|
||||
},
|
||||
"tr": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.13991199999999998
|
||||
},
|
||||
"uk": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:25:00.076Z",
|
||||
"costUsd": 0.13786
|
||||
},
|
||||
"vi": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:42:03.489Z",
|
||||
"costUsd": 0.15467299999999998
|
||||
},
|
||||
"id": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:42:03.489Z",
|
||||
"costUsd": 0.185581
|
||||
},
|
||||
"th": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:42:03.489Z",
|
||||
"costUsd": 0.177859
|
||||
},
|
||||
"hi": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:42:03.489Z",
|
||||
"costUsd": 0.17412700000000003
|
||||
},
|
||||
"bn": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:42:03.489Z",
|
||||
"costUsd": 0.202735
|
||||
},
|
||||
"ro": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:42:03.489Z",
|
||||
"costUsd": 0.12212875
|
||||
},
|
||||
"sv": {
|
||||
"hash": "9ab0d799179c66f9",
|
||||
"translatedAt": "2025-12-12T07:42:03.489Z",
|
||||
"costUsd": 0.12143675000000001
|
||||
}
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# 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
|
||||
+740
-6
@@ -4,6 +4,740 @@ 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/).
|
||||
|
||||
## [8.0.0] - 2025-12-14
|
||||
|
||||
### Fixed
|
||||
|
||||
**Timeline MCP Tools Parameter Bug**
|
||||
|
||||
Fixed critical bug where timeline tools were completely non-functional due to parameter name mismatch between MCP layer and SearchManager. The tools now use correct parameter names:
|
||||
- `anchor` (was incorrectly `anchor_id`)
|
||||
- `depth_before` (was incorrectly `before`)
|
||||
- `depth_after` (was incorrectly `after`)
|
||||
- `type` (was incorrectly `obs_type` in timeline tool only)
|
||||
|
||||
**Affected Tools:** `timeline`, `get_context_timeline`, `get_timeline_by_query`
|
||||
|
||||
**Impact:** These tools were previously broken and would fail with "Cannot read properties of undefined (reading 'length')" errors. They now work correctly with the proper parameter names that match the underlying SearchManager implementation.
|
||||
|
||||
### Added
|
||||
- New `get_batch_observations` MCP tool for efficiently fetching multiple observations in a single request
|
||||
- Enhanced SessionStore methods for fetching prompts and session summaries by ID
|
||||
|
||||
### Changed
|
||||
- Extracted magic numbers to constants (`RECENCY_WINDOW_DAYS`, `RECENCY_WINDOW_MS`)
|
||||
- Replaced debug logging calls with proper logger methods
|
||||
|
||||
---
|
||||
|
||||
## [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
|
||||
|
||||
- **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)
|
||||
|
||||
### Benefits
|
||||
|
||||
- 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
|
||||
|
||||
### 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
|
||||
|
||||
## Major Architectural Migration
|
||||
|
||||
This release completely replaces PM2 with native Bun-based process management and migrates from better-sqlite3 to bun:sqlite.
|
||||
|
||||
### Key Changes
|
||||
|
||||
**Process Management**
|
||||
- Replace PM2 with custom Bun-based ProcessManager
|
||||
- PID file-based process tracking
|
||||
- Automatic legacy PM2 process cleanup on all platforms
|
||||
|
||||
**Database Driver**
|
||||
- Migrate from better-sqlite3 npm package to bun:sqlite runtime module
|
||||
- Zero native compilation required
|
||||
- Same API compatibility
|
||||
|
||||
**Auto-Installation**
|
||||
- Bun runtime auto-installed if missing
|
||||
- uv (Python package manager) auto-installed for Chroma vector search
|
||||
- Smart installer with platform-specific methods (curl/PowerShell)
|
||||
|
||||
### Migration
|
||||
|
||||
**Automatic**: First hook trigger after update performs one-time PM2 cleanup and transitions to new architecture. No user action required.
|
||||
|
||||
### Documentation
|
||||
|
||||
Complete technical documentation in `docs/PM2-TO-BUN-MIGRATION.md`
|
||||
|
||||
## [7.0.11] - 2025-12-12
|
||||
|
||||
Patch release adding feature/bun-executable to experimental branch selector for testing Bun runtime integration.
|
||||
|
||||
## [7.0.9] - 2025-12-10
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed MCP response format in search route handlers - all 14 search endpoints now return complete response objects with error status instead of just content arrays, restoring MCP protocol compatibility
|
||||
|
||||
## Changes
|
||||
|
||||
- `SearchRoutes.ts`: Updated all route handlers to return full result object instead of extracted content property
|
||||
|
||||
## [7.0.8] - 2025-12-10
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Critical**: Filter out meta-observations for session-memory files to prevent recursive timeline pollution
|
||||
- Memory agent was creating observations about editing Agent SDK's session-memory/summary.md files
|
||||
- This created a recursive loop where investigating timeline pollution caused more pollution
|
||||
- Filter now skips Edit/Write/Read/NotebookEdit operations on any file path containing 'session-memory'
|
||||
- Eliminates 91+ meta-observations that were polluting the timeline
|
||||
|
||||
## Technical Details
|
||||
|
||||
Added filtering logic in SessionRoutes.ts to detect and skip file operations on session-memory files before observations are queued to the SDK agent. This prevents the memory agent from observing its own observation metadata files.
|
||||
|
||||
## [7.0.7] - 2025-12-10
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Code Quality Improvements
|
||||
- Refactored hooks codebase to reduce complexity and improve maintainability (#204)
|
||||
- Net reduction of 78 lines while adding new functionality
|
||||
- Improved type safety across all hook input interfaces
|
||||
|
||||
### New Features
|
||||
- Added `CLAUDE_MEM_SKIP_TOOLS` configuration setting for controlling which tools are excluded from observations
|
||||
- Default skip tools: `ListMcpResourcesTool`, `SlashCommand`, `Skill`, `TodoWrite`, `AskUserQuestion`
|
||||
|
||||
### Technical Improvements
|
||||
- Created shared utilities: `transcript-parser.ts`, `hook-constants.ts`, `hook-error-handler.ts`
|
||||
- Migrated business logic from hooks to worker service for better separation of concerns
|
||||
- Enhanced error handling and spinner management
|
||||
- Removed dead code and unnecessary abstractions
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.0.6...v7.0.7
|
||||
|
||||
## [7.0.6] - 2025-12-10
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed Windows terminal spawning to hide terminal windows when spawning child processes (#203, thanks @CrystallDEV)
|
||||
- Improved worker service process management on Windows
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks to @CrystallDEV for this contribution!
|
||||
|
||||
## [7.0.5] - 2025-12-09
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed settings schema inconsistency between write and read operations
|
||||
- Fixed PowerShell command injection vulnerability in worker-utils.ts
|
||||
- Enhanced PM2 existence check with clear error messages
|
||||
- Added error logging to silent tool serialization handlers
|
||||
|
||||
### Improvements
|
||||
- Settings centralization: Migrated to SettingsDefaultsManager across codebase
|
||||
- Auto-creation of settings.json file with defaults on first run
|
||||
- Settings schema migration from nested to flat format
|
||||
- Refactored HTTP-only new-hook implementation
|
||||
- Cross-platform worker service improvements
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.0.4...v7.0.5
|
||||
|
||||
## [7.0.4] - 2025-12-09
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Bug Fixes
|
||||
- **Windows**: Comprehensive fixes for Windows plugin installation
|
||||
- **Cache**: Add package.json to plugin directory for cache dependency resolution
|
||||
|
||||
Thanks to @kat-bell for the excellent contributions!
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.0.3...v7.0.4
|
||||
|
||||
## [7.0.3] - 2025-12-09
|
||||
|
||||
## What's Changed
|
||||
|
||||
**Refactoring:**
|
||||
- Completed rename of `search-server` to `mcp-server` throughout codebase
|
||||
- Updated all documentation references from search-server to mcp-server
|
||||
- Updated debug log messages to use `[mcp-server]` prefix
|
||||
- Removed legacy `search-server.cjs` file
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.0.2...v7.0.3
|
||||
|
||||
## [7.0.2] - 2025-12-09
|
||||
|
||||
## What's Changed
|
||||
|
||||
**Bug Fixes:**
|
||||
- Improved auto-start worker functionality for better reliability
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.0.1...v7.0.2
|
||||
|
||||
## [7.0.1] - 2025-12-09
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Hook Execution**: Ensure worker is running at the beginning of all hook files
|
||||
- **Context Hook**: Replace waitForPort with ensureWorkerRunning for better error handling
|
||||
- **Reliability**: Move ensureWorkerRunning to start of all hook functions to ensure worker is started before any logic executes
|
||||
|
||||
## Technical Changes
|
||||
|
||||
- context-hook.ts: Replace waitForPort logic with ensureWorkerRunning
|
||||
- summary-hook.ts: Move ensureWorkerRunning before input validation
|
||||
- new-hook.ts: Move ensureWorkerRunning before debug logging
|
||||
- save-hook.ts: Move ensureWorkerRunning before SKIP_TOOLS check
|
||||
- cleanup-hook.ts: Move ensureWorkerRunning before silentDebug calls
|
||||
|
||||
This ensures more reliable worker startup and clearer error messages when the worker fails to start.
|
||||
|
||||
## [7.0.0] - 2025-12-08
|
||||
|
||||
# Major Architectural Refactor
|
||||
|
||||
This major release represents a complete architectural transformation of claude-mem from a monolithic design to a clean, modular HTTP-based architecture.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
**None** - Despite being a major version bump due to the scope of changes, this release maintains full backward compatibility. All existing functionality works exactly as before.
|
||||
|
||||
## What Changed
|
||||
|
||||
### Hooks → HTTP Clients
|
||||
- All 5 lifecycle hooks converted from direct database access to lightweight HTTP clients
|
||||
- Each hook reduced from 400-800 lines to ~75 lines
|
||||
- Hooks now make simple HTTP calls to the worker service
|
||||
- Eliminates SQL duplication across hooks - single source of truth in worker
|
||||
|
||||
### Worker Service Modularization
|
||||
- `worker-service.ts` reduced from 1600+ lines to clean orchestration layer
|
||||
- New route-based HTTP architecture:
|
||||
- `SessionRoutes` - Session lifecycle management
|
||||
- `DataRoutes` - Database queries (observations, sessions, timeline)
|
||||
- `SearchRoutes` - Full-text and semantic search
|
||||
- `SettingsRoutes` - Configuration management
|
||||
- `ViewerRoutes` - UI endpoints
|
||||
|
||||
### New Service Layer
|
||||
- `BaseRouteHandler` - Centralized error handling, response formatting (used 46x)
|
||||
- `SessionEventBroadcaster` - Semantic SSE event broadcasting
|
||||
- `SessionCompletionHandler` - Consolidated session completion logic
|
||||
- `SettingsDefaultsManager` - Single source of truth for configuration defaults
|
||||
- `PrivacyCheckValidator` - Centralized privacy tag validation
|
||||
- `FormattingService` - Dual-format result rendering
|
||||
- `TimelineService` - Complex markdown timeline formatting
|
||||
- `SearchManager` - Extracted search logic from context generation
|
||||
|
||||
### Database Improvements
|
||||
- Migrated from \`bun:sqlite\` to \`better-sqlite3\` for broader compatibility
|
||||
- SQL queries moved from route handlers to \`SessionStore\` for separation of concerns
|
||||
- \`PaginationHelper\` centralizes paginated queries with LIMIT+1 optimization
|
||||
|
||||
### Testing Infrastructure
|
||||
- New comprehensive happy path tests for full session lifecycle
|
||||
- Integration tests covering session init, observation capture, search, summaries, cleanup
|
||||
- Test helpers and mocks for consistent testing patterns
|
||||
|
||||
### Type Safety
|
||||
- Removed 'as any' casts throughout codebase
|
||||
- New \`src/types/database.ts\` with proper type definitions
|
||||
- Enhanced null safety in SearchManager
|
||||
|
||||
## Stats
|
||||
- **60 files changed**
|
||||
- **8,671 insertions, 5,585 deletions**
|
||||
- Net: ~3,000 lines of new code (mostly tests and new modular services)
|
||||
|
||||
## Migration Notes
|
||||
|
||||
No migration required! Update and continue using claude-mem as before.
|
||||
|
||||
## [6.5.3] - 2025-12-05
|
||||
|
||||
## Bug Fixes
|
||||
@@ -1684,12 +2418,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,15 +6,13 @@
|
||||
|
||||
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.0.0
|
||||
|
||||
## Architecture
|
||||
|
||||
**5 Lifecycle Hooks**: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd
|
||||
|
||||
**Hooks** (`src/hooks/*.ts`) - TypeScript → ESM, built to `plugin/scripts/*-hook.js`
|
||||
|
||||
**Worker Service** (`src/services/worker-service.ts`) - Express API on port 37777, PM2-managed, handles AI processing asynchronously
|
||||
**Worker Service** (`src/services/worker-service.ts`) - Express API on port 37777, Bun-managed, handles AI processing asynchronously
|
||||
|
||||
**Database** (`src/services/sqlite/`) - SQLite3 at `~/.claude-mem/claude-mem.db` with FTS5 full-text search
|
||||
|
||||
@@ -34,20 +32,30 @@ 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 (most common)
|
||||
npm run build # Compile TypeScript only
|
||||
npm run sync-marketplace # Copy to ~/.claude/plugins only
|
||||
npm run worker:restart # Restart worker service only
|
||||
npm run worker:status # Check worker status
|
||||
npm run worker:logs # View worker logs
|
||||
```
|
||||
|
||||
**Worker changes**: `npm run build && npm run sync-marketplace && npm run worker:restart`
|
||||
**Viewer UI**: http://localhost:37777
|
||||
|
||||
**Skills only**: `npm run sync-marketplace`
|
||||
## Configuration
|
||||
|
||||
**Viewer UI**: `npm run build && npm run sync-marketplace && npm run worker:restart`
|
||||
Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created with defaults on first run.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `CLAUDE_MEM_MODEL` - Model for observations/summaries (default: claude-haiku-4-5)
|
||||
- `CLAUDE_MEM_CONTEXT_OBSERVATIONS` - Observations injected at SessionStart (default: 50)
|
||||
**Core Settings:**
|
||||
- `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_PYTHON_VERSION` - Python version for uvx/chroma-mcp (default: 3.13, avoids onnxruntime compatibility issues with Python 3.14+)
|
||||
- `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)
|
||||
|
||||
## File Locations
|
||||
|
||||
@@ -56,17 +64,15 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
|
||||
- **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`
|
||||
|
||||
## Quick Reference
|
||||
## Requirements
|
||||
|
||||
```bash
|
||||
npm run build # Compile TypeScript
|
||||
npm run sync-marketplace # Copy to ~/.claude/plugins
|
||||
npm run worker:restart # Restart PM2 worker
|
||||
npm run worker:logs # View worker logs
|
||||
pm2 list # Check worker status
|
||||
pm2 delete claude-mem-worker # Force clean start
|
||||
```
|
||||
- **Bun** (all platforms - auto-installed if missing)
|
||||
- **uv** (all platforms - auto-installed if missing, provides Python for Chroma)
|
||||
- Node.js (build tools only)
|
||||
|
||||
**Viewer UI**: http://localhost:37777
|
||||
## 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
|
||||
|
||||
@@ -27,6 +27,16 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/15496" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/thedotmack/claude-mem/main/docs/public/trendshift-badge-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/thedotmack/claude-mem/main/docs/public/trendshift-badge.svg">
|
||||
<img src="https://raw.githubusercontent.com/thedotmack/claude-mem/main/docs/public/trendshift-badge.svg" alt="thedotmack/claude-mem | Trendshift" width="250" height="55"/>
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
@@ -71,10 +81,11 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
- 📊 **Progressive Disclosure** - Layered memory retrieval with token cost visibility
|
||||
- 🔍 **Skill-Based Search** - Query your project history with mem-search skill (~2,250 token savings)
|
||||
- 🖥️ **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
|
||||
|
||||
---
|
||||
@@ -108,7 +119,7 @@ npx mintlify dev
|
||||
- **[Architecture Evolution](https://docs.claude-mem.ai/architecture-evolution)** - The journey from v3 to v5
|
||||
- **[Hooks Architecture](https://docs.claude-mem.ai/hooks-architecture)** - How Claude-Mem uses lifecycle hooks
|
||||
- **[Hooks Reference](https://docs.claude-mem.ai/architecture/hooks)** - 7 hook scripts explained
|
||||
- **[Worker Service](https://docs.claude-mem.ai/architecture/worker-service)** - HTTP API & PM2 management
|
||||
- **[Worker Service](https://docs.claude-mem.ai/architecture/worker-service)** - HTTP API & Bun management
|
||||
- **[Database](https://docs.claude-mem.ai/architecture/database)** - SQLite schema & FTS5 search
|
||||
- **[Search Architecture](https://docs.claude-mem.ai/architecture/search-architecture)** - Hybrid search with Chroma vector database
|
||||
|
||||
@@ -148,7 +159,7 @@ npx mintlify dev
|
||||
|
||||
1. **5 Lifecycle Hooks** - SessionStart, UserPromptSubmit, PostToolUse, Stop, SessionEnd (6 hook scripts)
|
||||
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 PM2
|
||||
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)
|
||||
6. **Chroma Vector Database** - Hybrid semantic + keyword search for intelligent context retrieval
|
||||
@@ -262,7 +273,8 @@ See [CHANGELOG.md](CHANGELOG.md) for complete version history.
|
||||
|
||||
- **Node.js**: 18.0.0 or higher
|
||||
- **Claude Code**: Latest version with plugin support
|
||||
- **PM2**: Process manager (bundled - no global install required)
|
||||
- **Bun**: JavaScript runtime and process manager (auto-installed if missing)
|
||||
- **uv**: Python package manager for vector search (auto-installed if missing)
|
||||
- **SQLite 3**: For persistent storage (bundled)
|
||||
|
||||
---
|
||||
@@ -306,18 +318,43 @@ See [CHANGELOG.md](CHANGELOG.md) for complete version history.
|
||||
|
||||
## Configuration
|
||||
|
||||
**Model Selection:**
|
||||
Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created with defaults on first run.
|
||||
|
||||
**Available Settings:**
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `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 |
|
||||
| `CLAUDE_MEM_LOG_LEVEL` | `INFO` | Log verbosity (DEBUG, INFO, WARN, ERROR, SILENT) |
|
||||
| `CLAUDE_MEM_PYTHON_VERSION` | `3.13` | Python version for chroma-mcp |
|
||||
| `CLAUDE_CODE_PATH` | _(auto-detect)_ | Path to Claude executable |
|
||||
| `CLAUDE_MEM_CONTEXT_OBSERVATIONS` | `50` | Number of observations to inject at SessionStart |
|
||||
|
||||
**Settings Management:**
|
||||
|
||||
```bash
|
||||
# Edit settings via CLI helper
|
||||
./claude-mem-settings.sh
|
||||
|
||||
# Or edit directly
|
||||
nano ~/.claude-mem/settings.json
|
||||
|
||||
# View current settings
|
||||
curl http://localhost:37777/api/settings
|
||||
```
|
||||
|
||||
**Environment Variables:**
|
||||
**Settings File Format:**
|
||||
|
||||
- `CLAUDE_MEM_MODEL` - AI model for processing (default: claude-haiku-4-5)
|
||||
- `CLAUDE_MEM_WORKER_PORT` - Worker port (default: 37777)
|
||||
- `CLAUDE_MEM_DATA_DIR` - Data directory override (dev only)
|
||||
- `CLAUDE_MEM_PYTHON_VERSION` - Python version for uvx/chroma-mcp (default: 3.13)
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_MODEL": "claude-sonnet-4-5",
|
||||
"CLAUDE_MEM_WORKER_PORT": "37777",
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "50"
|
||||
}
|
||||
```
|
||||
|
||||
See [Configuration Guide](https://docs.claude-mem.ai/configuration) for details.
|
||||
|
||||
@@ -361,6 +398,41 @@ If you're experiencing issues, describe the problem to Claude and the troublesho
|
||||
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
||||
# 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
|
||||
@@ -0,0 +1,561 @@
|
||||
# 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,386 @@
|
||||
# Test Suite Audit Report
|
||||
**Date:** 2025-12-13
|
||||
**Auditor:** Code Quality Assurance Manager
|
||||
**Focus:** Recent bugfixes and regression prevention
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The test suite has **critical gaps** in error handling coverage. While happy path tests exist, **zero tests verify that recent bugfixes actually prevent regressions**. The fish shell PATH bug (Issue #264), silent hook failures (observation 25389), and ChromaSync error standardization (observation 25458) are all unprotected by tests.
|
||||
|
||||
**Risk Level:** HIGH - Recent bugfixes can silently regress without detection.
|
||||
|
||||
---
|
||||
|
||||
## Coverage Analysis
|
||||
|
||||
### What We Have ✅
|
||||
|
||||
1. **Happy Path Tests** (`tests/happy-paths/`) - 6 files
|
||||
- Basic success scenarios work
|
||||
- Tool capture, search, session init/cleanup
|
||||
- Good foundation but insufficient
|
||||
|
||||
2. **Unit Tests**
|
||||
- `bun-path.test.ts` - Tests PATH resolution logic
|
||||
- `parser.test.ts` - SDK parser validation
|
||||
- `strip-memory-tags.test.ts` - Privacy tag handling
|
||||
|
||||
3. **Integration Test** (`full-lifecycle.test.ts`)
|
||||
- ONE error recovery test (too shallow)
|
||||
- Mostly happy paths
|
||||
- All tests mock `fetch()` - never test real failures
|
||||
|
||||
### What's Missing ❌
|
||||
|
||||
## 1. Silent Hook Failures (CRITICAL GAP)
|
||||
|
||||
**Issue:** Multiple hooks had no error logging until recently fixed
|
||||
|
||||
**Fixed In:**
|
||||
- `save-hook.ts` (observation 25389) - Added `handleFetchError`/`handleWorkerError`
|
||||
- `new-hook.ts` - Added error handlers
|
||||
- `context-hook.ts` - Added error handlers
|
||||
|
||||
**Test Gap:** ZERO tests verify hooks actually log errors when they fail
|
||||
|
||||
**Created:** `/Users/alexnewman/Scripts/claude-mem/tests/error-handling/hook-error-logging.test.ts`
|
||||
|
||||
**Tests:**
|
||||
- `handleFetchError()` logs with full context (status, hook, operation, tool, port)
|
||||
- `handleFetchError()` throws user-facing error with restart instructions
|
||||
- `handleWorkerError()` handles timeout/connection errors
|
||||
- Real hook scenarios (save-hook, new-hook, context-hook failures)
|
||||
- Error message quality (actionable, includes next steps)
|
||||
|
||||
**Why This Matters:**
|
||||
If someone refactors hooks and removes error handlers, the system will silently fail again. These tests catch that regression immediately.
|
||||
|
||||
---
|
||||
|
||||
## 2. ChromaSync Client Initialization (MEDIUM GAP)
|
||||
|
||||
**Issue:** Standardized error messages across all client checks (observation 25458)
|
||||
|
||||
**Code Locations:** ChromaSync.ts lines 140-145, 324-329, 504-509, 761-766
|
||||
|
||||
**Test Gap:** NO tests verify error messages are consistent or fire correctly
|
||||
|
||||
**Created:** `/Users/alexnewman/Scripts/claude-mem/tests/services/chroma-sync-errors.test.ts`
|
||||
|
||||
**Tests:**
|
||||
- Calling methods before `ensureConnection()` throws correct message
|
||||
- All error messages include project name
|
||||
- Error messages are consistent across all 4 locations
|
||||
- Fail-fast behavior (no silent retries)
|
||||
- Error context preservation
|
||||
|
||||
**Why This Matters:**
|
||||
Prevents "works on my machine" bugs where Chroma isn't properly initialized. Ensures all 4 error checks stay in sync during refactoring.
|
||||
|
||||
---
|
||||
|
||||
## 3. Fish Shell PATH Issues (PARTIAL COVERAGE)
|
||||
|
||||
**Issue:** Issue #264 - Hooks fail with fish shell because bun not in /bin/sh PATH
|
||||
|
||||
**Current Test:** `bun-path.test.ts` tests the utility function
|
||||
|
||||
**Gap:** Doesn't test the ACTUAL bug - hooks failing when bun not in PATH
|
||||
|
||||
**Created:** `/Users/alexnewman/Scripts/claude-mem/tests/integration/hook-execution-environments.test.ts`
|
||||
|
||||
**Tests:**
|
||||
- Running hook when `bun` only in `~/.bun/bin/bun` (not in PATH)
|
||||
- Hook finds bun from common install locations
|
||||
- Cross-platform bun resolution (macOS, Linux, Windows)
|
||||
- Fish shell with custom PATH
|
||||
- Zsh with homebrew in non-standard location
|
||||
- Error messages include PATH diagnostic info
|
||||
|
||||
**Why This Matters:**
|
||||
Fish shell users (and anyone with non-standard PATH) will get "command not found" errors if this regresses. Test ensures hooks work regardless of shell.
|
||||
|
||||
---
|
||||
|
||||
## 4. General Error Handling Patterns (CRITICAL GAP)
|
||||
|
||||
**Issue:** "264 silent failure locations" - widespread lack of error handling
|
||||
|
||||
**Current State:** Recent fixes added standardized error handlers
|
||||
|
||||
**Test Gap:** No systematic tests for error handling patterns
|
||||
|
||||
**Covered By:** `/Users/alexnewman/Scripts/claude-mem/tests/error-handling/hook-error-logging.test.ts`
|
||||
|
||||
**Why This Matters:**
|
||||
If new hooks are added without using `handleFetchError`/`handleWorkerError`, they'll fail silently. Tests enforce the pattern.
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration Test Weaknesses
|
||||
|
||||
**Current Test:** `full-lifecycle.test.ts` has ONE error recovery test (lines 292-352)
|
||||
|
||||
**Issues:**
|
||||
- Too shallow - just checks second request succeeds after first fails
|
||||
- Doesn't verify error logging
|
||||
- Never tests real worker failures (all mocked)
|
||||
|
||||
**Needs:**
|
||||
```
|
||||
/tests/integration/hook-failures.test.ts
|
||||
```
|
||||
|
||||
Should test:
|
||||
- Worker crashes mid-session - hooks fail gracefully
|
||||
- Worker returns 500 error - hook logs and throws
|
||||
- Worker times out - hook aborts with timeout message
|
||||
- Worker returns malformed JSON - hook handles parse error
|
||||
|
||||
---
|
||||
|
||||
## YAGNI Violations (Unnecessary Test Complexity)
|
||||
|
||||
### Problem: `/Users/alexnewman/Scripts/claude-mem/tests/happy-paths/search.test.ts`
|
||||
|
||||
**Lines 80-196:** Tests for features that DON'T EXIST:
|
||||
|
||||
1. **Line 80-107:** "supports filtering by observation type"
|
||||
- Endpoint: `/api/search/by-type` - DOES NOT EXIST
|
||||
|
||||
2. **Line 109-136:** "supports filtering by concept tags"
|
||||
- Endpoint: `/api/search/by-concept` - DOES NOT EXIST
|
||||
|
||||
3. **Line 138-168:** "supports pagination for large result sets"
|
||||
- Includes `page`, `limit`, `offset` params - NOT IMPLEMENTED
|
||||
|
||||
4. **Line 170-196:** "supports date range filtering"
|
||||
- `dateStart`, `dateEnd` params - NOT IMPLEMENTED
|
||||
|
||||
5. **Line 227-271:** "supports semantic search ranking"
|
||||
- `orderBy=relevance` with relevance scores - NOT IMPLEMENTED
|
||||
|
||||
**Impact:** These tests are ALL PASSING because they mock `fetch()`. They create false confidence - making it look like features exist when they don't.
|
||||
|
||||
**Fix:** DELETE these tests until features actually exist. Write tests AFTER implementing features, not before.
|
||||
|
||||
**Philosophy Violation:** "Write the dumb, obvious thing first" - these tests violate YAGNI by testing features we don't need yet.
|
||||
|
||||
---
|
||||
|
||||
## KISS Violations (Overcomplicated Tests)
|
||||
|
||||
### Problem: Excessive Mocking
|
||||
|
||||
**Pattern Found:** 49 instances of `global.fetch = vi.fn()` across 8 test files
|
||||
|
||||
**Issue:** Every test mocks the worker, so tests never verify real integration
|
||||
|
||||
**Example:** `/Users/alexnewman/Scripts/claude-mem/tests/integration/full-lifecycle.test.ts`
|
||||
- Called "integration test" but mocks everything
|
||||
- Never actually tests hooks talking to worker
|
||||
- Can't catch real integration bugs
|
||||
|
||||
**Fix:** Add TRUE integration tests that:
|
||||
1. Start real worker process
|
||||
2. Run real hooks
|
||||
3. Verify real database writes
|
||||
4. Tear down cleanly
|
||||
|
||||
**Philosophy Violation:** "Simple First" - mocking everything is more complex than just testing the real thing.
|
||||
|
||||
---
|
||||
|
||||
## DRY Violations (Test Code Duplication)
|
||||
|
||||
### Problem: Repeated Mock Setup
|
||||
|
||||
**Pattern:** Every test file has identical beforeEach blocks:
|
||||
|
||||
```typescript
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
```
|
||||
|
||||
**Pattern:** Every test manually mocks fetch with same structure:
|
||||
|
||||
```typescript
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({ ... })
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:** Extract to test helpers:
|
||||
|
||||
```typescript
|
||||
// tests/helpers/mock-worker.ts
|
||||
export function mockWorkerSuccess(responseData: any) {
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => responseData
|
||||
});
|
||||
}
|
||||
|
||||
export function mockWorkerError(status: number, message: string) {
|
||||
global.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status,
|
||||
text: async () => message
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:** Reduces 49 instances to ~10 helper calls. Makes test intent clearer.
|
||||
|
||||
---
|
||||
|
||||
## Actionable Recommendations
|
||||
|
||||
### Priority 1: Critical Regressions (Implement Now) ✅ DONE
|
||||
|
||||
1. **Hook Error Logging Tests** ✅ Created
|
||||
- File: `/Users/alexnewman/Scripts/claude-mem/tests/error-handling/hook-error-logging.test.ts`
|
||||
- Prevents silent failure regressions
|
||||
- Verifies error messages are actionable
|
||||
|
||||
2. **ChromaSync Error Tests** ✅ Created
|
||||
- File: `/Users/alexnewman/Scripts/claude-mem/tests/services/chroma-sync-errors.test.ts`
|
||||
- Ensures consistent error messages
|
||||
- Catches initialization bugs
|
||||
|
||||
3. **Hook Environment Tests** ✅ Created
|
||||
- File: `/Users/alexnewman/Scripts/claude-mem/tests/integration/hook-execution-environments.test.ts`
|
||||
- Prevents fish shell PATH regression
|
||||
- Cross-platform coverage
|
||||
|
||||
### Priority 2: Remove False Positives (Do Next)
|
||||
|
||||
1. **DELETE Unimplemented Feature Tests**
|
||||
- `/Users/alexnewman/Scripts/claude-mem/tests/happy-paths/search.test.ts` lines 80-271
|
||||
- These create false confidence
|
||||
- Re-add when features actually exist
|
||||
|
||||
### Priority 3: Reduce Test Complexity
|
||||
|
||||
1. **Extract Mock Helpers**
|
||||
- Create `/Users/alexnewman/Scripts/claude-mem/tests/helpers/mock-worker.ts`
|
||||
- Replace 49 instances of manual mocking
|
||||
- See DRY section above for example
|
||||
|
||||
2. **Add TRUE Integration Tests**
|
||||
- Create `/Users/alexnewman/Scripts/claude-mem/tests/integration/real-worker.test.ts`
|
||||
- Start real worker, run real hooks
|
||||
- Currently ALL integration tests are mocked
|
||||
|
||||
### Priority 4: Systematic Error Testing
|
||||
|
||||
1. **Worker Failure Scenarios**
|
||||
- Create `/Users/alexnewman/Scripts/claude-mem/tests/integration/hook-failures.test.ts`
|
||||
- Test crash, timeout, malformed response scenarios
|
||||
|
||||
2. **Spinner Timeout Tests**
|
||||
- Create `/Users/alexnewman/Scripts/claude-mem/tests/utils/spinner-timeout.test.ts`
|
||||
- Verify hardened spinner cleanup works
|
||||
|
||||
---
|
||||
|
||||
## Test Quality Checklist
|
||||
|
||||
For EVERY new test, verify:
|
||||
|
||||
- [ ] Tests actual bug, not mocked behavior
|
||||
- [ ] Will FAIL if bug reappears
|
||||
- [ ] Error messages are checked (not just success paths)
|
||||
- [ ] No YAGNI - tests code that exists NOW
|
||||
- [ ] DRY - uses test helpers, not duplicated setup
|
||||
- [ ] KISS - simple, obvious test structure
|
||||
- [ ] Fail fast - no silent fallbacks tested
|
||||
|
||||
---
|
||||
|
||||
## Coverage Metrics
|
||||
|
||||
**Before Audit:**
|
||||
- Error handling: 0% (no tests for error paths)
|
||||
- Silent failures: Undetected
|
||||
- Recent bugfixes: Unprotected
|
||||
|
||||
**After Audit:**
|
||||
- Error handling: ~40% (3 new test files)
|
||||
- Silent failures: Detected by hook-error-logging.test.ts
|
||||
- Recent bugfixes: Protected
|
||||
|
||||
**Remaining Gaps:**
|
||||
- True integration tests (worker + hooks + database)
|
||||
- Spinner error handling
|
||||
- Worker crash scenarios
|
||||
- Malformed response handling
|
||||
|
||||
---
|
||||
|
||||
## Files Created
|
||||
|
||||
1. `/Users/alexnewman/Scripts/claude-mem/tests/error-handling/hook-error-logging.test.ts`
|
||||
- 200+ lines
|
||||
- Tests handleFetchError, handleWorkerError
|
||||
- Real hook error scenarios
|
||||
- Error message quality checks
|
||||
|
||||
2. `/Users/alexnewman/Scripts/claude-mem/tests/services/chroma-sync-errors.test.ts`
|
||||
- 300+ lines
|
||||
- Client initialization errors
|
||||
- Error message consistency
|
||||
- Fail-fast behavior
|
||||
|
||||
3. `/Users/alexnewman/Scripts/claude-mem/tests/integration/hook-execution-environments.test.ts`
|
||||
- 250+ lines
|
||||
- Fish shell PATH resolution
|
||||
- Cross-platform bun finding
|
||||
- Real-world shell scenarios
|
||||
|
||||
**Total:** ~750 lines of new regression-preventing tests
|
||||
|
||||
---
|
||||
|
||||
## Philosophy Alignment
|
||||
|
||||
These tests follow the project's coding standards:
|
||||
|
||||
✅ **YAGNI** - Only test code that exists (removed future-feature tests)
|
||||
✅ **DRY** - Identified duplication, recommended helpers
|
||||
✅ **Fail Fast** - All tests verify explicit errors, not silent failures
|
||||
✅ **Simple First** - Recommended real integration over complex mocks
|
||||
✅ **Delete Aggressively** - Flagged unimplemented feature tests for deletion
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Run new tests:** `npm test tests/error-handling/ tests/services/ tests/integration/hook-execution-environments.test.ts`
|
||||
|
||||
2. **Delete false positives:** Remove search.test.ts lines 80-271 (unimplemented features)
|
||||
|
||||
3. **Extract helpers:** Create `tests/helpers/mock-worker.ts` to reduce duplication
|
||||
|
||||
4. **Add true integration:** Create real worker + hook integration test
|
||||
|
||||
5. **Continuous:** Apply "Test Quality Checklist" to all future tests
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The test suite now has **regression protection for recent bugfixes**. The three new test files will catch if:
|
||||
- Hooks start failing silently again
|
||||
- ChromaSync error messages become inconsistent
|
||||
- Fish shell PATH issues return
|
||||
|
||||
However, we still need **true integration tests** that don't mock everything. The current integration tests are really "mocked end-to-end tests" - they test the shape of the API, not the actual behavior.
|
||||
|
||||
**Risk reduced from HIGH → MEDIUM**. Remaining risk: real integration failures not caught by mocked tests.
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
# 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 |
|
||||
@@ -1,4 +1,9 @@
|
||||
# Architecture Evolution: The Journey from v3 to v5
|
||||
---
|
||||
title: "Architecture Evolution"
|
||||
description: "How claude-mem evolved from v3 to v5+"
|
||||
---
|
||||
|
||||
# Architecture Evolution
|
||||
|
||||
## The Problem We Solved
|
||||
|
||||
@@ -46,22 +51,15 @@ const ThemeProvider = ({ children }) => {
|
||||
|
||||
**Why It Matters**: Users working in different lighting conditions can now customize the viewer for comfort.
|
||||
|
||||
### v5.1.1: PM2 Windows Fix (November 2025)
|
||||
### v5.1.1: Worker Startup Fix (November 2025) - Now Deprecated
|
||||
|
||||
**The Problem**: PM2 startup failed on Windows with ENOENT error
|
||||
**Note**: This section describes a historical PM2-based approach that has been replaced with Bun in later versions.
|
||||
|
||||
**Root Cause**:
|
||||
```typescript
|
||||
// ❌ Failed on Windows - PM2 not in PATH
|
||||
execSync('pm2 start ecosystem.config.cjs');
|
||||
```
|
||||
**The Problem**: Worker startup failed on Windows with ENOENT error when using PM2
|
||||
|
||||
**The Fix**:
|
||||
```typescript
|
||||
// ✅ Use full path to PM2 binary
|
||||
const PM2_PATH = join(PLUGIN_ROOT, 'node_modules', '.bin', 'pm2');
|
||||
execSync(`"${PM2_PATH}" start "${ECOSYSTEM_CONFIG}"`);
|
||||
```
|
||||
**Historical Solution**: Used full path to PM2 binary instead of relying on PATH
|
||||
|
||||
**Current Approach**: The project now uses Bun for process management, which provides better cross-platform compatibility and eliminates these PATH-related issues.
|
||||
|
||||
**Impact**: Cross-platform compatibility restored, Windows users can now use claude-mem without issues.
|
||||
|
||||
@@ -158,7 +156,7 @@ if (currentVersion !== installedVersion) {
|
||||
**Cached Check Logic**:
|
||||
1. Does `node_modules` exist?
|
||||
2. Does `.install-version` match `package.json` version?
|
||||
3. Is `better-sqlite3` present?
|
||||
3. Is `better-sqlite3` present? (Legacy: now uses bun:sqlite which requires no installation)
|
||||
|
||||
**Impact**:
|
||||
- SessionStart hook: 2-5 seconds → 10ms (99.5% faster)
|
||||
@@ -203,7 +201,7 @@ async function ensureWorkerHealthy() {
|
||||
**Key Fixes**:
|
||||
- Fixed race conditions in observation queue processing
|
||||
- Improved error handling in SDK worker
|
||||
- Better cleanup of stale PM2 processes
|
||||
- Better cleanup of stale worker processes
|
||||
- Enhanced logging for debugging
|
||||
|
||||
### v5.0.0: Hybrid Search Architecture (October 2025)
|
||||
@@ -520,7 +518,7 @@ const response = query({
|
||||
|
||||
**Key change from v3:**
|
||||
- ✅ Stores raw prompts for search
|
||||
- ✅ Auto-starts PM2 worker
|
||||
- ✅ Auto-starts worker service
|
||||
</Tab>
|
||||
|
||||
<Tab title="PostToolUse">
|
||||
@@ -1062,7 +1060,7 @@ The result is a memory system that's both powerful and invisible. Users never no
|
||||
- [Progressive Disclosure](progressive-disclosure) - The philosophy behind v4
|
||||
- [Hooks Architecture](hooks-architecture) - How hooks power the system
|
||||
- [Context Engineering](context-engineering) - Foundational principles
|
||||
- [Viewer UI](VIEWER) - Real-time visualization (v5.1.0+)
|
||||
- [Worker Service](/architecture/worker-service) - Real-time visualization (v5.1.0+)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -5,17 +5,17 @@ description: "SQLite schema, FTS5 search, and data storage"
|
||||
|
||||
# Database Architecture
|
||||
|
||||
Claude-Mem uses SQLite 3 with the better-sqlite3 native module for persistent storage and FTS5 for full-text search.
|
||||
Claude-Mem uses SQLite 3 with the bun:sqlite native module for persistent storage and FTS5 for full-text search.
|
||||
|
||||
## Database Location
|
||||
|
||||
- **Current**: `~/.claude-mem/claude-mem.db`
|
||||
**Path**: `~/.claude-mem/claude-mem.db`
|
||||
|
||||
**Note**: Despite the README claiming v4.0.0+ moved the database to `${CLAUDE_PLUGIN_ROOT}/data/`, the actual implementation still uses `~/.claude-mem/`.
|
||||
The database uses SQLite's WAL (Write-Ahead Logging) mode for concurrent reads/writes.
|
||||
|
||||
## Database Implementation
|
||||
|
||||
**Primary Implementation**: better-sqlite3 (native SQLite module)
|
||||
**Primary Implementation**: bun:sqlite (native SQLite module)
|
||||
- Used by: SessionStore and SessionSearch
|
||||
- Format: Synchronous API with better performance
|
||||
- **Note**: Database.ts (using bun:sqlite) is legacy code
|
||||
@@ -301,8 +301,8 @@ Database schema is managed via migrations in `src/services/sqlite/migrations.ts`
|
||||
- **Indexes**: All foreign keys and frequently queried columns are indexed
|
||||
- **FTS5**: Full-text search is significantly faster than LIKE queries
|
||||
- **Triggers**: Automatic synchronization has minimal overhead
|
||||
- **Connection Pooling**: better-sqlite3 reuses connections efficiently
|
||||
- **Synchronous API**: better-sqlite3 uses synchronous API for better performance
|
||||
- **Connection Pooling**: bun:sqlite reuses connections efficiently
|
||||
- **Synchronous API**: bun:sqlite uses synchronous API for better performance
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
+805
-119
File diff suppressed because it is too large
Load Diff
@@ -22,14 +22,14 @@ Claude-Mem operates as a Claude Code plugin with five core components:
|
||||
|------------------------|-------------------------------------------|
|
||||
| **Language** | TypeScript (ES2022, ESNext modules) |
|
||||
| **Runtime** | Node.js 18+ |
|
||||
| **Database** | SQLite 3 with better-sqlite3 driver |
|
||||
| **Database** | SQLite 3 with bun:sqlite driver |
|
||||
| **Vector Store** | ChromaDB (optional, for semantic search) |
|
||||
| **HTTP Server** | Express.js 4.18 |
|
||||
| **Real-time** | Server-Sent Events (SSE) |
|
||||
| **UI Framework** | React + TypeScript |
|
||||
| **AI SDK** | @anthropic-ai/claude-agent-sdk |
|
||||
| **Build Tool** | esbuild (bundles TypeScript) |
|
||||
| **Process Manager** | PM2 |
|
||||
| **Process Manager** | Bun |
|
||||
| **Testing** | Node.js built-in test runner |
|
||||
|
||||
## Data Flow
|
||||
@@ -70,7 +70,7 @@ User Query → mem-search Skill Invoked → HTTP API → SessionSearch Service
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 1. Session Starts → Context Hook Fires │
|
||||
│ Starts PM2 worker if needed, injects context from previous │
|
||||
│ Starts Bun worker if needed, injects context from previous │
|
||||
│ sessions (configurable observation count) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
@@ -177,13 +177,13 @@ claude-mem/
|
||||
│
|
||||
├── tests/ # Test suite
|
||||
├── docs/ # Documentation
|
||||
└── ecosystem.config.cjs # PM2 configuration
|
||||
└── ecosystem.config.cjs # Process configuration (deprecated)
|
||||
```
|
||||
|
||||
## Component Details
|
||||
|
||||
### 1. Plugin Hooks (6 Hooks)
|
||||
- **context-hook.js** - SessionStart: Starts PM2 worker, injects context
|
||||
- **context-hook.js** - SessionStart: Starts Bun worker, injects context
|
||||
- **user-message-hook.js** - UserMessage: Debugging hook
|
||||
- **new-hook.js** - UserPromptSubmit: Creates session, saves prompt
|
||||
- **save-hook.js** - PostToolUse: Captures tool executions
|
||||
@@ -200,12 +200,12 @@ Express.js HTTP server on port 37777 (configurable) with:
|
||||
- 8 viewer UI HTTP/SSE endpoints
|
||||
- Async observation processing via Claude Agent SDK
|
||||
- Real-time updates via Server-Sent Events
|
||||
- Auto-managed by PM2 process manager
|
||||
- Auto-managed by Bun
|
||||
|
||||
See [Worker Service](/architecture/worker-service) for HTTP API and endpoints.
|
||||
|
||||
### 3. Database Layer
|
||||
SQLite3 with better-sqlite3 driver featuring:
|
||||
SQLite3 with bun:sqlite driver featuring:
|
||||
- FTS5 virtual tables for full-text search
|
||||
- SessionStore for CRUD operations
|
||||
- SessionSearch for FTS5 queries
|
||||
|
||||
@@ -0,0 +1,551 @@
|
||||
---
|
||||
title: "PM2 to Bun Migration"
|
||||
description: "Complete technical documentation for the process management and database driver migration in v7.1.0"
|
||||
---
|
||||
|
||||
# PM2 to Bun Migration: Complete Technical Documentation
|
||||
|
||||
**Version**: 7.1.0
|
||||
**Date**: December 2025
|
||||
**Migration Type**: Process Management (PM2 → Bun) + Database Driver (better-sqlite3 → bun:sqlite)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Claude-mem version 7.1.0 introduces two major architectural migrations:
|
||||
|
||||
1. **Process Management**: PM2 → Custom Bun-based ProcessManager
|
||||
2. **Database Driver**: better-sqlite3 npm package → bun:sqlite runtime module
|
||||
|
||||
Both migrations are **automatic** and **transparent** to end users. The first time a hook fires after updating to 7.1.0+, the system performs a one-time cleanup of legacy PM2 processes and transitions to the new architecture.
|
||||
|
||||
### Key Benefits
|
||||
|
||||
- **Simplified Dependencies**: Removes PM2 and better-sqlite3 npm packages
|
||||
- **Improved Cross-Platform Support**: Better Windows compatibility
|
||||
- **Faster Installation**: No native module compilation required
|
||||
- **Built-in Runtime**: Leverages Bun's built-in process management and SQLite
|
||||
- **Reduced Complexity**: Custom ProcessManager is simpler than PM2 integration
|
||||
|
||||
### Migration Impact
|
||||
|
||||
- **Data Preservation**: User data, settings, and database remain unchanged
|
||||
- **Automatic Cleanup**: Old PM2 processes automatically terminated (all platforms)
|
||||
- **No User Action Required**: Migration happens automatically on first hook trigger
|
||||
- **Backward Compatible**: SQLite database format unchanged (only driver changed)
|
||||
|
||||
## Architecture Comparison
|
||||
|
||||
### Old System (PM2-based)
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Process Management (PM2)">
|
||||
**Component**: PM2 (Process Manager 2)
|
||||
- **Package**: `pm2` npm dependency
|
||||
- **Process Name**: `claude-mem-worker`
|
||||
- **Management**: External PM2 daemon manages lifecycle
|
||||
- **Discovery**: `pm2 list`, `pm2 describe` commands
|
||||
- **Auto-restart**: PM2 automatically restarts on crash
|
||||
- **Logs**: `~/.pm2/logs/claude-mem-worker-*.log`
|
||||
- **PID File**: `~/.pm2/pids/claude-mem-worker.pid`
|
||||
|
||||
**Lifecycle Commands**:
|
||||
```bash
|
||||
pm2 start <script> # Start worker
|
||||
pm2 stop claude-mem-worker # Stop worker
|
||||
pm2 restart claude-mem-worker # Restart worker
|
||||
pm2 delete claude-mem-worker # Remove from PM2
|
||||
pm2 logs claude-mem-worker # View logs
|
||||
```
|
||||
|
||||
**Pain Points**:
|
||||
- Additional npm dependency required
|
||||
- PM2 daemon must be running
|
||||
- Potential conflicts with other PM2 processes
|
||||
- Windows compatibility issues
|
||||
- Complex configuration for simple use case
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Database Driver (better-sqlite3)">
|
||||
**Component**: better-sqlite3
|
||||
- **Package**: `better-sqlite3` npm package (native module)
|
||||
- **Installation**: Requires native compilation (node-gyp)
|
||||
- **Windows**: Requires Visual Studio build tools + Python
|
||||
- **Import**: `import Database from 'better-sqlite3'`
|
||||
|
||||
**Installation Requirements**:
|
||||
- Node.js development headers
|
||||
- C++ compiler (gcc/clang on Mac/Linux, MSVC on Windows)
|
||||
- Python (for node-gyp)
|
||||
- Windows: Visual Studio Build Tools
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### New System (Bun-based)
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Process Management (Custom ProcessManager)">
|
||||
**Component**: Custom ProcessManager (`src/services/process/ProcessManager.ts`)
|
||||
- **Package**: Built-in Bun APIs (no external dependency)
|
||||
- **Process Spawn**: `Bun.spawn()` with detached mode
|
||||
- **Management**: Direct process control via PID file
|
||||
- **Discovery**: PID file + process existence check + HTTP health check
|
||||
- **Auto-restart**: Hook-triggered restart on failure detection
|
||||
- **Logs**: `~/.claude-mem/logs/worker-YYYY-MM-DD.log`
|
||||
- **PID File**: `~/.claude-mem/.worker.pid`
|
||||
- **Port File**: `~/.claude-mem/.worker.port` (new)
|
||||
|
||||
**Lifecycle Commands**:
|
||||
```bash
|
||||
npm run worker:start # Start worker
|
||||
npm run worker:stop # Stop worker
|
||||
npm run worker:restart # Restart worker
|
||||
npm run worker:status # Check status
|
||||
npm run worker:logs # View logs
|
||||
```
|
||||
|
||||
**Core Mechanisms**:
|
||||
|
||||
1. **PID File Management**:
|
||||
- File: `~/.claude-mem/.worker.pid`
|
||||
- Content: Process ID (e.g., "35557")
|
||||
- Validation: Process existence via `kill(pid, 0)` signal
|
||||
|
||||
2. **Port File Management**:
|
||||
- File: `~/.claude-mem/.worker.port`
|
||||
- Content: Two lines (port number, PID)
|
||||
- Purpose: Track port binding and validate PID match
|
||||
|
||||
3. **Health Checking**:
|
||||
- Layer 1: PID file exists?
|
||||
- Layer 2: Process alive? (`kill(pid, 0)`)
|
||||
- Layer 3: HTTP health check (`GET /health`)
|
||||
- All three must pass for "healthy" status
|
||||
|
||||
**Advantages**:
|
||||
- No external dependencies
|
||||
- Simpler codebase (direct control)
|
||||
- Better error handling and validation
|
||||
- Platform-agnostic (Bun handles platform differences)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Database Driver (bun:sqlite)">
|
||||
**Component**: bun:sqlite
|
||||
- **Package**: Built into Bun runtime (no npm package)
|
||||
- **Installation**: None required (comes with Bun ≥1.0)
|
||||
- **Platform**: Works anywhere Bun works
|
||||
- **Import**: `import { Database } from 'bun:sqlite'`
|
||||
- **API**: Similar to better-sqlite3 (synchronous)
|
||||
|
||||
**Installation Requirements**:
|
||||
- Bun ≥1.0 (automatically installed if missing)
|
||||
- No native compilation required
|
||||
- No platform-specific build tools needed
|
||||
|
||||
**Compatibility**:
|
||||
- SQLite database format: **Unchanged**
|
||||
- Database file: `~/.claude-mem/claude-mem.db` (same location)
|
||||
- Query syntax: **Identical** (both use SQLite SQL)
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Migration Mechanics
|
||||
|
||||
### One-Time PM2 Cleanup
|
||||
|
||||
The migration system uses a marker-based approach to perform PM2 cleanup exactly once.
|
||||
|
||||
**Implementation**: `src/shared/worker-utils.ts:73-86`
|
||||
|
||||
```typescript
|
||||
// Clean up legacy PM2 (one-time migration)
|
||||
const pm2MigratedMarker = join(DATA_DIR, '.pm2-migrated');
|
||||
|
||||
if (!existsSync(pm2MigratedMarker)) {
|
||||
try {
|
||||
spawnSync('pm2', ['delete', 'claude-mem-worker'], { stdio: 'ignore' });
|
||||
// Mark migration as complete
|
||||
writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8');
|
||||
logger.debug('SYSTEM', 'PM2 cleanup completed and marked');
|
||||
} catch {
|
||||
// PM2 not installed or process doesn't exist - still mark as migrated
|
||||
writeFileSync(pm2MigratedMarker, new Date().toISOString(), 'utf-8');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Migration Trigger Points
|
||||
|
||||
<Steps>
|
||||
<Step title="Hook Execution">
|
||||
SessionStart, UserPromptSubmit, or PostToolUse hooks execute using new 7.1.0 code
|
||||
</Step>
|
||||
<Step title="Worker Status Check">
|
||||
`ensureWorkerRunning()` checks if `~/.claude-mem/.worker.pid` exists (it doesn't for first run after update)
|
||||
</Step>
|
||||
<Step title="Start Worker Decision">
|
||||
Worker not running → Call `startWorker()`
|
||||
</Step>
|
||||
<Step title="Migration Check">
|
||||
Check if `~/.claude-mem/.pm2-migrated` exists
|
||||
</Step>
|
||||
<Step title="PM2 Cleanup">
|
||||
Execute `pm2 delete claude-mem-worker` (errors ignored), create marker file
|
||||
</Step>
|
||||
<Step title="New Worker Start">
|
||||
Spawn new Bun-managed worker process with PID and port files
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Marker File
|
||||
|
||||
**Location**: `~/.claude-mem/.pm2-migrated`
|
||||
|
||||
**Content**: ISO 8601 timestamp
|
||||
```
|
||||
2025-12-13T00:18:39.673Z
|
||||
```
|
||||
|
||||
**Purpose**:
|
||||
- One-time migration flag
|
||||
- Prevents repeated PM2 cleanup on every start
|
||||
- Persists across restarts and reboots
|
||||
|
||||
**Lifecycle**:
|
||||
- Created: First hook trigger after update to 7.1.0+ (all platforms)
|
||||
- Updated: Never
|
||||
- Deleted: Never (user could manually delete to force re-migration)
|
||||
|
||||
## User Experience Timeline
|
||||
|
||||
### First Session After Update
|
||||
|
||||
<Note>
|
||||
This is the critical migration moment. The process takes approximately 2-5 seconds.
|
||||
</Note>
|
||||
|
||||
**Step-by-Step Execution**:
|
||||
|
||||
1. **Hook fires** (SessionStart most common)
|
||||
2. **Worker status check**: No PID file → worker not running
|
||||
3. **Migration check**: No marker file → run PM2 cleanup
|
||||
4. **PM2 cleanup**: `pm2 delete claude-mem-worker` (old worker terminated)
|
||||
5. **Marker creation**: `~/.claude-mem/.pm2-migrated` with timestamp
|
||||
6. **New worker start**: Bun process spawned, PID/port files created
|
||||
7. **Verification**: Process check + HTTP health check
|
||||
8. **Hook completes**: Claude Code session starts normally
|
||||
|
||||
**User Observable Behavior**:
|
||||
- Slight delay on first startup (PM2 cleanup + new worker spawn)
|
||||
- No error messages (cleanup failures silently handled)
|
||||
- Worker appears running via `npm run worker:status`
|
||||
- Old PM2 worker no longer in `pm2 list`
|
||||
|
||||
### Subsequent Sessions
|
||||
|
||||
After migration completes, every hook trigger follows the fast path:
|
||||
|
||||
1. PID file exists? **YES**
|
||||
2. Process alive? **YES**
|
||||
3. HTTP health check? **SUCCESS**
|
||||
4. Result: Worker already running, done (~50ms)
|
||||
|
||||
No migration logic runs on subsequent sessions.
|
||||
|
||||
## Platform-Specific Behavior
|
||||
|
||||
### Platform Comparison
|
||||
|
||||
| Feature | macOS | Linux | Windows |
|
||||
|---------|-------|-------|---------|
|
||||
| PM2 Cleanup | Attempted | Attempted | Attempted |
|
||||
| Marker File | Created | Created | Created |
|
||||
| Process Signals | POSIX (native) | POSIX (native) | Bun abstraction |
|
||||
| Bun Support | Full | Full | Full |
|
||||
| PID File | Yes | Yes | Yes |
|
||||
| Port File | Yes | Yes | Yes |
|
||||
| Health Check | HTTP | HTTP | HTTP |
|
||||
| Migration Delay | ~2-5s first time | ~2-5s first time | ~2-5s first time |
|
||||
|
||||
### Platform Notes
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
- POSIX signal handling works natively
|
||||
- Bun fully supported
|
||||
- No platform-specific workarounds needed
|
||||
</Tab>
|
||||
<Tab title="Linux">
|
||||
- Identical behavior to macOS
|
||||
- POSIX signal handling
|
||||
- Works on Ubuntu, Debian, RHEL, CentOS, Arch
|
||||
- Alpine may require glibc (not musl)
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
- PM2 cleanup now runs (safe due to try/catch)
|
||||
- Bun abstracts signal handling differences
|
||||
- Path module handles Windows separators
|
||||
- File locking handled by SQLite
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Observable Changes
|
||||
|
||||
### Command Changes
|
||||
|
||||
| Old (PM2) | New (Bun) | Notes |
|
||||
|-----------|-----------|-------|
|
||||
| `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 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 |
|
||||
| `pm2 monit` | No equivalent | PM2-specific monitoring |
|
||||
|
||||
### File Location Changes
|
||||
|
||||
**Logs**:
|
||||
```
|
||||
Old: ~/.pm2/logs/claude-mem-worker-out.log
|
||||
~/.pm2/logs/claude-mem-worker-error.log
|
||||
|
||||
New: ~/.claude-mem/logs/worker-YYYY-MM-DD.log
|
||||
```
|
||||
|
||||
**PID Files**:
|
||||
```
|
||||
Old: ~/.pm2/pids/claude-mem-worker.pid
|
||||
|
||||
New: ~/.claude-mem/.worker.pid
|
||||
```
|
||||
|
||||
**Process State**:
|
||||
```
|
||||
Old: PM2 daemon memory (pm2 save)
|
||||
|
||||
New: ~/.claude-mem/.worker.pid
|
||||
~/.claude-mem/.worker.port
|
||||
~/.claude-mem/.pm2-migrated (all platforms)
|
||||
```
|
||||
|
||||
**Database** (unchanged):
|
||||
```
|
||||
Same: ~/.claude-mem/claude-mem.db
|
||||
```
|
||||
|
||||
### User-Visible Changes
|
||||
|
||||
**Before Update**:
|
||||
```bash
|
||||
$ pm2 list
|
||||
┌────┬────────────────────┬─────────┬─────────┬──────────┐
|
||||
│ id │ name │ status │ restart │ uptime │
|
||||
├────┼────────────────────┼─────────┼─────────┼──────────┤
|
||||
│ 0 │ claude-mem-worker │ online │ 0 │ 2d 5h │
|
||||
└────┴────────────────────┴─────────┴─────────┴──────────┘
|
||||
```
|
||||
|
||||
**After Update**:
|
||||
```bash
|
||||
$ pm2 list
|
||||
# Empty - worker no longer managed by PM2
|
||||
|
||||
$ npm run worker:status
|
||||
Worker is running
|
||||
PID: 35557
|
||||
Port: 37777
|
||||
Uptime: 2h 15m
|
||||
```
|
||||
|
||||
### Orphaned Files
|
||||
|
||||
After migration, these PM2 files may remain (safe to delete):
|
||||
|
||||
```
|
||||
~/.pm2/ # Entire PM2 directory
|
||||
~/.pm2/logs/ # Old logs
|
||||
~/.pm2/pids/ # Old PID files
|
||||
~/.pm2/pm2.log # PM2 daemon log
|
||||
~/.pm2/dump.pm2 # PM2 process dump
|
||||
```
|
||||
|
||||
**Cleanup (optional)**:
|
||||
```bash
|
||||
# Remove PM2 entirely (if not used for other processes)
|
||||
pm2 kill
|
||||
rm -rf ~/.pm2
|
||||
|
||||
# Or just remove claude-mem logs
|
||||
rm -f ~/.pm2/logs/claude-mem-worker-*.log
|
||||
rm -f ~/.pm2/pids/claude-mem-worker.pid
|
||||
```
|
||||
|
||||
## File System State
|
||||
|
||||
### State Directory Structure
|
||||
|
||||
**Before Migration** (PM2 system):
|
||||
```
|
||||
~/.claude-mem/
|
||||
├── claude-mem.db # Database (unchanged)
|
||||
├── chroma/ # Vector embeddings (unchanged)
|
||||
├── logs/ # Application logs (unchanged)
|
||||
└── settings.json # User settings (unchanged)
|
||||
|
||||
~/.pm2/
|
||||
├── logs/
|
||||
│ ├── claude-mem-worker-out.log
|
||||
│ └── claude-mem-worker-error.log
|
||||
├── pids/
|
||||
│ └── claude-mem-worker.pid
|
||||
└── pm2.log
|
||||
```
|
||||
|
||||
**After Migration** (Bun system):
|
||||
```
|
||||
~/.claude-mem/
|
||||
├── claude-mem.db # Database (same file)
|
||||
├── chroma/ # Vector embeddings (unchanged)
|
||||
├── logs/
|
||||
│ └── worker-2025-12-13.log # New log format
|
||||
├── settings.json # User settings (unchanged)
|
||||
├── .worker.pid # NEW: Process ID
|
||||
├── .worker.port # NEW: Port + PID
|
||||
└── .pm2-migrated # NEW: Migration marker (all platforms)
|
||||
|
||||
~/.pm2/ # Orphaned (safe to delete)
|
||||
├── logs/ # Old logs (no longer written)
|
||||
├── pids/ # Old PID (no longer updated)
|
||||
└── pm2.log # PM2 daemon log (not used)
|
||||
```
|
||||
|
||||
## Edge Cases and Troubleshooting
|
||||
|
||||
### Scenario 1: Migration Fails (PM2 Still Running)
|
||||
|
||||
<Warning>
|
||||
This is rare but can happen if PM2 has watch mode enabled or the process is manually restarted.
|
||||
</Warning>
|
||||
|
||||
**Symptoms**:
|
||||
- `pm2 list` still shows `claude-mem-worker`
|
||||
- Port conflict errors in logs
|
||||
- Worker fails to start
|
||||
|
||||
**Resolution**:
|
||||
```bash
|
||||
# Manual cleanup
|
||||
pm2 delete claude-mem-worker
|
||||
pm2 save # Persist the deletion
|
||||
|
||||
# Force re-migration (optional)
|
||||
rm ~/.claude-mem/.pm2-migrated
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Scenario 2: Stale PID File (Process Dead)
|
||||
|
||||
**Symptoms**:
|
||||
- `npm run worker:status` shows "not running"
|
||||
- `.worker.pid` file exists
|
||||
- Process ID doesn't exist
|
||||
|
||||
**Automatic Recovery**: Next hook trigger detects dead process and starts a fresh worker.
|
||||
|
||||
**Manual Resolution**:
|
||||
```bash
|
||||
rm ~/.claude-mem/.worker.pid
|
||||
rm ~/.claude-mem/.worker.port
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
### Scenario 3: Port Already in Use
|
||||
|
||||
**Error**: `EADDRINUSE: address already in use`
|
||||
|
||||
**Resolution**:
|
||||
```bash
|
||||
# Check what's using the port
|
||||
lsof -i :37777
|
||||
|
||||
# Kill the process
|
||||
kill -9 <PID>
|
||||
|
||||
# Restart worker
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Common Error Messages
|
||||
|
||||
| Error | Cause | Resolution |
|
||||
|-------|-------|------------|
|
||||
| `EADDRINUSE` | Port already in use | `lsof -i :37777` then kill conflicting process |
|
||||
| `No such process` | Stale PID file | Automatic cleanup on next hook trigger |
|
||||
| `pm2: command not found` | PM2 not installed | None needed (error is caught and ignored) |
|
||||
| `Invalid port X` | Port validation failed | Update `CLAUDE_MEM_WORKER_PORT` in settings |
|
||||
|
||||
## Developer Notes
|
||||
|
||||
### Testing the Migration
|
||||
|
||||
```bash
|
||||
# 1. Install old version (with PM2)
|
||||
git checkout <pre-7.1.0-tag>
|
||||
npm install && npm run build && npm run sync-marketplace
|
||||
|
||||
# 2. Start PM2 worker
|
||||
pm2 start plugin/scripts/worker-cli.js --name claude-mem-worker
|
||||
|
||||
# 3. Update to new version
|
||||
git checkout main
|
||||
npm install && npm run build && npm run sync-marketplace
|
||||
|
||||
# 4. Trigger hook
|
||||
node plugin/scripts/session-start-hook.js
|
||||
|
||||
# 5. Verify migration
|
||||
pm2 list # Should NOT show claude-mem-worker
|
||||
cat ~/.claude-mem/.pm2-migrated # Should exist
|
||||
npm run worker:status # Should show Bun worker running
|
||||
```
|
||||
|
||||
### Architecture Decisions
|
||||
|
||||
**Why Custom ProcessManager Instead of PM2?**
|
||||
1. **Simplicity**: Direct control, no external daemon
|
||||
2. **Dependencies**: Remove npm dependency
|
||||
3. **Cross-platform**: Bun handles platform differences
|
||||
4. **Bundle Size**: Reduce plugin package size
|
||||
5. **Control**: Fine-grained error handling and validation
|
||||
|
||||
**Why One-Time Marker Instead of Always Running PM2 Delete?**
|
||||
1. **Performance**: Avoid unnecessary process spawning
|
||||
2. **Idempotency**: Migration runs exactly once
|
||||
3. **Debugging**: Timestamp shows when migration occurred
|
||||
4. **Simplicity**: Clear migration state
|
||||
|
||||
**Why Run PM2 Cleanup on All Platforms?**
|
||||
1. **Quality Migration**: Clean up orphaned processes
|
||||
2. **Consistency**: Same behavior across all platforms
|
||||
3. **Safety**: Error handling already in place (try/catch)
|
||||
4. **No Downside**: If PM2 not installed, error is caught and ignored
|
||||
|
||||
## Summary
|
||||
|
||||
The migration from PM2 to Bun-based ProcessManager is a **one-time, automatic, transparent** transition that:
|
||||
|
||||
1. **Removes external dependencies** (PM2, better-sqlite3)
|
||||
2. **Simplifies architecture** (direct process control)
|
||||
3. **Improves cross-platform support** (especially Windows)
|
||||
4. **Preserves user data** (database, settings, logs unchanged)
|
||||
5. **Requires no user action** (automatic on first hook trigger)
|
||||
|
||||
**Key Migration Moment**: First hook trigger after update to 7.1.0+
|
||||
**Duration**: ~2-5 seconds (one-time delay)
|
||||
**Impact**: Seamless transition, user-invisible
|
||||
**Rollback**: Not needed (migration is forward-only, safe)
|
||||
|
||||
For most users, the migration will be completely transparent - they'll see no errors, no data loss, and experience improved reliability and simpler troubleshooting going forward.
|
||||
@@ -379,7 +379,7 @@ Claude translates to appropriate API call.
|
||||
|
||||
### 4. Performance
|
||||
|
||||
**Fast Queries**: FTS5 full-text search <10ms for typical queries
|
||||
**Fast Queries**: FTS5 full-text search under 10ms for typical queries
|
||||
|
||||
**Caching**: HTTP layer allows response caching
|
||||
|
||||
@@ -397,7 +397,7 @@ Claude translates to appropriate API call.
|
||||
|
||||
### For Developers
|
||||
|
||||
**Deprecated**: MCP search server (`src/servers/search-server.ts`)
|
||||
**Renamed**: MCP server (formerly `search-server.ts`, now `src/servers/mcp-server.ts`)
|
||||
- Source file kept for reference
|
||||
- No longer built or registered
|
||||
- MCP configuration removed from `plugin/.mcp.json`
|
||||
@@ -415,7 +415,7 @@ Claude translates to appropriate API call.
|
||||
If searches fail, check worker service:
|
||||
|
||||
```bash
|
||||
pm2 list # Check status
|
||||
npm run worker:status # Check status
|
||||
npm run worker:restart # Restart worker
|
||||
npm run worker:logs # View logs
|
||||
```
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
---
|
||||
title: "Worker Service"
|
||||
description: "HTTP API and PM2 process management"
|
||||
description: "HTTP API and Bun process management"
|
||||
---
|
||||
|
||||
# Worker Service
|
||||
|
||||
The worker service is a long-running HTTP API built with Express.js and managed by PM2. It processes observations through the Claude Agent SDK separately from hook execution to prevent timeout issues.
|
||||
The worker service is a long-running HTTP API built with Express.js and managed natively by Bun. It processes observations through the Claude Agent SDK separately from hook execution to prevent timeout issues.
|
||||
|
||||
## Overview
|
||||
|
||||
- **Technology**: Express.js HTTP server
|
||||
- **Process Manager**: PM2
|
||||
- **Runtime**: Bun (auto-installed if missing)
|
||||
- **Process Manager**: Native Bun process management via ProcessManager
|
||||
- **Port**: Fixed port 37777 (configurable via `CLAUDE_MEM_WORKER_PORT`)
|
||||
- **Location**: `src/services/worker-service.ts`
|
||||
- **Built Output**: `plugin/scripts/worker-service.cjs`
|
||||
- **Model**: Configurable via `CLAUDE_MEM_MODEL` environment variable (default: claude-sonnet-4-5)
|
||||
- **Model**: Configurable via `CLAUDE_MEM_MODEL` environment variable (default: sonnet)
|
||||
|
||||
## REST API Endpoints
|
||||
|
||||
@@ -322,28 +323,15 @@ DELETE /sessions/:sessionDbId
|
||||
|
||||
**Note**: As of v4.1.0, the cleanup hook no longer calls this endpoint. Sessions are marked complete instead of deleted to allow graceful worker shutdown.
|
||||
|
||||
## PM2 Management
|
||||
## Bun Process Management
|
||||
|
||||
### Configuration
|
||||
### Overview
|
||||
|
||||
The worker is configured via `ecosystem.config.cjs`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'claude-mem-worker',
|
||||
script: './plugin/scripts/worker-service.cjs',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '1G',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
FORCE_COLOR: '1'
|
||||
}
|
||||
}]
|
||||
};
|
||||
```
|
||||
The worker is managed by the native `ProcessManager` class which handles:
|
||||
- Process spawning with Bun runtime
|
||||
- PID file tracking at `~/.claude-mem/worker.pid`
|
||||
- Health checks with automatic retry
|
||||
- Graceful shutdown with SIGTERM/SIGKILL fallback
|
||||
|
||||
### Commands
|
||||
|
||||
@@ -366,7 +354,18 @@ npm run worker:status
|
||||
|
||||
### Auto-Start Behavior
|
||||
|
||||
As of v4.0.0, the worker service auto-starts when the SessionStart hook fires. Manual start is optional.
|
||||
The worker service auto-starts when the SessionStart hook fires. Manual start is optional.
|
||||
|
||||
### Bun Requirement
|
||||
|
||||
Bun is required to run the worker service. If Bun is not installed, the smart-install script will automatically install it on first run:
|
||||
|
||||
- **Windows**: `powershell -c "irm bun.sh/install.ps1 | iex"`
|
||||
- **macOS/Linux**: `curl -fsSL https://bun.sh/install | bash`
|
||||
|
||||
You can also install manually via:
|
||||
- `winget install Oven-sh.Bun` (Windows)
|
||||
- `brew install oven-sh/bun/bun` (macOS)
|
||||
|
||||
## Claude Agent SDK Integration
|
||||
|
||||
@@ -390,14 +389,13 @@ The worker service routes observations to the Claude Agent SDK for AI-powered pr
|
||||
Set the AI model used for processing via environment variable:
|
||||
|
||||
```bash
|
||||
export CLAUDE_MEM_MODEL=claude-sonnet-4-5
|
||||
export CLAUDE_MEM_MODEL=sonnet
|
||||
```
|
||||
|
||||
Available models:
|
||||
- `claude-haiku-4-5` - Fast, cost-efficient
|
||||
- `claude-sonnet-4-5` - Balanced (default)
|
||||
- `claude-opus-4` - Most capable
|
||||
- `claude-3-7-sonnet` - Alternative version
|
||||
Available shorthand models (forward to latest version):
|
||||
- `haiku` - Fast, cost-efficient
|
||||
- `sonnet` - Balanced (default)
|
||||
- `opus` - Most capable
|
||||
|
||||
## Port Allocation
|
||||
|
||||
@@ -411,15 +409,15 @@ If port 37777 is in use, the worker will fail to start. Set a custom port via en
|
||||
|
||||
## Data Storage
|
||||
|
||||
The worker service stores data in the plugin data directory:
|
||||
The worker service stores data in the user data directory:
|
||||
|
||||
```
|
||||
${CLAUDE_PLUGIN_ROOT}/data/
|
||||
├── claude-mem.db # SQLite database
|
||||
├── worker.port # Current worker port file
|
||||
~/.claude-mem/
|
||||
├── claude-mem.db # SQLite database (bun:sqlite)
|
||||
├── worker.pid # PID file for process tracking
|
||||
├── settings.json # User settings
|
||||
└── logs/
|
||||
├── worker-out.log # Worker stdout logs
|
||||
└── worker-error.log # Worker stderr logs
|
||||
└── worker-YYYY-MM-DD.log # Daily rotating logs
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
@@ -5,18 +5,27 @@ description: "Environment variables and settings for Claude-Mem"
|
||||
|
||||
# Configuration
|
||||
|
||||
## Environment Variables
|
||||
## Settings File
|
||||
|
||||
| Variable | Default | Description |
|
||||
Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created with defaults on first run.
|
||||
|
||||
### Core Settings
|
||||
|
||||
| Setting | Default | Description |
|
||||
|-------------------------------|---------------------------------|---------------------------------------|
|
||||
| `CLAUDE_PLUGIN_ROOT` | Set by Claude Code | Plugin installation directory |
|
||||
| `CLAUDE_MEM_DATA_DIR` | `~/.claude-mem/` | Data directory (production default) |
|
||||
| `CLAUDE_CODE_PATH` | Auto-detected | Path to Claude Code CLI (for Windows) |
|
||||
| `CLAUDE_MEM_WORKER_PORT` | `37777` | Worker service port |
|
||||
| `CLAUDE_MEM_MODEL` | `claude-haiku-4-5` | AI model for processing observations |
|
||||
| `CLAUDE_MEM_MODEL` | `sonnet` | AI model for processing observations |
|
||||
| `CLAUDE_MEM_CONTEXT_OBSERVATIONS` | `50` | Number of observations to inject |
|
||||
| `NODE_ENV` | `production` | Environment mode |
|
||||
| `FORCE_COLOR` | `1` | Enable colored logs |
|
||||
| `CLAUDE_MEM_WORKER_PORT` | `37777` | Worker service port |
|
||||
| `CLAUDE_MEM_SKIP_TOOLS` | `ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion` | Comma-separated tools to exclude from observations |
|
||||
|
||||
### System Configuration
|
||||
|
||||
| Setting | Default | Description |
|
||||
|-------------------------------|---------------------------------|---------------------------------------|
|
||||
| `CLAUDE_MEM_DATA_DIR` | `~/.claude-mem` | Data directory location |
|
||||
| `CLAUDE_MEM_LOG_LEVEL` | `INFO` | Log verbosity (DEBUG, INFO, WARN, ERROR, SILENT) |
|
||||
| `CLAUDE_MEM_PYTHON_VERSION` | `3.13` | Python version for chroma-mcp |
|
||||
| `CLAUDE_CODE_PATH` | _(auto-detect)_ | Path to Claude Code CLI (for Windows) |
|
||||
|
||||
## Model Configuration
|
||||
|
||||
@@ -24,10 +33,11 @@ Configure which AI model processes your observations.
|
||||
|
||||
### Available Models
|
||||
|
||||
- `claude-haiku-4-5` - Fast, cost-efficient (default)
|
||||
- `claude-sonnet-4-5` - Balanced
|
||||
- `claude-opus-4` - Most capable
|
||||
- `claude-3-7-sonnet` - Alternative version
|
||||
Shorthand model names automatically forward to the latest version:
|
||||
|
||||
- `haiku` - Fast, cost-efficient
|
||||
- `sonnet` - Balanced (default)
|
||||
- `opus` - Most capable
|
||||
|
||||
### Using the Interactive Script
|
||||
|
||||
@@ -35,15 +45,15 @@ Configure which AI model processes your observations.
|
||||
./claude-mem-settings.sh
|
||||
```
|
||||
|
||||
This script manages `CLAUDE_MEM_MODEL` in `~/.claude/settings.json`.
|
||||
This script manages settings in `~/.claude-mem/settings.json`.
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
Edit `~/.claude/settings.json`:
|
||||
Edit `~/.claude-mem/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_MODEL": "claude-haiku-4-5"
|
||||
"CLAUDE_MEM_MODEL": "sonnet"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -82,7 +92,7 @@ ${CLAUDE_PLUGIN_ROOT}/
|
||||
│ ├── summary-hook.js # Summary generation hook
|
||||
│ ├── cleanup-hook.js # Session cleanup hook
|
||||
│ ├── worker-service.cjs # Worker service (CJS)
|
||||
│ └── search-server.cjs # MCP search server (CJS)
|
||||
│ └── mcp-server.cjs # MCP search server (CJS)
|
||||
└── ui/
|
||||
└── viewer.html # Web viewer UI bundle
|
||||
```
|
||||
@@ -171,33 +181,9 @@ Claude-Mem supports switching between stable and beta versions via the web viewe
|
||||
|
||||
See [Beta Features](beta-features) for details on what's available in beta.
|
||||
|
||||
## PM2 Configuration
|
||||
## Worker Service Management
|
||||
|
||||
Worker service is managed by PM2 via `ecosystem.config.cjs`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'claude-mem-worker',
|
||||
script: './plugin/scripts/worker-service.cjs',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '1G',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
FORCE_COLOR: '1'
|
||||
}
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
### PM2 Settings
|
||||
|
||||
- **instances**: 1 (single instance)
|
||||
- **autorestart**: true (auto-restart on crash)
|
||||
- **watch**: false (no file watching)
|
||||
- **max_memory_restart**: 1G (restart if memory exceeds 1GB)
|
||||
Worker service is managed by Bun as a background process. The worker auto-starts on first session and runs continuously in the background.
|
||||
|
||||
## Context Injection Configuration
|
||||
|
||||
@@ -276,7 +262,7 @@ Token economics help you understand the value of cached observations vs. re-read
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| **Model** | claude-haiku-4-5 | 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 |
|
||||
@@ -284,56 +270,91 @@ Token economics help you understand the value of cached observations vs. re-read
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
Settings are stored in `~/.claude-mem/settings.json`. You can also configure via environment variables in `~/.claude/settings.json`:
|
||||
Settings are stored in `~/.claude-mem/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "100",
|
||||
"CLAUDE_MEM_CONTEXT_SESSION_COUNT": "20",
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES": "bugfix,decision,discovery",
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS": "how-it-works,gotcha",
|
||||
"CLAUDE_MEM_CONTEXT_FULL_COUNT": "10",
|
||||
"CLAUDE_MEM_CONTEXT_FULL_FIELD": "narrative",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS": "true",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS": "true",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT": "true",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY": "false",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE": "false"
|
||||
}
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "100",
|
||||
"CLAUDE_MEM_CONTEXT_SESSION_COUNT": "20",
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES": "bugfix,decision,discovery",
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS": "how-it-works,gotcha",
|
||||
"CLAUDE_MEM_CONTEXT_FULL_COUNT": "10",
|
||||
"CLAUDE_MEM_CONTEXT_FULL_FIELD": "narrative",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS": "true",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS": "true",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT": "true",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY": "false",
|
||||
"CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE": "false"
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The Context Settings Modal is the recommended way to configure these settings, as it provides live preview of changes.
|
||||
**Note**: The Context Settings Modal (at http://localhost:37777) is the recommended way to configure these settings, as it provides live preview of changes.
|
||||
|
||||
## Customization
|
||||
|
||||
Settings can be customized in `~/.claude-mem/settings.json`.
|
||||
|
||||
### Custom Data Directory
|
||||
|
||||
For development or testing, override the data directory:
|
||||
|
||||
```bash
|
||||
export CLAUDE_MEM_DATA_DIR=/custom/path
|
||||
Edit `~/.claude-mem/settings.json`:
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_DATA_DIR": "/custom/path"
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Worker Port
|
||||
|
||||
If port 37777 is in use:
|
||||
Edit `~/.claude-mem/settings.json`:
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_WORKER_PORT": "38000"
|
||||
}
|
||||
```
|
||||
|
||||
Then restart the worker:
|
||||
```bash
|
||||
export CLAUDE_MEM_WORKER_PORT=38000
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Custom Model
|
||||
|
||||
Use a different AI model:
|
||||
Edit `~/.claude-mem/settings.json`:
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_MODEL": "opus"
|
||||
}
|
||||
```
|
||||
|
||||
Then restart the worker:
|
||||
```bash
|
||||
export CLAUDE_MEM_MODEL=claude-opus-4
|
||||
export CLAUDE_MEM_MODEL=opus
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
### Custom Skip Tools
|
||||
|
||||
Control which tools are excluded from observations. Edit `~/.claude-mem/settings.json`:
|
||||
```json
|
||||
{
|
||||
"CLAUDE_MEM_SKIP_TOOLS": "ListMcpResourcesTool,SlashCommand,Skill"
|
||||
}
|
||||
```
|
||||
|
||||
**Default excluded tools:**
|
||||
- `ListMcpResourcesTool`
|
||||
- `SlashCommand`
|
||||
- `Skill`
|
||||
- `TodoWrite`
|
||||
- `AskUserQuestion`
|
||||
|
||||
**Common customizations:**
|
||||
- Include TodoWrite: Remove from skip list to track task planning
|
||||
- Include AskUserQuestion: Remove to capture decision-making conversations
|
||||
- Skip additional tools: Add tool names to reduce observation noise
|
||||
|
||||
Changes take effect on the next tool execution (no worker restart needed).
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Hook Timeouts
|
||||
@@ -357,13 +378,7 @@ Recommended values:
|
||||
|
||||
### Worker Memory Limit
|
||||
|
||||
Modify PM2 memory limit in `ecosystem.config.cjs`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
max_memory_restart: '2G' // Increase if needed
|
||||
}
|
||||
```
|
||||
The worker service is managed by Bun and will automatically restart if it encounters issues. Memory usage is typically low (~100-200MB).
|
||||
|
||||
### Logging Verbosity
|
||||
|
||||
@@ -405,13 +420,12 @@ npm run worker:logs
|
||||
|
||||
### Invalid Model Name
|
||||
|
||||
If you specify an invalid model name, the worker will fall back to `claude-haiku-4-5` and log a warning.
|
||||
If you specify an invalid model name, the worker will fall back to `sonnet` and log a warning.
|
||||
|
||||
Valid models:
|
||||
- claude-haiku-4-5
|
||||
- claude-sonnet-4-5
|
||||
- claude-opus-4
|
||||
- claude-3-7-sonnet
|
||||
Valid shorthand models (forward to latest version):
|
||||
- haiku
|
||||
- sonnet
|
||||
- opus
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# Context Engineering for AI Agents: Best Practices Cheat Sheet
|
||||
---
|
||||
title: "Context Engineering"
|
||||
description: "Best practices for curating optimal token sets for AI agents"
|
||||
---
|
||||
|
||||
# Context Engineering for AI Agents
|
||||
|
||||
## Core Principle
|
||||
**Find the smallest possible set of high-signal tokens that maximize the likelihood of your desired outcome.**
|
||||
|
||||
@@ -33,7 +33,7 @@ The build process uses esbuild to compile TypeScript:
|
||||
|
||||
1. Compiles TypeScript to JavaScript
|
||||
2. Creates standalone executables for each hook in `plugin/scripts/`
|
||||
3. Bundles MCP search server to `plugin/scripts/search-server.cjs`
|
||||
3. Bundles MCP search server to `plugin/scripts/mcp-server.cjs`
|
||||
4. Bundles worker service to `plugin/scripts/worker-service.cjs`
|
||||
5. Bundles web viewer UI to `plugin/ui/viewer.html`
|
||||
|
||||
@@ -41,7 +41,7 @@ The build process uses esbuild to compile TypeScript:
|
||||
- Hook executables: `*-hook.js` (ESM format)
|
||||
- Smart installer: `smart-install.js` (ESM format)
|
||||
- Worker service: `worker-service.cjs` (CJS format)
|
||||
- Search server: `search-server.cjs` (CJS format)
|
||||
- MCP server: `mcp-server.cjs` (CJS format)
|
||||
- Viewer UI: `viewer.html` (self-contained HTML bundle)
|
||||
|
||||
### Build Scripts
|
||||
@@ -342,7 +342,7 @@ npm test
|
||||
|
||||
### Adding MCP Search Tools
|
||||
|
||||
1. Add tool definition in `src/servers/search-server.ts`:
|
||||
1. Add tool definition in `src/servers/mcp-server.ts`:
|
||||
|
||||
```typescript
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
@@ -650,7 +650,7 @@ rm -rf plugin/scripts/*.js plugin/scripts/*.cjs
|
||||
|
||||
1. Kill existing process:
|
||||
```bash
|
||||
pm2 delete claude-mem-worker
|
||||
npm run worker:stop
|
||||
```
|
||||
|
||||
2. Check port:
|
||||
|
||||
@@ -37,7 +37,9 @@
|
||||
"installation",
|
||||
"usage/getting-started",
|
||||
"usage/search-tools",
|
||||
"usage/claude-desktop",
|
||||
"usage/private-tags",
|
||||
"usage/export-import",
|
||||
"beta-features"
|
||||
]
|
||||
},
|
||||
@@ -55,7 +57,8 @@
|
||||
"pages": [
|
||||
"configuration",
|
||||
"development",
|
||||
"troubleshooting"
|
||||
"troubleshooting",
|
||||
"platform-integration"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -68,7 +71,8 @@
|
||||
"architecture/hooks",
|
||||
"architecture/worker-service",
|
||||
"architecture/database",
|
||||
"architecture/search-architecture"
|
||||
"architecture/search-architecture",
|
||||
"architecture/pm2-to-bun-migration"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -85,9 +85,8 @@ Claude-Mem uses 6 lifecycle hook scripts across 5 lifecycle events, plus 1 pre-h
|
||||
2. Only runs `npm install` when necessary:
|
||||
- First-time installation
|
||||
- Version changed in package.json
|
||||
- Critical dependency missing (better-sqlite3)
|
||||
3. Provides Windows-specific error messages
|
||||
4. Starts PM2 worker service
|
||||
4. Starts Bun worker service
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
@@ -215,7 +214,7 @@ Claude-Mem uses 6 lifecycle hook scripts across 5 lifecycle events, plus 1 pre-h
|
||||
1. Reads user prompt and session ID from stdin
|
||||
2. Creates new session record in SQLite
|
||||
3. Saves raw user prompt for full-text search (v4.2.0+)
|
||||
4. Starts PM2 worker service if not running
|
||||
4. Starts Bun worker service if not running
|
||||
5. Returns immediately (non-blocking)
|
||||
|
||||
**Configuration:**
|
||||
@@ -512,49 +511,33 @@ sequenceDiagram
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### PM2 Process Management
|
||||
### Bun Process Management
|
||||
|
||||
**Technology:** PM2 (process manager for Node.js)
|
||||
**Technology:** Bun (JavaScript runtime and process manager)
|
||||
|
||||
**Why PM2:**
|
||||
**Why Bun:**
|
||||
- Auto-restart on failure
|
||||
- Log management
|
||||
- Process monitoring
|
||||
- Fast startup and low memory footprint
|
||||
- Built-in TypeScript support
|
||||
- Cross-platform (works on macOS, Linux, Windows)
|
||||
- No systemd/launchd needed
|
||||
|
||||
**Configuration:**
|
||||
```javascript
|
||||
// ecosystem.config.cjs
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'claude-mem-worker',
|
||||
script: './plugin/scripts/worker-service.cjs',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '500M',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
CLAUDE_MEM_WORKER_PORT: 37777
|
||||
}
|
||||
}]
|
||||
};
|
||||
```
|
||||
- No separate process manager needed
|
||||
|
||||
**Worker lifecycle:**
|
||||
```bash
|
||||
# Started by new-hook (if not running)
|
||||
pm2 start ecosystem.config.cjs
|
||||
# Started by hooks automatically (if not running)
|
||||
npm run worker:start
|
||||
|
||||
# Status check
|
||||
pm2 status claude-mem-worker
|
||||
npm run worker:status
|
||||
|
||||
# View logs
|
||||
pm2 logs claude-mem-worker
|
||||
npm run worker:logs
|
||||
|
||||
# Restart
|
||||
pm2 restart claude-mem-worker
|
||||
npm run worker:restart
|
||||
|
||||
# Stop
|
||||
npm run worker:stop
|
||||
```
|
||||
|
||||
### Worker HTTP API
|
||||
@@ -632,7 +615,7 @@ try {
|
||||
|
||||
**Failure modes:**
|
||||
- Database locked → Skip observation, log error
|
||||
- Worker crashed → Auto-restart via PM2
|
||||
- Worker crashed → Auto-restart via Bun
|
||||
- Network issue → Retry with exponential backoff
|
||||
- Disk full → Warn user, disable memory
|
||||
|
||||
@@ -708,8 +691,8 @@ claude --debug
|
||||
**Debugging:**
|
||||
1. Check database: `sqlite3 ~/.claude-mem/claude-mem.db "SELECT * FROM observation_queue"`
|
||||
2. Verify session exists: `SELECT * FROM sdk_sessions`
|
||||
3. Check worker status: `pm2 status`
|
||||
4. View worker logs: `pm2 logs claude-mem-worker`
|
||||
3. Check worker status: `npm run worker:status`
|
||||
4. View worker logs: `npm run worker:logs`
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -761,7 +744,7 @@ claude --debug
|
||||
**Why smart-install is sometimes slow:**
|
||||
- First-time: Full npm install (2-5 seconds)
|
||||
- Cached: Version check only (~10ms)
|
||||
- Version change: Full npm install + PM2 restart
|
||||
- Version change: Full npm install + worker restart
|
||||
|
||||
**Optimization (v5.0.3):**
|
||||
- Version caching with `.install-version` marker
|
||||
|
||||
@@ -16,7 +16,7 @@ Install Claude-Mem directly from the plugin marketplace:
|
||||
|
||||
That's it! The plugin will automatically:
|
||||
- Download prebuilt binaries (no compilation needed)
|
||||
- Install all dependencies (including PM2 and SQLite binaries)
|
||||
- Install all dependencies (including SQLite binaries)
|
||||
- Configure hooks for session lifecycle management
|
||||
- Auto-start the worker service on first session
|
||||
|
||||
@@ -26,7 +26,7 @@ Start a new Claude Code session and you'll see context from previous sessions au
|
||||
|
||||
- **Node.js**: 18.0.0 or higher
|
||||
- **Claude Code**: Latest version with plugin support
|
||||
- **PM2**: Process manager (bundled with plugin - no global install required)
|
||||
- **Bun**: JavaScript runtime and process manager (auto-installed if missing)
|
||||
- **SQLite 3**: For persistent storage (bundled)
|
||||
|
||||
## Advanced Installation
|
||||
@@ -69,12 +69,14 @@ cat plugin/hooks/hooks.json
|
||||
|
||||
#### 3. Data Directory Location
|
||||
|
||||
v4.0.0+ stores data in `${CLAUDE_PLUGIN_ROOT}/data/`:
|
||||
- Database: `${CLAUDE_PLUGIN_ROOT}/data/claude-mem.db`
|
||||
- Worker port file: `${CLAUDE_PLUGIN_ROOT}/data/worker.port`
|
||||
- Logs: `${CLAUDE_PLUGIN_ROOT}/data/logs/`
|
||||
Data is stored in `~/.claude-mem/`:
|
||||
- Database: `~/.claude-mem/claude-mem.db`
|
||||
- PID file: `~/.claude-mem/.worker.pid`
|
||||
- Port file: `~/.claude-mem/.worker.port`
|
||||
- Logs: `~/.claude-mem/logs/worker-YYYY-MM-DD.log`
|
||||
- Settings: `~/.claude-mem/settings.json`
|
||||
|
||||
For development/testing, you can override:
|
||||
Override with environment variable:
|
||||
```bash
|
||||
export CLAUDE_MEM_DATA_DIR=/custom/path
|
||||
```
|
||||
@@ -91,19 +93,17 @@ npm run worker:logs
|
||||
npm run test:context
|
||||
```
|
||||
|
||||
## Upgrading from v3.x
|
||||
## Upgrading
|
||||
|
||||
**BREAKING CHANGES - Please Read:**
|
||||
Upgrades are automatic when updating via the plugin marketplace. Key changes in recent versions:
|
||||
|
||||
v4.0.0 introduces breaking changes:
|
||||
**v7.1.0**: PM2 replaced with native Bun process management. Migration is automatic on first hook trigger.
|
||||
|
||||
- **Data Location Changed**: Database moved from `~/.claude-mem/` to `${CLAUDE_PLUGIN_ROOT}/data/` (inside plugin directory)
|
||||
- **Fresh Start Required**: No automatic migration from v3.x. You must start with a clean database
|
||||
- **Worker Auto-Starts**: Worker service now starts automatically - no manual `npm run worker:start` needed
|
||||
- **MCP Search Server**: 7 new search tools with full-text search and citations
|
||||
- **Enhanced Architecture**: Improved plugin integration and data organization
|
||||
**v7.0.0+**: 11 configuration settings, dual-tag privacy system.
|
||||
|
||||
See [CHANGELOG](https://github.com/thedotmack/claude-mem/blob/main/CHANGELOG.md) for complete details.
|
||||
**v5.4.0+**: Skill-based search replaces MCP tools, saving ~2,250 tokens per session.
|
||||
|
||||
See [CHANGELOG](https://github.com/thedotmack/claude-mem/blob/main/CHANGELOG.md) for complete version history.
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,7 +58,7 @@ Restart Claude Code. Context from previous sessions will automatically appear in
|
||||
**Core Components:**
|
||||
1. **5 Lifecycle Hooks** - SessionStart, UserPromptSubmit, PostToolUse, Stop, SessionEnd (6 hook scripts)
|
||||
2. **Smart Install** - Cached dependency checker (pre-hook script)
|
||||
3. **Worker Service** - HTTP API on port 37777 managed by PM2
|
||||
3. **Worker Service** - HTTP API on port 37777 managed by Bun
|
||||
4. **SQLite Database** - Stores sessions, observations, summaries with FTS5 search
|
||||
5. **mem-search Skill** - Query historical context with natural language
|
||||
6. **Web Viewer UI** - Real-time visualization with SSE and infinite scroll
|
||||
@@ -69,26 +69,25 @@ See [Architecture Overview](architecture/overview) for details.
|
||||
|
||||
- **Node.js**: 18.0.0 or higher
|
||||
- **Claude Code**: Latest version with plugin support
|
||||
- **PM2**: Process manager (bundled - no global install required)
|
||||
- **Bun**: JavaScript runtime and process manager (auto-installed if missing)
|
||||
- **SQLite 3**: For persistent storage (bundled)
|
||||
|
||||
## What's New
|
||||
|
||||
**v6.4.9 - Context Configuration Settings:**
|
||||
- 11 new settings for fine-grained control over context injection
|
||||
- Configure token economics display, observation filtering by type/concept
|
||||
**v7.1.0 - Bun Migration:**
|
||||
- Replaced PM2 with native Bun process management
|
||||
- Switched from better-sqlite3 to bun:sqlite for faster database access
|
||||
- Automatic one-time migration on first hook trigger
|
||||
- Simplified cross-platform support
|
||||
|
||||
**v6.4.0 - Dual-Tag Privacy System:**
|
||||
- `<private>` tags for user-controlled privacy - wrap sensitive content to exclude from storage
|
||||
- Edge processing ensures private content never reaches database
|
||||
|
||||
**v6.3.0 - Version Channel:**
|
||||
- Switch between stable and beta versions from the web viewer UI
|
||||
**v7.0.0 - Context Configuration:**
|
||||
- 11 settings for fine-grained control over context injection
|
||||
- Dual-tag privacy system (`<private>` tags)
|
||||
|
||||
**Previous Highlights:**
|
||||
- **v6.0.0**: Major session management & transcript processing improvements
|
||||
- **v5.5.0**: mem-search skill enhancement with 100% effectiveness rate
|
||||
- **v5.4.0**: Skill-based search architecture (~2,250 tokens saved per session)
|
||||
- **v5.5.0**: mem-search skill with 100% effectiveness rate
|
||||
- **v5.4.0**: Skill-based search (~2,250 tokens saved per session)
|
||||
- **v5.1.0**: Web viewer UI at http://localhost:37777
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 250 53" width="250" height="55" data-date-format="longDate">
|
||||
<rect xmlns="http://www.w3.org/2000/svg" stroke="#b5a0d9" stroke-width="1" fill="#1a1a1a" x="0.5" y="0.5" width="249" height="53" rx="10"/>
|
||||
<foreignObject width="198" height="17" style="font-size: 9px;color: rgb(200, 180, 230);font-family: Arial;font-weight: 400;text-align: center;letter-spacing: 0em;line-height: 1.5;" x="6" y="10" selection="true">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">GITHUB TRENDING</div>
|
||||
</foreignObject>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Слой_1" viewBox="0 0 80 80" width="48" height="45" x="10" y="8">
|
||||
<path fill="#b5a0d9" d="M70.71,40.31C75.74,44.3,80,37.86,80,37.86s-5.64-2.17-8.55,0.61c0.59-1.62,1.02-3.31,1.28-5.01 c4.08,2.16,6.44-2.95,6.44-2.95s-4.41-0.97-6.26,1.4c0.08-0.91,0.12-1.82,0.1-2.73c-0.01-0.36-0.02-0.73-0.05-1.09 c2.96-3.68-1.73-6.99-1.73-6.99s-2.14,5.09,0.98,7.09c0.02,0.33,0.03,0.66,0.03,1c0.01,0.76-0.03,1.52-0.1,2.27 c-0.85-2.69-4.91-3.69-4.91-3.69s-0.13,5.78,4.68,5.48c-0.28,1.69-0.73,3.35-1.34,4.95c-0.19-4.03-5.79-6.33-5.79-6.33 s-1.33,7.55,5.01,8.16c-0.38,0.8-0.8,1.57-1.25,2.32c-0.56,0.95-1.21,1.84-1.89,2.71c0.97-3.99-3.96-7.72-3.96-7.72 s-3.18,6.94,2.73,9.15c-0.38,0.43-0.8,0.81-1.2,1.21c-0.21,0.2-0.43,0.38-0.64,0.58l-0.32,0.29c-0.11,0.09-0.22,0.18-0.33,0.27 l-0.67,0.54l-0.7,0.51c-0.08,0.05-0.16,0.11-0.23,0.16c1.62-3.42-2.07-7.77-2.07-7.77s-4.21,5.55,0.49,8.78 c-1.34,0.79-2.74,1.45-4.2,1.98c1.91-2.59-0.23-6.89-0.23-6.89s-4.66,3.77-1.52,7.46c-1.15,0.33-2.33,0.57-3.51,0.74 c1.46-1.68,0.55-4.83,0.55-4.83s-3.7,2.03-2.18,5c-0.52,0.03-1.05,0.07-1.57,0.06c-0.29,0-0.57,0.01-0.86,0l-0.86-0.04 c-0.85-0.06-1.7-0.15-2.54-0.28l0.68-0.27l0.42-0.17l0.41-0.19l0.82-0.38c0,0,0.01,0,0.01,0c0.39-0.18,0.55-0.65,0.37-1.03 c-0.18-0.39-0.65-0.55-1.03-0.37l-0.04,0.02l-0.77,0.37l-0.39,0.18l-0.39,0.16l-0.79,0.33l-0.8,0.29l-0.4,0.14l-0.41,0.12L40,53.6 l-0.51-0.15l-0.41-0.12l-0.4-0.14l-0.8-0.29l-0.79-0.33l-0.39-0.16l-0.39-0.18l-0.77-0.37l-0.04-0.02c0,0,0,0-0.01,0 c-0.39-0.18-0.85-0.01-1.03,0.38c-0.18,0.39-0.01,0.85,0.38,1.03l0.82,0.38l0.41,0.19l0.42,0.17l0.68,0.27 c-0.84,0.14-1.69,0.22-2.54,0.28l-0.86,0.04c-0.29,0.01-0.57,0-0.86,0c-0.53,0.01-1.05-0.03-1.57-0.06c1.51-2.98-2.18-5-2.18-5 s-0.92,3.15,0.55,4.83c-1.19-0.16-2.36-0.41-3.51-0.74c3.15-3.7-1.52-7.46-1.52-7.46s-2.14,4.31-0.23,6.89 c-1.46-0.53-2.86-1.19-4.2-1.98c4.7-3.22,0.49-8.78,0.49-8.78s-3.69,4.34-2.07,7.77c-0.08-0.05-0.16-0.1-0.23-0.16l-0.7-0.51 l-0.67-0.54c-0.11-0.09-0.23-0.18-0.33-0.27l-0.32-0.29c-0.21-0.19-0.43-0.38-0.64-0.58c-0.4-0.4-0.82-0.79-1.2-1.21 c5.91-2.21,2.73-9.15,2.73-9.15s-4.93,3.73-3.96,7.72c-0.68-0.86-1.33-1.76-1.89-2.71c-0.46-0.75-0.87-1.53-1.25-2.32 c6.33-0.61,5.01-8.16,5.01-8.16s-5.6,2.31-5.79,6.33c-0.61-1.6-1.06-3.26-1.34-4.95c4.81,0.3,4.68-5.48,4.68-5.48 s-4.05,0.99-4.91,3.69c-0.07-0.76-0.1-1.51-0.1-2.27c0-0.33,0.01-0.66,0.03-1c3.11-2.01,0.98-7.09,0.98-7.09s-4.69,3.31-1.73,6.99 C7,28.46,6.99,28.82,6.98,29.18c-0.02,0.91,0.01,1.82,0.1,2.73c-1.84-2.38-6.26-1.4-6.26-1.4s2.37,5.11,6.44,2.95 c0.26,1.71,0.69,3.39,1.28,5.01C5.64,35.69,0,37.86,0,37.86s4.26,6.43,9.29,2.45c0.39,0.87,0.83,1.72,1.31,2.54 c0.47,0.83,1.01,1.63,1.58,2.4C8.71,43.7,4.11,47,4.11,47s5.7,5.1,9.56,0.08c0.04,0.04,0.07,0.08,0.11,0.12 c0.39,0.45,0.82,0.87,1.24,1.3c0.21,0.21,0.44,0.41,0.66,0.61l0.33,0.3c0.11,0.1,0.23,0.19,0.34,0.29l0.69,0.57l0.23,0.17 c-3.34-0.34-6.58,3.29-6.58,3.29s6.19,3.47,8.69-1.83c1.2,0.75,2.47,1.41,3.78,1.96c-2.76,0.6-4.62,4.13-4.62,4.13 s5.89,1.62,6.98-3.26c1.03,0.32,2.07,0.58,3.13,0.78c-1.63,0.99-2.39,3.38-2.39,3.38s4.31,0.39,4.61-3.07 c0.07,0.01,0.14,0.02,0.21,0.02c0.6,0.04,1.2,0.1,1.8,0.09c0.3,0,0.6,0.02,0.9,0.01l0.9-0.03c1.2-0.07,2.41-0.18,3.59-0.42 l0.45-0.08c0.15-0.03,0.29-0.07,0.44-0.1L40,55.13l0.81,0.19c0.15,0.03,0.29,0.07,0.44,0.1l0.45,0.08c1.18,0.23,2.39,0.35,3.59,0.42 l0.9,0.03c0.3,0.01,0.6-0.01,0.9-0.01c0.6,0,1.2-0.06,1.8-0.09c0.07-0.01,0.14-0.02,0.21-0.02c0.31,3.45,4.61,3.07,4.61,3.07 s-0.76-2.39-2.39-3.38c1.06-0.2,2.11-0.46,3.13-0.78c1.09,4.88,6.98,3.26,6.98,3.26s-1.86-3.52-4.62-4.13 c1.31-0.55,2.57-1.21,3.78-1.96c2.5,5.3,8.69,1.83,8.69,1.83s-3.24-3.63-6.58-3.29l0.23-0.17l0.69-0.57 c0.11-0.1,0.23-0.19,0.34-0.29l0.33-0.3c0.22-0.2,0.45-0.4,0.66-0.61c0.42-0.43,0.85-0.84,1.24-1.3c0.03-0.04,0.07-0.08,0.11-0.12 C70.19,52.1,75.89,47,75.89,47s-4.6-3.31-8.08-1.75c0.57-0.77,1.11-1.56,1.58-2.4C69.88,42.03,70.31,41.18,70.71,40.31z"/>
|
||||
</svg>
|
||||
<foreignObject width="230" height="35" style="font-size: 14px;color: rgb(200, 180, 230);font-family: Arial;font-weight: 700;text-align: left;letter-spacing: 0em;line-height: 1.5;" x="64" y="24">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">#1 Repository Of The Day</div>
|
||||
</foreignObject>
|
||||
<foreignObject width="141" height="36" style="font-size: 18px;color: rgb(180, 160, 217);font-family: Arial;font-weight: 400;text-align: center;letter-spacing: 0em;line-height: 1.5;" x="-36" y="9">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">1</div>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 250 53" width="250" height="55" data-date-format="longDate">
|
||||
<rect xmlns="http://www.w3.org/2000/svg" stroke="#4a0e99" stroke-width="1" fill="#FFFFFF" x="0.5" y="0.5" width="249" height="53" rx="10"/>
|
||||
<foreignObject width="198" height="17" style="font-size: 9px;color: rgb(67, 39, 135);font-family: Arial;font-weight: 400;text-align: center;letter-spacing: 0em;line-height: 1.5;" x="6" y="10" selection="true">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">GITHUB TRENDING</div>
|
||||
</foreignObject>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Слой_1" viewBox="0 0 80 80" width="48" height="45" x="10" y="8">
|
||||
<path fill="#49278e" d="M70.71,40.31C75.74,44.3,80,37.86,80,37.86s-5.64-2.17-8.55,0.61c0.59-1.62,1.02-3.31,1.28-5.01 c4.08,2.16,6.44-2.95,6.44-2.95s-4.41-0.97-6.26,1.4c0.08-0.91,0.12-1.82,0.1-2.73c-0.01-0.36-0.02-0.73-0.05-1.09 c2.96-3.68-1.73-6.99-1.73-6.99s-2.14,5.09,0.98,7.09c0.02,0.33,0.03,0.66,0.03,1c0.01,0.76-0.03,1.52-0.1,2.27 c-0.85-2.69-4.91-3.69-4.91-3.69s-0.13,5.78,4.68,5.48c-0.28,1.69-0.73,3.35-1.34,4.95c-0.19-4.03-5.79-6.33-5.79-6.33 s-1.33,7.55,5.01,8.16c-0.38,0.8-0.8,1.57-1.25,2.32c-0.56,0.95-1.21,1.84-1.89,2.71c0.97-3.99-3.96-7.72-3.96-7.72 s-3.18,6.94,2.73,9.15c-0.38,0.43-0.8,0.81-1.2,1.21c-0.21,0.2-0.43,0.38-0.64,0.58l-0.32,0.29c-0.11,0.09-0.22,0.18-0.33,0.27 l-0.67,0.54l-0.7,0.51c-0.08,0.05-0.16,0.11-0.23,0.16c1.62-3.42-2.07-7.77-2.07-7.77s-4.21,5.55,0.49,8.78 c-1.34,0.79-2.74,1.45-4.2,1.98c1.91-2.59-0.23-6.89-0.23-6.89s-4.66,3.77-1.52,7.46c-1.15,0.33-2.33,0.57-3.51,0.74 c1.46-1.68,0.55-4.83,0.55-4.83s-3.7,2.03-2.18,5c-0.52,0.03-1.05,0.07-1.57,0.06c-0.29,0-0.57,0.01-0.86,0l-0.86-0.04 c-0.85-0.06-1.7-0.15-2.54-0.28l0.68-0.27l0.42-0.17l0.41-0.19l0.82-0.38c0,0,0.01,0,0.01,0c0.39-0.18,0.55-0.65,0.37-1.03 c-0.18-0.39-0.65-0.55-1.03-0.37l-0.04,0.02l-0.77,0.37l-0.39,0.18l-0.39,0.16l-0.79,0.33l-0.8,0.29l-0.4,0.14l-0.41,0.12L40,53.6 l-0.51-0.15l-0.41-0.12l-0.4-0.14l-0.8-0.29l-0.79-0.33l-0.39-0.16l-0.39-0.18l-0.77-0.37l-0.04-0.02c0,0,0,0-0.01,0 c-0.39-0.18-0.85-0.01-1.03,0.38c-0.18,0.39-0.01,0.85,0.38,1.03l0.82,0.38l0.41,0.19l0.42,0.17l0.68,0.27 c-0.84,0.14-1.69,0.22-2.54,0.28l-0.86,0.04c-0.29,0.01-0.57,0-0.86,0c-0.53,0.01-1.05-0.03-1.57-0.06c1.51-2.98-2.18-5-2.18-5 s-0.92,3.15,0.55,4.83c-1.19-0.16-2.36-0.41-3.51-0.74c3.15-3.7-1.52-7.46-1.52-7.46s-2.14,4.31-0.23,6.89 c-1.46-0.53-2.86-1.19-4.2-1.98c4.7-3.22,0.49-8.78,0.49-8.78s-3.69,4.34-2.07,7.77c-0.08-0.05-0.16-0.1-0.23-0.16l-0.7-0.51 l-0.67-0.54c-0.11-0.09-0.23-0.18-0.33-0.27l-0.32-0.29c-0.21-0.19-0.43-0.38-0.64-0.58c-0.4-0.4-0.82-0.79-1.2-1.21 c5.91-2.21,2.73-9.15,2.73-9.15s-4.93,3.73-3.96,7.72c-0.68-0.86-1.33-1.76-1.89-2.71c-0.46-0.75-0.87-1.53-1.25-2.32 c6.33-0.61,5.01-8.16,5.01-8.16s-5.6,2.31-5.79,6.33c-0.61-1.6-1.06-3.26-1.34-4.95c4.81,0.3,4.68-5.48,4.68-5.48 s-4.05,0.99-4.91,3.69c-0.07-0.76-0.1-1.51-0.1-2.27c0-0.33,0.01-0.66,0.03-1c3.11-2.01,0.98-7.09,0.98-7.09s-4.69,3.31-1.73,6.99 C7,28.46,6.99,28.82,6.98,29.18c-0.02,0.91,0.01,1.82,0.1,2.73c-1.84-2.38-6.26-1.4-6.26-1.4s2.37,5.11,6.44,2.95 c0.26,1.71,0.69,3.39,1.28,5.01C5.64,35.69,0,37.86,0,37.86s4.26,6.43,9.29,2.45c0.39,0.87,0.83,1.72,1.31,2.54 c0.47,0.83,1.01,1.63,1.58,2.4C8.71,43.7,4.11,47,4.11,47s5.7,5.1,9.56,0.08c0.04,0.04,0.07,0.08,0.11,0.12 c0.39,0.45,0.82,0.87,1.24,1.3c0.21,0.21,0.44,0.41,0.66,0.61l0.33,0.3c0.11,0.1,0.23,0.19,0.34,0.29l0.69,0.57l0.23,0.17 c-3.34-0.34-6.58,3.29-6.58,3.29s6.19,3.47,8.69-1.83c1.2,0.75,2.47,1.41,3.78,1.96c-2.76,0.6-4.62,4.13-4.62,4.13 s5.89,1.62,6.98-3.26c1.03,0.32,2.07,0.58,3.13,0.78c-1.63,0.99-2.39,3.38-2.39,3.38s4.31,0.39,4.61-3.07 c0.07,0.01,0.14,0.02,0.21,0.02c0.6,0.04,1.2,0.1,1.8,0.09c0.3,0,0.6,0.02,0.9,0.01l0.9-0.03c1.2-0.07,2.41-0.18,3.59-0.42 l0.45-0.08c0.15-0.03,0.29-0.07,0.44-0.1L40,55.13l0.81,0.19c0.15,0.03,0.29,0.07,0.44,0.1l0.45,0.08c1.18,0.23,2.39,0.35,3.59,0.42 l0.9,0.03c0.3,0.01,0.6-0.01,0.9-0.01c0.6,0,1.2-0.06,1.8-0.09c0.07-0.01,0.14-0.02,0.21-0.02c0.31,3.45,4.61,3.07,4.61,3.07 s-0.76-2.39-2.39-3.38c1.06-0.2,2.11-0.46,3.13-0.78c1.09,4.88,6.98,3.26,6.98,3.26s-1.86-3.52-4.62-4.13 c1.31-0.55,2.57-1.21,3.78-1.96c2.5,5.3,8.69,1.83,8.69,1.83s-3.24-3.63-6.58-3.29l0.23-0.17l0.69-0.57 c0.11-0.1,0.23-0.19,0.34-0.29l0.33-0.3c0.22-0.2,0.45-0.4,0.66-0.61c0.42-0.43,0.85-0.84,1.24-1.3c0.03-0.04,0.07-0.08,0.11-0.12 C70.19,52.1,75.89,47,75.89,47s-4.6-3.31-8.08-1.75c0.57-0.77,1.11-1.56,1.58-2.4C69.88,42.03,70.31,41.18,70.71,40.31z"/>
|
||||
</svg>
|
||||
<foreignObject width="230" height="35" style="font-size: 14px;color: rgb(67, 39, 135);font-family: Arial;font-weight: 700;text-align: left;letter-spacing: 0em;line-height: 1.5;" x="64" y="24">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">#1 Repository Of The Day</div>
|
||||
</foreignObject>
|
||||
<foreignObject width="141" height="36" style="font-size: 18px;color: rgb(74, 14, 153);font-family: Arial;font-weight: 400;text-align: center;letter-spacing: 0em;line-height: 1.5;" x="-36" y="9">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">1</div>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -10,7 +10,7 @@ description: "Common issues and solutions for Claude-Mem"
|
||||
Describe any issues you're experiencing to Claude, and the troubleshoot skill will automatically activate to provide diagnosis and fixes.
|
||||
|
||||
The troubleshoot skill will:
|
||||
- ✅ Check PM2 worker status and health
|
||||
- ✅ Check worker status and health
|
||||
- ✅ Verify database existence and integrity
|
||||
- ✅ Test worker service connectivity
|
||||
- ✅ Validate dependencies installation
|
||||
@@ -170,39 +170,18 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
4. Restart Claude Code after manual install
|
||||
|
||||
### PM2 ENOENT Error on Windows (v5.1.1 Fix)
|
||||
|
||||
**Symptoms**: Worker fails to start with "ENOENT" error on Windows.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. This was fixed in v5.1.1 - update to latest version:
|
||||
```bash
|
||||
/plugin update claude-mem
|
||||
```
|
||||
|
||||
2. If still experiencing issues, verify PM2 path:
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack
|
||||
dir node_modules\.bin\pm2.cmd
|
||||
```
|
||||
|
||||
3. Manual PM2 install if needed:
|
||||
```bash
|
||||
npm install pm2@latest
|
||||
```
|
||||
|
||||
## Worker Service Issues
|
||||
|
||||
### Worker Service Not Starting
|
||||
|
||||
**Symptoms**: Worker doesn't start, or `pm2 status` shows no processes.
|
||||
**Symptoms**: Worker doesn't start, or worker status shows it's not running.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check if PM2 is running:
|
||||
1. Check worker status:
|
||||
```bash
|
||||
pm2 status
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
2. Try starting manually:
|
||||
@@ -217,14 +196,14 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
4. Full reset:
|
||||
```bash
|
||||
pm2 delete claude-mem-worker
|
||||
npm run worker:stop
|
||||
npm run worker:start
|
||||
```
|
||||
|
||||
5. Verify PM2 is installed:
|
||||
5. Verify Bun is installed:
|
||||
```bash
|
||||
which pm2
|
||||
npm list pm2
|
||||
which bun
|
||||
bun --version
|
||||
```
|
||||
|
||||
### Port Allocation Failed
|
||||
@@ -256,7 +235,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
### Worker Keeps Crashing
|
||||
|
||||
**Symptoms**: Worker restarts repeatedly, PM2 shows high restart count.
|
||||
**Symptoms**: Worker restarts repeatedly or fails to stay running.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
@@ -265,23 +244,21 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
npm run worker:logs
|
||||
```
|
||||
|
||||
2. Check memory usage:
|
||||
2. Check worker status:
|
||||
```bash
|
||||
pm2 status
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
3. Increase memory limit in `ecosystem.config.cjs`:
|
||||
```javascript
|
||||
{
|
||||
max_memory_restart: '2G' // Increase if needed
|
||||
}
|
||||
```
|
||||
|
||||
4. Check database for corruption:
|
||||
3. Check database for corruption:
|
||||
```bash
|
||||
sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA integrity_check;"
|
||||
```
|
||||
|
||||
4. Verify Bun installation:
|
||||
```bash
|
||||
bun --version
|
||||
```
|
||||
|
||||
### Worker Not Processing Observations
|
||||
|
||||
**Symptoms**: Observations saved but not processed, no summaries generated.
|
||||
@@ -424,7 +401,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
1. Close all connections:
|
||||
```bash
|
||||
pm2 stop claude-mem-worker
|
||||
npm run worker:stop
|
||||
```
|
||||
|
||||
2. Check for stale locks:
|
||||
@@ -542,7 +519,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
2. Verify search server is built:
|
||||
```bash
|
||||
ls -l plugin/scripts/search-server.js
|
||||
ls -l plugin/scripts/mcp-server.cjs
|
||||
```
|
||||
|
||||
3. Rebuild if needed:
|
||||
@@ -656,29 +633,21 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
|
||||
|
||||
### High Memory Usage
|
||||
|
||||
**Symptoms**: Worker uses too much memory, frequent restarts.
|
||||
**Symptoms**: Worker uses too much memory.
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Check current usage:
|
||||
```bash
|
||||
pm2 status
|
||||
npm run worker:status
|
||||
```
|
||||
|
||||
2. Increase memory limit:
|
||||
```javascript
|
||||
// In ecosystem.config.cjs
|
||||
{
|
||||
max_memory_restart: '2G'
|
||||
}
|
||||
```
|
||||
|
||||
3. Restart worker:
|
||||
2. Restart worker:
|
||||
```bash
|
||||
npm run worker:restart
|
||||
```
|
||||
|
||||
4. Clean up old data (see "Database Too Large" above)
|
||||
3. Clean up old data (see "Database Too Large" above)
|
||||
|
||||
## Installation Issues
|
||||
|
||||
@@ -773,10 +742,10 @@ sqlite3 ~/.claude-mem/claude-mem.db "
|
||||
|
||||
```bash
|
||||
# Check if worker is running
|
||||
pm2 status
|
||||
npm run worker:status
|
||||
|
||||
# View logs
|
||||
pm2 logs claude-mem-worker
|
||||
npm run worker:logs
|
||||
|
||||
# Check port file
|
||||
cat ~/.claude-mem/worker.port
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
---
|
||||
title: Claude Desktop Skill
|
||||
description: Use claude-mem memory search in Claude Desktop with the mem-search skill
|
||||
icon: desktop
|
||||
---
|
||||
|
||||
<Note>
|
||||
**Availability:** The mem-search skill works with Claude Desktop on macOS and Windows.
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
Claude Desktop can access your claude-mem memory database through the **mem-search** skill. This allows you to search past sessions, decisions, and observations directly from Claude Desktop conversations.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before installing the skill, ensure:
|
||||
|
||||
1. **claude-mem is installed** and the worker service is running
|
||||
2. **MCP server is configured** in Claude Desktop (the skill uses the `mem-search` MCP server)
|
||||
|
||||
### Verify Worker is Running
|
||||
|
||||
```bash
|
||||
curl http://localhost:37777/api/health
|
||||
# Should return: {"status":"ok"}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Step 1: Download the Skill
|
||||
|
||||
Download the skill package from the repository:
|
||||
|
||||
<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
|
||||
npm run build # Generates plugin/skills/mem-search.zip
|
||||
```
|
||||
|
||||
### Step 2: Install in Claude Desktop
|
||||
|
||||
1. Open **Claude Desktop**
|
||||
2. Go to **Settings** (gear icon)
|
||||
3. Navigate to **Skills**
|
||||
4. Click **Install Skill** or drag the `mem-search.zip` file
|
||||
5. Confirm installation
|
||||
|
||||
### Step 3: Configure MCP Server
|
||||
|
||||
The skill requires the `mem-search` MCP server. Add this to your Claude Desktop configuration:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"mem-search": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/Users/YOUR_USERNAME/.claude/plugins/marketplaces/thedotmack/plugin/scripts/mcp-server.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
Edit `%APPDATA%\Claude\claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"mem-search": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"C:\\Users\\YOUR_USERNAME\\.claude\\plugins\\marketplaces\\thedotmack\\plugin\\scripts\\mcp-server.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Warning>
|
||||
Replace `YOUR_USERNAME` with your actual username. Restart Claude Desktop after editing the configuration.
|
||||
</Warning>
|
||||
|
||||
### Step 4: Restart Claude Desktop
|
||||
|
||||
Close and reopen Claude Desktop for the MCP server configuration to take effect.
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, the skill auto-activates when you ask about past work:
|
||||
|
||||
```
|
||||
"What did we do last session?"
|
||||
"Did we fix this bug before?"
|
||||
"How did we implement authentication?"
|
||||
"What decisions did we make about the API?"
|
||||
"Show me changes to worker-service.ts"
|
||||
```
|
||||
|
||||
## Available MCP Tools
|
||||
|
||||
The skill provides access to these MCP tools:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `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_batch_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 |
|
||||
| `progressive_description` | Load detailed usage instructions |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Skill Not Appearing
|
||||
|
||||
1. Verify the zip file was properly installed
|
||||
2. Check Claude Desktop's skill installation logs
|
||||
3. Restart Claude Desktop
|
||||
|
||||
### MCP Server Connection Failed
|
||||
|
||||
1. Verify the worker is running: `curl http://localhost:37777/api/health`
|
||||
2. Check the MCP server path in configuration
|
||||
3. Look for errors in Claude Desktop logs
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
```bash
|
||||
# View Claude Desktop logs
|
||||
tail -f ~/Library/Logs/Claude/claude.log
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
Check `%APPDATA%\Claude\logs\`
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Search Returns No Results
|
||||
|
||||
1. Ensure claude-mem has recorded sessions (check http://localhost:37777)
|
||||
2. Verify the database exists: `ls ~/.claude-mem/claude-mem.db`
|
||||
3. Test the API directly: `curl "http://localhost:37777/api/search?query=test"`
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Search Tools" icon="magnifying-glass" href="/usage/search-tools">
|
||||
Complete search API reference
|
||||
</Card>
|
||||
<Card title="Platform Integration" icon="plug" href="/platform-integration">
|
||||
Build custom integrations
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -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
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Private Tags"
|
||||
description: "Control what gets stored in memory with <private> tags"
|
||||
description: "Control what gets stored in memory with privacy tags"
|
||||
---
|
||||
|
||||
# Private Tags
|
||||
@@ -165,8 +165,8 @@ This design ensures that private content never reaches the database, search indi
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Search Tools](search-tools) - How to search past observations
|
||||
- [Getting Started](getting-started) - Basic usage guide
|
||||
- [Search Tools](/usage/search-tools) - How to search past observations
|
||||
- [Getting Started](/usage/getting-started) - Basic usage guide
|
||||
- [Configuration](/configuration) - System settings and environment variables
|
||||
|
||||
## Troubleshooting
|
||||
@@ -175,7 +175,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: `pm2 list`
|
||||
3. Ensure worker is running: `npm run worker:status`
|
||||
4. Restart worker: `npm run worker: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.
|
||||
|
||||
@@ -364,7 +363,7 @@ search_sessions with query="[YOUR PROJECT NAME]" and orderBy="date_desc"
|
||||
If search isn't working, check the worker service:
|
||||
|
||||
```bash
|
||||
pm2 list # Check worker status
|
||||
npm run worker:status # Check worker status
|
||||
npm run worker:restart # Restart if needed
|
||||
npm run worker:logs # View logs
|
||||
```
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* PM2 Ecosystem Configuration for claude-mem Worker Service
|
||||
*
|
||||
* Usage:
|
||||
* pm2 start ecosystem.config.cjs
|
||||
* pm2 stop claude-mem-worker
|
||||
* pm2 restart claude-mem-worker
|
||||
* pm2 logs claude-mem-worker
|
||||
* pm2 status
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'claude-mem-worker',
|
||||
script: './plugin/scripts/worker-service.cjs',
|
||||
// INTENTIONAL: Watch mode enables auto-restart on plugin updates
|
||||
//
|
||||
// Why this is enabled:
|
||||
// - When you run `npm run sync-marketplace` or rebuild the plugin,
|
||||
// files in ~/.claude/plugins/marketplaces/thedotmack/ change
|
||||
// - Watch mode detects these changes and auto-restarts the worker
|
||||
// - Users get the latest code without manually running `pm2 restart`
|
||||
//
|
||||
// This is a feature, not a bug - it ensures users always run the
|
||||
// latest version after plugin updates.
|
||||
watch: true,
|
||||
ignore_watch: [
|
||||
'node_modules',
|
||||
'logs',
|
||||
'*.log',
|
||||
'*.db',
|
||||
'*.db-*',
|
||||
'.git'
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
Generated
-5855
File diff suppressed because it is too large
Load Diff
+21
-13
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.0.0",
|
||||
"version": "7.2.2",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
@@ -27,39 +27,47 @@
|
||||
},
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=18.0.0",
|
||||
"bun": ">=1.0.0"
|
||||
},
|
||||
"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",
|
||||
"test:context:verbose": "echo '{\"session_id\":\"test-'$(date +%s)'\",\"cwd\":\"'$(pwd)'\",\"source\":\"startup\"}' | node plugin/scripts/context-hook.js",
|
||||
"sync-marketplace": "rsync -av --delete --exclude=.git ./ ~/.claude/plugins/marketplaces/thedotmack/ && cd ~/.claude/plugins/marketplaces/thedotmack/ && npm install",
|
||||
"worker:start": "pm2 start ecosystem.config.cjs",
|
||||
"worker:stop": "pm2 stop claude-mem-worker",
|
||||
"worker:restart": "pm2 restart claude-mem-worker",
|
||||
"worker:logs": "pm2 flush claude-mem-worker && pm2 logs claude-mem-worker --lines 100 --nostream",
|
||||
"worker:logs:no-flush": "pm2 logs claude-mem-worker --lines 100 --nostream",
|
||||
"sync-marketplace": "node scripts/sync-marketplace.cjs",
|
||||
"sync-marketplace:force": "node scripts/sync-marketplace.cjs --force",
|
||||
"build:binaries": "node scripts/build-worker-binary.js",
|
||||
"worker:start": "bun plugin/scripts/worker-cli.js start",
|
||||
"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 -n 50 ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log",
|
||||
"changelog:generate": "node scripts/generate-changelog.js",
|
||||
"usage:analyze": "node scripts/analyze-usage.js",
|
||||
"usage:today": "node scripts/analyze-usage.js $(date +%Y-%m-%d)"
|
||||
"usage:today": "node scripts/analyze-usage.js $(date +%Y-%m-%d)",
|
||||
"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.27",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.67",
|
||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"better-sqlite3": "^12.5.0",
|
||||
"express": "^4.18.2",
|
||||
"glob": "^11.0.3",
|
||||
"handlebars": "^4.7.8",
|
||||
"pm2": "^6.0.13",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"zod-to-json-schema": "^3.24.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.8",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "7.0.0",
|
||||
"version": "7.2.2",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/../scripts/smart-install.js\" && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
|
||||
"timeout": 5
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\" && node \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
|
||||
"timeout": 300
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js",
|
||||
"timeout": 5
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node ${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": "node ${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": "node ${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": "node ${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/cleanup-hook.js\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "claude-mem-plugin",
|
||||
"version": "7.2.2",
|
||||
"private": true,
|
||||
"description": "Runtime dependencies for claude-mem bundled hooks",
|
||||
"type": "module",
|
||||
"dependencies": {},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"bun": ">=1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/bin/bash
|
||||
# claude-mem-settings.sh - User settings manager for claude-mem plugin
|
||||
|
||||
USER_SETTINGS_FILE="$HOME/.claude/settings.json"
|
||||
|
||||
# Function to check if jq is available
|
||||
check_jq() {
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Error: jq is required for JSON manipulation"
|
||||
echo "Install with: brew install jq"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to create settings file if it doesn't exist
|
||||
ensure_settings_file() {
|
||||
if [ ! -f "$USER_SETTINGS_FILE" ]; then
|
||||
mkdir -p "$(dirname "$USER_SETTINGS_FILE")"
|
||||
echo '{}' > "$USER_SETTINGS_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get current model setting
|
||||
get_model() {
|
||||
if [ -f "$USER_SETTINGS_FILE" ]; then
|
||||
jq -r '.env.CLAUDE_MEM_MODEL // "claude-sonnet-4-5"' "$USER_SETTINGS_FILE"
|
||||
else
|
||||
echo "claude-sonnet-4-5"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to set model setting
|
||||
set_model() {
|
||||
local model=$1
|
||||
|
||||
ensure_settings_file
|
||||
|
||||
# Update or create the env.CLAUDE_MEM_MODEL setting
|
||||
jq --arg model "$model" '.env.CLAUDE_MEM_MODEL = $model' "$USER_SETTINGS_FILE" > tmp.json && mv tmp.json "$USER_SETTINGS_FILE"
|
||||
echo "Set CLAUDE_MEM_MODEL to: $model"
|
||||
}
|
||||
|
||||
# Function to remove model setting
|
||||
remove_model() {
|
||||
if [ -f "$USER_SETTINGS_FILE" ]; then
|
||||
jq 'del(.env.CLAUDE_MEM_MODEL)' "$USER_SETTINGS_FILE" > tmp.json && mv tmp.json "$USER_SETTINGS_FILE"
|
||||
echo "Removed CLAUDE_MEM_MODEL (will use default: claude-sonnet-4-5)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to list available models
|
||||
list_models() {
|
||||
echo "Available models:"
|
||||
echo " claude-haiku-4-5 - Fast and efficient"
|
||||
echo " claude-sonnet-4-5 - Balanced (default)"
|
||||
echo " claude-opus-4 - Most capable"
|
||||
echo " claude-3-7-sonnet - Alternative version"
|
||||
}
|
||||
|
||||
# Interactive menu
|
||||
show_menu() {
|
||||
echo "Claude Mem Plugin - Model Configuration"
|
||||
echo "======================================"
|
||||
echo "Current model: $(get_model)"
|
||||
echo "Settings file: $USER_SETTINGS_FILE"
|
||||
echo ""
|
||||
echo "1) Set model"
|
||||
echo "2) Remove model setting (use default)"
|
||||
echo "3) List available models"
|
||||
echo "4) Exit"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main interactive loop
|
||||
main() {
|
||||
check_jq
|
||||
|
||||
while true; do
|
||||
show_menu
|
||||
read -p "Choose an option (1-4): " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
list_models
|
||||
echo ""
|
||||
read -p "Enter model name: " model
|
||||
set_model "$model"
|
||||
;;
|
||||
2)
|
||||
remove_model
|
||||
;;
|
||||
3)
|
||||
list_models
|
||||
;;
|
||||
4)
|
||||
echo "Goodbye!"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option. Please choose 1-4."
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
read -p "Press Enter to continue..."
|
||||
done
|
||||
}
|
||||
|
||||
# Run main if script is executed directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
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
+13
-425
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
@@ -0,0 +1,357 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Smart Install Script for claude-mem
|
||||
*
|
||||
* Ensures Bun runtime and uv (Python package manager) are installed
|
||||
* (auto-installs if missing) and handles dependency installation when needed.
|
||||
*/
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { execSync, spawnSync } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
||||
const MARKER = join(ROOT, '.install-version');
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
|
||||
/**
|
||||
* Check if Bun is installed and accessible
|
||||
*/
|
||||
function isBunInstalled() {
|
||||
try {
|
||||
const result = spawnSync('bun', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
if (result.status === 0) return true;
|
||||
} catch {
|
||||
// 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(bunPath, ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0 ? result.stdout.trim() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if uv is installed and accessible
|
||||
*/
|
||||
function isUvInstalled() {
|
||||
try {
|
||||
const result = spawnSync('uv', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
if (result.status === 0) return true;
|
||||
} catch {
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uv version if installed
|
||||
*/
|
||||
function getUvVersion() {
|
||||
try {
|
||||
const result = spawnSync('uv', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0 ? result.stdout.trim() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Bun automatically based on platform
|
||||
*/
|
||||
function installBun() {
|
||||
console.error('🔧 Bun not found. Installing Bun runtime...');
|
||||
|
||||
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',
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to install Bun automatically');
|
||||
console.error(' Please install manually:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' - winget install Oven-sh.Bun');
|
||||
console.error(' - Or: powershell -c "irm bun.sh/install.ps1 | iex"');
|
||||
} else {
|
||||
console.error(' - curl -fsSL https://bun.sh/install | bash');
|
||||
console.error(' - Or: brew install oven-sh/bun/bun');
|
||||
}
|
||||
console.error(' Then restart your terminal and try again.');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install uv automatically based on platform
|
||||
*/
|
||||
function installUv() {
|
||||
console.error('🐍 Installing uv for Python/Chroma support...');
|
||||
|
||||
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',
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to install uv automatically');
|
||||
console.error(' Please install manually:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' - winget install astral-sh.uv');
|
||||
console.error(' - Or: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"');
|
||||
} else {
|
||||
console.error(' - curl -LsSf https://astral.sh/uv/install.sh | sh');
|
||||
console.error(' - Or: brew install uv (macOS)');
|
||||
}
|
||||
console.error(' Then restart your terminal and try again.');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if dependencies need to be installed
|
||||
*/
|
||||
function needsInstall() {
|
||||
if (!existsSync(join(ROOT, 'node_modules'))) return true;
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
|
||||
const marker = JSON.parse(readFileSync(MARKER, 'utf-8'));
|
||||
return pkg.version !== marker.version || getBunVersion() !== marker.bun;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(`${bunCmd} install`, { cwd: ROOT, stdio: 'inherit', shell: IS_WINDOWS });
|
||||
bunSucceeded = true;
|
||||
} catch {
|
||||
// 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
|
||||
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
|
||||
writeFileSync(MARKER, JSON.stringify({
|
||||
version: pkg.version,
|
||||
bun: getBunVersion(),
|
||||
uv: getUvVersion(),
|
||||
installedAt: new Date().toISOString()
|
||||
}));
|
||||
}
|
||||
|
||||
// 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 (needsInstall()) {
|
||||
installDeps();
|
||||
console.error('✅ Dependencies installed');
|
||||
}
|
||||
} 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
Executable
+5
File diff suppressed because one or more lines are too long
+177
-190
File diff suppressed because one or more lines are too long
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_batch_observations(ids=[11131, 10942, 10855])
|
||||
```
|
||||
|
||||
# Fetch prompt
|
||||
curl "http://localhost:37777/api/prompt/5421"
|
||||
**With ordering and limit:**
|
||||
|
||||
```
|
||||
get_batch_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_batch_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,65 @@ 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 `progressive_description` tool to load full instructions on-demand:
|
||||
|
||||
```
|
||||
progressive_description(topic="workflow") # Get 4-step workflow
|
||||
progressive_description(topic="search_params") # Get parameters reference
|
||||
progressive_description(topic="examples") # Get usage examples
|
||||
progressive_description(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_batch_observations` when fetching 2+ observations
|
||||
- The workflow is optimized: search → timeline → batch fetch = 10-100x faster
|
||||
|
||||
@@ -87,7 +87,7 @@ Quick fixes for frequently encountered claude-mem problems.
|
||||
**Fix:**
|
||||
1. Check the observation count setting:
|
||||
```bash
|
||||
grep CLAUDE_MEM_CONTEXT_OBSERVATIONS ~/.claude/settings.json
|
||||
grep CLAUDE_MEM_CONTEXT_OBSERVATIONS ~/.claude-mem/settings.json
|
||||
```
|
||||
|
||||
2. Default is 50 observations - you can adjust this:
|
||||
|
||||
@@ -6,7 +6,7 @@ SQLite database troubleshooting for claude-mem.
|
||||
|
||||
Claude-mem uses SQLite3 for persistent storage:
|
||||
- **Location:** `~/.claude-mem/claude-mem.db`
|
||||
- **Library:** better-sqlite3 (synchronous, not bun:sqlite)
|
||||
- **Library:** bun:sqlite (native Bun SQLite, synchronous)
|
||||
- **Features:** FTS5 full-text search, triggers, indexes
|
||||
- **Tables:** observations, sessions, user_prompts, observations_fts, sessions_fts, prompts_fts
|
||||
|
||||
|
||||
@@ -97,7 +97,6 @@ cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
|
||||
# Check for critical packages
|
||||
ls node_modules/@anthropic-ai/claude-agent-sdk 2>&1 | head -1
|
||||
ls node_modules/better-sqlite3 2>&1 | head -1
|
||||
ls node_modules/express 2>&1 | head -1
|
||||
ls node_modules/pm2 2>&1 | head -1
|
||||
```
|
||||
@@ -188,7 +187,7 @@ echo " Health check: $(curl -s http://127.0.0.1:37777/health 2>/dev/null || ec
|
||||
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/settings.json 2>/dev/null | grep CLAUDE_MEM_CONTEXT_OBSERVATIONS || echo 'default (50)')"
|
||||
echo " Observation count: $(cat ~/.claude-mem/settings.json 2>/dev/null | grep CLAUDE_MEM_CONTEXT_OBSERVATIONS || echo 'default (50)')"
|
||||
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')"
|
||||
|
||||
@@ -85,7 +85,7 @@ cat ~/.claude/settings.json
|
||||
echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
|
||||
|
||||
# Change context observation count
|
||||
# Edit ~/.claude/settings.json and add:
|
||||
# Edit ~/.claude-mem/settings.json and add:
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "25"
|
||||
@@ -95,7 +95,7 @@ echo '{"env":{"CLAUDE_MEM_WORKER_PORT":"37778"}}' > ~/.claude-mem/settings.json
|
||||
# Change AI model
|
||||
{
|
||||
"env": {
|
||||
"CLAUDE_MEM_MODEL": "claude-haiku-4-5"
|
||||
"CLAUDE_MEM_MODEL": "claude-sonnet-4-5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -187,7 +187,6 @@ pm2 delete claude-mem-worker
|
||||
```bash
|
||||
cd ~/.claude/plugins/marketplaces/thedotmack/
|
||||
ls node_modules/@anthropic-ai/claude-agent-sdk
|
||||
ls node_modules/better-sqlite3
|
||||
ls node_modules/express
|
||||
ls node_modules/pm2
|
||||
```
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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,
|
||||
};
|
||||
}
|
||||
+71
-7
@@ -36,6 +36,11 @@ const CONTEXT_GENERATOR = {
|
||||
source: 'src/services/context-generator.ts'
|
||||
};
|
||||
|
||||
const WORKER_CLI = {
|
||||
name: 'worker-cli',
|
||||
source: 'src/cli/worker-cli.ts'
|
||||
};
|
||||
|
||||
async function buildHooks() {
|
||||
console.log('🔨 Building claude-mem hooks and worker service...\n');
|
||||
|
||||
@@ -58,6 +63,24 @@ async function buildHooks() {
|
||||
}
|
||||
console.log('✓ Output directories ready');
|
||||
|
||||
// Generate plugin/package.json for cache directory dependency installation
|
||||
// Note: bun:sqlite is a Bun built-in, no external dependencies needed for SQLite
|
||||
console.log('\n📦 Generating plugin package.json...');
|
||||
const pluginPackageJson = {
|
||||
name: 'claude-mem-plugin',
|
||||
version: version,
|
||||
private: true,
|
||||
description: 'Runtime dependencies for claude-mem bundled hooks',
|
||||
type: 'module',
|
||||
dependencies: {},
|
||||
engines: {
|
||||
node: '>=18.0.0',
|
||||
bun: '>=1.0.0'
|
||||
}
|
||||
};
|
||||
fs.writeFileSync('plugin/package.json', JSON.stringify(pluginPackageJson, null, 2) + '\n');
|
||||
console.log('✓ plugin/package.json generated');
|
||||
|
||||
// Build React viewer
|
||||
console.log('\n📋 Building React viewer...');
|
||||
const { spawn } = await import('child_process');
|
||||
@@ -83,12 +106,12 @@ async function buildHooks() {
|
||||
outfile: `${hooksDir}/${WORKER_SERVICE.name}.cjs`,
|
||||
minify: true,
|
||||
logLevel: 'error', // Suppress warnings (import.meta warning is benign)
|
||||
external: ['better-sqlite3'],
|
||||
external: ['bun:sqlite'],
|
||||
define: {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
},
|
||||
banner: {
|
||||
js: '#!/usr/bin/env node'
|
||||
js: '#!/usr/bin/env bun'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,12 +131,12 @@ async function buildHooks() {
|
||||
outfile: `${hooksDir}/${MCP_SERVER.name}.cjs`,
|
||||
minify: true,
|
||||
logLevel: 'error',
|
||||
external: ['better-sqlite3'],
|
||||
external: ['bun:sqlite'],
|
||||
define: {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
},
|
||||
banner: {
|
||||
js: '#!/usr/bin/env node'
|
||||
js: '#!/usr/bin/env bun'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,7 +156,7 @@ async function buildHooks() {
|
||||
outfile: `${hooksDir}/${CONTEXT_GENERATOR.name}.cjs`,
|
||||
minify: true,
|
||||
logLevel: 'error',
|
||||
external: ['better-sqlite3'],
|
||||
external: ['bun:sqlite'],
|
||||
define: {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
}
|
||||
@@ -142,6 +165,31 @@ async function buildHooks() {
|
||||
const contextGenStats = fs.statSync(`${hooksDir}/${CONTEXT_GENERATOR.name}.cjs`);
|
||||
console.log(`✓ context-generator built (${(contextGenStats.size / 1024).toFixed(2)} KB)`);
|
||||
|
||||
// Build worker CLI
|
||||
console.log(`\n🔧 Building worker CLI...`);
|
||||
await build({
|
||||
entryPoints: [WORKER_CLI.source],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
target: 'node18',
|
||||
format: 'esm',
|
||||
outfile: `${hooksDir}/${WORKER_CLI.name}.js`,
|
||||
minify: true,
|
||||
logLevel: 'error',
|
||||
external: ['bun:sqlite'],
|
||||
define: {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
},
|
||||
banner: {
|
||||
js: '#!/usr/bin/env bun'
|
||||
}
|
||||
});
|
||||
|
||||
// Make worker CLI executable
|
||||
fs.chmodSync(`${hooksDir}/${WORKER_CLI.name}.js`, 0o755);
|
||||
const workerCliStats = fs.statSync(`${hooksDir}/${WORKER_CLI.name}.js`);
|
||||
console.log(`✓ worker-cli built (${(workerCliStats.size / 1024).toFixed(2)} KB)`);
|
||||
|
||||
// Build each hook
|
||||
for (const hook of HOOKS) {
|
||||
console.log(`\n🔧 Building ${hook.name}...`);
|
||||
@@ -156,12 +204,12 @@ async function buildHooks() {
|
||||
format: 'esm',
|
||||
outfile,
|
||||
minify: true,
|
||||
external: ['better-sqlite3'],
|
||||
external: ['bun:sqlite'],
|
||||
define: {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
},
|
||||
banner: {
|
||||
js: '#!/usr/bin/env node'
|
||||
js: '#!/usr/bin/env bun'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -174,12 +222,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) {
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Build Windows executable for claude-mem worker service
|
||||
* Uses Bun's compile feature to create a standalone exe
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
|
||||
const version = JSON.parse(fs.readFileSync('package.json', 'utf-8')).version;
|
||||
const outDir = 'dist/binaries';
|
||||
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
console.log(`Building Windows exe v${version}...`);
|
||||
|
||||
try {
|
||||
execSync(
|
||||
`bun build --compile --minify --target=bun-windows-x64 ./src/services/worker-service.ts --outfile ${outDir}/worker-service-v${version}-win-x64.exe`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
console.log(`\nBuilt: ${outDir}/worker-service-v${version}-win-x64.exe`);
|
||||
} catch (error) {
|
||||
console.error('Failed to build Windows binary:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
#!/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 Database from 'better-sqlite3';
|
||||
import { existsSync, 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 from database
|
||||
// (We need this because the API doesn't expose sdk_sessions table directly)
|
||||
console.log('📡 Fetching SDK sessions metadata...');
|
||||
const sessions: SdkSessionRecord[] = [];
|
||||
if (sdkSessionIds.size > 0) {
|
||||
// Read directly from database for sdk_sessions table
|
||||
const Database = (await import('better-sqlite3')).default;
|
||||
const dbPath = join(homedir(), '.claude-mem', 'claude-mem.db');
|
||||
|
||||
if (!existsSync(dbPath)) {
|
||||
console.error(`❌ Database not found at: ${dbPath}`);
|
||||
console.error('💡 Has claude-mem been initialized? Try running a session first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
|
||||
try {
|
||||
const placeholders = Array.from(sdkSessionIds).map(() => '?').join(',');
|
||||
const sessionQuery = `
|
||||
SELECT * FROM sdk_sessions
|
||||
WHERE sdk_session_id IN (${placeholders})
|
||||
ORDER BY started_at_epoch DESC
|
||||
`;
|
||||
sessions.push(...db.prepare(sessionQuery).all(...Array.from(sdkSessionIds)));
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
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);
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Find Silent Failure Patterns
|
||||
#
|
||||
# This script searches for defensive OR patterns (|| '' || null || undefined)
|
||||
# that should potentially use happy_path_error__with_fallback instead.
|
||||
#
|
||||
# Usage: ./scripts/find-silent-failures.sh
|
||||
|
||||
echo "=================================================="
|
||||
echo "Searching for defensive OR patterns in src/"
|
||||
echo "These MAY be silent failures that should log errors"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
echo "🔍 Searching for: || ''"
|
||||
echo "---"
|
||||
grep -rn "|| ''" src/ --include="*.ts" --color=always || echo " (none found)"
|
||||
echo ""
|
||||
|
||||
echo "🔍 Searching for: || \"\""
|
||||
echo "---"
|
||||
grep -rn '|| ""' src/ --include="*.ts" --color=always || echo " (none found)"
|
||||
echo ""
|
||||
|
||||
echo "🔍 Searching for: || null"
|
||||
echo "---"
|
||||
grep -rn "|| null" src/ --include="*.ts" --color=always || echo " (none found)"
|
||||
echo ""
|
||||
|
||||
echo "🔍 Searching for: || undefined"
|
||||
echo "---"
|
||||
grep -rn "|| undefined" src/ --include="*.ts" --color=always || echo " (none found)"
|
||||
echo ""
|
||||
|
||||
echo "=================================================="
|
||||
echo "Review each match and determine if it should use:"
|
||||
echo " happy_path_error__with_fallback('description', data, fallback)"
|
||||
echo "=================================================="
|
||||
@@ -0,0 +1,245 @@
|
||||
#!/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
|
||||
*/
|
||||
|
||||
import Database from 'better-sqlite3';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
interface ImportStats {
|
||||
sessionsImported: number;
|
||||
sessionsSkipped: number;
|
||||
summariesImported: number;
|
||||
summariesSkipped: number;
|
||||
observationsImported: number;
|
||||
observationsSkipped: number;
|
||||
promptsImported: number;
|
||||
promptsSkipped: number;
|
||||
}
|
||||
|
||||
function importMemories(inputFile: string) {
|
||||
if (!existsSync(inputFile)) {
|
||||
console.error(`❌ Input file not found: ${inputFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const dbPath = join(homedir(), '.claude-mem', 'claude-mem.db');
|
||||
|
||||
if (!existsSync(dbPath)) {
|
||||
console.error(`❌ Database not found at: ${dbPath}`);
|
||||
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('');
|
||||
|
||||
const db = new Database(dbPath);
|
||||
const stats: ImportStats = {
|
||||
sessionsImported: 0,
|
||||
sessionsSkipped: 0,
|
||||
summariesImported: 0,
|
||||
summariesSkipped: 0,
|
||||
observationsImported: 0,
|
||||
observationsSkipped: 0,
|
||||
promptsImported: 0,
|
||||
promptsSkipped: 0
|
||||
};
|
||||
|
||||
try {
|
||||
// Prepare statements for duplicate checking
|
||||
const checkSession = db.prepare('SELECT id FROM sdk_sessions WHERE claude_session_id = ?');
|
||||
const checkSummary = db.prepare('SELECT id FROM session_summaries WHERE sdk_session_id = ?');
|
||||
const checkObservation = db.prepare(`
|
||||
SELECT id FROM observations
|
||||
WHERE sdk_session_id = ?
|
||||
AND title = ?
|
||||
AND created_at_epoch = ?
|
||||
`);
|
||||
const checkPrompt = db.prepare(`
|
||||
SELECT id FROM user_prompts
|
||||
WHERE claude_session_id = ?
|
||||
AND prompt_number = ?
|
||||
`);
|
||||
|
||||
// Prepare insert statements
|
||||
const insertSession = db.prepare(`
|
||||
INSERT INTO sdk_sessions (
|
||||
claude_session_id, sdk_session_id, project, user_prompt,
|
||||
started_at, started_at_epoch, completed_at, completed_at_epoch,
|
||||
status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const insertSummary = db.prepare(`
|
||||
INSERT INTO session_summaries (
|
||||
sdk_session_id, project, request, investigated, learned,
|
||||
completed, next_steps, files_read, files_edited, notes,
|
||||
prompt_number, discovery_tokens, created_at, created_at_epoch
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const insertObservation = db.prepare(`
|
||||
INSERT INTO observations (
|
||||
sdk_session_id, project, text, type, title, subtitle,
|
||||
facts, narrative, concepts, files_read, files_modified,
|
||||
prompt_number, discovery_tokens, created_at, created_at_epoch
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const insertPrompt = db.prepare(`
|
||||
INSERT INTO user_prompts (
|
||||
claude_session_id, prompt_number, prompt_text,
|
||||
created_at, created_at_epoch
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
// Import in transaction
|
||||
db.transaction(() => {
|
||||
// 1. Import sessions first (dependency for everything else)
|
||||
console.log('🔄 Importing sessions...');
|
||||
for (const session of exportData.sessions) {
|
||||
const exists = checkSession.get(session.claude_session_id);
|
||||
if (exists) {
|
||||
stats.sessionsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
insertSession.run(
|
||||
session.claude_session_id,
|
||||
session.sdk_session_id,
|
||||
session.project,
|
||||
session.user_prompt,
|
||||
session.started_at,
|
||||
session.started_at_epoch,
|
||||
session.completed_at,
|
||||
session.completed_at_epoch,
|
||||
session.status
|
||||
);
|
||||
stats.sessionsImported++;
|
||||
}
|
||||
console.log(` ✅ Imported: ${stats.sessionsImported}, Skipped: ${stats.sessionsSkipped}`);
|
||||
|
||||
// 2. Import summaries (depends on sessions)
|
||||
console.log('🔄 Importing summaries...');
|
||||
for (const summary of exportData.summaries) {
|
||||
const exists = checkSummary.get(summary.sdk_session_id);
|
||||
if (exists) {
|
||||
stats.summariesSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
insertSummary.run(
|
||||
summary.sdk_session_id,
|
||||
summary.project,
|
||||
summary.request,
|
||||
summary.investigated,
|
||||
summary.learned,
|
||||
summary.completed,
|
||||
summary.next_steps,
|
||||
summary.files_read,
|
||||
summary.files_edited,
|
||||
summary.notes,
|
||||
summary.prompt_number,
|
||||
summary.discovery_tokens || 0,
|
||||
summary.created_at,
|
||||
summary.created_at_epoch
|
||||
);
|
||||
stats.summariesImported++;
|
||||
}
|
||||
console.log(` ✅ Imported: ${stats.summariesImported}, Skipped: ${stats.summariesSkipped}`);
|
||||
|
||||
// 3. Import observations (depends on sessions)
|
||||
console.log('🔄 Importing observations...');
|
||||
for (const obs of exportData.observations) {
|
||||
const exists = checkObservation.get(
|
||||
obs.sdk_session_id,
|
||||
obs.title,
|
||||
obs.created_at_epoch
|
||||
);
|
||||
if (exists) {
|
||||
stats.observationsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
insertObservation.run(
|
||||
obs.sdk_session_id,
|
||||
obs.project,
|
||||
obs.text,
|
||||
obs.type,
|
||||
obs.title,
|
||||
obs.subtitle,
|
||||
obs.facts,
|
||||
obs.narrative,
|
||||
obs.concepts,
|
||||
obs.files_read,
|
||||
obs.files_modified,
|
||||
obs.prompt_number,
|
||||
obs.discovery_tokens || 0,
|
||||
obs.created_at,
|
||||
obs.created_at_epoch
|
||||
);
|
||||
stats.observationsImported++;
|
||||
}
|
||||
console.log(` ✅ Imported: ${stats.observationsImported}, Skipped: ${stats.observationsSkipped}`);
|
||||
|
||||
// 4. Import prompts (depends on sessions)
|
||||
console.log('🔄 Importing prompts...');
|
||||
for (const prompt of exportData.prompts) {
|
||||
const exists = checkPrompt.get(
|
||||
prompt.claude_session_id,
|
||||
prompt.prompt_number
|
||||
);
|
||||
if (exists) {
|
||||
stats.promptsSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
insertPrompt.run(
|
||||
prompt.claude_session_id,
|
||||
prompt.prompt_number,
|
||||
prompt.prompt_text,
|
||||
prompt.created_at,
|
||||
prompt.created_at_epoch
|
||||
);
|
||||
stats.promptsImported++;
|
||||
}
|
||||
console.log(` ✅ Imported: ${stats.promptsImported}, Skipped: ${stats.promptsSkipped}`);
|
||||
|
||||
})();
|
||||
|
||||
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`);
|
||||
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
+211
-355
@@ -1,403 +1,259 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Smart Install Script for claude-mem
|
||||
*
|
||||
* Features:
|
||||
* - Only runs npm install when necessary (version change or missing deps)
|
||||
* - Caches installation state with version marker
|
||||
* - Provides helpful Windows-specific error messages
|
||||
* - Cross-platform compatible (pure Node.js)
|
||||
* - Fast when already installed (just version check)
|
||||
* Ensures Bun runtime and uv (Python package manager) are installed
|
||||
* (auto-installs if missing) and handles dependency installation when needed.
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { execSync, spawnSync } from 'child_process';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
||||
const MARKER = join(ROOT, '.install-version');
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
|
||||
// Plugin root is parent directory of scripts/
|
||||
const PLUGIN_ROOT = join(__dirname, '..');
|
||||
const PACKAGE_JSON_PATH = join(PLUGIN_ROOT, 'package.json');
|
||||
const VERSION_MARKER_PATH = join(PLUGIN_ROOT, '.install-version');
|
||||
const NODE_MODULES_PATH = join(PLUGIN_ROOT, 'node_modules');
|
||||
const BETTER_SQLITE3_PATH = join(NODE_MODULES_PATH, 'better-sqlite3');
|
||||
// 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'];
|
||||
|
||||
// Colors for output
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
red: '\x1b[31m',
|
||||
cyan: '\x1b[36m',
|
||||
dim: '\x1b[2m',
|
||||
};
|
||||
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'];
|
||||
|
||||
function log(message, color = colors.reset) {
|
||||
console.error(`${color}${message}${colors.reset}`);
|
||||
/**
|
||||
* 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
|
||||
return BUN_COMMON_PATHS.find(existsSync) || null;
|
||||
}
|
||||
|
||||
function getPackageVersion() {
|
||||
/**
|
||||
* 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 packageJson = JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf-8'));
|
||||
return packageJson.version;
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to read package.json: ${error.message}`, colors.yellow);
|
||||
const result = spawnSync(bunPath, ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0 ? result.stdout.trim() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeVersion() {
|
||||
return process.version; // e.g., "v22.21.1"
|
||||
}
|
||||
|
||||
function getInstalledVersion() {
|
||||
/**
|
||||
* Get the uv executable path (from PATH or common install locations)
|
||||
*/
|
||||
function getUvPath() {
|
||||
// Try PATH first
|
||||
try {
|
||||
if (existsSync(VERSION_MARKER_PATH)) {
|
||||
const content = readFileSync(VERSION_MARKER_PATH, 'utf-8').trim();
|
||||
|
||||
// Try parsing as JSON (new format)
|
||||
try {
|
||||
const marker = JSON.parse(content);
|
||||
return {
|
||||
packageVersion: marker.packageVersion,
|
||||
nodeVersion: marker.nodeVersion,
|
||||
installedAt: marker.installedAt
|
||||
};
|
||||
} catch {
|
||||
// Fallback: old format (plain text version string)
|
||||
return {
|
||||
packageVersion: content,
|
||||
nodeVersion: null, // Unknown
|
||||
installedAt: null
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Marker doesn't exist or can't be read
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setInstalledVersion(packageVersion, nodeVersion) {
|
||||
try {
|
||||
const marker = {
|
||||
packageVersion,
|
||||
nodeVersion,
|
||||
installedAt: new Date().toISOString()
|
||||
};
|
||||
writeFileSync(VERSION_MARKER_PATH, JSON.stringify(marker, null, 2), 'utf-8');
|
||||
} catch (error) {
|
||||
log(`⚠️ Failed to write version marker: ${error.message}`, colors.yellow);
|
||||
}
|
||||
}
|
||||
|
||||
function needsInstall() {
|
||||
// Check if node_modules exists
|
||||
if (!existsSync(NODE_MODULES_PATH)) {
|
||||
log('📦 Dependencies not found - first time setup', colors.cyan);
|
||||
return true;
|
||||
const result = spawnSync('uv', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
if (result.status === 0) return 'uv';
|
||||
} catch {
|
||||
// Not in PATH
|
||||
}
|
||||
|
||||
// Check if better-sqlite3 is installed
|
||||
if (!existsSync(BETTER_SQLITE3_PATH)) {
|
||||
log('📦 better-sqlite3 missing - reinstalling', colors.cyan);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check version marker
|
||||
const currentPackageVersion = getPackageVersion();
|
||||
const currentNodeVersion = getNodeVersion();
|
||||
const installed = getInstalledVersion();
|
||||
|
||||
if (!installed) {
|
||||
log('📦 No version marker found - installing', colors.cyan);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check package version
|
||||
if (currentPackageVersion !== installed.packageVersion) {
|
||||
log(`📦 Version changed (${installed.packageVersion} → ${currentPackageVersion}) - updating`, colors.cyan);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check Node.js version
|
||||
if (installed.nodeVersion && currentNodeVersion !== installed.nodeVersion) {
|
||||
log(`📦 Node.js version changed (${installed.nodeVersion} → ${currentNodeVersion}) - rebuilding native modules`, colors.cyan);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If old format (no nodeVersion), assume needs install
|
||||
if (!installed.nodeVersion) {
|
||||
log('📦 Old version marker format - updating', colors.cyan);
|
||||
return true;
|
||||
}
|
||||
|
||||
// All good - no install needed
|
||||
log(`✓ Dependencies already installed (v${currentPackageVersion})`, colors.dim);
|
||||
return false;
|
||||
// Check common installation paths
|
||||
return UV_COMMON_PATHS.find(existsSync) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that better-sqlite3 native module loads correctly
|
||||
* This catches ABI mismatches and corrupted builds
|
||||
* Check if uv is installed and accessible
|
||||
*/
|
||||
async function verifyNativeModules() {
|
||||
function isUvInstalled() {
|
||||
return getUvPath() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uv version if installed
|
||||
*/
|
||||
function getUvVersion() {
|
||||
const uvPath = getUvPath();
|
||||
if (!uvPath) return null;
|
||||
|
||||
try {
|
||||
log('🔍 Verifying native modules...', colors.dim);
|
||||
|
||||
// Try to actually load better-sqlite3
|
||||
const { default: Database } = await import('better-sqlite3');
|
||||
|
||||
// Try to create a test in-memory database
|
||||
const db = new Database(':memory:');
|
||||
|
||||
// Run a simple query to ensure it works
|
||||
const result = db.prepare('SELECT 1 + 1 as result').get();
|
||||
|
||||
// Clean up
|
||||
db.close();
|
||||
|
||||
if (result.result !== 2) {
|
||||
throw new Error('SQLite math check failed');
|
||||
}
|
||||
|
||||
log('✓ Native modules verified', colors.dim);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_DLOPEN_FAILED') {
|
||||
log('⚠️ Native module ABI mismatch detected', colors.yellow);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Other errors are unexpected - log and fail
|
||||
log(`❌ Native module verification failed: ${error.message}`, colors.red);
|
||||
return false;
|
||||
const result = spawnSync(uvPath, ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: IS_WINDOWS
|
||||
});
|
||||
return result.status === 0 ? result.stdout.trim() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getWindowsErrorHelp(errorOutput) {
|
||||
// Detect Python version at runtime
|
||||
let pythonStatus = ' Python not detected or version unknown';
|
||||
/**
|
||||
* Install Bun automatically based on platform
|
||||
*/
|
||||
function installBun() {
|
||||
console.error('🔧 Bun not found. Installing Bun runtime...');
|
||||
|
||||
try {
|
||||
const pythonVersion = execSync('python --version', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
||||
const versionMatch = pythonVersion.match(/Python\s+([\d.]+)/);
|
||||
if (versionMatch) {
|
||||
pythonStatus = ` You have ${versionMatch[0]} installed ✓`;
|
||||
}
|
||||
} catch (error) {
|
||||
// Python not available or failed to detect - use default message
|
||||
}
|
||||
|
||||
const help = [
|
||||
'',
|
||||
'╔══════════════════════════════════════════════════════════════════════╗',
|
||||
'║ Windows Installation Help ║',
|
||||
'╚══════════════════════════════════════════════════════════════════════╝',
|
||||
'',
|
||||
'📋 better-sqlite3 requires build tools to compile native modules.',
|
||||
'',
|
||||
'🔧 Option 1: Install Visual Studio Build Tools (Recommended)',
|
||||
' 1. Download: https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022',
|
||||
' 2. Install "Desktop development with C++"',
|
||||
' 3. Restart your terminal',
|
||||
' 4. Try again',
|
||||
'',
|
||||
'🔧 Option 2: Install via npm (automated)',
|
||||
' Run as Administrator:',
|
||||
' npm install --global windows-build-tools',
|
||||
'',
|
||||
'🐍 Python Requirement:',
|
||||
' Python 3.6+ is required.',
|
||||
pythonStatus,
|
||||
'',
|
||||
];
|
||||
|
||||
// Check for specific error patterns
|
||||
if (errorOutput.includes('MSBuild.exe')) {
|
||||
help.push('❌ MSBuild not found - install Visual Studio Build Tools');
|
||||
}
|
||||
if (errorOutput.includes('MSVS')) {
|
||||
help.push('❌ Visual Studio not detected - install Build Tools');
|
||||
}
|
||||
if (errorOutput.includes('permission') || errorOutput.includes('EPERM')) {
|
||||
help.push('❌ Permission denied - try running as Administrator');
|
||||
}
|
||||
|
||||
help.push('');
|
||||
help.push('📖 Full documentation: https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md');
|
||||
help.push('');
|
||||
|
||||
return help.join('\n');
|
||||
}
|
||||
|
||||
async function runNpmInstall() {
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
log('', colors.cyan);
|
||||
log('🔨 Installing dependencies...', colors.bright);
|
||||
log('', colors.reset);
|
||||
|
||||
// Try normal install first, then retry with force if it fails
|
||||
const strategies = [
|
||||
{ command: 'npm install', label: 'normal' },
|
||||
{ command: 'npm install --force', label: 'with force flag' },
|
||||
];
|
||||
|
||||
let lastError = null;
|
||||
|
||||
for (const { command, label } of strategies) {
|
||||
try {
|
||||
log(`Attempting install ${label}...`, colors.dim);
|
||||
|
||||
// Run npm install silently
|
||||
execSync(command, {
|
||||
cwd: PLUGIN_ROOT,
|
||||
stdio: 'pipe', // Silent output unless error
|
||||
encoding: 'utf-8',
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' Installing via PowerShell...');
|
||||
execSync('powershell -c "irm bun.sh/install.ps1 | iex"', {
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
} else {
|
||||
console.error(' Installing via curl...');
|
||||
execSync('curl -fsSL https://bun.sh/install | bash', {
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
|
||||
// Verify better-sqlite3 was installed
|
||||
if (!existsSync(BETTER_SQLITE3_PATH)) {
|
||||
throw new Error('better-sqlite3 installation verification failed');
|
||||
}
|
||||
|
||||
// NEW: Verify native modules actually work
|
||||
const nativeModulesWork = await verifyNativeModules();
|
||||
if (!nativeModulesWork) {
|
||||
throw new Error('Native modules failed to load after install');
|
||||
}
|
||||
|
||||
const packageVersion = getPackageVersion();
|
||||
const nodeVersion = getNodeVersion();
|
||||
setInstalledVersion(packageVersion, nodeVersion);
|
||||
|
||||
log('', colors.green);
|
||||
log('✅ Dependencies installed successfully!', colors.bright);
|
||||
log(` Package version: ${packageVersion}`, colors.dim);
|
||||
log(` Node.js version: ${nodeVersion}`, colors.dim);
|
||||
log('', colors.reset);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
// Continue to next strategy
|
||||
}
|
||||
}
|
||||
|
||||
// All strategies failed - show error
|
||||
log('', colors.red);
|
||||
log('❌ Installation failed after retrying!', colors.bright);
|
||||
log('', colors.reset);
|
||||
|
||||
// Provide Windows-specific help
|
||||
if (isWindows && lastError && lastError.message && lastError.message.includes('better-sqlite3')) {
|
||||
log(getWindowsErrorHelp(lastError.message), colors.yellow);
|
||||
}
|
||||
|
||||
// Show generic error info with troubleshooting steps
|
||||
if (lastError) {
|
||||
if (lastError.stderr) {
|
||||
log('Error output:', colors.dim);
|
||||
log(lastError.stderr.toString(), colors.red);
|
||||
} else if (lastError.message) {
|
||||
log(lastError.message, colors.red);
|
||||
}
|
||||
|
||||
log('', colors.yellow);
|
||||
log('📋 Troubleshooting Steps:', colors.bright);
|
||||
log('', colors.reset);
|
||||
log('1. Check your internet connection', colors.yellow);
|
||||
log('2. Try running: npm cache clean --force', colors.yellow);
|
||||
log('3. Try running: npm install (in plugin directory)', colors.yellow);
|
||||
log('4. Check npm version: npm --version (requires npm 7+)', colors.yellow);
|
||||
log('5. Try updating npm: npm install -g npm@latest', colors.yellow);
|
||||
log('', colors.reset);
|
||||
}
|
||||
if (!isBunInstalled()) {
|
||||
throw new Error(
|
||||
'Bun installation completed but binary not found. ' +
|
||||
'Please restart your terminal and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
const version = getBunVersion();
|
||||
console.error(`✅ Bun ${version} installed successfully`);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to install Bun');
|
||||
console.error(' Please install manually:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' - winget install Oven-sh.Bun');
|
||||
console.error(' - Or: powershell -c "irm bun.sh/install.ps1 | iex"');
|
||||
} else {
|
||||
console.error(' - curl -fsSL https://bun.sh/install | bash');
|
||||
console.error(' - Or: brew install oven-sh/bun/bun');
|
||||
}
|
||||
console.error(' Then restart your terminal and try again.');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should fail when worker startup fails
|
||||
* Returns true if worker failed AND dependencies are missing
|
||||
* Install uv automatically based on platform
|
||||
*/
|
||||
function shouldFailOnWorkerStartup(workerStarted) {
|
||||
return !workerStarted && !existsSync(NODE_MODULES_PATH);
|
||||
}
|
||||
function installUv() {
|
||||
console.error('🐍 Installing uv for Python/Chroma support...');
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Check if we need to install dependencies
|
||||
const installNeeded = needsInstall();
|
||||
|
||||
if (installNeeded) {
|
||||
// Run installation (now async)
|
||||
const installSuccess = await runNpmInstall();
|
||||
|
||||
if (!installSuccess) {
|
||||
log('', colors.red);
|
||||
log('⚠️ Installation failed', colors.yellow);
|
||||
log('', colors.reset);
|
||||
process.exit(1);
|
||||
}
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' Installing via PowerShell...');
|
||||
execSync('powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"', {
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
} else {
|
||||
// NEW: Even if install not needed, verify native modules work
|
||||
const nativeModulesWork = await verifyNativeModules();
|
||||
|
||||
if (!nativeModulesWork) {
|
||||
log('📦 Native modules need rebuild - reinstalling', colors.cyan);
|
||||
const installSuccess = await runNpmInstall();
|
||||
|
||||
if (!installSuccess) {
|
||||
log('', colors.red);
|
||||
log('⚠️ Native module rebuild failed', colors.yellow);
|
||||
log('', colors.reset);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
console.error(' Installing via curl...');
|
||||
execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', {
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
|
||||
// Try to start the PM2 worker after fresh install
|
||||
try {
|
||||
log('🚀 Starting worker service...', colors.cyan);
|
||||
// On Windows, PM2 executable is pm2.cmd, not pm2
|
||||
const localPm2Base = join(NODE_MODULES_PATH, '.bin', 'pm2');
|
||||
const localPm2Cmd = process.platform === 'win32' ? localPm2Base + '.cmd' : localPm2Base;
|
||||
const pm2Command = existsSync(localPm2Cmd) ? localPm2Cmd : 'pm2';
|
||||
const ecosystemPath = join(PLUGIN_ROOT, 'ecosystem.config.cjs');
|
||||
|
||||
// Using spawnSync with array args to avoid command injection risks
|
||||
const result = spawnSync(pm2Command, ['start', ecosystemPath], {
|
||||
cwd: PLUGIN_ROOT,
|
||||
stdio: 'pipe',
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(result.stderr || 'PM2 start failed');
|
||||
}
|
||||
|
||||
log('✅ Worker service started', colors.green);
|
||||
} catch (error) {
|
||||
// Worker might already be running or PM2 not available - that's okay
|
||||
// The ensureWorkerRunning() function will handle auto-start when needed
|
||||
log('ℹ️ Worker startup error', colors.dim);
|
||||
}
|
||||
|
||||
// Success - dependencies installed (if needed)
|
||||
process.exit(0);
|
||||
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) {
|
||||
log(`❌ Unexpected error: ${error.message}`, colors.red);
|
||||
log('', colors.reset);
|
||||
process.exit(1);
|
||||
console.error('❌ Failed to install uv');
|
||||
console.error(' Please install manually:');
|
||||
if (IS_WINDOWS) {
|
||||
console.error(' - winget install astral-sh.uv');
|
||||
console.error(' - Or: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"');
|
||||
} else {
|
||||
console.error(' - curl -LsSf https://astral.sh/uv/install.sh | sh');
|
||||
console.error(' - Or: brew install uv (macOS)');
|
||||
}
|
||||
console.error(' Then restart your terminal and try again.');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
/**
|
||||
* Check if dependencies need to be installed
|
||||
*/
|
||||
function needsInstall() {
|
||||
if (!existsSync(join(ROOT, 'node_modules'))) return true;
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'));
|
||||
const marker = JSON.parse(readFileSync(MARKER, 'utf-8'));
|
||||
return pkg.version !== marker.version || getBunVersion() !== marker.bun;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install dependencies using Bun
|
||||
*/
|
||||
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;
|
||||
|
||||
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({
|
||||
version: pkg.version,
|
||||
bun: getBunVersion(),
|
||||
uv: getUvVersion(),
|
||||
installedAt: new Date().toISOString()
|
||||
}));
|
||||
}
|
||||
|
||||
// Main execution
|
||||
try {
|
||||
if (!isBunInstalled()) installBun();
|
||||
if (!isUvInstalled()) installUv();
|
||||
if (needsInstall()) {
|
||||
installDeps();
|
||||
console.error('✅ Dependencies installed');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('❌ Installation failed:', e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
*/
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const { existsSync } = require('fs');
|
||||
const { existsSync, readFileSync } = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const INSTALLED_PATH = path.join(os.homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
||||
const CACHE_BASE_PATH = path.join(os.homedir(), '.claude', 'plugins', 'cache', 'thedotmack', 'claude-mem');
|
||||
|
||||
function getCurrentBranch() {
|
||||
try {
|
||||
@@ -29,8 +30,9 @@ function getCurrentBranch() {
|
||||
}
|
||||
|
||||
const branch = getCurrentBranch();
|
||||
const isForce = process.argv.includes('--force');
|
||||
|
||||
if (branch && branch !== 'main') {
|
||||
if (branch && branch !== 'main' && !isForce) {
|
||||
console.log('');
|
||||
console.log('\x1b[33m%s\x1b[0m', `WARNING: Installed plugin is on beta branch: ${branch}`);
|
||||
console.log('\x1b[33m%s\x1b[0m', 'Running rsync would overwrite beta code.');
|
||||
@@ -43,11 +45,23 @@ if (branch && branch !== 'main') {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get version from plugin.json
|
||||
function getPluginVersion() {
|
||||
try {
|
||||
const pluginJsonPath = path.join(__dirname, '..', 'plugin', '.claude-plugin', 'plugin.json');
|
||||
const pluginJson = JSON.parse(readFileSync(pluginJsonPath, 'utf-8'));
|
||||
return pluginJson.version;
|
||||
} catch (error) {
|
||||
console.error('\x1b[31m%s\x1b[0m', 'Failed to read plugin version:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Normal rsync for main branch or fresh install
|
||||
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' }
|
||||
);
|
||||
|
||||
@@ -57,7 +71,43 @@ try {
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
// Sync to cache folder with version
|
||||
const version = getPluginVersion();
|
||||
const CACHE_VERSION_PATH = path.join(CACHE_BASE_PATH, version);
|
||||
|
||||
console.log(`Syncing to cache folder (version ${version})...`);
|
||||
execSync(
|
||||
`rsync -av --delete --exclude=.git plugin/ "${CACHE_VERSION_PATH}/"`,
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
|
||||
console.log('\x1b[32m%s\x1b[0m', 'Sync complete!');
|
||||
|
||||
// Trigger worker restart after file sync
|
||||
console.log('\n🔄 Triggering worker restart...');
|
||||
const http = require('http');
|
||||
const req = http.request({
|
||||
hostname: '127.0.0.1',
|
||||
port: 37777,
|
||||
path: '/api/admin/restart',
|
||||
method: 'POST',
|
||||
timeout: 2000
|
||||
}, (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('\x1b[32m%s\x1b[0m', '✓ Worker restart triggered');
|
||||
} else {
|
||||
console.log('\x1b[33m%s\x1b[0m', `ℹ Worker restart returned status ${res.statusCode}`);
|
||||
}
|
||||
});
|
||||
req.on('error', () => {
|
||||
console.log('\x1b[33m%s\x1b[0m', 'ℹ Worker not running, will start on next hook');
|
||||
});
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
console.log('\x1b[33m%s\x1b[0m', 'ℹ Worker restart timed out');
|
||||
});
|
||||
req.end();
|
||||
|
||||
} catch (error) {
|
||||
console.error('\x1b[31m%s\x1b[0m', 'Sync failed:', error.message);
|
||||
process.exit(1);
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
# README Translator
|
||||
|
||||
Translate README.md files to multiple languages using the Claude Agent SDK. Perfect for build scripts and CI/CD pipelines.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install readme-translator
|
||||
# or
|
||||
npm install -g readme-translator # for CLI usage
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 18+
|
||||
- **Authentication** (one of the following):
|
||||
- Claude Code installed and authenticated (Pro/Max subscription) - **no API key needed**
|
||||
- `ANTHROPIC_API_KEY` environment variable set (for API-based usage)
|
||||
- AWS Bedrock (`CLAUDE_CODE_USE_BEDROCK=1` + AWS credentials)
|
||||
- Google Vertex AI (`CLAUDE_CODE_USE_VERTEX=1` + GCP credentials)
|
||||
|
||||
If you have Claude Code installed and logged in with your Pro/Max subscription, the SDK will automatically use that authentication.
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
# Basic usage
|
||||
translate-readme README.md es fr de
|
||||
|
||||
# With options
|
||||
translate-readme -v -o ./i18n --pattern docs.{lang}.md README.md es fr de ja zh
|
||||
|
||||
# List supported languages
|
||||
translate-readme --list-languages
|
||||
```
|
||||
|
||||
### CLI Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-o, --output <dir>` | Output directory (default: same as source) |
|
||||
| `-p, --pattern <pat>` | Output filename pattern (default: `README.{lang}.md`) |
|
||||
| `--no-preserve-code` | Translate code blocks too (not recommended) |
|
||||
| `-m, --model <model>` | Claude model to use (default: `sonnet`) |
|
||||
| `--max-budget <usd>` | Maximum budget in USD |
|
||||
| `-v, --verbose` | Show detailed progress |
|
||||
| `-h, --help` | Show help message |
|
||||
| `--list-languages` | List all supported language codes |
|
||||
|
||||
## Programmatic Usage
|
||||
|
||||
```typescript
|
||||
import { translateReadme } from "readme-translator";
|
||||
|
||||
const result = await translateReadme({
|
||||
source: "./README.md",
|
||||
languages: ["es", "fr", "de", "ja", "zh"],
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
console.log(`Translated ${result.successful} files`);
|
||||
console.log(`Total cost: $${result.totalCostUsd.toFixed(4)}`);
|
||||
```
|
||||
|
||||
### API Options
|
||||
|
||||
```typescript
|
||||
interface TranslationOptions {
|
||||
/** Source README file path */
|
||||
source: string;
|
||||
|
||||
/** Target language codes */
|
||||
languages: string[];
|
||||
|
||||
/** Output directory (defaults to same directory as source) */
|
||||
outputDir?: string;
|
||||
|
||||
/** Output filename pattern (use {lang} placeholder) */
|
||||
pattern?: string; // default: "README.{lang}.md"
|
||||
|
||||
/** Preserve code blocks without translation */
|
||||
preserveCode?: boolean; // default: true
|
||||
|
||||
/** Claude model to use */
|
||||
model?: string; // default: "sonnet"
|
||||
|
||||
/** Maximum budget in USD */
|
||||
maxBudgetUsd?: number;
|
||||
|
||||
/** Verbose output */
|
||||
verbose?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Return Value
|
||||
|
||||
```typescript
|
||||
interface TranslationJobResult {
|
||||
results: TranslationResult[];
|
||||
totalCostUsd: number;
|
||||
successful: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
interface TranslationResult {
|
||||
language: string;
|
||||
outputPath: string;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
costUsd?: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Build Script Integration
|
||||
|
||||
### package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"translate": "translate-readme README.md es fr de ja zh",
|
||||
"translate:all": "translate-readme -v -o ./i18n README.md es fr de it pt ja ko zh ru ar",
|
||||
"prebuild": "npm run translate"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
Note: CI/CD environments require an API key since Claude Code won't be authenticated there.
|
||||
|
||||
```yaml
|
||||
name: Translate README
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: [README.md]
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- run: npm install -g readme-translator
|
||||
|
||||
- name: Translate README
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
run: |
|
||||
translate-readme -v -o ./i18n README.md es fr de ja zh
|
||||
|
||||
- name: Commit translations
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add i18n/
|
||||
git diff --staged --quiet || git commit -m "chore: update README translations"
|
||||
git push
|
||||
```
|
||||
|
||||
### Programmatic Build Script
|
||||
|
||||
```typescript
|
||||
// scripts/translate.ts
|
||||
import { translateReadme } from "readme-translator";
|
||||
|
||||
async function main() {
|
||||
const result = await translateReadme({
|
||||
source: "./README.md",
|
||||
languages: (process.env.TRANSLATE_LANGS || "es,fr,de").split(","),
|
||||
outputDir: "./docs/i18n",
|
||||
maxBudgetUsd: 5.0,
|
||||
verbose: !process.env.CI,
|
||||
});
|
||||
|
||||
if (result.failed > 0) {
|
||||
console.error("Some translations failed");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## Supported Languages
|
||||
|
||||
| Code | Language | Code | Language |
|
||||
|------|----------|------|----------|
|
||||
| `ar` | Arabic | `ko` | Korean |
|
||||
| `bg` | Bulgarian | `lt` | Lithuanian |
|
||||
| `cs` | Czech | `lv` | Latvian |
|
||||
| `da` | Danish | `nl` | Dutch |
|
||||
| `de` | German | `no` | Norwegian |
|
||||
| `el` | Greek | `pl` | Polish |
|
||||
| `es` | Spanish | `pt` | Portuguese |
|
||||
| `et` | Estonian | `pt-br` | Brazilian Portuguese |
|
||||
| `fi` | Finnish | `ro` | Romanian |
|
||||
| `fr` | French | `ru` | Russian |
|
||||
| `he` | Hebrew | `sk` | Slovak |
|
||||
| `hi` | Hindi | `sl` | Slovenian |
|
||||
| `hu` | Hungarian | `sv` | Swedish |
|
||||
| `id` | Indonesian | `th` | Thai |
|
||||
| `it` | Italian | `tr` | Turkish |
|
||||
| `ja` | Japanese | `uk` | Ukrainian |
|
||||
| | | `vi` | Vietnamese |
|
||||
| | | `zh` | Chinese (Simplified) |
|
||||
| | | `zh-tw` | Chinese (Traditional) |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Preserve Code Blocks**: Keep `preserveCode: true` (default) to avoid breaking code examples
|
||||
|
||||
2. **Set Budget Limits**: Use `maxBudgetUsd` to prevent runaway costs
|
||||
|
||||
3. **Run on Releases Only**: In CI/CD, trigger translations only on main branch or releases
|
||||
|
||||
4. **Review Translations**: Automated translations are good but not perfect - consider human review for critical docs
|
||||
|
||||
5. **Cache Results**: Don't re-translate unchanged content - check if README changed before running
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
Typical costs per language (varies by README length):
|
||||
- Short README (~500 words): ~$0.01-0.02
|
||||
- Medium README (~2000 words): ~$0.05-0.10
|
||||
- Long README (~5000 words): ~$0.15-0.25
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { translateReadme, SUPPORTED_LANGUAGES } from "./index.ts";
|
||||
|
||||
interface CliArgs {
|
||||
source: string;
|
||||
languages: string[];
|
||||
outputDir?: string;
|
||||
pattern?: string;
|
||||
preserveCode: boolean;
|
||||
model?: string;
|
||||
maxBudget?: number;
|
||||
verbose: boolean;
|
||||
force: boolean;
|
||||
parallel: number;
|
||||
help: boolean;
|
||||
listLanguages: boolean;
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log(`
|
||||
readme-translator - Translate README.md files using Claude Agent SDK
|
||||
|
||||
AUTHENTICATION:
|
||||
If Claude Code is installed and authenticated (Pro/Max subscription),
|
||||
no API key is needed. Otherwise, set ANTHROPIC_API_KEY environment variable.
|
||||
|
||||
USAGE:
|
||||
translate-readme [options] <source> <languages...>
|
||||
translate-readme --help
|
||||
translate-readme --list-languages
|
||||
|
||||
ARGUMENTS:
|
||||
source Path to the source README.md file
|
||||
languages Target language codes (e.g., es fr de ja zh)
|
||||
|
||||
OPTIONS:
|
||||
-o, --output <dir> Output directory (default: same as source)
|
||||
-p, --pattern <pat> Output filename pattern (default: README.{lang}.md)
|
||||
--no-preserve-code Translate code blocks too (not recommended)
|
||||
-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
|
||||
|
||||
EXAMPLES:
|
||||
# Translate to Spanish and French
|
||||
translate-readme README.md es fr
|
||||
|
||||
# Translate to multiple languages with custom output
|
||||
translate-readme -v -o ./i18n --pattern docs.{lang}.md README.md de ja ko zh
|
||||
|
||||
# Use in npm scripts
|
||||
# package.json: "translate": "translate-readme README.md es fr de"
|
||||
|
||||
SUPPORTED LANGUAGES:
|
||||
Run with --list-languages to see all supported language codes
|
||||
`);
|
||||
}
|
||||
|
||||
function printLanguages(): void {
|
||||
const LANGUAGE_NAMES: Record<string, string> = {
|
||||
// Tier 1 - No-brainers
|
||||
zh: "Chinese (Simplified)",
|
||||
ja: "Japanese",
|
||||
"pt-br": "Brazilian Portuguese",
|
||||
ko: "Korean",
|
||||
es: "Spanish",
|
||||
de: "German",
|
||||
fr: "French",
|
||||
// Tier 2 - Strong tech scenes
|
||||
he: "Hebrew",
|
||||
ar: "Arabic",
|
||||
ru: "Russian",
|
||||
pl: "Polish",
|
||||
cs: "Czech",
|
||||
nl: "Dutch",
|
||||
tr: "Turkish",
|
||||
uk: "Ukrainian",
|
||||
// Tier 3 - Emerging/Growing fast
|
||||
vi: "Vietnamese",
|
||||
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)",
|
||||
};
|
||||
|
||||
console.log("\nSupported Language Codes:\n");
|
||||
const sorted = Object.entries(LANGUAGE_NAMES).sort((a, b) =>
|
||||
a[1].localeCompare(b[1])
|
||||
);
|
||||
for (const [code, name] of sorted) {
|
||||
console.log(` ${code.padEnd(8)} ${name}`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]): CliArgs {
|
||||
const args: CliArgs = {
|
||||
source: "",
|
||||
languages: [],
|
||||
preserveCode: true,
|
||||
verbose: false,
|
||||
force: false,
|
||||
parallel: 1,
|
||||
help: false,
|
||||
listLanguages: false,
|
||||
};
|
||||
|
||||
const positional: string[] = [];
|
||||
let i = 2; // Skip node and script path
|
||||
|
||||
while (i < argv.length) {
|
||||
const arg = argv[i];
|
||||
|
||||
switch (arg) {
|
||||
case "-h":
|
||||
case "--help":
|
||||
args.help = true;
|
||||
break;
|
||||
case "--list-languages":
|
||||
args.listLanguages = true;
|
||||
break;
|
||||
case "-v":
|
||||
case "--verbose":
|
||||
args.verbose = true;
|
||||
break;
|
||||
case "-f":
|
||||
case "--force":
|
||||
args.force = true;
|
||||
break;
|
||||
case "--no-preserve-code":
|
||||
args.preserveCode = false;
|
||||
break;
|
||||
case "-o":
|
||||
case "--output":
|
||||
args.outputDir = argv[++i];
|
||||
break;
|
||||
case "-p":
|
||||
case "--pattern":
|
||||
args.pattern = argv[++i];
|
||||
break;
|
||||
case "-m":
|
||||
case "--model":
|
||||
args.model = argv[++i];
|
||||
break;
|
||||
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}`);
|
||||
process.exit(1);
|
||||
}
|
||||
positional.push(arg);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (positional.length > 0) {
|
||||
args.source = positional[0];
|
||||
args.languages = positional.slice(1);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const args = parseArgs(process.argv);
|
||||
|
||||
if (args.help) {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args.listLanguages) {
|
||||
printLanguages();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!args.source) {
|
||||
console.error("Error: No source file specified");
|
||||
console.error("Run with --help for usage information");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (args.languages.length === 0) {
|
||||
console.error("Error: No target languages specified");
|
||||
console.error("Run with --help for usage information");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate language codes
|
||||
const invalidLangs = args.languages.filter(
|
||||
(lang) => !SUPPORTED_LANGUAGES.includes(lang.toLowerCase())
|
||||
);
|
||||
if (invalidLangs.length > 0) {
|
||||
console.error(`Error: Unknown language codes: ${invalidLangs.join(", ")}`);
|
||||
console.error("Run with --list-languages to see supported codes");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await translateReadme({
|
||||
source: args.source,
|
||||
languages: args.languages,
|
||||
outputDir: args.outputDir,
|
||||
pattern: args.pattern,
|
||||
preserveCode: args.preserveCode,
|
||||
model: args.model,
|
||||
maxBudgetUsd: args.maxBudget,
|
||||
verbose: args.verbose,
|
||||
force: args.force,
|
||||
parallel: args.parallel,
|
||||
});
|
||||
|
||||
// Exit with error code if any translations failed
|
||||
if (result.failed > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Translation failed:",
|
||||
error instanceof Error ? error.message : error
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Example: Using readme-translator in build scripts
|
||||
*
|
||||
* These examples show how to integrate the translator into your build pipeline.
|
||||
*/
|
||||
|
||||
import { translateReadme, TranslationJobResult, SUPPORTED_LANGUAGES } from "./index.js";
|
||||
|
||||
// Example 1: Simple usage - translate to a few common languages
|
||||
async function translateToCommonLanguages(): Promise<void> {
|
||||
const result = await translateReadme({
|
||||
source: "./README.md",
|
||||
languages: ["es", "fr", "de", "ja", "zh"],
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
console.log(`Translated to ${result.successful} languages`);
|
||||
}
|
||||
|
||||
// Example 2: Full i18n setup with custom output directory
|
||||
async function fullI18nSetup(): Promise<void> {
|
||||
const result = await translateReadme({
|
||||
source: "./README.md",
|
||||
languages: ["es", "fr", "de", "it", "pt", "ja", "ko", "zh", "ru", "ar"],
|
||||
outputDir: "./docs/i18n",
|
||||
pattern: "README.{lang}.md",
|
||||
preserveCode: true,
|
||||
model: "sonnet",
|
||||
maxBudgetUsd: 5.0, // Cap spending at $5
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Handle results programmatically
|
||||
for (const r of result.results) {
|
||||
if (!r.success) {
|
||||
console.error(`Failed to translate to ${r.language}: ${r.error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example 3: Build script integration with error handling
|
||||
// Note: If Claude Code is authenticated, no API key needed locally.
|
||||
// CI/CD environments will need ANTHROPIC_API_KEY set.
|
||||
async function buildScriptIntegration(): Promise<number> {
|
||||
try {
|
||||
const result = await translateReadme({
|
||||
source: process.env.README_PATH || "./README.md",
|
||||
languages: (process.env.TRANSLATE_LANGS || "es,fr,de").split(","),
|
||||
outputDir: process.env.I18N_OUTPUT || "./i18n",
|
||||
verbose: process.env.CI !== "true", // Quiet in CI
|
||||
});
|
||||
|
||||
// Return exit code for build scripts
|
||||
return result.failed > 0 ? 1 : 0;
|
||||
} catch (error) {
|
||||
console.error("Translation failed:", error);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Example 4: Batch translation of multiple READMEs
|
||||
async function batchTranslation(): Promise<void> {
|
||||
const readmes = [
|
||||
"./README.md",
|
||||
"./packages/core/README.md",
|
||||
"./packages/cli/README.md",
|
||||
];
|
||||
|
||||
const languages = ["es", "fr", "de"];
|
||||
|
||||
for (const readme of readmes) {
|
||||
console.log(`\nProcessing: ${readme}`);
|
||||
await translateReadme({
|
||||
source: readme,
|
||||
languages,
|
||||
verbose: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Example 5: Custom output pattern for docs sites
|
||||
async function docsiteSetup(): Promise<void> {
|
||||
// For docusaurus/vitepress style: docs/README.es.md
|
||||
await translateReadme({
|
||||
source: "./README.md",
|
||||
languages: ["es", "fr", "de", "ja", "zh"],
|
||||
outputDir: "./docs",
|
||||
pattern: "README.{lang}.md",
|
||||
verbose: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Example 6: Conditional translation in CI/CD
|
||||
async function cicdTranslation(): Promise<void> {
|
||||
// Only translate on main branch releases
|
||||
const isRelease = process.env.GITHUB_REF === "refs/heads/main";
|
||||
const isManualTrigger = process.env.GITHUB_EVENT_NAME === "workflow_dispatch";
|
||||
|
||||
if (!isRelease && !isManualTrigger) {
|
||||
console.log("Skipping translation - not a release build");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await translateReadme({
|
||||
source: "./README.md",
|
||||
languages: ["es", "fr", "de", "ja", "ko", "zh", "pt-br"],
|
||||
outputDir: "./dist/i18n",
|
||||
maxBudgetUsd: 10.0,
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Write summary for GitHub Actions
|
||||
if (process.env.GITHUB_STEP_SUMMARY) {
|
||||
const summary = `
|
||||
## Translation Summary
|
||||
- ✅ Successful: ${result.successful}
|
||||
- ❌ Failed: ${result.failed}
|
||||
- 💰 Cost: $${result.totalCostUsd.toFixed(4)}
|
||||
`;
|
||||
// In real usage, write to GITHUB_STEP_SUMMARY
|
||||
console.log(summary);
|
||||
}
|
||||
}
|
||||
|
||||
// Run an example
|
||||
const example = process.argv[2];
|
||||
|
||||
switch (example) {
|
||||
case "simple":
|
||||
translateToCommonLanguages();
|
||||
break;
|
||||
case "full":
|
||||
fullI18nSetup();
|
||||
break;
|
||||
case "batch":
|
||||
batchTranslation();
|
||||
break;
|
||||
case "docs":
|
||||
docsiteSetup();
|
||||
break;
|
||||
case "ci":
|
||||
cicdTranslation();
|
||||
break;
|
||||
default:
|
||||
console.log("Available examples: simple, full, batch, docs, ci");
|
||||
console.log("\nSupported languages:", SUPPORTED_LANGUAGES.join(", "));
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
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 */
|
||||
source: string;
|
||||
/** Target languages (e.g., ['es', 'fr', 'de', 'ja', 'zh']) */
|
||||
languages: string[];
|
||||
/** Output directory (defaults to same directory as source) */
|
||||
outputDir?: string;
|
||||
/** Output filename pattern (use {lang} placeholder, defaults to 'README.{lang}.md') */
|
||||
pattern?: string;
|
||||
/** Preserve code blocks without translation */
|
||||
preserveCode?: boolean;
|
||||
/** Model to use (defaults to 'sonnet') */
|
||||
model?: string;
|
||||
/** Maximum budget in USD for the entire translation job */
|
||||
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 {
|
||||
language: string;
|
||||
outputPath: string;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
costUsd?: number;
|
||||
/** Whether this was served from cache */
|
||||
cached?: boolean;
|
||||
}
|
||||
|
||||
export interface TranslationJobResult {
|
||||
results: TranslationResult[];
|
||||
totalCostUsd: number;
|
||||
successful: number;
|
||||
failed: number;
|
||||
}
|
||||
|
||||
const LANGUAGE_NAMES: Record<string, string> = {
|
||||
// Tier 1 - No-brainers
|
||||
zh: "Chinese (Simplified)",
|
||||
ja: "Japanese",
|
||||
"pt-br": "Brazilian Portuguese",
|
||||
ko: "Korean",
|
||||
es: "Spanish",
|
||||
de: "German",
|
||||
fr: "French",
|
||||
// Tier 2 - Strong tech scenes
|
||||
he: "Hebrew",
|
||||
ar: "Arabic",
|
||||
ru: "Russian",
|
||||
pl: "Polish",
|
||||
cs: "Czech",
|
||||
nl: "Dutch",
|
||||
tr: "Turkish",
|
||||
uk: "Ukrainian",
|
||||
// Tier 3 - Emerging/Growing fast
|
||||
vi: "Vietnamese",
|
||||
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)",
|
||||
};
|
||||
|
||||
function getLanguageName(code: string): string {
|
||||
return LANGUAGE_NAMES[code.toLowerCase()] || code;
|
||||
}
|
||||
|
||||
async function translateToLanguage(
|
||||
content: string,
|
||||
targetLang: string,
|
||||
options: Pick<TranslationOptions, "preserveCode" | "model" | "verbose">
|
||||
): Promise<{ translation: string; costUsd: number }> {
|
||||
const languageName = getLanguageName(targetLang);
|
||||
|
||||
const preserveCodeInstructions = options.preserveCode
|
||||
? `
|
||||
IMPORTANT: Preserve all code blocks exactly as they are. Do NOT translate:
|
||||
- Code inside \`\`\` blocks
|
||||
- Inline code inside \` backticks
|
||||
- Command examples
|
||||
- File paths
|
||||
- Variable names, function names, and technical identifiers
|
||||
- URLs and links
|
||||
`
|
||||
: "";
|
||||
|
||||
const prompt = `Translate the following README.md content from English to ${languageName} (${targetLang}).
|
||||
|
||||
${preserveCodeInstructions}
|
||||
Guidelines:
|
||||
- Maintain all Markdown formatting (headers, lists, links, etc.)
|
||||
- Keep the same document structure
|
||||
- Translate headings, descriptions, and explanatory text naturally
|
||||
- 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:
|
||||
|
||||
---
|
||||
${content}
|
||||
---
|
||||
|
||||
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;
|
||||
let charCount = 0;
|
||||
const startTime = Date.now();
|
||||
|
||||
const stream = query({
|
||||
prompt,
|
||||
options: {
|
||||
model: options.model || "sonnet",
|
||||
systemPrompt: `You are an expert technical translator specializing in software documentation.
|
||||
You translate README files while preserving Markdown formatting and technical accuracy.
|
||||
Always output only the translated content without any surrounding explanation.`,
|
||||
permissionMode: "bypassPermissions",
|
||||
allowDangerouslySkipPermissions: true,
|
||||
includePartialMessages: true, // Enable streaming events
|
||||
},
|
||||
});
|
||||
|
||||
// Progress spinner frames
|
||||
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
let spinnerIdx = 0;
|
||||
|
||||
for await (const message of stream) {
|
||||
// Handle streaming text deltas
|
||||
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) {
|
||||
translation += event.delta.text;
|
||||
charCount += event.delta.text.length;
|
||||
|
||||
if (options.verbose) {
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
const spinner = spinnerFrames[spinnerIdx++ % spinnerFrames.length];
|
||||
process.stdout.write(`\r ${spinner} Translating... ${charCount} chars (${elapsed}s)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle full assistant messages (fallback)
|
||||
if (message.type === "assistant") {
|
||||
for (const block of message.message.content) {
|
||||
if (block.type === "text" && !translation) {
|
||||
translation = block.text;
|
||||
charCount = translation.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === "result") {
|
||||
const result = message as SDKResultMessage;
|
||||
if (result.subtype === "success") {
|
||||
costUsd = result.total_cost_usd;
|
||||
// Use the result text if we didn't get it from streaming
|
||||
if (!translation && result.result) {
|
||||
translation = result.result;
|
||||
charCount = translation.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the progress line
|
||||
if (options.verbose) {
|
||||
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
||||
}
|
||||
|
||||
// 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(
|
||||
options: TranslationOptions
|
||||
): Promise<TranslationJobResult> {
|
||||
const {
|
||||
source,
|
||||
languages,
|
||||
outputDir,
|
||||
pattern = "README.{lang}.md",
|
||||
preserveCode = true,
|
||||
model,
|
||||
maxBudgetUsd,
|
||||
verbose = false,
|
||||
force = false,
|
||||
parallel = 1,
|
||||
} = options;
|
||||
|
||||
// Read source file
|
||||
const sourcePath = path.resolve(source);
|
||||
const content = await fs.readFile(sourcePath, "utf-8");
|
||||
|
||||
// Determine output directory
|
||||
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;
|
||||
|
||||
if (verbose) {
|
||||
console.log(`📖 Source: ${sourcePath}`);
|
||||
console.log(`📂 Output: ${outDir}`);
|
||||
console.log(`🌍 Languages: ${languages.join(", ")}`);
|
||||
if (parallel > 1) {
|
||||
console.log(`⚡ Parallel: ${parallel} concurrent translations`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
|
||||
// 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})...`);
|
||||
}
|
||||
|
||||
try {
|
||||
const { translation, costUsd } = await translateToLanguage(content, lang, {
|
||||
preserveCode,
|
||||
model,
|
||||
verbose: verbose && parallel === 1, // Only show progress spinner for sequential
|
||||
});
|
||||
|
||||
await fs.writeFile(outputPath, translation, "utf-8");
|
||||
|
||||
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);
|
||||
if (verbose) {
|
||||
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;
|
||||
|
||||
if (verbose) {
|
||||
console.log("");
|
||||
console.log(`📊 Summary: ${successful} succeeded, ${failed} failed`);
|
||||
console.log(`💰 Total cost: $${totalCostUsd.toFixed(4)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
results,
|
||||
totalCostUsd,
|
||||
successful,
|
||||
failed,
|
||||
};
|
||||
}
|
||||
|
||||
// Export language codes for convenience
|
||||
export const SUPPORTED_LANGUAGES = Object.keys(LANGUAGE_NAMES);
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ProcessManager } from '../services/process/ProcessManager.js';
|
||||
import { getWorkerPort } from '../shared/worker-utils.js';
|
||||
|
||||
const command = process.argv[2];
|
||||
const port = getWorkerPort();
|
||||
|
||||
async function main() {
|
||||
switch (command) {
|
||||
case 'start': {
|
||||
const result = await ProcessManager.start(port);
|
||||
if (result.success) {
|
||||
console.log(`Worker started (PID: ${result.pid})`);
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
console.log(`Logs: ~/.claude-mem/logs/worker-${date}.log`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error(`Failed to start: ${result.error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'stop': {
|
||||
await ProcessManager.stop();
|
||||
console.log('Worker stopped');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
case 'restart': {
|
||||
const result = await ProcessManager.restart(port);
|
||||
if (result.success) {
|
||||
console.log(`Worker restarted (PID: ${result.pid})`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error(`Failed to restart: ${result.error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'status': {
|
||||
const status = await ProcessManager.status();
|
||||
if (status.running) {
|
||||
console.log('Worker is running');
|
||||
console.log(` PID: ${status.pid}`);
|
||||
console.log(` Port: ${status.port}`);
|
||||
console.log(` Uptime: ${status.uptime}`);
|
||||
} else {
|
||||
console.log('Worker is not running');
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
default:
|
||||
console.log('Usage: worker-cli.js <start|stop|restart|status>');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -7,14 +7,11 @@
|
||||
*/
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { silentDebug } from '../utils/silent-debug.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
|
||||
export interface SessionEndInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
transcript_path?: string;
|
||||
hook_event_name: string;
|
||||
reason: 'exit' | 'clear' | 'logout' | 'prompt_input_exit' | 'other';
|
||||
}
|
||||
|
||||
@@ -22,24 +19,11 @@ export interface SessionEndInput {
|
||||
* Cleanup Hook Main Logic - Fire-and-forget HTTP client
|
||||
*/
|
||||
async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
silentDebug('[cleanup-hook] Hook fired', {
|
||||
session_id: input?.session_id,
|
||||
cwd: input?.cwd,
|
||||
reason: input?.reason
|
||||
});
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
// Handle standalone execution (no input provided)
|
||||
if (!input) {
|
||||
console.log('No input provided - this script is designed to run as a Claude Code SessionEnd hook');
|
||||
console.log('\nExpected input format:');
|
||||
console.log(JSON.stringify({
|
||||
session_id: "string",
|
||||
cwd: "string",
|
||||
transcript_path: "string",
|
||||
hook_event_name: "SessionEnd",
|
||||
reason: "exit"
|
||||
}, null, 2));
|
||||
process.exit(0);
|
||||
throw new Error('cleanup-hook requires input from Claude Code');
|
||||
}
|
||||
|
||||
const { session_id, reason } = input;
|
||||
@@ -55,21 +39,15 @@ async function cleanupHook(input?: SessionEndInput): Promise<void> {
|
||||
claudeSessionId: session_id,
|
||||
reason
|
||||
}),
|
||||
signal: AbortSignal.timeout(2000)
|
||||
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
silentDebug('[cleanup-hook] Session cleanup completed', result);
|
||||
} else {
|
||||
if (!response.ok) {
|
||||
// Non-fatal - session might not exist
|
||||
silentDebug('[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
|
||||
silentDebug('[cleanup-hook] Worker not reachable (non-critical)', {
|
||||
error: error.message
|
||||
});
|
||||
// Worker might not be running - that's okay (non-critical)
|
||||
}
|
||||
|
||||
console.log('{"continue": true, "suppressOutput": true}');
|
||||
|
||||
+29
-34
@@ -8,51 +8,46 @@
|
||||
|
||||
import path from "path";
|
||||
import { stdin } from "process";
|
||||
import { execSync } from "child_process";
|
||||
import { getWorkerPort } from "../shared/worker-utils.js";
|
||||
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";
|
||||
|
||||
export interface SessionStartInput {
|
||||
session_id?: string;
|
||||
transcript_path?: string;
|
||||
cwd?: string;
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
cwd: string;
|
||||
hook_event_name?: string;
|
||||
source?: "startup" | "resume" | "clear" | "compact";
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
async function waitForPort(port: number, maxWaitMs: number = 10000): Promise<boolean> {
|
||||
const startTime = Date.now();
|
||||
const pollInterval = 100;
|
||||
|
||||
while (Date.now() - startTime < maxWaitMs) {
|
||||
try {
|
||||
execSync(`curl -s -f -m 1 "http://127.0.0.1:${port}/api/health" > /dev/null 2>&1`, {
|
||||
timeout: 1000,
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function contextHook(input?: SessionStartInput): Promise<string> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const cwd = input?.cwd ?? process.cwd();
|
||||
const project = cwd ? path.basename(cwd) : "unknown-project";
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Wait for worker to be available
|
||||
const isAvailable = await waitForPort(port);
|
||||
if (!isAvailable) {
|
||||
throw new Error(
|
||||
`Worker service not available on port ${port} after 10s. Try: npm run worker:restart`
|
||||
);
|
||||
}
|
||||
|
||||
const url = `http://127.0.0.1:${port}/api/context/inject?project=${encodeURIComponent(project)}`;
|
||||
const result = execSync(`curl -s "${url}"`, { encoding: "utf-8", timeout: 5000 });
|
||||
return result.trim();
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT) });
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
handleFetchError(response, errorText, {
|
||||
hookName: 'context',
|
||||
operation: 'Context generation',
|
||||
project,
|
||||
port
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.text();
|
||||
return result.trim();
|
||||
} catch (error: any) {
|
||||
handleWorkerError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Point - handle stdin/stdout
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type HookType = 'PreCompact' | 'SessionStart' | 'UserPromptSubmit' | 'PostToolUse' | 'Stop' | string;
|
||||
export type HookType = 'SessionStart' | 'UserPromptSubmit' | 'PostToolUse' | 'Stop';
|
||||
|
||||
export interface HookResponseOptions {
|
||||
reason?: string;
|
||||
@@ -20,21 +20,6 @@ function buildHookResponse(
|
||||
success: boolean,
|
||||
options: HookResponseOptions
|
||||
): HookResponse {
|
||||
if (hookType === 'PreCompact') {
|
||||
if (success) {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
continue: false,
|
||||
stopReason: options.reason || 'Pre-compact operation failed',
|
||||
suppressOutput: true
|
||||
};
|
||||
}
|
||||
|
||||
if (hookType === 'SessionStart') {
|
||||
if (success && options.context) {
|
||||
return {
|
||||
|
||||
+57
-102
@@ -1,51 +1,14 @@
|
||||
/**
|
||||
* New Hook - UserPromptSubmit
|
||||
*
|
||||
* DUAL PURPOSE HOOK: Handles BOTH session initialization AND continuation
|
||||
* ==========================================================================
|
||||
*
|
||||
* CRITICAL ARCHITECTURE FACTS (NEVER FORGET):
|
||||
*
|
||||
* 1. SESSION ID THREADING - The Single Source of Truth
|
||||
* - Claude Code assigns ONE session_id per conversation
|
||||
* - ALL hooks in that conversation receive the SAME session_id
|
||||
* - We ALWAYS use this session_id - NEVER generate our own
|
||||
* - This is how NEW hook, SAVE hook, and SUMMARY hook stay connected
|
||||
*
|
||||
* 2. NO EXISTENCE CHECKS NEEDED
|
||||
* - createSDKSession is idempotent (INSERT OR IGNORE)
|
||||
* - Prompt #1: Creates new database row, returns new ID
|
||||
* - Prompt #2+: Row exists, returns existing ID
|
||||
* - We NEVER need to check "does session exist?" - just use the session_id
|
||||
*
|
||||
* 3. CONTINUATION LOGIC LOCATION
|
||||
* - This hook does NOT contain continuation prompt logic
|
||||
* - That lives in SDKAgent.ts (lines 125-127)
|
||||
* - SDKAgent checks promptNumber to choose init vs continuation prompt
|
||||
* - BOTH prompts receive the SAME session_id from this hook
|
||||
*
|
||||
* 4. UNIFIED WITH SAVE HOOK
|
||||
* - SAVE hook uses: db.createSDKSession(session_id, '', '')
|
||||
* - NEW hook uses: db.createSDKSession(session_id, project, prompt)
|
||||
* - Both use session_id from hook context - this keeps everything connected
|
||||
*
|
||||
* This is KISS in action: Use the session_id we're given, trust idempotent
|
||||
* database operations, and let SDKAgent handle init vs continuation logic.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { stdin } from 'process';
|
||||
import { SessionStore } from '../services/sqlite/SessionStore.js';
|
||||
import { createHookResponse } from './hook-response.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { silentDebug } from '../utils/silent-debug.js';
|
||||
import { stripMemoryTagsFromPrompt } from '../utils/tag-stripping.js';
|
||||
import { handleWorkerError } from '../shared/hook-error-handler.js';
|
||||
import { handleFetchError } from './shared/error-handler.js';
|
||||
|
||||
export interface UserPromptSubmitInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
prompt: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,93 +16,85 @@ export interface UserPromptSubmitInput {
|
||||
* New Hook Main Logic
|
||||
*/
|
||||
async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
if (!input) {
|
||||
throw new Error('newHook requires input');
|
||||
}
|
||||
|
||||
const { session_id, cwd, prompt } = input;
|
||||
|
||||
// Debug: Log what we received
|
||||
silentDebug('[new-hook] Input received', {
|
||||
session_id,
|
||||
cwd,
|
||||
cwd_type: typeof cwd,
|
||||
cwd_length: cwd?.length,
|
||||
has_cwd: !!cwd,
|
||||
prompt_length: prompt?.length
|
||||
});
|
||||
|
||||
const project = path.basename(cwd);
|
||||
|
||||
silentDebug('[new-hook] Project extracted', {
|
||||
project,
|
||||
project_type: typeof project,
|
||||
project_length: project?.length,
|
||||
is_empty: project === '',
|
||||
cwd_was: cwd
|
||||
});
|
||||
|
||||
// Ensure worker is running
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const db = new SessionStore();
|
||||
|
||||
// CRITICAL: Use session_id from hook as THE source of truth
|
||||
// createSDKSession is idempotent - creates new or returns existing
|
||||
// This is how ALL hooks stay connected to the same session
|
||||
const sessionDbId = db.createSDKSession(session_id, project, prompt);
|
||||
const promptNumber = db.incrementPromptCounter(sessionDbId);
|
||||
|
||||
// Strip memory tags before saving user prompt to prevent privacy leaks
|
||||
// Tags like <private> and <claude-mem-context> should not be stored or searchable
|
||||
const cleanedUserPrompt = stripMemoryTagsFromPrompt(prompt);
|
||||
|
||||
// Skip memory operations for fully private prompts
|
||||
// If the entire prompt was wrapped in <private> tags, don't create any observations
|
||||
if (!cleanedUserPrompt || cleanedUserPrompt.trim() === '') {
|
||||
silentDebug('[new-hook] Prompt entirely private, skipping memory operations', {
|
||||
session_id,
|
||||
promptNumber,
|
||||
originalLength: prompt.length
|
||||
});
|
||||
db.close();
|
||||
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber} (fully private - skipped)`);
|
||||
console.log(createHookResponse('UserPromptSubmit', true));
|
||||
return;
|
||||
}
|
||||
|
||||
db.saveUserPrompt(session_id, promptNumber, cleanedUserPrompt);
|
||||
|
||||
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber}`);
|
||||
|
||||
db.close();
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Initialize session via HTTP - handles DB operations and privacy checks
|
||||
let sessionDbId: number;
|
||||
let promptNumber: number;
|
||||
|
||||
try {
|
||||
const initResponse = await fetch(`http://127.0.0.1:${port}/api/sessions/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
claudeSessionId: session_id,
|
||||
project,
|
||||
prompt
|
||||
}),
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
if (!initResponse.ok) {
|
||||
const errorText = await initResponse.text();
|
||||
handleFetchError(initResponse, errorText, {
|
||||
hookName: 'new',
|
||||
operation: 'Session initialization',
|
||||
project,
|
||||
port
|
||||
});
|
||||
}
|
||||
|
||||
const initResult = await initResponse.json();
|
||||
sessionDbId = initResult.sessionDbId;
|
||||
promptNumber = initResult.promptNumber;
|
||||
|
||||
// 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));
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`[new-hook] Session ${sessionDbId}, prompt #${promptNumber}`);
|
||||
} catch (error: any) {
|
||||
handleWorkerError(error);
|
||||
}
|
||||
|
||||
// Strip leading slash from commands for memory agent
|
||||
// /review 101 → review 101 (more semantic for observations)
|
||||
const cleanedPrompt = prompt.startsWith('/') ? prompt.substring(1) : prompt;
|
||||
|
||||
try {
|
||||
// Initialize session via HTTP
|
||||
// Initialize SDK agent session via HTTP (starts the agent!)
|
||||
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ project, userPrompt: cleanedPrompt, promptNumber }),
|
||||
body: JSON.stringify({ userPrompt: cleanedPrompt, promptNumber }),
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to initialize session: ${response.status} ${errorText}`);
|
||||
handleFetchError(response, errorText, {
|
||||
hookName: 'new',
|
||||
operation: 'SDK agent start',
|
||||
project,
|
||||
port,
|
||||
sessionId: String(sessionDbId)
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Only show restart message for connection errors, not HTTP errors
|
||||
if (error.cause?.code === 'ECONNREFUSED' || error.name === 'TimeoutError' || error.message.includes('fetch failed')) {
|
||||
throw new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue");
|
||||
}
|
||||
// Re-throw HTTP errors and other errors as-is
|
||||
throw error;
|
||||
handleWorkerError(error);
|
||||
}
|
||||
|
||||
console.log(createHookResponse('UserPromptSubmit', true));
|
||||
|
||||
+22
-29
@@ -10,6 +10,9 @@ import { stdin } from 'process';
|
||||
import { createHookResponse } 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 { handleWorkerError } from '../shared/hook-error-handler.js';
|
||||
import { handleFetchError } from './shared/error-handler.js';
|
||||
|
||||
export interface PostToolUseInput {
|
||||
session_id: string;
|
||||
@@ -17,36 +20,21 @@ export interface PostToolUseInput {
|
||||
tool_name: string;
|
||||
tool_input: any;
|
||||
tool_response: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Tools to skip (low value or too frequent)
|
||||
const SKIP_TOOLS = new Set([
|
||||
'ListMcpResourcesTool', // MCP infrastructure
|
||||
'SlashCommand', // Command invocation (observe what it produces, not the call)
|
||||
'Skill', // Skill invocation (observe what it produces, not the call)
|
||||
'TodoWrite', // Task management meta-tool
|
||||
'AskUserQuestion' // User interaction, not substantive work
|
||||
]);
|
||||
|
||||
/**
|
||||
* Save Hook Main Logic - Fire-and-forget HTTP client
|
||||
*/
|
||||
async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
if (!input) {
|
||||
throw new Error('saveHook requires input');
|
||||
}
|
||||
|
||||
const { session_id, cwd, tool_name, tool_input, tool_response } = input;
|
||||
|
||||
if (SKIP_TOOLS.has(tool_name)) {
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure worker is running
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
const toolStr = logger.formatTool(tool_name, tool_input);
|
||||
@@ -65,26 +53,31 @@ async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
tool_name,
|
||||
tool_input,
|
||||
tool_response,
|
||||
cwd: cwd || ''
|
||||
cwd: cwd || logger.happyPathError(
|
||||
'HOOK',
|
||||
'Missing cwd in PostToolUse hook input',
|
||||
undefined,
|
||||
{ session_id, tool_name },
|
||||
''
|
||||
)
|
||||
}),
|
||||
signal: AbortSignal.timeout(2000)
|
||||
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 });
|
||||
} catch (error: any) {
|
||||
// Only show restart message for connection errors, not HTTP errors
|
||||
if (error.cause?.code === 'ECONNREFUSED' || error.name === 'TimeoutError' || error.message.includes('fetch failed')) {
|
||||
throw new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue");
|
||||
}
|
||||
throw error;
|
||||
handleWorkerError(error);
|
||||
}
|
||||
|
||||
console.log(createHookResponse('PostToolUse', true));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+39
-128
@@ -10,140 +10,45 @@
|
||||
*/
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { createHookResponse } 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 { handleWorkerError } from '../shared/hook-error-handler.js';
|
||||
import { handleFetchError } from './shared/error-handler.js';
|
||||
import { extractLastMessage } from '../shared/transcript-parser.js';
|
||||
|
||||
export interface StopInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
transcript_path?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract last user message from transcript JSONL file
|
||||
*/
|
||||
function extractLastUserMessage(transcriptPath: string): string {
|
||||
if (!transcriptPath || !existsSync(transcriptPath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(transcriptPath, 'utf-8').trim();
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Parse JSONL and find last user message
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
const line = JSON.parse(lines[i]);
|
||||
|
||||
// Claude Code transcript format: {type: "user", message: {role: "user", content: [...]}}
|
||||
if (line.type === 'user' && line.message?.content) {
|
||||
const content = line.message.content;
|
||||
|
||||
// Extract text content (handle both string and array formats)
|
||||
if (typeof content === 'string') {
|
||||
return content;
|
||||
} else if (Array.isArray(content)) {
|
||||
const textParts = content
|
||||
.filter((c: any) => c.type === 'text')
|
||||
.map((c: any) => c.text);
|
||||
return textParts.join('\n');
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
// Skip malformed lines
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('HOOK', 'Failed to read transcript', { transcriptPath }, error as Error);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract last assistant message from transcript JSONL file
|
||||
* Filters out system-reminder tags to avoid polluting summaries
|
||||
*/
|
||||
function extractLastAssistantMessage(transcriptPath: string): string {
|
||||
if (!transcriptPath || !existsSync(transcriptPath)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(transcriptPath, 'utf-8').trim();
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Parse JSONL and find last assistant message
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
const line = JSON.parse(lines[i]);
|
||||
|
||||
// Claude Code transcript format: {type: "assistant", message: {role: "assistant", content: [...]}}
|
||||
if (line.type === 'assistant' && line.message?.content) {
|
||||
let text = '';
|
||||
const content = line.message.content;
|
||||
|
||||
// Extract text content (handle both string and array formats)
|
||||
if (typeof content === 'string') {
|
||||
text = content;
|
||||
} else if (Array.isArray(content)) {
|
||||
const textParts = content
|
||||
.filter((c: any) => c.type === 'text')
|
||||
.map((c: any) => c.text);
|
||||
text = textParts.join('\n');
|
||||
}
|
||||
|
||||
// Filter out system-reminder tags and their content
|
||||
text = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '');
|
||||
|
||||
// Clean up excessive whitespace
|
||||
text = text.replace(/\n{3,}/g, '\n\n').trim();
|
||||
|
||||
return text;
|
||||
}
|
||||
} catch (parseError) {
|
||||
// Skip malformed lines
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('HOOK', 'Failed to read transcript', { transcriptPath }, error as Error);
|
||||
}
|
||||
|
||||
return '';
|
||||
transcript_path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary Hook Main Logic - Fire-and-forget HTTP client
|
||||
*/
|
||||
async function summaryHook(input?: StopInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
if (!input) {
|
||||
throw new Error('summaryHook requires input');
|
||||
}
|
||||
|
||||
const { session_id } = input;
|
||||
|
||||
// Ensure worker is running
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Extract last user AND assistant messages from transcript
|
||||
const lastUserMessage = extractLastUserMessage(input.transcript_path || '');
|
||||
const lastAssistantMessage = extractLastAssistantMessage(input.transcript_path || '');
|
||||
const transcriptPath = input.transcript_path || logger.happyPathError(
|
||||
'HOOK',
|
||||
'Missing transcript_path in Stop hook input',
|
||||
undefined,
|
||||
{ session_id },
|
||||
''
|
||||
);
|
||||
const lastUserMessage = extractLastMessage(transcriptPath, 'user');
|
||||
const lastAssistantMessage = extractLastMessage(transcriptPath, 'assistant', true);
|
||||
|
||||
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
||||
workerPort: port,
|
||||
@@ -161,31 +66,37 @@ async function summaryHook(input?: StopInput): Promise<void> {
|
||||
last_user_message: lastUserMessage,
|
||||
last_assistant_message: lastAssistantMessage
|
||||
}),
|
||||
signal: AbortSignal.timeout(2000)
|
||||
signal: AbortSignal.timeout(HOOK_TIMEOUTS.DEFAULT)
|
||||
});
|
||||
|
||||
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) {
|
||||
// Only show restart message for connection errors, not HTTP errors
|
||||
if (error.cause?.code === 'ECONNREFUSED' || error.name === 'TimeoutError' || error.message.includes('fetch failed')) {
|
||||
throw new Error("There's a problem with the worker. If you just updated, type `pm2 restart claude-mem-worker` in your terminal to continue");
|
||||
}
|
||||
throw error;
|
||||
handleWorkerError(error);
|
||||
} finally {
|
||||
// Notify worker to stop spinner (fire-and-forget)
|
||||
fetch(`http://127.0.0.1:${port}/api/processing`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ isProcessing: false })
|
||||
}).catch(() => {});
|
||||
// Stop processing spinner
|
||||
try {
|
||||
const spinnerResponse = await fetch(`http://127.0.0.1:${port}/api/processing`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ isProcessing: false }),
|
||||
signal: AbortSignal.timeout(2000)
|
||||
});
|
||||
if (!spinnerResponse.ok) {
|
||||
logger.warn('HOOK', 'Failed to stop spinner', { status: spinnerResponse.status });
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.warn('HOOK', 'Could not stop spinner', { error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
console.log(createHookResponse('Stop', true));
|
||||
|
||||
@@ -6,40 +6,15 @@
|
||||
* has been loaded into their session. Uses stderr as the communication channel
|
||||
* since it's currently the only way to display messages in Claude Code UI.
|
||||
*/
|
||||
import { join, basename } from "path";
|
||||
import { homedir } from "os";
|
||||
import { existsSync } from "fs";
|
||||
import { getWorkerPort } from "../shared/worker-utils.js";
|
||||
|
||||
// Check if node_modules exists - if not, this is first run
|
||||
const pluginDir = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
||||
const nodeModulesPath = join(pluginDir, 'node_modules');
|
||||
|
||||
if (!existsSync(nodeModulesPath)) {
|
||||
// First-time installation - dependencies not yet installed
|
||||
console.error(`
|
||||
---
|
||||
🎉 Note: This appears under Plugin Hook Error, but it's not an error. That's the only option for
|
||||
user messages in Claude Code UI until a better method is provided.
|
||||
---
|
||||
|
||||
⚠️ Claude-Mem: First-Time Setup
|
||||
|
||||
Dependencies have been installed in the background. This only happens once.
|
||||
|
||||
💡 TIPS:
|
||||
• Memories will start generating while you work
|
||||
• Use /init to write or update your CLAUDE.md for better project context
|
||||
• Try /clear after one session to see what context looks like
|
||||
|
||||
Thank you for installing Claude-Mem!
|
||||
|
||||
This message was not added to your startup context, so you can continue working as normal.
|
||||
`);
|
||||
process.exit(3);
|
||||
}
|
||||
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
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const port = getWorkerPort();
|
||||
const project = basename(process.cwd());
|
||||
|
||||
@@ -50,65 +25,41 @@ try {
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Worker error ${response.status}`);
|
||||
throw new Error(getWorkerRestartInstructions({ includeSkillFallback: true }));
|
||||
}
|
||||
|
||||
const output = await response.text();
|
||||
|
||||
// If it's after Dec 5, 2025 7pm EST, patch this out
|
||||
const now = new Date();
|
||||
const amaEndDate = new Date('2025-12-06T00:00:00Z'); // Dec 5, 2025 7pm EST
|
||||
|
||||
// Product Hunt launch announcement - expires Dec 5, 2025 12am EST (05:00 UTC)
|
||||
const phLaunchEndDate = new Date('2025-12-05T05:00:00Z');
|
||||
let productHuntAnnouncement = "";
|
||||
if (now < phLaunchEndDate) {
|
||||
productHuntAnnouncement = `
|
||||
|
||||
🚀 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🚀
|
||||
|
||||
We launched on Product Hunt!
|
||||
https://tinyurl.com/claude-mem-ph
|
||||
|
||||
⭐ Your upvote means the world - thank you!
|
||||
|
||||
🚀 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🚀
|
||||
`;
|
||||
}
|
||||
|
||||
let amaAnnouncement = "";
|
||||
if (now < amaEndDate) {
|
||||
// Check if we're during the live event (Dec 1-5, 5pm-7pm EST daily)
|
||||
const estOffset = 5 * 60; // EST is UTC-5
|
||||
const nowUtcMinutes = now.getUTCHours() * 60 + now.getUTCMinutes();
|
||||
const estHour = Math.floor((nowUtcMinutes - estOffset + 1440) % 1440 / 60);
|
||||
const day = now.getUTCDate();
|
||||
const month = now.getUTCMonth();
|
||||
const year = now.getUTCFullYear();
|
||||
|
||||
const isDec1to5 = year === 2025 && month === 11 && day >= 1 && day <= 5;
|
||||
const isDuringLiveHours = estHour >= 17 && estHour < 19; // 5pm-7pm EST
|
||||
|
||||
if (isDec1to5 && isDuringLiveHours) {
|
||||
amaAnnouncement = "\n 🔴 LIVE NOW: AMA w/ Dev (@thedotmack) until 7pm EST\n";
|
||||
} else {
|
||||
amaAnnouncement = "\n – LIVE AMA w/ Dev (@thedotmack) Dec 1st–5th, 5pm to 7pm EST\n";
|
||||
}
|
||||
}
|
||||
|
||||
console.error(
|
||||
"\n\n📝 Claude-Mem Context Loaded\n" +
|
||||
" ℹ️ Note: This appears as stderr but is informational only\n\n" +
|
||||
output +
|
||||
"\n\n💡 New! Wrap all or part of any message with <private> ... </private> to prevent storing sensitive information in your observation history.\n" +
|
||||
"\n💬 Community https://discord.gg/J4wttp9vDu" +
|
||||
productHuntAnnouncement +
|
||||
amaAnnouncement +
|
||||
`\n📺 Watch live in browser http://localhost:${port}/\n`
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to load context display: ${error}`);
|
||||
// Context not available yet - likely first run or worker starting up
|
||||
console.error(`
|
||||
---
|
||||
🎉 Note: This appears under Plugin Hook Error, but it's not an error. That's the only option for
|
||||
user messages in Claude Code UI until a better method is provided.
|
||||
---
|
||||
|
||||
⚠️ Claude-Mem: First-Time Setup
|
||||
|
||||
Dependencies are installing in the background. This only happens once.
|
||||
|
||||
💡 TIPS:
|
||||
• Memories will start generating while you work
|
||||
• Use /init to write or update your CLAUDE.md for better project context
|
||||
• Try /clear after one session to see what context looks like
|
||||
|
||||
Thank you for installing Claude-Mem!
|
||||
|
||||
This message was not added to your startup context, so you can continue working as normal.
|
||||
`);
|
||||
}
|
||||
|
||||
process.exit(3);
|
||||
process.exit(HOOK_EXIT_CODES.USER_MESSAGE_ONLY);
|
||||
+9
-1
@@ -3,6 +3,8 @@
|
||||
* Generates prompts for the Claude Agent SDK memory worker
|
||||
*/
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
export interface Observation {
|
||||
id: number;
|
||||
tool_name: string;
|
||||
@@ -175,7 +177,13 @@ export function buildObservationPrompt(obs: Observation): string {
|
||||
* Build prompt to generate progress summary
|
||||
*/
|
||||
export function buildSummaryPrompt(session: SDKSession): string {
|
||||
const lastAssistantMessage = session.last_assistant_message || '';
|
||||
const lastAssistantMessage = session.last_assistant_message || logger.happyPathError(
|
||||
'SDK',
|
||||
'Missing last_assistant_message in session for summary prompt',
|
||||
{ sessionId: session.id },
|
||||
undefined,
|
||||
''
|
||||
);
|
||||
|
||||
return `PROGRESS SUMMARY CHECKPOINT
|
||||
===========================
|
||||
|
||||
+189
-239
@@ -14,13 +14,15 @@ import {
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { z } from 'zod';
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||
import { silentDebug } from '../utils/silent-debug.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { getWorkerPort, getWorkerHost } from '../shared/worker-utils.js';
|
||||
|
||||
/**
|
||||
* Worker HTTP API configuration
|
||||
*/
|
||||
const WORKER_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || '37777', 10);
|
||||
const WORKER_BASE_URL = `http://localhost:${WORKER_PORT}`;
|
||||
const WORKER_PORT = getWorkerPort();
|
||||
const WORKER_HOST = getWorkerHost();
|
||||
const WORKER_BASE_URL = `http://${WORKER_HOST}:${WORKER_PORT}`;
|
||||
|
||||
/**
|
||||
* Map tool names to Worker HTTP endpoints
|
||||
@@ -28,18 +30,9 @@ 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'
|
||||
'progressive_description': '/api/instructions'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -49,7 +42,7 @@ async function callWorkerAPI(
|
||||
endpoint: string,
|
||||
params: Record<string, any>
|
||||
): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
|
||||
silentDebug('[search-server] → Worker API', { endpoint, params });
|
||||
logger.debug('SYSTEM', '→ Worker API', undefined, { endpoint, params });
|
||||
|
||||
try {
|
||||
const searchParams = new URLSearchParams();
|
||||
@@ -71,12 +64,100 @@ async function callWorkerAPI(
|
||||
|
||||
const data = await response.json() as { content: Array<{ type: 'text'; text: string }>; isError?: boolean };
|
||||
|
||||
silentDebug('[search-server] ← Worker API success', { endpoint });
|
||||
logger.debug('SYSTEM', '← Worker API success', undefined, { endpoint });
|
||||
|
||||
// Worker returns { content: [...] } format directly
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
silentDebug('[search-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,
|
||||
@@ -101,24 +182,24 @@ async function verifyWorkerConnection(): Promise<boolean> {
|
||||
|
||||
/**
|
||||
* Tool definitions with HTTP-based handlers
|
||||
* Descriptions removed - use progressive_description tool for parameter documentation
|
||||
*/
|
||||
const tools = [
|
||||
{
|
||||
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.',
|
||||
description: 'Search memory',
|
||||
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')
|
||||
query: z.string().optional(),
|
||||
type: z.enum(['observations', 'sessions', 'prompts']).optional(),
|
||||
obs_type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional(),
|
||||
dateStart: z.union([z.string(), z.number()]).optional(),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional(),
|
||||
limit: z.number().min(1).max(100).default(20),
|
||||
offset: z.number().min(0).default(0),
|
||||
orderBy: z.enum(['relevance', 'date_desc', 'date_asc']).default('date_desc')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['search'];
|
||||
@@ -127,197 +208,33 @@ 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.',
|
||||
description: 'Timeline context',
|
||||
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')
|
||||
query: z.string().optional(),
|
||||
anchor: z.number().optional(),
|
||||
depth_before: z.number().min(0).max(100).default(10),
|
||||
depth_after: z.number().min(0).max(100).default(10),
|
||||
type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional()
|
||||
}),
|
||||
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.',
|
||||
description: 'Recent context',
|
||||
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)')
|
||||
limit: z.number().min(1).max(100).default(30),
|
||||
type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional(),
|
||||
dateStart: z.union([z.string(), z.number()]).optional(),
|
||||
dateEnd: z.union([z.string(), z.number()]).optional()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_recent_context'];
|
||||
@@ -326,16 +243,15 @@ 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.',
|
||||
description: 'Timeline around ID',
|
||||
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')
|
||||
anchor: z.number(),
|
||||
depth_before: z.number().min(0).max(100).default(10),
|
||||
depth_after: z.number().min(0).max(100).default(10),
|
||||
type: z.string().optional(),
|
||||
concepts: z.string().optional(),
|
||||
files: z.string().optional(),
|
||||
project: z.string().optional()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_context_timeline'];
|
||||
@@ -343,24 +259,58 @@ const tools = [
|
||||
}
|
||||
},
|
||||
{
|
||||
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.',
|
||||
name: 'progressive_description',
|
||||
description: 'Usage help',
|
||||
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)')
|
||||
topic: z.enum(['workflow', 'search_params', 'examples', 'all']).default('all')
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
const endpoint = TOOL_ENDPOINT_MAP['get_timeline_by_query'];
|
||||
const endpoint = TOOL_ENDPOINT_MAP['progressive_description'];
|
||||
return await callWorkerAPI(endpoint, args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_observation',
|
||||
description: 'Fetch by ID',
|
||||
inputSchema: z.object({
|
||||
id: z.number()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/observation', args.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_batch_observations',
|
||||
description: 'Batch fetch',
|
||||
inputSchema: z.object({
|
||||
ids: z.array(z.number()),
|
||||
orderBy: z.enum(['date_desc', 'date_asc']).optional(),
|
||||
limit: z.number().optional(),
|
||||
project: z.string().optional()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIPost('/api/observations/batch', args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_session',
|
||||
description: 'Session by ID',
|
||||
inputSchema: z.object({
|
||||
id: z.number()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/session', args.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_prompt',
|
||||
description: 'Prompt by ID',
|
||||
inputSchema: z.object({
|
||||
id: z.number()
|
||||
}),
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIWithPath('/api/prompt', args.id);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -411,7 +361,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
|
||||
// Cleanup function
|
||||
async function cleanup() {
|
||||
silentDebug('[search-server] Shutting down...');
|
||||
logger.info('SYSTEM', 'MCP server shutting down');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -424,22 +374,22 @@ async function main() {
|
||||
// Start the MCP server
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
silentDebug('[search-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) {
|
||||
silentDebug('[search-server] WARNING: Worker not available at', WORKER_BASE_URL);
|
||||
silentDebug('[search-server] Tools will fail until Worker is started');
|
||||
silentDebug('[search-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: npm run worker:restart');
|
||||
} else {
|
||||
silentDebug('[search-server] Worker available at', WORKER_BASE_URL);
|
||||
logger.info('SYSTEM', 'Worker available', undefined, { workerUrl: WORKER_BASE_URL });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
silentDebug('[search-server] Fatal error:', error);
|
||||
logger.error('SYSTEM', 'Fatal error', undefined, error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,15 @@ import {
|
||||
TYPE_WORK_EMOJI_MAP
|
||||
} from '../constants/observation-metadata.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { SettingsDefaultsManager } from './worker/settings/SettingsDefaultsManager.js';
|
||||
import { SettingsDefaultsManager } from '../shared/SettingsDefaultsManager.js';
|
||||
import {
|
||||
parseJsonArray,
|
||||
formatDateTime,
|
||||
formatTime,
|
||||
formatDate,
|
||||
toRelativePath,
|
||||
extractFirstFile
|
||||
} from '../shared/timeline-formatting.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 +153,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 [];
|
||||
@@ -335,7 +292,7 @@ export async function generateContext(input?: ContextInput, useColors: boolean =
|
||||
priorAssistantMessage = messages.assistantMessage;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
// Expected: Transcript file may not exist or be readable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,20 +501,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