a2a-memory 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -45
- package/dist/adapters/anthropic.d.ts +69 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +116 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/claude/sync.d.ts +57 -0
- package/dist/claude/sync.d.ts.map +1 -0
- package/dist/claude/sync.js +201 -0
- package/dist/claude/sync.js.map +1 -0
- package/dist/cli/commands/claude-sync.d.ts +11 -0
- package/dist/cli/commands/claude-sync.d.ts.map +1 -0
- package/dist/cli/commands/claude-sync.js +69 -0
- package/dist/cli/commands/claude-sync.js.map +1 -0
- package/dist/cli/commands/health.d.ts +8 -0
- package/dist/cli/commands/health.d.ts.map +1 -0
- package/dist/cli/commands/health.js +108 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/sync.d.ts.map +1 -1
- package/dist/cli/commands/sync.js +55 -3
- package/dist/cli/commands/sync.js.map +1 -1
- package/dist/cli/index.js +27 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/manager.d.ts +9 -0
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +74 -0
- package/dist/config/manager.js.map +1 -1
- package/dist/db/database.d.ts +5 -3
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/database.js +124 -21
- package/dist/db/database.js.map +1 -1
- package/dist/embedding/index.d.ts +1 -0
- package/dist/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +1 -0
- package/dist/embedding/index.js.map +1 -1
- package/dist/embedding/quantization.d.ts +34 -0
- package/dist/embedding/quantization.d.ts.map +1 -0
- package/dist/embedding/quantization.js +89 -0
- package/dist/embedding/quantization.js.map +1 -0
- package/dist/extraction/filter.d.ts.map +1 -1
- package/dist/extraction/filter.js +25 -3
- package/dist/extraction/filter.js.map +1 -1
- package/dist/hooks/post-tool-use.d.ts.map +1 -1
- package/dist/hooks/post-tool-use.js +64 -49
- package/dist/hooks/post-tool-use.js.map +1 -1
- package/dist/hooks/session-end.d.ts.map +1 -1
- package/dist/hooks/session-end.js +45 -28
- package/dist/hooks/session-end.js.map +1 -1
- package/dist/hooks/session-start.d.ts.map +1 -1
- package/dist/hooks/session-start.js +21 -3
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/messages.d.ts +82 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +150 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +2 -0
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/tiering.d.ts +50 -0
- package/dist/lifecycle/tiering.d.ts.map +1 -0
- package/dist/lifecycle/tiering.js +235 -0
- package/dist/lifecycle/tiering.js.map +1 -0
- package/dist/sync/client.d.ts +34 -2
- package/dist/sync/client.d.ts.map +1 -1
- package/dist/sync/client.js +89 -10
- package/dist/sync/client.js.map +1 -1
- package/dist/sync/encryption.d.ts +72 -0
- package/dist/sync/encryption.d.ts.map +1 -0
- package/dist/sync/encryption.js +203 -0
- package/dist/sync/encryption.js.map +1 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/queue.d.ts +1 -5
- package/dist/sync/queue.d.ts.map +1 -1
- package/dist/sync/queue.js +28 -37
- package/dist/sync/queue.js.map +1 -1
- package/dist/sync/scheduler.d.ts +69 -0
- package/dist/sync/scheduler.d.ts.map +1 -0
- package/dist/sync/scheduler.js +140 -0
- package/dist/sync/scheduler.js.map +1 -0
- package/dist/sync/synchronizer.d.ts +3 -1
- package/dist/sync/synchronizer.d.ts.map +1 -1
- package/dist/sync/synchronizer.js +4 -2
- package/dist/sync/synchronizer.js.map +1 -1
- package/dist/types/index.d.ts +32 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +34 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/keychain.d.ts +50 -0
- package/dist/utils/keychain.d.ts.map +1 -0
- package/dist/utils/keychain.js +166 -0
- package/dist/utils/keychain.js.map +1 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +118 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
# a2a-
|
|
1
|
+
# @a2a/memory - Persistent AI Memory for Claude Code
|
|
2
|
+
|
|
3
|
+
> v0.6.0 — Local-first memory system with server sync, team collaboration, and intelligent search
|
|
2
4
|
|
|
3
5
|
Persistent AI memory for Claude Code. Automatically captures, stores, and retrieves knowledge across coding sessions.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- **
|
|
9
|
+
### Core Capabilities
|
|
10
|
+
- **Session Extraction** - Extract memories from Claude Code sessions (JSONL parsing)
|
|
11
|
+
- **Local-First DB** - SQLite with FTS5 full-text search + vector similarity
|
|
9
12
|
- **Auto-Capture Hooks** - Claude Code hooks for automatic memory capture
|
|
10
|
-
- **Context Injection** -
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
+
- **Context Injection** - Inject relevant memories at session start (hybrid search)
|
|
14
|
+
- **Hybrid Search** - FTS + Vector + Recency ranking (Reciprocal Rank Fusion)
|
|
15
|
+
- **Lifecycle Management** - Quality scoring, TTL-based cleanup, memory tiering (Hot/Warm/Cold)
|
|
16
|
+
|
|
17
|
+
### AI & Embeddings
|
|
18
|
+
- **LLM Integration** - AI-powered extraction and classification (OpenAI, Anthropic)
|
|
19
|
+
- **Embeddings** - Local TF-IDF (64D) or OpenAI (1536D) embeddings
|
|
20
|
+
- **Vector Quantization** - Float32 and Int8 scalar quantization for compression
|
|
21
|
+
|
|
22
|
+
### Team & Sync
|
|
23
|
+
- **Server Sync** - Synchronize with A2A server via REST API
|
|
24
|
+
- **Team Collaboration** - Share memories across team members (CRDT vector clocks)
|
|
25
|
+
- **E2E Encryption** - AES-256-GCM encryption for sensitive content
|
|
26
|
+
- **Scheduled Sync** - Automatic periodic sync with configurable interval
|
|
27
|
+
- **OS Keychain** - Secure API key storage (macOS/Linux/Windows)
|
|
28
|
+
|
|
29
|
+
### Developer Experience
|
|
30
|
+
- **CLAUDE.md Sync** - Sync CLAUDE.md sections to memory DB
|
|
31
|
+
- **CLI (16 commands)** - Full command-line interface for all operations
|
|
32
|
+
- **Logging** - JSON Lines hook logs with rotation and level filtering
|
|
33
|
+
- **i18n** - Internationalization support (Korean, English)
|
|
34
|
+
- **Sensitive Info Filter** - Auto-redaction of API keys, passwords, tokens (21 patterns)
|
|
13
35
|
|
|
14
36
|
## Quick Start
|
|
15
37
|
|
|
@@ -28,44 +50,77 @@ a2a-memory status
|
|
|
28
50
|
|
|
29
51
|
# Search memories
|
|
30
52
|
a2a-memory search "authentication error"
|
|
53
|
+
|
|
54
|
+
# Health check
|
|
55
|
+
a2a-memory health
|
|
31
56
|
```
|
|
32
57
|
|
|
33
|
-
## Commands
|
|
58
|
+
## CLI Commands
|
|
34
59
|
|
|
35
|
-
| Command | Description |
|
|
36
|
-
|
|
37
|
-
| `a2a-memory setup` | Initialize plugin, create DB, register
|
|
38
|
-
| `a2a-memory status` | Show memory count, DB size, category breakdown |
|
|
39
|
-
| `a2a-memory extract` | Extract memories from
|
|
40
|
-
| `a2a-memory search <query>` |
|
|
41
|
-
| `a2a-memory list` | List
|
|
42
|
-
| `a2a-memory
|
|
43
|
-
| `a2a-memory
|
|
60
|
+
| Command | Description | Options |
|
|
61
|
+
|---------|-------------|---------|
|
|
62
|
+
| `a2a-memory setup` | Initialize plugin, create DB, register hooks | - |
|
|
63
|
+
| `a2a-memory status` | Show memory count, DB size, category breakdown | - |
|
|
64
|
+
| `a2a-memory extract` | Extract memories from session files | `--limit <n>`, `--project <path>` |
|
|
65
|
+
| `a2a-memory search <query>` | Hybrid search across all memories | `--limit <n>`, `--category <cat>` |
|
|
66
|
+
| `a2a-memory list` | List memories with category/tier filters | `--category`, `--tier`, `--limit` |
|
|
67
|
+
| `a2a-memory add` | Manually add a memory (interactive) | - |
|
|
68
|
+
| `a2a-memory edit <id>` | Edit an existing memory (interactive) | - |
|
|
69
|
+
| `a2a-memory rm <id>` | Delete a memory | - |
|
|
70
|
+
| `a2a-memory config` | View and modify configuration | `get <key>`, `set <key> <value>` |
|
|
71
|
+
| `a2a-memory sync` | Synchronize with A2A server | `--push`, `--pull`, `--watch`, `--interval <ms>` |
|
|
72
|
+
| `a2a-memory team` | Team memory management | `join <teamId>`, `sync`, `members` |
|
|
73
|
+
| `a2a-memory embed` | Manage embeddings | `generate`, `stats` |
|
|
74
|
+
| `a2a-memory cleanup` | Remove low-quality/expired memories | `--dry-run` |
|
|
75
|
+
| `a2a-memory health` | System health check (DB, config, logs) | `--verbose` |
|
|
76
|
+
| `a2a-memory claude-sync` | Sync CLAUDE.md to memory DB | `--dry-run`, `--force` |
|
|
44
77
|
|
|
45
|
-
##
|
|
78
|
+
## Architecture
|
|
46
79
|
|
|
47
|
-
### 1.
|
|
48
|
-
Parses `~/.claude/projects/<project>/<session>.jsonl` files and extracts:
|
|
49
|
-
- **Error Solutions** - Error + resolution pairs
|
|
50
|
-
- **Code Patterns** - Repeated tool usage patterns
|
|
51
|
-
- **Decisions** - Architectural decisions with reasoning
|
|
52
|
-
- **Context** - Project background and rules
|
|
53
|
-
- **Preferences** - Development style and conventions
|
|
80
|
+
### 1. Claude Code Hooks
|
|
54
81
|
|
|
55
|
-
### 2. Claude Code Hooks
|
|
56
82
|
After `a2a-memory setup`, three hooks are registered:
|
|
57
83
|
|
|
58
|
-
| Hook | Trigger | Action |
|
|
59
|
-
|
|
60
|
-
| **SessionStart** | Session begins | Injects relevant memories
|
|
61
|
-
| **PostToolUse** | After Write/Edit/Bash | Auto-captures significant actions |
|
|
62
|
-
| **SessionEnd** | Session ends | Extracts
|
|
84
|
+
| Hook | Trigger | Action | Features |
|
|
85
|
+
|------|---------|--------|----------|
|
|
86
|
+
| **SessionStart** | Session begins | Injects relevant memories via hybrid search | Team memory pull, context injection |
|
|
87
|
+
| **PostToolUse** | After Write/Edit/Bash | Auto-captures significant actions | Significance scoring, sensitive info filtering |
|
|
88
|
+
| **SessionEnd** | Session ends | Extracts memories + optional team sync | Session summarization, team push, scheduled sync |
|
|
89
|
+
|
|
90
|
+
### 2. Memory Extraction
|
|
91
|
+
|
|
92
|
+
Parses `~/.claude/projects/<project>/<session>.jsonl` files and extracts:
|
|
93
|
+
|
|
94
|
+
| Category | Examples |
|
|
95
|
+
|----------|---------|
|
|
96
|
+
| `error_solution` | Error + resolution pairs |
|
|
97
|
+
| `code_pattern` | Repeated tool usage patterns |
|
|
98
|
+
| `decision` | Architectural decisions with reasoning |
|
|
99
|
+
| `project_knowledge` | Project context and rules |
|
|
100
|
+
| `convention` | Development conventions and preferences |
|
|
101
|
+
|
|
102
|
+
### 3. Hybrid Search
|
|
103
|
+
|
|
104
|
+
Three-signal Reciprocal Rank Fusion (RRF):
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
Score = w1 * FTS_rank + w2 * Vector_similarity + w3 * Recency_score
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- **FTS**: SQLite FTS5 full-text search
|
|
111
|
+
- **Vector**: TF-IDF (local, 64D) or OpenAI (1536D) embeddings
|
|
112
|
+
- **Recency**: Time decay scoring
|
|
113
|
+
|
|
114
|
+
### 4. Storage & Indexing
|
|
63
115
|
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
- WAL mode for concurrent access
|
|
68
|
-
-
|
|
116
|
+
- **SQLite database** at `~/.a2a/memory.db`
|
|
117
|
+
- FTS5 full-text search index
|
|
118
|
+
- Vector embeddings table (with quantization support)
|
|
119
|
+
- WAL mode for concurrent access
|
|
120
|
+
- Sync status tracking with CRDT vector clocks
|
|
121
|
+
- **Memory Tiering**: Hot (0-7 days), Warm (8-30 days), Cold (30+ days)
|
|
122
|
+
- **Vector Quantization**: Float32 (50% reduction), Int8 (75% reduction)
|
|
123
|
+
- **E2E Encryption**: AES-256-GCM with PBKDF2 key derivation
|
|
69
124
|
|
|
70
125
|
## Configuration
|
|
71
126
|
|
|
@@ -84,31 +139,193 @@ Config file: `~/.a2a/config.json`
|
|
|
84
139
|
"maxMemories": 5,
|
|
85
140
|
"maxTokens": 2000
|
|
86
141
|
},
|
|
142
|
+
"autoSync": {
|
|
143
|
+
"enabled": false,
|
|
144
|
+
"pushOnSessionEnd": true,
|
|
145
|
+
"pullOnSessionStart": true,
|
|
146
|
+
"timeoutMs": 10000,
|
|
147
|
+
"intervalMs": 1800000
|
|
148
|
+
},
|
|
87
149
|
"db": {
|
|
88
150
|
"path": "~/.a2a/memory.db",
|
|
89
151
|
"maxSizeMB": 100
|
|
152
|
+
},
|
|
153
|
+
"embedding": {
|
|
154
|
+
"provider": "local",
|
|
155
|
+
"dimensions": 64
|
|
156
|
+
},
|
|
157
|
+
"lifecycle": {
|
|
158
|
+
"enabled": true,
|
|
159
|
+
"maxMemories": 10000,
|
|
160
|
+
"ttlDays": { "working": 7, "episodic": 90 },
|
|
161
|
+
"minQualityScore": 0.3
|
|
162
|
+
},
|
|
163
|
+
"logging": {
|
|
164
|
+
"enabled": false,
|
|
165
|
+
"level": "info",
|
|
166
|
+
"outputDir": "~/.a2a/logs",
|
|
167
|
+
"maxFileSizeMB": 10,
|
|
168
|
+
"maxFiles": 3
|
|
90
169
|
}
|
|
91
170
|
}
|
|
92
171
|
```
|
|
93
172
|
|
|
94
|
-
###
|
|
95
|
-
```bash
|
|
96
|
-
a2a-memory config set autoCapture.enabled true
|
|
97
|
-
```
|
|
173
|
+
### Server Sync
|
|
98
174
|
|
|
99
|
-
### Server Sync (Team Mode)
|
|
100
175
|
```bash
|
|
101
176
|
# Configure server
|
|
102
177
|
a2a-memory config set server.url https://your-a2a-server.com
|
|
103
178
|
a2a-memory config set server.apiKey your-api-key
|
|
104
179
|
|
|
105
|
-
#
|
|
106
|
-
a2a-memory sync
|
|
107
|
-
a2a-memory sync --
|
|
108
|
-
|
|
109
|
-
|
|
180
|
+
# One-time sync
|
|
181
|
+
a2a-memory sync --push # local -> remote
|
|
182
|
+
a2a-memory sync --pull # remote -> local
|
|
183
|
+
|
|
184
|
+
# Continuous sync (every 30 min)
|
|
185
|
+
a2a-memory sync --watch
|
|
186
|
+
a2a-memory sync --watch --interval 60000 # every 60s
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Team Mode
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Join a team
|
|
193
|
+
a2a-memory team join my-team
|
|
194
|
+
|
|
195
|
+
# Sync team memories
|
|
196
|
+
a2a-memory team sync
|
|
197
|
+
|
|
198
|
+
# List team members
|
|
199
|
+
a2a-memory team members
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### CLAUDE.md Sync
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Sync CLAUDE.md sections to memory DB
|
|
206
|
+
a2a-memory claude-sync
|
|
207
|
+
|
|
208
|
+
# Preview without writing
|
|
209
|
+
a2a-memory claude-sync --dry-run
|
|
210
|
+
|
|
211
|
+
# Force re-sync
|
|
212
|
+
a2a-memory claude-sync --force
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Programmatic API
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import {
|
|
219
|
+
// Core
|
|
220
|
+
MemoryDatabase,
|
|
221
|
+
ConfigManager,
|
|
222
|
+
extractMemories,
|
|
223
|
+
|
|
224
|
+
// Search
|
|
225
|
+
HybridRanker,
|
|
226
|
+
createEmbeddingProvider,
|
|
227
|
+
|
|
228
|
+
// Sync
|
|
229
|
+
A2AClient,
|
|
230
|
+
MemorySynchronizer,
|
|
231
|
+
TeamSynchronizer,
|
|
232
|
+
SyncScheduler,
|
|
233
|
+
|
|
234
|
+
// Lifecycle
|
|
235
|
+
cleanupMemories,
|
|
236
|
+
rebalanceTiers,
|
|
237
|
+
|
|
238
|
+
// Encryption
|
|
239
|
+
encryptContent,
|
|
240
|
+
decryptContent,
|
|
241
|
+
|
|
242
|
+
// Claude.md
|
|
243
|
+
syncClaudeMd,
|
|
244
|
+
} from 'a2a-memory';
|
|
245
|
+
|
|
246
|
+
// Database operations
|
|
247
|
+
const db = new MemoryDatabase('~/.a2a/memory.db');
|
|
248
|
+
db.initialize();
|
|
249
|
+
|
|
250
|
+
db.createMemory({
|
|
251
|
+
content: 'JWT tokens expire after 30 minutes',
|
|
252
|
+
category: 'convention',
|
|
253
|
+
tier: 'semantic',
|
|
254
|
+
tags: ['auth', 'jwt'],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Hybrid search with embeddings
|
|
258
|
+
const provider = createEmbeddingProvider({ provider: 'local', dimensions: 64 });
|
|
259
|
+
const ranker = new HybridRanker(db, provider);
|
|
260
|
+
const ranked = await ranker.search('auth error', { limit: 5 });
|
|
261
|
+
|
|
262
|
+
// Server sync
|
|
263
|
+
const client = new A2AClient({
|
|
264
|
+
serverUrl: 'https://a2a-api-production-8d17.up.railway.app',
|
|
265
|
+
apiKey: 'your-api-key',
|
|
266
|
+
});
|
|
267
|
+
const sync = new MemorySynchronizer(db, client);
|
|
268
|
+
await sync.push();
|
|
269
|
+
await sync.pull();
|
|
270
|
+
|
|
271
|
+
// Scheduled sync
|
|
272
|
+
const scheduler = new SyncScheduler(sync, { intervalMs: 1800000 });
|
|
273
|
+
scheduler.start();
|
|
274
|
+
|
|
275
|
+
// Team collaboration
|
|
276
|
+
const teamSync = new TeamSynchronizer(db, client, 'team-id');
|
|
277
|
+
await teamSync.pushTeamMemories();
|
|
278
|
+
await teamSync.pullTeamMemories();
|
|
279
|
+
|
|
280
|
+
// Lifecycle management
|
|
281
|
+
await cleanupMemories(db, { ttlDays: 90, minQualityScore: 0.3 });
|
|
282
|
+
await rebalanceTiers(db);
|
|
283
|
+
|
|
284
|
+
// Anthropic Memory API adapter (for future integration)
|
|
285
|
+
import { toAnthropicFormat, fromAnthropicFormat } from 'a2a-memory';
|
|
286
|
+
|
|
287
|
+
const memory = db.getMemory('mem_123');
|
|
288
|
+
const anthropicFormat = toAnthropicFormat(memory);
|
|
289
|
+
// → { id, content, type: 'user_preference' | 'learned_info' | 'conversation_context', ... }
|
|
290
|
+
|
|
291
|
+
const anthropicMemory = { id: '...', content: '...', type: 'learned_info', ... };
|
|
292
|
+
const a2aInput = fromAnthropicFormat(anthropicMemory);
|
|
293
|
+
db.createMemory(a2aInput);
|
|
110
294
|
```
|
|
111
295
|
|
|
296
|
+
## Security
|
|
297
|
+
|
|
298
|
+
### Sensitive Information Protection
|
|
299
|
+
|
|
300
|
+
Automatically filters 21 patterns including:
|
|
301
|
+
- Cloud provider keys (AWS, Google, Azure)
|
|
302
|
+
- API keys and secrets (OpenAI, Stripe, GitHub)
|
|
303
|
+
- Database connection strings
|
|
304
|
+
- Private keys and certificates (RSA, EC, DSA)
|
|
305
|
+
- JWT tokens and session cookies
|
|
306
|
+
- Personal identifiable information (Korean SSN, Business registration)
|
|
307
|
+
|
|
308
|
+
### Encryption
|
|
309
|
+
|
|
310
|
+
**E2E Encryption** (opt-in via config):
|
|
311
|
+
```bash
|
|
312
|
+
a2a-memory config set autoSync.encryption.enabled true
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- Algorithm: AES-256-GCM
|
|
316
|
+
- Key derivation: PBKDF2-HMAC-SHA256 (600,000 iterations)
|
|
317
|
+
- Storage: OS keychain integration (macOS, Linux, Windows)
|
|
318
|
+
- Scope: Content + embeddings encrypted before transmission
|
|
319
|
+
|
|
320
|
+
### API Key Storage
|
|
321
|
+
|
|
322
|
+
Uses OS-native secure storage:
|
|
323
|
+
- **macOS**: Keychain (`security` command)
|
|
324
|
+
- **Linux**: Secret Service API (`secret-tool`)
|
|
325
|
+
- **Windows**: Credential Manager (via `node-keytar`)
|
|
326
|
+
|
|
327
|
+
Fallback: Encrypted file storage at `~/.a2a/credentials.enc`
|
|
328
|
+
|
|
112
329
|
## Requirements
|
|
113
330
|
|
|
114
331
|
- Node.js >= 18.0.0
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Memory API Adapter
|
|
3
|
+
*
|
|
4
|
+
* A2A 메모리를 Anthropic의 Claude Memory API 형식으로 변환합니다.
|
|
5
|
+
* Anthropic Memory API가 공개되면 실제 연동으로 전환 가능합니다.
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.anthropic.com/news/contextual-retrieval
|
|
8
|
+
*/
|
|
9
|
+
import type { Memory, MemoryCreateInput, MemoryCategory } from '../types/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Anthropic Memory API 형식의 메모리 타입
|
|
12
|
+
*/
|
|
13
|
+
export interface AnthropicMemory {
|
|
14
|
+
id: string;
|
|
15
|
+
content: string;
|
|
16
|
+
type: 'user_preference' | 'learned_info' | 'conversation_context';
|
|
17
|
+
source: 'user' | 'assistant' | 'system';
|
|
18
|
+
created_at: string;
|
|
19
|
+
updated_at: string;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Anthropic Memory API 응답 형식 (페이지네이션)
|
|
24
|
+
*/
|
|
25
|
+
export interface AnthropicMemoryListResponse {
|
|
26
|
+
data: AnthropicMemory[];
|
|
27
|
+
has_more: boolean;
|
|
28
|
+
first_id?: string;
|
|
29
|
+
last_id?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A2A 메모리를 Anthropic 형식으로 변환
|
|
33
|
+
*/
|
|
34
|
+
export declare function toAnthropicFormat(memory: Memory): AnthropicMemory;
|
|
35
|
+
/**
|
|
36
|
+
* Anthropic 형식을 A2A 메모리 생성 입력으로 변환
|
|
37
|
+
*/
|
|
38
|
+
export declare function fromAnthropicFormat(anthropic: AnthropicMemory): MemoryCreateInput;
|
|
39
|
+
/**
|
|
40
|
+
* A2A 카테고리 → Anthropic 타입 매핑
|
|
41
|
+
*
|
|
42
|
+
* A2A 카테고리:
|
|
43
|
+
* - error_solution: 에러와 해결책
|
|
44
|
+
* - code_pattern: 코드 패턴
|
|
45
|
+
* - decision: 아키텍처 결정
|
|
46
|
+
* - context: 대화 컨텍스트
|
|
47
|
+
* - project_knowledge: 프로젝트 지식
|
|
48
|
+
* - convention: 규칙/컨벤션
|
|
49
|
+
* - learning: 학습 내용
|
|
50
|
+
*
|
|
51
|
+
* Anthropic 타입:
|
|
52
|
+
* - user_preference: 사용자 선호도
|
|
53
|
+
* - learned_info: 학습된 정보
|
|
54
|
+
* - conversation_context: 대화 컨텍스트
|
|
55
|
+
*/
|
|
56
|
+
export declare function mapCategoryToAnthropicType(category: MemoryCategory): AnthropicMemory['type'];
|
|
57
|
+
/**
|
|
58
|
+
* Anthropic 타입 → A2A 카테고리 매핑
|
|
59
|
+
*/
|
|
60
|
+
export declare function mapAnthropicTypeToCategory(type: AnthropicMemory['type']): MemoryCategory;
|
|
61
|
+
/**
|
|
62
|
+
* A2A 메모리 배열을 Anthropic 응답 형식으로 변환
|
|
63
|
+
*/
|
|
64
|
+
export declare function toAnthropicBatch(memories: Memory[]): AnthropicMemoryListResponse;
|
|
65
|
+
/**
|
|
66
|
+
* Anthropic 응답 형식을 A2A 메모리 생성 입력 배열로 변환
|
|
67
|
+
*/
|
|
68
|
+
export declare function fromAnthropicBatch(response: AnthropicMemoryListResponse): MemoryCreateInput[];
|
|
69
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,iBAAiB,GAAG,cAAc,GAAG,sBAAsB,CAAC;IAClE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAkBjE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,eAAe,GAAG,iBAAiB,CAiBjF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,cAAc,GAAG,eAAe,CAAC,MAAM,CAAC,CAe5F;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,cAAc,CAWxF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,2BAA2B,CAShF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,iBAAiB,EAAE,CAE7F"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Memory API Adapter
|
|
3
|
+
*
|
|
4
|
+
* A2A 메모리를 Anthropic의 Claude Memory API 형식으로 변환합니다.
|
|
5
|
+
* Anthropic Memory API가 공개되면 실제 연동으로 전환 가능합니다.
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.anthropic.com/news/contextual-retrieval
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* A2A 메모리를 Anthropic 형식으로 변환
|
|
11
|
+
*/
|
|
12
|
+
export function toAnthropicFormat(memory) {
|
|
13
|
+
return {
|
|
14
|
+
id: memory.id,
|
|
15
|
+
content: memory.content,
|
|
16
|
+
type: mapCategoryToAnthropicType(memory.category),
|
|
17
|
+
source: 'assistant', // A2A는 AI가 추출한 메모리
|
|
18
|
+
created_at: memory.createdAt,
|
|
19
|
+
updated_at: memory.updatedAt,
|
|
20
|
+
metadata: {
|
|
21
|
+
category: memory.category,
|
|
22
|
+
tier: memory.tier,
|
|
23
|
+
tags: memory.tags,
|
|
24
|
+
projectPath: memory.projectPath,
|
|
25
|
+
sessionId: memory.sessionId,
|
|
26
|
+
accessCount: memory.accessCount,
|
|
27
|
+
lastAccessedAt: memory.lastAccessedAt,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Anthropic 형식을 A2A 메모리 생성 입력으로 변환
|
|
33
|
+
*/
|
|
34
|
+
export function fromAnthropicFormat(anthropic) {
|
|
35
|
+
const metadata = anthropic.metadata || {};
|
|
36
|
+
// tier 타입 검증 및 fallback
|
|
37
|
+
const validTiers = ['working', 'episodic', 'semantic'];
|
|
38
|
+
const tier = typeof metadata.tier === 'string' && validTiers.includes(metadata.tier)
|
|
39
|
+
? metadata.tier
|
|
40
|
+
: 'episodic';
|
|
41
|
+
return {
|
|
42
|
+
content: anthropic.content,
|
|
43
|
+
category: mapAnthropicTypeToCategory(anthropic.type),
|
|
44
|
+
tier,
|
|
45
|
+
tags: Array.isArray(metadata.tags) ? metadata.tags : [],
|
|
46
|
+
projectPath: typeof metadata.projectPath === 'string' ? metadata.projectPath : undefined,
|
|
47
|
+
sessionId: typeof metadata.sessionId === 'string' ? metadata.sessionId : undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A2A 카테고리 → Anthropic 타입 매핑
|
|
52
|
+
*
|
|
53
|
+
* A2A 카테고리:
|
|
54
|
+
* - error_solution: 에러와 해결책
|
|
55
|
+
* - code_pattern: 코드 패턴
|
|
56
|
+
* - decision: 아키텍처 결정
|
|
57
|
+
* - context: 대화 컨텍스트
|
|
58
|
+
* - project_knowledge: 프로젝트 지식
|
|
59
|
+
* - convention: 규칙/컨벤션
|
|
60
|
+
* - learning: 학습 내용
|
|
61
|
+
*
|
|
62
|
+
* Anthropic 타입:
|
|
63
|
+
* - user_preference: 사용자 선호도
|
|
64
|
+
* - learned_info: 학습된 정보
|
|
65
|
+
* - conversation_context: 대화 컨텍스트
|
|
66
|
+
*/
|
|
67
|
+
export function mapCategoryToAnthropicType(category) {
|
|
68
|
+
switch (category) {
|
|
69
|
+
case 'convention':
|
|
70
|
+
return 'user_preference';
|
|
71
|
+
case 'context':
|
|
72
|
+
return 'conversation_context';
|
|
73
|
+
case 'error_solution':
|
|
74
|
+
case 'code_pattern':
|
|
75
|
+
case 'decision':
|
|
76
|
+
case 'project_knowledge':
|
|
77
|
+
case 'learning':
|
|
78
|
+
return 'learned_info';
|
|
79
|
+
default:
|
|
80
|
+
return 'learned_info';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Anthropic 타입 → A2A 카테고리 매핑
|
|
85
|
+
*/
|
|
86
|
+
export function mapAnthropicTypeToCategory(type) {
|
|
87
|
+
switch (type) {
|
|
88
|
+
case 'user_preference':
|
|
89
|
+
return 'convention';
|
|
90
|
+
case 'conversation_context':
|
|
91
|
+
return 'context';
|
|
92
|
+
case 'learned_info':
|
|
93
|
+
return 'project_knowledge';
|
|
94
|
+
default:
|
|
95
|
+
return 'project_knowledge';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A2A 메모리 배열을 Anthropic 응답 형식으로 변환
|
|
100
|
+
*/
|
|
101
|
+
export function toAnthropicBatch(memories) {
|
|
102
|
+
const data = memories.map(toAnthropicFormat);
|
|
103
|
+
return {
|
|
104
|
+
data,
|
|
105
|
+
has_more: false, // 단일 배치 변환이므로 항상 false
|
|
106
|
+
first_id: data.length > 0 ? data[0].id : undefined,
|
|
107
|
+
last_id: data.length > 0 ? data[data.length - 1].id : undefined,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Anthropic 응답 형식을 A2A 메모리 생성 입력 배열로 변환
|
|
112
|
+
*/
|
|
113
|
+
export function fromAnthropicBatch(response) {
|
|
114
|
+
return response.data.map(fromAnthropicFormat);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLAUDE.md Sync
|
|
3
|
+
*
|
|
4
|
+
* CLAUDE.md 파일을 파싱하여 섹션별 메모리를 생성/업데이트합니다.
|
|
5
|
+
*
|
|
6
|
+
* 동작:
|
|
7
|
+
* 1. CLAUDE.md 파일 탐색 (프로젝트 루트, .claude/ 등)
|
|
8
|
+
* 2. 마크다운 헤딩(##) 기준으로 섹션 분리
|
|
9
|
+
* 3. 각 섹션을 convention/context 카테고리 메모리로 저장
|
|
10
|
+
* 4. content hash로 중복/변경 감지
|
|
11
|
+
*/
|
|
12
|
+
import type { MemoryDatabase } from '../db/database.js';
|
|
13
|
+
import type { MemoryCategory } from '../types/index.js';
|
|
14
|
+
export interface ClaudeSyncOptions {
|
|
15
|
+
/** CLAUDE.md 경로 (없으면 자동 탐색) */
|
|
16
|
+
claudeMdPath?: string;
|
|
17
|
+
/** dry-run 모드 */
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
/** 변경 없어도 강제 동기화 */
|
|
20
|
+
force?: boolean;
|
|
21
|
+
/** 프로젝트 경로 (없으면 cwd) */
|
|
22
|
+
projectPath?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ClaudeSyncResult {
|
|
25
|
+
found: boolean;
|
|
26
|
+
filePath: string;
|
|
27
|
+
totalSections: number;
|
|
28
|
+
created: number;
|
|
29
|
+
updated: number;
|
|
30
|
+
skipped: number;
|
|
31
|
+
sections: SectionResult[];
|
|
32
|
+
}
|
|
33
|
+
export interface SectionResult {
|
|
34
|
+
title: string;
|
|
35
|
+
category: MemoryCategory;
|
|
36
|
+
action: 'created' | 'updated' | 'skipped';
|
|
37
|
+
}
|
|
38
|
+
interface ParsedSection {
|
|
39
|
+
title: string;
|
|
40
|
+
content: string;
|
|
41
|
+
level: number;
|
|
42
|
+
hash: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* CLAUDE.md 파일 동기화
|
|
46
|
+
*/
|
|
47
|
+
export declare function syncClaudeMd(db: MemoryDatabase, options?: ClaudeSyncOptions): Promise<ClaudeSyncResult>;
|
|
48
|
+
/**
|
|
49
|
+
* CLAUDE.md 파일 탐색 (우선순위)
|
|
50
|
+
*/
|
|
51
|
+
export declare function findClaudeMd(projectPath?: string): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* CLAUDE.md를 섹션으로 파싱
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseClaudeMd(content: string): ParsedSection[];
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/claude/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,WAAW,iBAAiB;IAChC,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;CAC3C;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAID;;GAEG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA4E3B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAchE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CA8C9D"}
|