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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "claude-supermemory-dev",
"version": "1.0.0",
"description": "Build tools for claude-supermemory plugin",
"version": "2.0.0",
"description": "Claude code plugin by Supermemory AI",
"private": true,
"type": "commonjs",
"scripts": {
Expand Down
12 changes: 0 additions & 12 deletions plugin/.mcp.json

This file was deleted.

48 changes: 48 additions & 0 deletions plugin/commands/project-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
description: Update Supermemory project-level configuration
allowed-tools: ["Bash", "Read", "Edit", "AskUserQuestion"]
---

# Project Config

Update project-level config stored in `.claude/.supermemory-claude/config.json`.

## Steps

1. First, find the git root and read the current project config:

```bash
git_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "$(pwd)")
cat "$git_root/.claude/.supermemory-claude/config.json" 2>/dev/null || echo "{}"
```

2. Ask the user what they want to update using AskUserQuestion:
- **API Key** (`apiKey`): Project-level API key that overrides global
- **Personal Container Tag** (`personalContainerTag`): Used for saving and searching personal memories across sessions
- **Repo Container Tag** (`repoContainerTag`): Used for repo-level memories shared across team

3. Ask the user for the new value. Confirm the value they provide.

4. Update the project config file:

```bash
node -e "
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
let gitRoot;
try { gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim(); } catch { gitRoot = process.cwd(); }
const dir = path.join(gitRoot, '.claude', '.supermemory-claude');
const file = path.join(dir, 'config.json');
let data = {};
try { data = JSON.parse(fs.readFileSync(file, 'utf-8')); } catch {}
data['KEY_NAME'] = 'NEW_VALUE';
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(file, JSON.stringify(data, null, 2));
console.log('Updated: ' + file);
"
```

Replace `KEY_NAME` with `apiKey`, `personalContainerTag`, or `repoContainerTag` and `NEW_VALUE` with the user's provided value.

5. Confirm to the user the project configuration has been updated.
23 changes: 0 additions & 23 deletions plugin/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,6 @@
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/prompt-hook.cjs",
"timeout": 15
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|Bash|Task",
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/observation-hook.cjs",
"timeout": 15
}
]
}
],
"Stop": [
{
"hooks": [
Expand Down
52 changes: 46 additions & 6 deletions plugin/scripts/add-memory.cjs

Large diffs are not rendered by default.

94 changes: 74 additions & 20 deletions plugin/scripts/context-hook.cjs

Large diffs are not rendered by default.

58 changes: 0 additions & 58 deletions plugin/scripts/observation-hook.cjs

This file was deleted.

Large diffs are not rendered by default.

82 changes: 74 additions & 8 deletions plugin/scripts/search-memory.cjs

Large diffs are not rendered by default.

77 changes: 54 additions & 23 deletions plugin/scripts/summary-hook.cjs

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions plugin/skills/super-save/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
name: super-save
description: Save important project knowledge to memory. Use when user wants to preserve architectural decisions, significant bug fixes, design patterns, or important implementation details for team reference.
allowed-tools: Bash(node *)
---

# Super Save

Save important project knowledge based on what the user wants to preserve.

## Step 1: Understand User Request

Analyze what the user is asking to save from the conversation.

## Step 2: Format Content

```
[SAVE:<username>:<date>]

<Username> wanted to <goal/problem>.

Claude suggested <approach/solution>.

<Username> decided to <decision made>.

<key details, files if relevant>

[/SAVE]
```

Example:
```
[SAVE:prasanna:2026-02-04]

Prasanna wanted to create a skill for saving project knowledge.

Claude suggested using a separate container tag (repo_<hash>) for shared team knowledge.

Prasanna decided to keep it simple - no transcript fetching, just save what user asks for.

Files: src/save-project-memory.js, src/lib/container-tag.js

[/SAVE]
```

Keep it natural. Capture the conversation flow.

## Step 3: Save

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/save-project-memory.cjs" "FORMATTED_CONTENT"
```
23 changes: 17 additions & 6 deletions plugin/skills/super-search/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,37 @@ Search Supermemory for past coding sessions, decisions, and saved information.

## How to Search

Run the search script with the user's query:
Run the search script with the user's query and optional scope flag:

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" "USER_QUERY_HERE"
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" [--user|--repo|--both] "USER_QUERY_HERE"
```

Replace `USER_QUERY_HERE` with what the user is searching for.
### Scope Flags

- `--both` (default): Search both personal session and project memories across team members in parallel
- `--user`: Search personal/user memories across sessions
- `--repo`: Search project/repo memories across team members

## Examples

- User asks "what did I work on yesterday":

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" "work yesterday recent activity"
```

- User asks "how did I implement auth":
- User asks "how did we implement auth" (project-specific):

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" --repo "authentication implementation"
```

- User asks "what are my coding preferences":
```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" "authentication implementation"
node "${CLAUDE_PLUGIN_ROOT}/scripts/search-memory.cjs" --user "coding preferences style"
```

## Present Results

The script outputs formatted memory results. Present them clearly to the user and offer to search again with different terms if needed.
The script outputs formatted memory results with timestamps and relevance scores. Present them clearly to the user and offer to search again with different terms if needed.
3 changes: 1 addition & 2 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ const OUT = path.join(ROOT, 'plugin', 'scripts');

const hooks = [
'context-hook',
'prompt-hook',
'observation-hook',
'summary-hook',
'search-memory',
'add-memory',
'save-project-memory',
];

async function build() {
Expand Down
20 changes: 14 additions & 6 deletions src/add-memory.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const { SupermemoryClient } = require('./lib/supermemory-client');
const {
SupermemoryClient,
PERSONAL_ENTITY_CONTEXT,
} = require('./lib/supermemory-client');
const { getContainerTag, getProjectName } = require('./lib/container-tag');
const { loadSettings, getApiKey } = require('./lib/settings');

Expand Down Expand Up @@ -29,11 +32,16 @@ async function main() {

try {
const client = new SupermemoryClient(apiKey, containerTag);
const result = await client.addMemory(content, containerTag, {
type: 'manual',
project: projectName,
timestamp: new Date().toISOString(),
});
const result = await client.addMemory(
content,
containerTag,
{
type: 'manual',
project: projectName,
timestamp: new Date().toISOString(),
},
{ entityContext: PERSONAL_ENTITY_CONTEXT },
);

console.log(`Memory saved to project: ${projectName}`);
console.log(`ID: ${result.id}`);
Expand Down
46 changes: 37 additions & 9 deletions src/context-hook.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
const { SupermemoryClient } = require('./lib/supermemory-client');
const { getContainerTag, getProjectName } = require('./lib/container-tag');
const {
getContainerTag,
getRepoContainerTag,
getProjectName,
} = require('./lib/container-tag');
const { loadSettings, getApiKey, debugLog } = require('./lib/settings');
const { readStdin, writeOutput } = require('./lib/stdin');
const { startAuthFlow } = require('./lib/auth');
const { formatContext } = require('./lib/format-context');
const { formatContext, combineContexts } = require('./lib/format-context');

async function main() {
const settings = loadSettings();

try {
const input = await readStdin();
const cwd = input.cwd || process.cwd();
const containerTag = getContainerTag(cwd);
const projectName = getProjectName(cwd);

debugLog(settings, 'SessionStart', { cwd, containerTag, projectName });
debugLog(settings, 'SessionStart', { cwd, projectName });

let apiKey;
try {
Expand All @@ -41,17 +44,40 @@ Or set SUPERMEMORY_CC_API_KEY environment variable manually.
}

const client = new SupermemoryClient(apiKey);
const profileResult = await client
.getProfile(containerTag, projectName)
.catch(() => null);
const personalTag = getContainerTag(cwd);
const repoTag = getRepoContainerTag(cwd);

const additionalContext = formatContext(
profileResult,
debugLog(settings, 'Fetching contexts', { personalTag, repoTag });

const [personalResult, repoResult] = await Promise.all([
client.getProfile(personalTag, projectName).catch(() => null),
client.getProfile(repoTag, projectName).catch(() => null),
]);

const personalContext = formatContext(
personalResult,
true,
false,
settings.maxProfileItems,
false,
);

const repoContext = formatContext(
repoResult,
true,
false,
settings.maxProfileItems,
false,
);

const additionalContext = combineContexts([
{ label: '### Personal Memories', content: personalContext },
{
label: '### Project Knowledge (Shared across team)',
content: repoContext,
},
]);

if (!additionalContext) {
writeOutput({
hookSpecificOutput: {
Expand All @@ -67,6 +93,8 @@ Memories will be saved as you work.

debugLog(settings, 'Context generated', {
length: additionalContext.length,
hasPersonal: !!personalContext,
hasRepo: !!repoContext,
});

writeOutput({
Expand Down
38 changes: 25 additions & 13 deletions src/lib/container-tag.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { execSync } = require('node:child_process');
const crypto = require('node:crypto');
const { loadProjectConfig } = require('./project-config');

function sha256(input) {
return crypto.createHash('sha256').update(input).digest('hex').slice(0, 16);
Expand All @@ -19,33 +20,44 @@ function getGitRoot(cwd) {
}

function getContainerTag(cwd) {
const projectConfig = loadProjectConfig(cwd);
if (projectConfig?.personalContainerTag) {
return projectConfig.personalContainerTag;
}
const gitRoot = getGitRoot(cwd);
const basePath = gitRoot || cwd;
return `claudecode_project_${sha256(basePath)}`;
}

function getProjectName(cwd) {
function sanitizeRepoName(name) {
return name
.toLowerCase()
.replace(/[^a-z0-9]/g, '_')
.replace(/_+/g, '_')
.replace(/^_|_$/g, '');
}

function getRepoContainerTag(cwd) {
const projectConfig = loadProjectConfig(cwd);
if (projectConfig?.repoContainerTag) {
return projectConfig.repoContainerTag;
}
const gitRoot = getGitRoot(cwd);
const basePath = gitRoot || cwd;
return basePath.split('/').pop() || 'unknown';
const repoName = basePath.split('/').pop() || 'unknown';
return `repo_${sanitizeRepoName(repoName)}`;
}

function getUserContainerTag() {
try {
const email = execSync('git config user.email', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim();
if (email) return `claudecode_user_${sha256(email)}`;
} catch {}
const username = process.env.USER || process.env.USERNAME || 'anonymous';
return `claudecode_user_${sha256(username)}`;
function getProjectName(cwd) {
const gitRoot = getGitRoot(cwd);
const basePath = gitRoot || cwd;
return basePath.split('/').pop() || 'unknown';
}

module.exports = {
sha256,
getGitRoot,
getContainerTag,
getRepoContainerTag,
getProjectName,
getUserContainerTag,
};
Loading