@nogataka/claw-memory 0.1.3 → 0.3.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/marketplace.json +5 -3
- package/.claude-plugin/plugin.json +11 -3
- package/README.ja.md +55 -11
- package/README.md +60 -11
- package/dist/cli.js +215 -1
- package/dist/core/db.js +132 -9
- package/dist/core/distill.js +97 -5
- package/dist/core/hooks.js +8 -7
- package/dist/core/installer/codex.js +19 -5
- package/dist/core/lesson-extract.js +233 -0
- package/dist/core/lesson-quality.js +79 -0
- package/dist/core/lesson-search.js +145 -0
- package/dist/core/lesson-share.js +80 -0
- package/dist/core/lessons.js +332 -0
- package/dist/core/logsearch/parse.js +0 -0
- package/dist/core/logsearch/paths.js +22 -0
- package/dist/core/logsearch/recent.js +46 -2
- package/dist/core/logsearch/search.js +57 -3
- package/dist/core/recall.js +61 -0
- package/dist/core/vector-memory.js +27 -10
- package/dist/mcp/server.js +177 -5
- package/dist/ui/page.js +125 -4
- package/dist/ui/server.js +65 -1
- package/hooks/claw-hook.sh +33 -9
- package/hooks/hooks.codex.json +0 -11
- package/hooks/hooks.json +0 -11
- package/package.json +4 -6
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claw-memory-marketplace",
|
|
3
|
-
"owner": {
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "nogataka"
|
|
5
|
+
},
|
|
4
6
|
"metadata": {
|
|
5
7
|
"description": "claw-memory: local semantic memory + raw-log search for Claude Code",
|
|
6
|
-
"version": "0.
|
|
8
|
+
"version": "0.3.0"
|
|
7
9
|
},
|
|
8
10
|
"plugins": [
|
|
9
11
|
{
|
|
10
12
|
"name": "claw-memory",
|
|
11
13
|
"source": "./",
|
|
12
14
|
"description": "Local semantic memory: auto-distill, recall injection, and raw Claude Code + Codex transcript search.",
|
|
13
|
-
"version": "0.
|
|
15
|
+
"version": "0.3.0"
|
|
14
16
|
}
|
|
15
17
|
]
|
|
16
18
|
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claw-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Local semantic memory for Claude Code: auto-distills sessions, injects relevant past context, and searches raw Claude Code + Codex transcripts. sqlite-vec + local embeddings, no daemon, no Python.",
|
|
5
|
-
"author": {
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "nogataka"
|
|
7
|
+
},
|
|
6
8
|
"repository": "https://github.com/nogataka/claw-memory",
|
|
7
9
|
"license": "MIT",
|
|
8
|
-
"keywords": [
|
|
10
|
+
"keywords": [
|
|
11
|
+
"memory",
|
|
12
|
+
"mcp",
|
|
13
|
+
"context",
|
|
14
|
+
"search",
|
|
15
|
+
"codex"
|
|
16
|
+
]
|
|
9
17
|
}
|
package/README.ja.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **日本語**
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
**AI コーディングエージェント(Claude Code / Codex)のためのローカル完結型・長期記憶。**
|
|
6
8
|
エージェントが過去のセッション・あなたの好み・以前の決定事項を記憶し、記録済みの全
|
|
7
9
|
トランスクリプトを横断検索できます。デーモン不要、Python 不要、外部ベクトル DB 不要。
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
npm install -g @nogataka/claw-memory
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
- **ストレージ**: `
|
|
17
|
+
- **ストレージ**: `node:sqlite`(Node >= 24 組み込み・ネイティブ ABI なし)+ `sqlite-vec` — ベクトルを 1 つの SQLite ファイル内に格納
|
|
16
18
|
- **埋め込み**: ローカルの `Xenova/multilingual-e5-small`(384次元・多言語・オフライン)
|
|
17
19
|
- **2 つの記憶ソース**: 蒸留済みのセマンティック DB と、Claude Code / Codex の生ログ全文検索
|
|
18
20
|
- **自動取り込み**: フックで終了済みセッションを蒸留し、新しいセッションへ関連記憶を注入
|
|
@@ -42,11 +44,16 @@ npm install -g @nogataka/claw-memory
|
|
|
42
44
|
| ソース | 内容 | ツール |
|
|
43
45
|
|--------|------|--------|
|
|
44
46
|
| **蒸留 DB** | LLM がセッションを要約 → 要約・好み・構造化メタデータ付きの埋め込みチャンク。セマンティック検索可能。 | `memory_recall`, `memory_search`, `memory_get` |
|
|
45
|
-
| **生ログ検索** | 実際の Claude Code(`~/.claude/projects`)/ Codex(`~/.codex/sessions`)ログを全文検索。蒸留していないセッションも対象。 | `memory_search_logs` |
|
|
47
|
+
| **生ログ検索** | 実際の Claude Code(`~/.claude/projects`)/ Codex(`~/.codex/sessions`)/ **ChatGPT web** エクスポート(`conversations.json`)ログを全文検索。蒸留していないセッションも対象。 | `memory_search_logs` |
|
|
46
48
|
|
|
47
49
|
蒸留 DB は整理済みで高速に想起でき、生ログ検索は claw-memory 導入前を含め「いつか話した
|
|
48
50
|
こと」を必ず見つけられるセーフティネットです。
|
|
49
51
|
|
|
52
|
+
**ChatGPT web** の会話は OpenAI サーバー側にありローカルに残らないため、公式エクスポートから読み込みます。
|
|
53
|
+
ChatGPT → Settings → **Data controls** → **Export data** でダウンロードし、解凍して `conversations.json` を
|
|
54
|
+
`~/.claw-memory/chatgpt/` に置く(または `CLAW_MEMORY_CHATGPT_EXPORT` でファイル/フォルダを指定)だけで、
|
|
55
|
+
他ログと同様に検索でき、`claw-memory distill-chatgpt` で専用 `chatgpt` プロジェクトとして意味 DB にも取り込めます。
|
|
56
|
+
|
|
50
57
|
### 2. 自動取り込み(distill)
|
|
51
58
|
|
|
52
59
|
セッション終了時、トランスクリプトを次の形に蒸留します。
|
|
@@ -94,7 +101,19 @@ npm install -g @nogataka/claw-memory
|
|
|
94
101
|
|
|
95
102
|
ビルド不要・読み取り専用のビューア(`claw-memory ui`)。プロジェクト・要約・チャンク
|
|
96
103
|
(メタデータ付き)・好みを閲覧でき、生ログ検索も実行できます。SSE で開いている間は自動
|
|
97
|
-
|
|
104
|
+
更新。起動した時だけ動きます。抽出されたレッスンをレビュー・承認・編集できる **Lessons**
|
|
105
|
+
タブも備えています。
|
|
106
|
+
|
|
107
|
+
### 8. 再利用可能なレッスン
|
|
108
|
+
|
|
109
|
+
過去ログをそのまま検索するだけでなく、claw-memory は AI コーディングセッションを
|
|
110
|
+
**再利用可能なレッスン**へと蒸留します。プロジェクト固有の制約、デバッグパターン、設計
|
|
111
|
+
判断、ユーザーの開発方針などの、実行可能で抽象化された知識です。レッスンは通常の要約と
|
|
112
|
+
同時に抽出され(追加の LLM 呼び出しなし)、ローカルに保存され、同じローカルモデルで埋め込
|
|
113
|
+
まれ、(承認後のみ)類似タスク時に想起されます。各レッスンは `scope`・`confidence`・
|
|
114
|
+
`applies_when` / `avoid_when` と、ライフサイクル(candidate → approved → archived /
|
|
115
|
+
superseded)を持ち、重複・矛盾検出と時間経過による confidence 減衰を備えます。raw ログは
|
|
116
|
+
根拠として保持しつつ、通常の想起では簡潔で再利用可能なレッスンを優先します。
|
|
98
117
|
|
|
99
118
|
---
|
|
100
119
|
|
|
@@ -125,7 +144,8 @@ npm install -g @nogataka/claw-memory
|
|
|
125
144
|
Claude Code を再起動してください。次が自動登録されます。
|
|
126
145
|
|
|
127
146
|
- **MCP サーバー**(8 つの記憶ツール)
|
|
128
|
-
- **フック**: `SessionStart`
|
|
147
|
+
- **フック**: `SessionStart` → コンパクトな recall 注入(Pull 型)、`Stop` → 自動 distill
|
|
148
|
+
フックのエラーは握り潰さず `~/.claw-memory/logs/hook-error.log` に記録されます
|
|
129
149
|
|
|
130
150
|
手動設定は不要です。確認は `/mcp` で `claw-memory` が見えるかどうかで行えます。
|
|
131
151
|
|
|
@@ -146,7 +166,7 @@ codex
|
|
|
146
166
|
プラグインは Codex が互換提供する `${CLAUDE_PLUGIN_ROOT}` を介して次を登録します。
|
|
147
167
|
|
|
148
168
|
- `claw-memory` MCP サーバー(`.mcp.json`)
|
|
149
|
-
- `SessionStart`
|
|
169
|
+
- `SessionStart` → **コンパクトな recall 自動注入**(設定+1行要約。詳細は `memory_recall` でオンデマンド取得)
|
|
150
170
|
- `Stop` → 最近の Codex セッションの**自動 distill**(watermark 重複回避・非同期)
|
|
151
171
|
- `memory-recall` skill
|
|
152
172
|
|
|
@@ -178,7 +198,7 @@ claw-memory distill-codex --all # 全件バックフィル
|
|
|
178
198
|
```bash
|
|
179
199
|
git clone https://github.com/nogataka/claw-memory
|
|
180
200
|
cd claw-memory
|
|
181
|
-
npm install #
|
|
201
|
+
npm install # ネイティブビルドなし(ストレージは Node 組み込みの node:sqlite)
|
|
182
202
|
npm run build # tsc -> dist/
|
|
183
203
|
npm link # 任意: `claw-memory` バイナリを公開
|
|
184
204
|
```
|
|
@@ -195,11 +215,17 @@ npm link # 任意: `claw-memory` バイナリを公開
|
|
|
195
215
|
| `memory_remember(text, cwd?, sessionId?)` | フリーテキストのメモを永続保存。 |
|
|
196
216
|
| `memory_distill(cwd, sessionId? \| transcriptPath?)` | セッションを要約して記憶化(LLM バックエンドが必要)。 |
|
|
197
217
|
| `memory_get_preferences(cwd?)` | プロジェクトの保存済み好みを一覧。 |
|
|
198
|
-
| `memory_search_logs(query, sources?, projectPath?, startDate?, endDate?, limit?, offset?)` | Claude Code + Codex
|
|
218
|
+
| `memory_search_logs(query, sources?, projectPath?, startDate?, endDate?, limit?, offset?)` | Claude Code + Codex + ChatGPT-web の生ログを全文検索(`sources`: `claude-code` / `codex` / `chatgpt-web`)。 |
|
|
199
219
|
| `memory_forget(ids)` | チャンクをソフト削除(検索 / recall / ビューアから除外)。 |
|
|
220
|
+
| `lesson_search(query, cwd?, limit?)` | 承認済みレッスンを関連度 + scope + confidence で検索。 |
|
|
221
|
+
| `lesson_inject(query, cwd?, limit?)` | 同上を `<relevant-lessons>` ブロックとして返す。 |
|
|
222
|
+
| `lesson_get(lesson_id)` | レッスン1件の詳細(全フィールド + 履歴 + リンク)。 |
|
|
223
|
+
| `lesson_extract(cwd, sessionId? \| transcriptPath?)` | セッションから専用のレッスン抽出(LLM 必要)。 |
|
|
224
|
+
| `lesson_approve / lesson_reject / lesson_archive(lesson_id, reason?)` | ステータス遷移。 |
|
|
225
|
+
| `lesson_supersede(old_lesson_id, new_lesson_id)` | 古いレッスンを新しいもので置き換え。 |
|
|
200
226
|
|
|
201
|
-
`memory_distill`(LLM)と `memory_search_logs
|
|
202
|
-
を直接読む)以外は完全ローカルで動作します。
|
|
227
|
+
`memory_distill` / `lesson_extract`(LLM)と `memory_search_logs`
|
|
228
|
+
(`~/.claude/projects`・`~/.codex/sessions` を直接読む)以外は完全ローカルで動作します。
|
|
203
229
|
|
|
204
230
|
---
|
|
205
231
|
|
|
@@ -218,6 +244,12 @@ npm link # 任意: `claw-memory` バイナリを公開
|
|
|
218
244
|
| `CLAW_MEMORY_EXCLUDED_PROJECTS` | — | 記録・想起しないパス部分文字列(カンマ/コロン区切り)。 |
|
|
219
245
|
| `MEMORY_SIMILARITY_MAX_DISTANCE` | `0.6` | セマンティックヒットの最大コサイン距離(小さいほど厳格)。 |
|
|
220
246
|
| `CLAW_MEMORY_UI_PORT` | `4319` | ビューアのポート。 |
|
|
247
|
+
| `LESSON_RECALL_LIMIT` | `3` | recall ブロックに注入する承認済みレッスン数(`0` で無効)。 |
|
|
248
|
+
| `CLAW_MEMORY_LESSON_DEDICATED` | — | `1` で専用の高品質レッスン抽出パスを実行(LLM 追加呼び出し)。 |
|
|
249
|
+
| `CLAW_MEMORY_LESSON_CONFLICT_LLM` | — | `1` で抽出時に LLM による矛盾検出を有効化。 |
|
|
250
|
+
| `LESSON_DECAY_FACTOR` / `LESSON_STALE_DAYS` | `0.9` / `30` | `lessons decay` の減衰係数と陳腐化しきい値。 |
|
|
251
|
+
| `CLAW_MEMORY_CHATGPT_EXPORT` | `~/.claw-memory/chatgpt` | ChatGPT `conversations.json` エクスポート(ファイル or `*.json` を含むフォルダ)。 |
|
|
252
|
+
| `CLAW_MEMORY_CHATGPT_MAX_BYTES` | `209715200`(200 MB) | これを超える ChatGPT エクスポートはスキップ(メモリ内パースのため)。 |
|
|
221
253
|
|
|
222
254
|
### LLM バックエンド
|
|
223
255
|
|
|
@@ -241,8 +273,18 @@ claw-memory mcp # stdio MCP サーバー(エ
|
|
|
241
273
|
claw-memory ui [--port N] [--open] # 読み取り専用 Web ビューア
|
|
242
274
|
claw-memory distill --cwd P --session ID [--path FILE] [--if-stale]
|
|
243
275
|
claw-memory distill-codex [--recent] [--limit N] [--all]
|
|
276
|
+
claw-memory distill-chatgpt [--limit N] [--all] # ChatGPT web エクスポートの会話を蒸留
|
|
244
277
|
claw-memory remember --cwd P "メモ"
|
|
245
|
-
claw-memory
|
|
278
|
+
claw-memory lessons list [--status candidate|approved|...] [--cwd P]
|
|
279
|
+
claw-memory lessons search "クエリ" [--cwd P] [--limit N]
|
|
280
|
+
claw-memory lessons inject "クエリ" [--cwd P] [--limit N]
|
|
281
|
+
claw-memory lessons extract --session ID [--cwd P] [--path FILE]
|
|
282
|
+
claw-memory lessons approve|reject|archive <lesson_id> [--reason R]
|
|
283
|
+
claw-memory lessons supersede <old_id> <new_id>
|
|
284
|
+
claw-memory lessons decay [--days N] [--factor F] [--dry]
|
|
285
|
+
claw-memory lessons export [--status S] [--cwd P] > bundle.json
|
|
286
|
+
claw-memory lessons import bundle.json [--status S] [--cwd P]
|
|
287
|
+
claw-memory search-logs "クエリ" [--source claude-code,codex,chatgpt-web] [--project P]
|
|
246
288
|
[--start ISO] [--end ISO] [--limit N] [--offset N]
|
|
247
289
|
claw-memory hook <recall|distill> # ライフサイクルフック(JSON を stdin で受領)
|
|
248
290
|
claw-memory install [--codex | --claude-code] # MCP + フックを登録(既定: codex)
|
|
@@ -311,7 +353,9 @@ npm uninstall -g @nogataka/claw-memory
|
|
|
311
353
|
|
|
312
354
|
## 補足
|
|
313
355
|
|
|
314
|
-
-
|
|
356
|
+
- ストレージは Node 組み込みの `node:sqlite` を使用します(Node >= 24 必須)。Node を更新してもネイティブ ABI 不一致で壊れることはありません。`sqlite-vec` 拡張はプリビルト配布で ABI 非依存です。
|
|
357
|
+
- v0.3.0 で毎プロンプト注入を廃止しました(コンテキスト圧迫対策)。フックは `SessionStart` にコンパクトなブロックを1回注入するのみで、詳細は `memory_recall` / `memory_search`(MCP)または `memory-recall` スキルで明示的に取得します。
|
|
358
|
+
- `claw-memory cleanse` で、生 JSON が混入した過去チャンクを検出(`--apply` でソフトデリート)できます。
|
|
315
359
|
- MCP サーバーはエージェントセッションごとに常駐するため、埋め込みモデルは 1 回だけロードされます。
|
|
316
360
|
- ビューアと MCP は同時実行できます(SQLite WAL が並行読み書きを処理)。
|
|
317
361
|
- インストール時、依存は `legacy-peer-deps=true` で解決されます(同梱 SDK 間の zod の
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [日本語](README.ja.md)
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
**Local, in-process long-term memory for AI coding agents (Claude Code & Codex).**
|
|
6
8
|
Your agent remembers past sessions, your preferences, and prior decisions — and can
|
|
7
9
|
search every raw transcript you've ever recorded. No daemon, no Python, no external
|
|
@@ -12,7 +14,7 @@ session, which you control).
|
|
|
12
14
|
npm install -g @nogataka/claw-memory
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
- **Storage**: `
|
|
17
|
+
- **Storage**: `node:sqlite` (built into Node >= 24, no native ABI) + `sqlite-vec` — vectors live *inside* one SQLite file
|
|
16
18
|
- **Embeddings**: local `Xenova/multilingual-e5-small` (384-dim, multilingual, offline)
|
|
17
19
|
- **Two memory sources**: a distilled semantic DB **and** full-text search over raw
|
|
18
20
|
Claude Code + Codex transcripts
|
|
@@ -44,11 +46,18 @@ npm install -g @nogataka/claw-memory
|
|
|
44
46
|
| Source | What it is | Tooling |
|
|
45
47
|
|--------|-----------|---------|
|
|
46
48
|
| **Distilled DB** | LLM-summarized sessions → summaries, preferences, and embedded conversation chunks with structured metadata. Semantically searchable. | `memory_recall`, `memory_search`, `memory_get` |
|
|
47
|
-
| **Raw transcript search** | Full-text grep over your *actual* Claude Code (`~/.claude/projects`)
|
|
49
|
+
| **Raw transcript search** | Full-text grep over your *actual* Claude Code (`~/.claude/projects`), Codex (`~/.codex/sessions`), and **ChatGPT web** export (`conversations.json`) logs — including sessions that were never distilled. | `memory_search_logs` |
|
|
48
50
|
|
|
49
51
|
The distilled DB is curated and fast to recall; raw search is a safety net that finds
|
|
50
52
|
anything you ever discussed, even before claw-memory was installed.
|
|
51
53
|
|
|
54
|
+
**ChatGPT web** conversations live on OpenAI's servers, not on disk, so claw-memory
|
|
55
|
+
reads them from the official export: ChatGPT → Settings → **Data controls** → **Export
|
|
56
|
+
data**; unzip and drop `conversations.json` into `~/.claw-memory/chatgpt/` (or point
|
|
57
|
+
`CLAW_MEMORY_CHATGPT_EXPORT` at the file/folder). Then it's searchable like any other
|
|
58
|
+
log, and `claw-memory distill-chatgpt` adds it to the semantic DB under a dedicated
|
|
59
|
+
`chatgpt` project.
|
|
60
|
+
|
|
52
61
|
### 2. Automatic capture (distill)
|
|
53
62
|
|
|
54
63
|
When a session ends, claw-memory distills the transcript into:
|
|
@@ -99,7 +108,21 @@ handle the high-frequency distill work.
|
|
|
99
108
|
|
|
100
109
|
A zero-build, read-only viewer (`claw-memory ui`) to browse projects, summaries,
|
|
101
110
|
chunks (with their metadata), preferences, and to run raw-log search — with live
|
|
102
|
-
updates via SSE. It runs only when you start it.
|
|
111
|
+
updates via SSE. It runs only when you start it. The viewer also has a **Lessons**
|
|
112
|
+
tab for reviewing, approving and editing extracted lessons.
|
|
113
|
+
|
|
114
|
+
### 8. Reusable lessons
|
|
115
|
+
|
|
116
|
+
Beyond retrieving past logs, claw-memory distills AI coding sessions into
|
|
117
|
+
**reusable lessons** — actionable, abstracted knowledge such as project-specific
|
|
118
|
+
constraints, debugging patterns, design decisions and user preferences. Lessons
|
|
119
|
+
are extracted alongside the normal summary (no extra LLM call), stored locally,
|
|
120
|
+
embedded with the same local model, and surfaced (only after approval) when a
|
|
121
|
+
similar task appears later. Each lesson carries `scope`, `confidence`,
|
|
122
|
+
`applies_when` / `avoid_when` and a lifecycle (candidate → approved → archived /
|
|
123
|
+
superseded), with duplicate / conflict detection and confidence decay over time.
|
|
124
|
+
Raw logs remain available as evidence; day-to-day recall focuses on concise,
|
|
125
|
+
reusable lessons.
|
|
103
126
|
|
|
104
127
|
---
|
|
105
128
|
|
|
@@ -130,7 +153,8 @@ plugin falls back to `npx -y @nogataka/claw-memory@latest`, which is slower on f
|
|
|
130
153
|
Restart Claude Code. This auto-registers:
|
|
131
154
|
|
|
132
155
|
- the **MCP server** (8 memory tools), and
|
|
133
|
-
- the **hooks**: `SessionStart`
|
|
156
|
+
- the **hooks**: `SessionStart` → compact recall injection (pull model), `Stop` → auto-distill.
|
|
157
|
+
Hook errors are logged to `~/.claw-memory/logs/hook-error.log` instead of being swallowed.
|
|
134
158
|
|
|
135
159
|
No manual config. To verify, run `/mcp` and look for `claw-memory`.
|
|
136
160
|
|
|
@@ -151,7 +175,7 @@ codex
|
|
|
151
175
|
The plugin registers, via Codex's `${CLAUDE_PLUGIN_ROOT}` (provided for compatibility):
|
|
152
176
|
|
|
153
177
|
- the `claw-memory` MCP server (`.mcp.json`),
|
|
154
|
-
- `SessionStart`
|
|
178
|
+
- `SessionStart` → **compact recall injection** (preferences + one-line summaries; details are pulled on demand via `memory_recall`),
|
|
155
179
|
- `Stop` → **auto distill** of recent Codex sessions (watermark-deduped, async), and
|
|
156
180
|
- the `memory-recall` skill.
|
|
157
181
|
|
|
@@ -183,7 +207,7 @@ claw-memory distill-codex --all # backfill everything
|
|
|
183
207
|
```bash
|
|
184
208
|
git clone https://github.com/nogataka/claw-memory
|
|
185
209
|
cd claw-memory
|
|
186
|
-
npm install #
|
|
210
|
+
npm install # no native builds — storage is Node's built-in node:sqlite
|
|
187
211
|
npm run build # tsc -> dist/
|
|
188
212
|
npm link # optional: expose the `claw-memory` binary
|
|
189
213
|
```
|
|
@@ -200,11 +224,18 @@ npm link # optional: expose the `claw-memory` binary
|
|
|
200
224
|
| `memory_remember(text, cwd?, sessionId?)` | Store a durable free-text note. |
|
|
201
225
|
| `memory_distill(cwd, sessionId? \| transcriptPath?)` | Summarize a session into memory (needs an LLM backend). |
|
|
202
226
|
| `memory_get_preferences(cwd?)` | List stored preferences for the project. |
|
|
203
|
-
| `memory_search_logs(query, sources?, projectPath?, startDate?, endDate?, limit?, offset?)` | Full-text search over RAW Claude Code + Codex transcripts. |
|
|
227
|
+
| `memory_search_logs(query, sources?, projectPath?, startDate?, endDate?, limit?, offset?)` | Full-text search over RAW Claude Code + Codex + ChatGPT-web transcripts (`sources`: `claude-code` / `codex` / `chatgpt-web`). |
|
|
204
228
|
| `memory_forget(ids)` | Soft-delete chunks (hidden from search / recall / viewer). |
|
|
229
|
+
| `lesson_search(query, cwd?, limit?)` | Search approved reusable lessons, ranked by relevance + scope + confidence. |
|
|
230
|
+
| `lesson_inject(query, cwd?, limit?)` | Same, returned as a ready-to-read `<relevant-lessons>` block. |
|
|
231
|
+
| `lesson_get(lesson_id)` | Full detail of one lesson (fields + history + links). |
|
|
232
|
+
| `lesson_extract(cwd, sessionId? \| transcriptPath?)` | Dedicated lesson-extraction pass over a session (needs an LLM backend). |
|
|
233
|
+
| `lesson_approve / lesson_reject / lesson_archive(lesson_id, reason?)` | Lifecycle transitions. |
|
|
234
|
+
| `lesson_supersede(old_lesson_id, new_lesson_id)` | Replace an old lesson with a newer one. |
|
|
205
235
|
|
|
206
|
-
All tools are fully local except `memory_distill` (LLM) and
|
|
207
|
-
(reads `~/.claude/projects` and `~/.codex/sessions`
|
|
236
|
+
All tools are fully local except `memory_distill` / `lesson_extract` (LLM) and
|
|
237
|
+
`memory_search_logs` (reads `~/.claude/projects` and `~/.codex/sessions`
|
|
238
|
+
directly).
|
|
208
239
|
|
|
209
240
|
---
|
|
210
241
|
|
|
@@ -223,6 +254,12 @@ All tools are fully local except `memory_distill` (LLM) and `memory_search_logs`
|
|
|
223
254
|
| `CLAW_MEMORY_EXCLUDED_PROJECTS` | — | Comma/colon-separated path substrings to never record or recall. |
|
|
224
255
|
| `MEMORY_SIMILARITY_MAX_DISTANCE` | `0.6` | Max cosine distance for a semantic hit (lower = stricter). |
|
|
225
256
|
| `CLAW_MEMORY_UI_PORT` | `4319` | Viewer port. |
|
|
257
|
+
| `LESSON_RECALL_LIMIT` | `3` | Approved lessons injected into the recall block (`0` disables). |
|
|
258
|
+
| `CLAW_MEMORY_LESSON_DEDICATED` | — | `1` = run a separate, higher-quality lesson-extraction pass (extra LLM call). |
|
|
259
|
+
| `CLAW_MEMORY_LESSON_CONFLICT_LLM` | — | `1` = use the LLM to detect conflicting lessons during extraction. |
|
|
260
|
+
| `LESSON_DECAY_FACTOR` / `LESSON_STALE_DAYS` | `0.9` / `30` | Confidence-decay factor and staleness threshold for `lessons decay`. |
|
|
261
|
+
| `CLAW_MEMORY_CHATGPT_EXPORT` | `~/.claw-memory/chatgpt` | ChatGPT `conversations.json` export — a file or a folder of `*.json`. |
|
|
262
|
+
| `CLAW_MEMORY_CHATGPT_MAX_BYTES` | `209715200` (200 MB) | Skip ChatGPT export files larger than this (parsed in memory). |
|
|
226
263
|
|
|
227
264
|
### LLM backends
|
|
228
265
|
|
|
@@ -246,8 +283,18 @@ claw-memory mcp # stdio MCP server (what agents
|
|
|
246
283
|
claw-memory ui [--port N] [--open] # read-only web viewer
|
|
247
284
|
claw-memory distill --cwd P --session ID [--path FILE] [--if-stale]
|
|
248
285
|
claw-memory distill-codex [--recent] [--limit N] [--all]
|
|
286
|
+
claw-memory distill-chatgpt [--limit N] [--all] # distill ChatGPT web export conversations
|
|
249
287
|
claw-memory remember --cwd P "a note"
|
|
250
|
-
claw-memory
|
|
288
|
+
claw-memory lessons list [--status candidate|approved|...] [--cwd P]
|
|
289
|
+
claw-memory lessons search "query" [--cwd P] [--limit N]
|
|
290
|
+
claw-memory lessons inject "query" [--cwd P] [--limit N]
|
|
291
|
+
claw-memory lessons extract --session ID [--cwd P] [--path FILE]
|
|
292
|
+
claw-memory lessons approve|reject|archive <lesson_id> [--reason R]
|
|
293
|
+
claw-memory lessons supersede <old_id> <new_id>
|
|
294
|
+
claw-memory lessons decay [--days N] [--factor F] [--dry]
|
|
295
|
+
claw-memory lessons export [--status S] [--cwd P] > bundle.json
|
|
296
|
+
claw-memory lessons import bundle.json [--status S] [--cwd P]
|
|
297
|
+
claw-memory search-logs "query" [--source claude-code,codex,chatgpt-web] [--project P]
|
|
251
298
|
[--start ISO] [--end ISO] [--limit N] [--offset N]
|
|
252
299
|
claw-memory hook <recall|distill> # lifecycle hook (reads JSON on stdin)
|
|
253
300
|
claw-memory install [--codex | --claude-code] # register MCP + hooks (default: codex)
|
|
@@ -316,7 +363,9 @@ Your memory database is left untouched; delete `~/.claw-memory` to wipe it.
|
|
|
316
363
|
|
|
317
364
|
## Notes
|
|
318
365
|
|
|
319
|
-
-
|
|
366
|
+
- Storage uses Node's built-in `node:sqlite` (Node >= 24 required), so Node upgrades can't break a native ABI. The `sqlite-vec` extension ships prebuilt and is version-independent.
|
|
367
|
+
- Per-prompt injection was removed in v0.3.0 (context-bloat fix): hooks inject a compact block at `SessionStart` only; pull details on demand with `memory_recall` / `memory_search` or the `memory-recall` skill.
|
|
368
|
+
- `claw-memory cleanse` finds (and with `--apply` tombstones) legacy chunks polluted by raw JSON payloads.
|
|
320
369
|
- The MCP server is long-lived per agent session, so the embedding model loads once.
|
|
321
370
|
- Viewer + MCP can run simultaneously — SQLite WAL handles concurrent read/write.
|
|
322
371
|
- On install, dependencies resolve with `legacy-peer-deps=true` (a zod peer-range overlap
|
package/dist/cli.js
CHANGED
|
@@ -162,6 +162,79 @@ async function main() {
|
|
|
162
162
|
console.log(JSON.stringify({ scanned: files.length, distilled, skipped, failed }, null, 2));
|
|
163
163
|
process.exit(0);
|
|
164
164
|
}
|
|
165
|
+
case "distill-chatgpt": {
|
|
166
|
+
const { loadChatgptConversations } = await import("./core/logsearch/recent.js");
|
|
167
|
+
const { chatgptProjectPath } = await import("./core/logsearch/paths.js");
|
|
168
|
+
const { distill } = await import("./core/distill.js");
|
|
169
|
+
const { getOrCreateProjectByPath } = await import("./core/projects.js");
|
|
170
|
+
const { shouldDistill, markDistilled } = await import("./core/watermark.js");
|
|
171
|
+
const all = hasFlag(rest, "all");
|
|
172
|
+
const limit = Number(getFlag(rest, "limit") ?? (all ? 100000 : 50));
|
|
173
|
+
const convos = (await loadChatgptConversations()).slice(0, limit);
|
|
174
|
+
// ChatGPT has no cwd; all conversations live under one stable synthetic
|
|
175
|
+
// project so they never pollute a real repository's recall.
|
|
176
|
+
const project = getOrCreateProjectByPath(chatgptProjectPath);
|
|
177
|
+
let distilled = 0, skipped = 0, failed = 0;
|
|
178
|
+
for (const conv of convos) {
|
|
179
|
+
// Watermark per conversation, keyed by id + last write time.
|
|
180
|
+
const wmKey = `chatgpt:${conv.conversationId}`;
|
|
181
|
+
const mtimeMs = conv.updateTime ? new Date(conv.updateTime).getTime() : 0;
|
|
182
|
+
if (!shouldDistill(wmKey, mtimeMs)) {
|
|
183
|
+
skipped++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const res = await distill({
|
|
188
|
+
projectId: project.id,
|
|
189
|
+
sessionId: conv.conversationId,
|
|
190
|
+
messages: conv.messages.map((m) => ({ role: m.role, text: m.text })),
|
|
191
|
+
});
|
|
192
|
+
markDistilled(wmKey, mtimeMs);
|
|
193
|
+
if (res.skipped)
|
|
194
|
+
skipped++;
|
|
195
|
+
else
|
|
196
|
+
distilled++;
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
failed++;
|
|
200
|
+
console.error(`distill failed ${conv.conversationId}: ${String(e)}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
console.log(JSON.stringify({ scanned: convos.length, distilled, skipped, failed }, null, 2));
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
case "cleanse": {
|
|
207
|
+
// Hygiene pass over stored chunks: find rows whose text is a raw JSON
|
|
208
|
+
// payload (e.g. old distill responses stored verbatim) and tombstone
|
|
209
|
+
// them via the existing soft-delete. Dry-run by default; --apply acts.
|
|
210
|
+
const { sqlite } = await import("./core/db.js");
|
|
211
|
+
const { isContaminatedChunkText } = await import("./core/distill.js");
|
|
212
|
+
const { forgetChunks } = await import("./core/vector-memory.js");
|
|
213
|
+
const rows = sqlite
|
|
214
|
+
.prepare("SELECT id, user_text, assistant_text FROM conversation_chunks WHERE deleted_at IS NULL")
|
|
215
|
+
.all();
|
|
216
|
+
const bad = rows.filter((r) => isContaminatedChunkText(r.user_text) ||
|
|
217
|
+
isContaminatedChunkText(r.assistant_text));
|
|
218
|
+
if (hasFlag(rest, "apply")) {
|
|
219
|
+
const n = forgetChunks(bad.map((b) => b.id));
|
|
220
|
+
console.log(JSON.stringify({ scanned: rows.length, contaminated: bad.length, tombstoned: n }, null, 2));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
for (const b of bad.slice(0, Number(getFlag(rest, "show") ?? 5))) {
|
|
224
|
+
const t = isContaminatedChunkText(b.user_text)
|
|
225
|
+
? b.user_text
|
|
226
|
+
: b.assistant_text;
|
|
227
|
+
console.log(`${b.id} ${t.slice(0, 120).replace(/\s+/g, " ")}`);
|
|
228
|
+
}
|
|
229
|
+
console.log(JSON.stringify({
|
|
230
|
+
scanned: rows.length,
|
|
231
|
+
contaminated: bad.length,
|
|
232
|
+
dryRun: true,
|
|
233
|
+
hint: "run with --apply to tombstone (soft-delete)",
|
|
234
|
+
}, null, 2));
|
|
235
|
+
}
|
|
236
|
+
process.exit(0);
|
|
237
|
+
}
|
|
165
238
|
case "remember": {
|
|
166
239
|
const { getOrCreateProjectByPath } = await import("./core/projects.js");
|
|
167
240
|
const { rememberText } = await import("./core/distill.js");
|
|
@@ -201,14 +274,155 @@ async function main() {
|
|
|
201
274
|
console.log(JSON.stringify(out, null, 2));
|
|
202
275
|
process.exit(0);
|
|
203
276
|
}
|
|
277
|
+
case "lessons": {
|
|
278
|
+
const sub = rest[0];
|
|
279
|
+
const subRest = rest.slice(1);
|
|
280
|
+
const positionals = (args) => {
|
|
281
|
+
const out = [];
|
|
282
|
+
for (let i = 0; i < args.length; i++) {
|
|
283
|
+
if (args[i].startsWith("--")) {
|
|
284
|
+
i++;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
out.push(args[i]);
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
};
|
|
291
|
+
const projectId = async () => {
|
|
292
|
+
const { getOrCreateProjectByPath } = await import("./core/projects.js");
|
|
293
|
+
return getOrCreateProjectByPath(getFlag(subRest, "cwd") ?? process.cwd()).id;
|
|
294
|
+
};
|
|
295
|
+
switch (sub) {
|
|
296
|
+
case "list": {
|
|
297
|
+
const { listLessons } = await import("./core/lessons.js");
|
|
298
|
+
const status = getFlag(subRest, "status");
|
|
299
|
+
const rows = listLessons({ status, projectId: getFlag(subRest, "cwd") ? await projectId() : undefined });
|
|
300
|
+
for (const l of rows) {
|
|
301
|
+
console.log(`${l.id} [${l.status}/${l.scope}] conf=${l.confidence.toFixed(2)} ${l.title}`);
|
|
302
|
+
}
|
|
303
|
+
console.log(`(${rows.length} lessons)`);
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
case "search": {
|
|
307
|
+
const { searchLessons } = await import("./core/lesson-search.js");
|
|
308
|
+
const query = positionals(subRest).join(" ").trim();
|
|
309
|
+
if (!query)
|
|
310
|
+
throw new Error("lessons search requires a query");
|
|
311
|
+
const hits = await searchLessons(query, { projectId: await projectId() }, { limit: Number(getFlag(subRest, "limit") ?? 5) });
|
|
312
|
+
for (const l of hits) {
|
|
313
|
+
console.log(`${l.id} score=${l.score.toFixed(3)} [${l.scope}] ${l.title}`);
|
|
314
|
+
}
|
|
315
|
+
process.exit(0);
|
|
316
|
+
}
|
|
317
|
+
case "inject": {
|
|
318
|
+
const { injectLessons } = await import("./core/lesson-search.js");
|
|
319
|
+
const query = positionals(subRest).join(" ").trim();
|
|
320
|
+
if (!query)
|
|
321
|
+
throw new Error("lessons inject requires a query");
|
|
322
|
+
console.log(await injectLessons(query, { projectId: await projectId() }, { limit: Number(getFlag(subRest, "limit") ?? 5) }));
|
|
323
|
+
process.exit(0);
|
|
324
|
+
}
|
|
325
|
+
case "approve":
|
|
326
|
+
case "reject":
|
|
327
|
+
case "archive": {
|
|
328
|
+
const { setStatus } = await import("./core/lessons.js");
|
|
329
|
+
const id = positionals(subRest)[0];
|
|
330
|
+
if (!id)
|
|
331
|
+
throw new Error(`lessons ${sub} requires a lesson_id`);
|
|
332
|
+
const newStatus = sub === "approve" ? "approved" : sub === "reject" ? "rejected" : "archived";
|
|
333
|
+
const ok = setStatus(id, newStatus, getFlag(subRest, "reason"));
|
|
334
|
+
console.log(ok ? `${id} -> ${newStatus}` : "(該当なし)");
|
|
335
|
+
process.exit(ok ? 0 : 1);
|
|
336
|
+
}
|
|
337
|
+
case "supersede": {
|
|
338
|
+
const { supersede } = await import("./core/lessons.js");
|
|
339
|
+
const [oldId, newId] = positionals(subRest);
|
|
340
|
+
if (!oldId || !newId)
|
|
341
|
+
throw new Error("lessons supersede requires <old_id> <new_id>");
|
|
342
|
+
const ok = supersede(oldId, newId);
|
|
343
|
+
console.log(ok ? `${oldId} superseded by ${newId}` : "(該当なし)");
|
|
344
|
+
process.exit(ok ? 0 : 1);
|
|
345
|
+
}
|
|
346
|
+
case "decay": {
|
|
347
|
+
const { decayConfidence, listStale } = await import("./core/lesson-quality.js");
|
|
348
|
+
const dryRun = hasFlag(subRest, "dry");
|
|
349
|
+
const res = decayConfidence({
|
|
350
|
+
factor: getFlag(subRest, "factor") ? Number(getFlag(subRest, "factor")) : undefined,
|
|
351
|
+
staleDays: getFlag(subRest, "days") ? Number(getFlag(subRest, "days")) : undefined,
|
|
352
|
+
dryRun,
|
|
353
|
+
});
|
|
354
|
+
console.log(JSON.stringify({ ...res, dryRun, stale: listStale().length }, null, 2));
|
|
355
|
+
process.exit(0);
|
|
356
|
+
}
|
|
357
|
+
case "export": {
|
|
358
|
+
const { exportLessons } = await import("./core/lesson-share.js");
|
|
359
|
+
const bundle = exportLessons({
|
|
360
|
+
projectId: getFlag(subRest, "cwd") ? await projectId() : undefined,
|
|
361
|
+
status: getFlag(subRest, "status"),
|
|
362
|
+
});
|
|
363
|
+
console.log(JSON.stringify(bundle, null, 2));
|
|
364
|
+
process.exit(0);
|
|
365
|
+
}
|
|
366
|
+
case "import": {
|
|
367
|
+
const { importLessons } = await import("./core/lesson-share.js");
|
|
368
|
+
const { readFileSync } = await import("node:fs");
|
|
369
|
+
const file = positionals(subRest)[0];
|
|
370
|
+
if (!file)
|
|
371
|
+
throw new Error("lessons import requires a bundle file path");
|
|
372
|
+
const bundle = JSON.parse(readFileSync(file, "utf-8"));
|
|
373
|
+
const res = await importLessons(bundle, {
|
|
374
|
+
projectId: await projectId(),
|
|
375
|
+
status: getFlag(subRest, "status"),
|
|
376
|
+
});
|
|
377
|
+
console.log(JSON.stringify(res, null, 2));
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
case "extract": {
|
|
381
|
+
const { getOrCreateProjectByPath } = await import("./core/projects.js");
|
|
382
|
+
const { resolveSessionJsonl, loadTranscript } = await import("./core/transcript.js");
|
|
383
|
+
const { stripPrivate } = await import("./core/private.js");
|
|
384
|
+
const { extractDedicated, saveCandidates } = await import("./core/lesson-extract.js");
|
|
385
|
+
const cwd = getFlag(subRest, "cwd") ?? process.cwd();
|
|
386
|
+
const session = getFlag(subRest, "session") ?? "";
|
|
387
|
+
const path = getFlag(subRest, "path") ?? (session ? resolveSessionJsonl(cwd, session) : undefined);
|
|
388
|
+
if (!path)
|
|
389
|
+
throw new Error("lessons extract requires --session or --path");
|
|
390
|
+
const project = getOrCreateProjectByPath(cwd);
|
|
391
|
+
const msgs = loadTranscript(path).map((m) => ({ ...m, text: stripPrivate(m.text) })).filter((m) => m.text.trim());
|
|
392
|
+
const transcript = msgs.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.text.slice(0, 500)}`).join("\n");
|
|
393
|
+
const candidates = await extractDedicated(transcript);
|
|
394
|
+
const ids = await saveCandidates({ projectId: project.id, sessionId: session || path, candidates });
|
|
395
|
+
console.log(JSON.stringify({ extracted: candidates.length, saved: ids.length }, null, 2));
|
|
396
|
+
process.exit(0);
|
|
397
|
+
}
|
|
398
|
+
default:
|
|
399
|
+
console.error("Usage: claw-memory lessons <subcommand>\n" +
|
|
400
|
+
" list [--status candidate|approved|...] [--cwd P]\n" +
|
|
401
|
+
" search \"query\" [--cwd P] [--limit N]\n" +
|
|
402
|
+
" inject \"query\" [--cwd P] [--limit N]\n" +
|
|
403
|
+
" extract --session ID [--cwd P] [--path FILE]\n" +
|
|
404
|
+
" approve <lesson_id>\n" +
|
|
405
|
+
" reject <lesson_id> [--reason R]\n" +
|
|
406
|
+
" archive <lesson_id> [--reason R]\n" +
|
|
407
|
+
" supersede <old_id> <new_id>\n" +
|
|
408
|
+
" decay [--days N] [--factor F] [--dry]\n" +
|
|
409
|
+
" export [--status S] [--cwd P] print a portable JSON bundle\n" +
|
|
410
|
+
" import <file> [--status S] [--cwd P] import a bundle (default status: candidate)");
|
|
411
|
+
process.exit(sub ? 1 : 0);
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
204
415
|
default:
|
|
205
416
|
console.error("Usage: claw-memory <command>\n" +
|
|
206
417
|
" mcp start stdio MCP server\n" +
|
|
207
418
|
" ui [--port N] [--open] start memory viewer\n" +
|
|
208
419
|
" distill --cwd P --session ID [--path FILE] [--if-stale]\n" +
|
|
209
420
|
" distill-codex [--recent] [--limit N] [--all] distill recent Codex sessions\n" +
|
|
421
|
+
" distill-chatgpt [--limit N] [--all] distill ChatGPT web export conversations\n" +
|
|
210
422
|
" remember --cwd P \"text\"\n" +
|
|
211
|
-
"
|
|
423
|
+
" cleanse [--apply] [--show N] find (and with --apply tombstone) JSON-contaminated chunks\n" +
|
|
424
|
+
" lessons <list|search|inject|extract|approve|reject|archive|supersede>\n" +
|
|
425
|
+
" search-logs \"query\" [--source claude-code,codex,chatgpt-web] [--project P] [--start ISO] [--end ISO] [--limit N] [--offset N]\n" +
|
|
212
426
|
" hook <distill|recall> run a Claude Code lifecycle hook (reads JSON on stdin)\n" +
|
|
213
427
|
" inject-recall alias for `hook recall`\n" +
|
|
214
428
|
" install [--codex|--claude-code] register MCP + hooks (default: codex)\n" +
|