@hir4ta/mneme 0.20.2 → 0.22.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/.claude-plugin/plugin.json +2 -5
- package/README.ja.md +45 -283
- package/README.md +48 -280
- package/dist/lib/db.js +7 -5
- package/dist/lib/incremental-save.js +122 -28
- package/dist/lib/prompt-search.js +570 -0
- package/dist/lib/search-core.js +516 -0
- package/dist/lib/session-finalize.js +983 -0
- package/dist/lib/session-init.js +397 -0
- package/dist/lib/suppress-sqlite-warning.js +8 -0
- package/dist/public/assets/index-Bvl_IrPy.css +1 -0
- package/dist/public/assets/index-k5JYSPV6.js +351 -0
- package/dist/public/assets/{react-force-graph-2d-CGnpkwRw.js → react-force-graph-2d-Dlcfvz01.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/server.js +565 -37
- package/dist/servers/db-server.js +1301 -98
- package/dist/servers/search-server.js +613 -333
- package/hooks/hooks.json +1 -0
- package/hooks/lib/common.sh +55 -0
- package/hooks/post-tool-use.sh +52 -58
- package/hooks/pre-compact.sh +30 -42
- package/hooks/session-end.sh +30 -142
- package/hooks/session-start.sh +32 -337
- package/hooks/stop.sh +31 -42
- package/hooks/user-prompt-submit.sh +58 -212
- package/package.json +10 -3
- package/scripts/export-weekly-knowledge-html.ts +906 -0
- package/scripts/search-benchmark.queries.json +78 -0
- package/scripts/search-benchmark.ts +120 -0
- package/scripts/validate-source-artifacts.mjs +378 -0
- package/servers/db-server.ts +995 -65
- package/servers/search-server.ts +117 -528
- package/skills/harvest/SKILL.md +78 -0
- package/skills/init-mneme/{skill.md → SKILL.md} +7 -1
- package/skills/resume/{skill.md → SKILL.md} +24 -9
- package/skills/save/SKILL.md +131 -0
- package/skills/search/SKILL.md +76 -0
- package/skills/using-mneme/SKILL.md +38 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/public/assets/index-CeHiZXwl.js +0 -345
- package/dist/public/assets/index-t_srr1OD.css +0 -1
- package/learn_claude_code/figma_exports/claude_code_map.svg +0 -107
- package/learn_claude_code/figma_exports/claude_code_whiteboard.excalidraw +0 -2578
- package/skills/AGENTS.override.md +0 -5
- package/skills/harvest/skill.md +0 -295
- package/skills/plan/skill.md +0 -422
- package/skills/report/skill.md +0 -74
- package/skills/review/skill.md +0 -419
- package/skills/save/skill.md +0 -496
- package/skills/search/skill.md +0 -175
- package/skills/using-mneme/skill.md +0 -185
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# mneme
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+

|
|
3
5
|
[](https://www.npmjs.com/package/@hir4ta/mneme)
|
|
4
6
|
[](https://github.com/hir4ta/mneme/blob/main/LICENSE)
|
|
5
7
|
|
|
@@ -9,41 +11,26 @@ Provides automatic session saving, intelligent memory search, and web dashboard
|
|
|
9
11
|
|
|
10
12
|
## Features
|
|
11
13
|
|
|
12
|
-
### Core Features
|
|
13
14
|
- **Incremental save**: Save only new interactions on each turn completion (Node.js, fast)
|
|
14
15
|
- **Auto memory search**: Related past sessions/decisions automatically injected on each prompt
|
|
15
16
|
- **PreCompact support**: Catch up unsaved interactions before Auto-Compact (context 95% full)
|
|
16
17
|
- **Full data extraction**: Save summary, decisions, patterns, and rules with `/mneme:save`
|
|
17
|
-
- **Memory-informed planning**: Design and plan with past knowledge via `/mneme:plan`
|
|
18
18
|
- **Session Resume**: Resume past sessions with `/mneme:resume` (with chain tracking)
|
|
19
19
|
- **Session Suggestion**: Recent 3 sessions shown at session start
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **Web Dashboard**: View sessions, decisions, patterns, and rules
|
|
20
|
+
- **Knowledge Harvesting**: Extract decision/pattern/rule sources from PR comments with `/mneme:harvest`
|
|
21
|
+
- **Web Dashboard**: View sessions, source artifacts, and development rules
|
|
22
|
+
- **Development Rules + Approval**: Generate rules from decisions/patterns/rules and approve/reject inline
|
|
23
|
+
- **Knowledge Graph Layer**: Visualize sessions and approved development rules as one graph
|
|
25
24
|
|
|
26
25
|
## Problems Solved
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
Claude Code sessions lose context on exit or Auto-Compact, making past decisions untraceable and knowledge hard to reuse.
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
- **Opaque Decisions**: "Why did we choose this design?" becomes untraceable
|
|
32
|
-
- **Repeated Mistakes**: Same errors solved multiple times without learning
|
|
33
|
-
- **Hard to Reuse Knowledge**: Past interactions and decisions are hard to search
|
|
29
|
+
**Common Issues**: Context loss across sessions, repeated mistakes, opaque design decisions
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
**What mneme Enables**: Auto-save with resume, automatic memory search on every prompt, searchable decision/pattern history
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
- **Auto memory search** brings relevant past knowledge to every conversation
|
|
39
|
-
- **Decision & Pattern Recording** tracks reasoning and error solutions
|
|
40
|
-
- **Search and Dashboard** for quick access to past records
|
|
41
|
-
- **Review Feature** for repository-specific code review
|
|
42
|
-
|
|
43
|
-
### Team Benefits
|
|
44
|
-
|
|
45
|
-
- `.mneme/` JSON files are **Git-manageable**, enabling team sharing of decisions and session history
|
|
46
|
-
- Quickly understand background and context during onboarding or reviews
|
|
33
|
+
**Team Benefits**: `.mneme/` JSON files are Git-managed, enabling team sharing of decisions and session history.
|
|
47
34
|
|
|
48
35
|
## Installation
|
|
49
36
|
|
|
@@ -119,62 +106,29 @@ This will auto-update on Claude Code startup.
|
|
|
119
106
|
|
|
120
107
|
## Usage
|
|
121
108
|
|
|
122
|
-
### Incremental Save
|
|
123
|
-
|
|
124
|
-
**Conversation logs are auto-saved on each turn completion** (Node.js streaming). No configuration needed.
|
|
125
|
-
|
|
126
|
-
- **Stop hook**: Saves only new interactions on each assistant response completion
|
|
127
|
-
- **PreCompact hook**: Catches up unsaved interactions before Auto-Compact
|
|
128
|
-
- **SessionEnd hook**: Lightweight cleanup only (no heavy processing)
|
|
129
|
-
|
|
130
|
-
**If you don't run `/mneme:save`, conversation history is deleted at session end** (prevents garbage data).
|
|
131
|
-
|
|
132
|
-
### Auto Memory Search
|
|
133
|
-
|
|
134
|
-
**On every prompt**, mneme automatically:
|
|
135
|
-
1. Extracts keywords from your message
|
|
136
|
-
2. Searches sessions/decisions/patterns
|
|
137
|
-
3. Injects relevant context to Claude
|
|
138
|
-
|
|
139
|
-
This means past knowledge is always available without manual lookup.
|
|
140
|
-
|
|
141
|
-
### Session Suggestion
|
|
142
|
-
|
|
143
|
-
At session start, recent 3 sessions are shown:
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
**Recent sessions:**
|
|
147
|
-
1. [abc123] JWT authentication implementation (2026-01-27, main)
|
|
148
|
-
2. [def456] Dashboard UI improvements (2026-01-26, main)
|
|
149
|
-
3. [ghi789] Bug fixes (2026-01-25, main)
|
|
150
|
-
|
|
151
|
-
Continue from a previous session? Use `/mneme:resume <id>`
|
|
152
|
-
```
|
|
153
|
-
|
|
154
109
|
### Commands
|
|
155
110
|
|
|
156
|
-
| Command
|
|
157
|
-
|
|
158
|
-
| `/init-mneme`
|
|
159
|
-
| `/mneme:save`
|
|
160
|
-
| `/mneme:
|
|
161
|
-
| `/mneme:
|
|
162
|
-
| `/mneme:
|
|
163
|
-
| `/mneme:review [--staged\|--all\|--diff=branch\|--full]` | Rule-based code review |
|
|
164
|
-
| `/mneme:review <PR URL>` | Review GitHub PR |
|
|
165
|
-
| `/mneme:harvest <PR URL>` | Extract knowledge from PR review comments |
|
|
166
|
-
| `/mneme:report [--from YYYY-MM-DD --to YYYY-MM-DD]` | Weekly review report |
|
|
111
|
+
| Command | Description |
|
|
112
|
+
| ------------------------ | ----------------------------------------------------- |
|
|
113
|
+
| `/init-mneme` | Initialize mneme in current project |
|
|
114
|
+
| `/mneme:save` | Extract all data: summary, decisions, patterns, rules |
|
|
115
|
+
| `/mneme:resume [id]` | Resume session (show list if ID omitted) |
|
|
116
|
+
| `/mneme:search "query"` | Search sessions and approved development rules |
|
|
117
|
+
| `/mneme:harvest <PR URL>`| Extract knowledge from PR review comments |
|
|
167
118
|
|
|
168
119
|
### Recommended Workflow
|
|
169
120
|
|
|
170
121
|
```
|
|
171
|
-
|
|
122
|
+
implement → save → approve rules
|
|
172
123
|
```
|
|
173
124
|
|
|
174
|
-
1. **
|
|
175
|
-
2. **
|
|
176
|
-
3. **
|
|
177
|
-
4. **
|
|
125
|
+
1. **implement**: Write code
|
|
126
|
+
2. **save**: Extract source knowledge and generate development rule candidates
|
|
127
|
+
3. **validate**: Run `npm run validate:sources` to enforce required fields/priority/tags
|
|
128
|
+
4. **approve rules**: Review and approve/reject generated development rules inline
|
|
129
|
+
|
|
130
|
+
Detailed runtime flow (hooks, uncommitted policy, auto-compact path):
|
|
131
|
+
- `docs/mneme-runtime-flow.md`
|
|
178
132
|
|
|
179
133
|
### Dashboard
|
|
180
134
|
|
|
@@ -195,9 +149,7 @@ npx @hir4ta/mneme --dashboard --port 8080
|
|
|
195
149
|
#### Screens
|
|
196
150
|
|
|
197
151
|
- **Sessions**: List and view sessions
|
|
198
|
-
- **
|
|
199
|
-
- **Rules**: View dev rules and review guidelines
|
|
200
|
-
- **Patterns**: View learned patterns (good patterns, anti-patterns, error solutions)
|
|
152
|
+
- **Development Rules**: Review and approve rules generated from decisions/patterns/rules
|
|
201
153
|
- **Statistics**: View activity charts and session statistics
|
|
202
154
|
- **Graph**: Visualize session connections by shared tags
|
|
203
155
|
|
|
@@ -205,227 +157,43 @@ npx @hir4ta/mneme --dashboard --port 8080
|
|
|
205
157
|
|
|
206
158
|
The dashboard supports English and Japanese. Click the language toggle (EN/JA) in the header to switch. The preference is saved to localStorage.
|
|
207
159
|
|
|
208
|
-
###
|
|
209
|
-
|
|
210
|
-
mneme provides MCP servers with search and database tools callable directly from Claude Code:
|
|
211
|
-
|
|
212
|
-
| Server | Tool | Description |
|
|
213
|
-
|--------|------|-------------|
|
|
214
|
-
| mneme-search | `mneme_search` | Unified search (FTS5, tag alias resolution) |
|
|
215
|
-
| mneme-search | `mneme_get_session` | Get session details |
|
|
216
|
-
| mneme-search | `mneme_get_decision` | Get decision details |
|
|
217
|
-
| mneme-db | `mneme_list_projects` | List all projects |
|
|
218
|
-
| mneme-db | `mneme_cross_project_search` | Cross-project search |
|
|
219
|
-
|
|
220
|
-
### Subagents
|
|
221
|
-
|
|
222
|
-
| Agent | Description |
|
|
223
|
-
|-------|-------------|
|
|
224
|
-
| `mneme-reviewer` | Rule-based code review (isolated context) |
|
|
225
|
-
|
|
226
|
-
## How It Works
|
|
227
|
-
|
|
228
|
-
```mermaid
|
|
229
|
-
flowchart TB
|
|
230
|
-
subgraph incremental [Incremental Save]
|
|
231
|
-
A[Each Turn] --> B[Stop Hook]
|
|
232
|
-
B --> C[Node.js streaming]
|
|
233
|
-
C --> D[Save only new interactions]
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
subgraph autosearch [Auto Memory Search]
|
|
237
|
-
E[User Prompt] --> F[UserPromptSubmit Hook]
|
|
238
|
-
F --> G[Search sessions/decisions/patterns]
|
|
239
|
-
G --> H[Inject relevant context]
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
subgraph precompact [PreCompact Catch-up]
|
|
243
|
-
I[Context 95% Full] --> J[PreCompact Hook]
|
|
244
|
-
J --> K[Catch up missed interactions]
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
subgraph sessionend [Session End]
|
|
248
|
-
L[Exit] --> M[SessionEnd Hook]
|
|
249
|
-
M --> N{Committed?}
|
|
250
|
-
N -->|Yes| O[Keep interactions]
|
|
251
|
-
N -->|No| P[Delete interactions]
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
subgraph manual [Manual Actions]
|
|
255
|
-
Q["mneme:save"] --> R[Extract decisions + patterns + rules]
|
|
256
|
-
R --> S[Mark session committed]
|
|
257
|
-
T["mneme:plan"] --> U[Memory-informed design + tasks]
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
subgraph resume [Session Resume]
|
|
261
|
-
V["mneme:resume"] --> W[Select from list]
|
|
262
|
-
W --> X[Restore past context + set resumedFrom]
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
subgraph review [Review]
|
|
266
|
-
Y["mneme:review"] --> Z[Rule-based findings]
|
|
267
|
-
Z --> AA[Save review results]
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
subgraph dashboard [Dashboard]
|
|
271
|
-
AB["npx @hir4ta/mneme -d"] --> AC[Open in browser]
|
|
272
|
-
AC --> AD[View all data]
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
D --> Q
|
|
276
|
-
H --> Q
|
|
277
|
-
S --> AB
|
|
278
|
-
AA --> AB
|
|
279
|
-
```
|
|
160
|
+
### Weekly Knowledge HTML Export
|
|
280
161
|
|
|
281
|
-
|
|
162
|
+
Generate a shareable HTML snapshot for the last 7 days of knowledge activity:
|
|
282
163
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
| Storage | Location | Purpose | Sharing |
|
|
286
|
-
|---------|----------|---------|---------|
|
|
287
|
-
| **JSON** | `.mneme/` | Summaries, decisions, patterns, rules | Git-managed (team shared) |
|
|
288
|
-
| **SQLite** | `.mneme/local.db` | Interactions, backups | Local only (gitignored) |
|
|
289
|
-
|
|
290
|
-
**Why hybrid?**
|
|
291
|
-
- **Privacy**: Conversation history (interactions) stays local (gitignored)
|
|
292
|
-
- **Lightweight**: JSON files reduced from 100KB+ to ~5KB (interactions excluded)
|
|
293
|
-
- **Future-ready**: Embeddings table prepared for semantic search
|
|
294
|
-
|
|
295
|
-
### Directory Structure
|
|
296
|
-
|
|
297
|
-
**Project-local (`.mneme/`)**:
|
|
298
|
-
```text
|
|
299
|
-
.mneme/
|
|
300
|
-
├── local.db # SQLite with interactions (gitignored)
|
|
301
|
-
├── tags.json # Tag master file (93 tags, prevents notation variations)
|
|
302
|
-
├── sessions/ # Session metadata (YYYY/MM) - Git-managed
|
|
303
|
-
│ └── YYYY/MM/
|
|
304
|
-
│ └── {id}.json # Metadata only (interactions in local.db)
|
|
305
|
-
├── decisions/ # Technical decisions (from /save) - Git-managed
|
|
306
|
-
│ └── YYYY/MM/
|
|
307
|
-
│ └── {id}.json
|
|
308
|
-
├── patterns/ # Error patterns (from /save) - Git-managed
|
|
309
|
-
│ └── {user}.json
|
|
310
|
-
├── rules/ # Dev rules / review guidelines - Git-managed
|
|
311
|
-
├── reviews/ # Review results (YYYY/MM) - Git-managed
|
|
312
|
-
└── reports/ # Weekly reports (YYYY-MM) - Git-managed
|
|
164
|
+
```bash
|
|
165
|
+
npm run export:weekly-html
|
|
313
166
|
```
|
|
314
167
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
### Session JSON Schema
|
|
318
|
-
|
|
319
|
-
Session metadata is stored in JSON (interactions are stored in SQLite for privacy):
|
|
320
|
-
|
|
321
|
-
```json
|
|
322
|
-
{
|
|
323
|
-
"id": "abc12345",
|
|
324
|
-
"sessionId": "full-uuid-from-claude-code",
|
|
325
|
-
"createdAt": "2026-01-27T10:00:00Z",
|
|
326
|
-
"endedAt": "2026-01-27T12:00:00Z",
|
|
327
|
-
"title": "JWT authentication implementation",
|
|
328
|
-
"tags": ["auth", "jwt"],
|
|
329
|
-
"context": {
|
|
330
|
-
"branch": "feature/auth",
|
|
331
|
-
"projectDir": "/path/to/project",
|
|
332
|
-
"user": { "name": "tanaka", "email": "tanaka@example.com" }
|
|
333
|
-
},
|
|
334
|
-
"metrics": {
|
|
335
|
-
"userMessages": 5,
|
|
336
|
-
"assistantResponses": 5,
|
|
337
|
-
"thinkingBlocks": 5,
|
|
338
|
-
"toolUsage": [{"name": "Edit", "count": 3}, {"name": "Write", "count": 2}]
|
|
339
|
-
},
|
|
340
|
-
"files": [
|
|
341
|
-
{ "path": "src/auth/jwt.ts", "action": "create" }
|
|
342
|
-
],
|
|
343
|
-
"resumedFrom": "def45678",
|
|
344
|
-
"status": "complete",
|
|
345
|
-
|
|
346
|
-
"summary": {
|
|
347
|
-
"title": "JWT authentication implementation",
|
|
348
|
-
"goal": "Implement JWT-based auth with refresh token support",
|
|
349
|
-
"outcome": "success",
|
|
350
|
-
"description": "Implemented JWT auth with RS256 signing",
|
|
351
|
-
"sessionType": "implementation"
|
|
352
|
-
},
|
|
353
|
-
|
|
354
|
-
"plan": {
|
|
355
|
-
"tasks": ["[x] JWT signing method selection", "[x] Middleware implementation", "[ ] Add tests"],
|
|
356
|
-
"remaining": ["Add tests"]
|
|
357
|
-
},
|
|
358
|
-
|
|
359
|
-
"discussions": [
|
|
360
|
-
{
|
|
361
|
-
"topic": "Signing algorithm",
|
|
362
|
-
"decision": "Adopt RS256",
|
|
363
|
-
"reasoning": "Security considerations for production",
|
|
364
|
-
"alternatives": ["HS256 (simpler but requires shared secret)"]
|
|
365
|
-
}
|
|
366
|
-
],
|
|
367
|
-
|
|
368
|
-
"errors": [
|
|
369
|
-
{
|
|
370
|
-
"error": "secretOrPrivateKey must be asymmetric",
|
|
371
|
-
"cause": "Using HS256 secret with RS256",
|
|
372
|
-
"solution": "Generate RS256 key pair"
|
|
373
|
-
}
|
|
374
|
-
],
|
|
375
|
-
|
|
376
|
-
"handoff": {
|
|
377
|
-
"stoppedReason": "Test creation postponed to next session",
|
|
378
|
-
"notes": ["vitest configured", "Mock key pair in test/fixtures/"],
|
|
379
|
-
"nextSteps": ["Create jwt.test.ts", "Add E2E tests"]
|
|
380
|
-
},
|
|
381
|
-
|
|
382
|
-
"references": [
|
|
383
|
-
{ "url": "https://jwt.io/introduction", "title": "JWT Introduction" }
|
|
384
|
-
]
|
|
385
|
-
}
|
|
386
|
-
```
|
|
168
|
+
Output:
|
|
169
|
+
- `.mneme/exports/weekly-knowledge-YYYY-MM-DD.html`
|
|
387
170
|
|
|
388
|
-
|
|
171
|
+
## Data Storage
|
|
389
172
|
|
|
390
|
-
|
|
173
|
+
mneme uses a **hybrid storage** approach: JSON files are Git-managed for team sharing, while SQLite keeps conversations private (gitignored).
|
|
391
174
|
|
|
392
|
-
|
|
|
393
|
-
|
|
394
|
-
|
|
|
395
|
-
| `
|
|
396
|
-
| `research` | Research, learning, catchup |
|
|
397
|
-
| `exploration` | Codebase exploration |
|
|
398
|
-
| `discussion` | Discussion, consultation only |
|
|
399
|
-
| `debug` | Debugging, investigation |
|
|
400
|
-
| `review` | Code review |
|
|
175
|
+
| Storage | Location | Purpose | Sharing |
|
|
176
|
+
| ---------- | ----------------- | ------------------------------------- | ------------------------- |
|
|
177
|
+
| **JSON** | `.mneme/` | Summaries, decisions, patterns, rules | Git-managed (team shared) |
|
|
178
|
+
| **SQLite** | `.mneme/local.db` | Interactions, backups | Local only (gitignored) |
|
|
401
179
|
|
|
402
|
-
|
|
180
|
+
Conversations are auto-saved on each turn. No configuration needed.
|
|
403
181
|
|
|
404
|
-
|
|
182
|
+
Auto memory search runs on every prompt: keywords are extracted, past sessions/development rules are searched, and relevant context is injected automatically.
|
|
405
183
|
|
|
406
|
-
|
|
407
|
-
- **phase**: feature, bugfix, refactor, test, docs
|
|
408
|
-
- **ai**: llm, ai-agent, mcp, rag, vector-db, embedding
|
|
409
|
-
- **cloud**: serverless, microservices, edge, wasm
|
|
410
|
-
- And more...
|
|
184
|
+
At session start, the 3 most recent sessions are shown so you can quickly resume with `/mneme:resume <id>`.
|
|
411
185
|
|
|
412
186
|
## Security and Privacy
|
|
413
187
|
|
|
414
188
|
mneme operates **entirely locally** with no data sent to external servers.
|
|
415
189
|
|
|
416
|
-
| Item
|
|
417
|
-
|
|
418
|
-
| **External Communication** | None - no curl/fetch/HTTP requests are made
|
|
419
|
-
| **Data Storage**
|
|
420
|
-
| **Conversation History**
|
|
421
|
-
| **Tools Used**
|
|
422
|
-
| **Code**
|
|
423
|
-
|
|
424
|
-
### Privacy by Design
|
|
425
|
-
|
|
426
|
-
- **Conversations (interactions) are local-only**: Stored in SQLite (`local.db`), auto-added to `.gitignore`
|
|
427
|
-
- **Only metadata is Git-shareable**: Session summaries, decisions, patterns can be shared with team via JSON
|
|
428
|
-
- **No telemetry**: No usage tracking or external data transmission
|
|
190
|
+
| Item | Description |
|
|
191
|
+
| -------------------------- | ------------------------------------------------------------------- |
|
|
192
|
+
| **External Communication** | None - no curl/fetch/HTTP requests are made |
|
|
193
|
+
| **Data Storage** | All data stored in project's `.mneme/` directory |
|
|
194
|
+
| **Conversation History** | Stored in `local.db`, automatically gitignored (not shared via Git) |
|
|
195
|
+
| **Tools Used** | bash, Node.js, jq, sqlite3 (no external dependencies) |
|
|
196
|
+
| **Code** | Open source - all code is auditable |
|
|
429
197
|
|
|
430
198
|
## License
|
|
431
199
|
|
package/dist/lib/db.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
// lib/
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
// lib/suppress-sqlite-warning.ts
|
|
6
2
|
var originalEmit = process.emit;
|
|
7
3
|
process.emit = (event, ...args) => {
|
|
8
4
|
if (event === "warning" && typeof args[0] === "object" && args[0] !== null && "name" in args[0] && args[0].name === "ExperimentalWarning" && "message" in args[0] && typeof args[0].message === "string" && args[0].message.includes("SQLite")) {
|
|
@@ -10,6 +6,12 @@ process.emit = (event, ...args) => {
|
|
|
10
6
|
}
|
|
11
7
|
return originalEmit.apply(process, [event, ...args]);
|
|
12
8
|
};
|
|
9
|
+
|
|
10
|
+
// lib/db.ts
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
13
15
|
var { DatabaseSync } = await import("node:sqlite");
|
|
14
16
|
var __filename = fileURLToPath(import.meta.url);
|
|
15
17
|
var __dirname = dirname(__filename);
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// lib/
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as os from "node:os";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import * as readline from "node:readline";
|
|
3
|
+
// lib/suppress-sqlite-warning.ts
|
|
8
4
|
var originalEmit = process.emit;
|
|
9
5
|
process.emit = (event, ...args) => {
|
|
10
6
|
if (event === "warning" && typeof args[0] === "object" && args[0] !== null && "name" in args[0] && args[0].name === "ExperimentalWarning" && "message" in args[0] && typeof args[0].message === "string" && args[0].message.includes("SQLite")) {
|
|
@@ -12,6 +8,12 @@ process.emit = (event, ...args) => {
|
|
|
12
8
|
}
|
|
13
9
|
return originalEmit.apply(process, [event, ...args]);
|
|
14
10
|
};
|
|
11
|
+
|
|
12
|
+
// lib/incremental-save.ts
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import * as readline from "node:readline";
|
|
15
17
|
var { DatabaseSync } = await import("node:sqlite");
|
|
16
18
|
function getSchemaPath() {
|
|
17
19
|
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
|
@@ -414,6 +416,32 @@ function resolveMnemeSessionId(projectPath, claudeSessionId) {
|
|
|
414
416
|
}
|
|
415
417
|
return shortId;
|
|
416
418
|
}
|
|
419
|
+
function findSessionFileById(projectPath, mnemeSessionId) {
|
|
420
|
+
const sessionsDir = path.join(projectPath, ".mneme", "sessions");
|
|
421
|
+
const searchDir = (dir) => {
|
|
422
|
+
if (!fs.existsSync(dir)) return null;
|
|
423
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
424
|
+
const fullPath = path.join(dir, entry.name);
|
|
425
|
+
if (entry.isDirectory()) {
|
|
426
|
+
const result = searchDir(fullPath);
|
|
427
|
+
if (result) return result;
|
|
428
|
+
} else if (entry.name === `${mnemeSessionId}.json`) {
|
|
429
|
+
return fullPath;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
};
|
|
434
|
+
return searchDir(sessionsDir);
|
|
435
|
+
}
|
|
436
|
+
function hasSessionSummary(sessionFile) {
|
|
437
|
+
if (!sessionFile) return false;
|
|
438
|
+
try {
|
|
439
|
+
const session = JSON.parse(fs.readFileSync(sessionFile, "utf8"));
|
|
440
|
+
return !!session.summary;
|
|
441
|
+
} catch {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
417
445
|
async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
|
|
418
446
|
if (!claudeSessionId || !transcriptPath || !projectPath) {
|
|
419
447
|
return {
|
|
@@ -572,29 +600,8 @@ function cleanupUncommittedSession(claudeSessionId, projectPath) {
|
|
|
572
600
|
return { deleted: false, count: 0 };
|
|
573
601
|
}
|
|
574
602
|
const mnemeSessionId = resolveMnemeSessionId(projectPath, claudeSessionId);
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
const searchDir = (dir) => {
|
|
578
|
-
if (!fs.existsSync(dir)) return null;
|
|
579
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
580
|
-
if (entry.isDirectory()) {
|
|
581
|
-
const result = searchDir(path.join(dir, entry.name));
|
|
582
|
-
if (result) return result;
|
|
583
|
-
} else if (entry.name === `${mnemeSessionId}.json`) {
|
|
584
|
-
return path.join(dir, entry.name);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
return null;
|
|
588
|
-
};
|
|
589
|
-
const sessionFile = searchDir(sessionsDir);
|
|
590
|
-
if (sessionFile) {
|
|
591
|
-
try {
|
|
592
|
-
const session = JSON.parse(fs.readFileSync(sessionFile, "utf8"));
|
|
593
|
-
hasSummary = !!session.summary;
|
|
594
|
-
} catch {
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
if (hasSummary) {
|
|
603
|
+
const sessionFile = findSessionFileById(projectPath, mnemeSessionId);
|
|
604
|
+
if (hasSessionSummary(sessionFile)) {
|
|
598
605
|
return { deleted: false, count: 0 };
|
|
599
606
|
}
|
|
600
607
|
const countStmt = db.prepare(
|
|
@@ -620,6 +627,80 @@ function cleanupUncommittedSession(claudeSessionId, projectPath) {
|
|
|
620
627
|
db.close();
|
|
621
628
|
}
|
|
622
629
|
}
|
|
630
|
+
function cleanupStaleUncommittedSessions(projectPath, graceDays) {
|
|
631
|
+
const dbPath = path.join(projectPath, ".mneme", "local.db");
|
|
632
|
+
if (!fs.existsSync(dbPath)) {
|
|
633
|
+
return { deletedSessions: 0, deletedInteractions: 0 };
|
|
634
|
+
}
|
|
635
|
+
const db = new DatabaseSync(dbPath);
|
|
636
|
+
let deletedSessions = 0;
|
|
637
|
+
let deletedInteractions = 0;
|
|
638
|
+
const normalizedGraceDays = Math.max(1, Math.floor(graceDays));
|
|
639
|
+
try {
|
|
640
|
+
const staleStmt = db.prepare(
|
|
641
|
+
`
|
|
642
|
+
SELECT claude_session_id, mneme_session_id
|
|
643
|
+
FROM session_save_state
|
|
644
|
+
WHERE is_committed = 0
|
|
645
|
+
AND updated_at <= datetime('now', ?)
|
|
646
|
+
`
|
|
647
|
+
);
|
|
648
|
+
const staleRows = staleStmt.all(`-${normalizedGraceDays} days`);
|
|
649
|
+
if (staleRows.length === 0) {
|
|
650
|
+
return { deletedSessions: 0, deletedInteractions: 0 };
|
|
651
|
+
}
|
|
652
|
+
const deleteInteractionStmt = db.prepare(
|
|
653
|
+
"DELETE FROM interactions WHERE claude_session_id = ?"
|
|
654
|
+
);
|
|
655
|
+
const countInteractionStmt = db.prepare(
|
|
656
|
+
"SELECT COUNT(*) as count FROM interactions WHERE claude_session_id = ?"
|
|
657
|
+
);
|
|
658
|
+
const deleteStateStmt = db.prepare(
|
|
659
|
+
"DELETE FROM session_save_state WHERE claude_session_id = ?"
|
|
660
|
+
);
|
|
661
|
+
for (const row of staleRows) {
|
|
662
|
+
const sessionFile = findSessionFileById(
|
|
663
|
+
projectPath,
|
|
664
|
+
row.mneme_session_id
|
|
665
|
+
);
|
|
666
|
+
if (hasSessionSummary(sessionFile)) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const countResult = countInteractionStmt.get(row.claude_session_id);
|
|
670
|
+
const count = countResult?.count || 0;
|
|
671
|
+
if (count > 0) {
|
|
672
|
+
deleteInteractionStmt.run(row.claude_session_id);
|
|
673
|
+
deletedInteractions += count;
|
|
674
|
+
}
|
|
675
|
+
deleteStateStmt.run(row.claude_session_id);
|
|
676
|
+
if (sessionFile && fs.existsSync(sessionFile)) {
|
|
677
|
+
try {
|
|
678
|
+
fs.unlinkSync(sessionFile);
|
|
679
|
+
deletedSessions += 1;
|
|
680
|
+
} catch {
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const linkPath = path.join(
|
|
684
|
+
projectPath,
|
|
685
|
+
".mneme",
|
|
686
|
+
"session-links",
|
|
687
|
+
`${row.claude_session_id.slice(0, 8)}.json`
|
|
688
|
+
);
|
|
689
|
+
if (fs.existsSync(linkPath)) {
|
|
690
|
+
try {
|
|
691
|
+
fs.unlinkSync(linkPath);
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return { deletedSessions, deletedInteractions };
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error(`[mneme] Error cleaning stale sessions: ${error}`);
|
|
699
|
+
return { deletedSessions: 0, deletedInteractions: 0 };
|
|
700
|
+
} finally {
|
|
701
|
+
db.close();
|
|
702
|
+
}
|
|
703
|
+
}
|
|
623
704
|
async function main() {
|
|
624
705
|
const args = process.argv.slice(2);
|
|
625
706
|
const getArg = (name) => {
|
|
@@ -668,6 +749,18 @@ async function main() {
|
|
|
668
749
|
const result = cleanupUncommittedSession(sessionId, projectPath);
|
|
669
750
|
console.log(JSON.stringify(result));
|
|
670
751
|
process.exit(0);
|
|
752
|
+
} else if (command === "cleanup-stale") {
|
|
753
|
+
const projectPath = getArg("project");
|
|
754
|
+
const graceDays = Number.parseInt(getArg("grace-days") || "7", 10);
|
|
755
|
+
if (!projectPath) {
|
|
756
|
+
console.error(
|
|
757
|
+
"Usage: incremental-save.js cleanup-stale --project <path> [--grace-days <n>]"
|
|
758
|
+
);
|
|
759
|
+
process.exit(1);
|
|
760
|
+
}
|
|
761
|
+
const result = cleanupStaleUncommittedSessions(projectPath, graceDays);
|
|
762
|
+
console.log(JSON.stringify(result));
|
|
763
|
+
process.exit(0);
|
|
671
764
|
} else {
|
|
672
765
|
console.error("Commands: save, commit, cleanup");
|
|
673
766
|
process.exit(1);
|
|
@@ -677,6 +770,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
677
770
|
main();
|
|
678
771
|
}
|
|
679
772
|
export {
|
|
773
|
+
cleanupStaleUncommittedSessions,
|
|
680
774
|
cleanupUncommittedSession,
|
|
681
775
|
incrementalSave,
|
|
682
776
|
markSessionCommitted
|