@hir4ta/memoria 0.10.1 → 0.11.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 +1 -1
- package/README.ja.md +81 -37
- package/README.md +79 -37
- package/hooks/hooks.json +11 -0
- package/hooks/pre-compact.sh +237 -0
- package/hooks/session-end.sh +290 -8
- package/hooks/session-start.sh +65 -22
- package/package.json +1 -1
- package/skills/init/skill.md +106 -0
- package/skills/save/skill.md +215 -34
- package/skills/using-memoria/skill.md +41 -23
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memoria",
|
|
3
3
|
"description": "A plugin that provides long-term memory for Claude Code. It automatically saves context lost during auto-compact, offering features for session restoration, recording technical decisions, and learning developer patterns.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.11.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "hir4ta"
|
|
7
7
|
},
|
package/README.ja.md
CHANGED
|
@@ -7,7 +7,8 @@ Claude Codeの長期記憶を実現するプラグイン
|
|
|
7
7
|
## 機能
|
|
8
8
|
|
|
9
9
|
### コア機能
|
|
10
|
-
-
|
|
10
|
+
- **明示的セッション保存**: `/memoria:save` または「セッション保存して」で保存
|
|
11
|
+
- **APIフォールバック**: Auto-Compact前とセッション終了時にOpenAI APIで自動保存
|
|
11
12
|
- **セッション再開**: `/memoria:resume` で過去のセッションを再開
|
|
12
13
|
- **技術的な判断の記録**: `/memoria:decision` で判断を記録
|
|
13
14
|
- **ルールベースレビュー**: `dev-rules.json` / `review-guidelines.json` に基づくレビュー
|
|
@@ -31,7 +32,7 @@ Claude Codeの長期記憶を実現するプラグイン
|
|
|
31
32
|
|
|
32
33
|
### memoria でできること/解消できること
|
|
33
34
|
|
|
34
|
-
-
|
|
35
|
+
- **自動保存 + 再開**で、セッションを跨いだ文脈の継続が可能
|
|
35
36
|
- **判断記録**で、理由・代替案を後から追跡
|
|
36
37
|
- **検索とダッシュボード**で、過去の記録を素早く参照
|
|
37
38
|
- **レビュー機能**で、リポジトリ固有の観点に基づいて指摘
|
|
@@ -95,22 +96,47 @@ Claude Codeを再起動
|
|
|
95
96
|
|
|
96
97
|
これによりClaude Code起動時に自動でアップデートされます
|
|
97
98
|
|
|
99
|
+
## 設定(オプション)
|
|
100
|
+
|
|
101
|
+
APIフォールバックによる自動セッション保存を有効にするには、`/memoria:init` を実行するか、手動で `~/.claude/memoria.json` を作成してください:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"openai_api_key": "sk-...",
|
|
106
|
+
"model": "gpt-5-mini"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| フィールド | 必須 | デフォルト | 説明 |
|
|
111
|
+
|-----------|------|-----------|------|
|
|
112
|
+
| `openai_api_key` | いいえ | - | 設定すると自動保存が有効になる |
|
|
113
|
+
| `model` | いいえ | `gpt-5-mini` | 使用するモデル(gpt-5-mini, gpt-5, gpt-5.2など) |
|
|
114
|
+
|
|
115
|
+
**`openai_api_key` なし**: `/memoria:save` での手動保存のみ。
|
|
116
|
+
|
|
117
|
+
**`openai_api_key` あり**: 自動保存が有効になり、以下のタイミングで保存:
|
|
118
|
+
- Auto-Compact前(status: `draft`)
|
|
119
|
+
- セッション終了時(status: `complete`)
|
|
120
|
+
|
|
98
121
|
## 使い方
|
|
99
122
|
|
|
100
|
-
###
|
|
123
|
+
### セッション保存
|
|
101
124
|
|
|
102
|
-
| タイミング |
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
125
|
+
| 方法 | タイミング | ステータス |
|
|
126
|
+
|------|----------|-----------|
|
|
127
|
+
| **明示的保存** | `/memoria:save` または「セッション保存して」 | `complete` |
|
|
128
|
+
| **PreCompact** | Auto-Compact前(API) | `draft` |
|
|
129
|
+
| **SessionEnd** | セッション終了時(API) | `complete` |
|
|
130
|
+
|
|
131
|
+
**注意**: PreCompact/SessionEnd のAPIフォールバックには `~/.claude/memoria.json` の設定が必要です。
|
|
107
132
|
|
|
108
133
|
### コマンド
|
|
109
134
|
|
|
110
135
|
| コマンド | 説明 |
|
|
111
136
|
| --------- | ------ |
|
|
137
|
+
| `/memoria:init` | 設定ファイルを初期化(`~/.claude/memoria.json`) |
|
|
112
138
|
| `/memoria:resume [id]` | セッションを再開(ID省略で一覧表示) |
|
|
113
|
-
| `/memoria:save` |
|
|
139
|
+
| `/memoria:save` | セッション保存 + 会話からルール抽出 |
|
|
114
140
|
| `/memoria:decision "タイトル"` | 技術的な判断を記録 |
|
|
115
141
|
| `/memoria:search "クエリ"` | セッション・判断記録を検索 |
|
|
116
142
|
| `/memoria:review [--staged\|--all\|--diff=branch\|--full]` | ルールに基づくレビュー(--fullで二段階) |
|
|
@@ -217,42 +243,59 @@ Gitでバージョン管理可能です。`.gitignore` に追加するかはプ
|
|
|
217
243
|
|
|
218
244
|
### セッションJSONスキーマ
|
|
219
245
|
|
|
220
|
-
セッションは
|
|
246
|
+
セッションは **分析向け** のスキーマを使用します。定量データ(metrics)と定性データ(summary)を分離し、files/decisions/errorsを独立配列として集計可能にしています。
|
|
221
247
|
|
|
222
248
|
```json
|
|
223
249
|
{
|
|
224
|
-
"id": "
|
|
250
|
+
"id": "abc12345",
|
|
225
251
|
"sessionId": "claude-code-からの-full-uuid",
|
|
226
252
|
"createdAt": "2026-01-27T10:00:00Z",
|
|
253
|
+
"endedAt": "2026-01-27T12:00:00Z",
|
|
227
254
|
"context": {
|
|
228
255
|
"branch": "feature/auth",
|
|
229
256
|
"projectDir": "/path/to/project",
|
|
230
257
|
"user": { "name": "tanaka", "email": "tanaka@example.com" }
|
|
231
258
|
},
|
|
232
|
-
"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
259
|
+
"summary": {
|
|
260
|
+
"title": "JWT認証機能の実装",
|
|
261
|
+
"goal": "JWTベースの認証機能を実装",
|
|
262
|
+
"outcome": "success",
|
|
263
|
+
"description": "RS256署名でJWT認証を実装"
|
|
264
|
+
},
|
|
265
|
+
"metrics": {
|
|
266
|
+
"durationMinutes": 120,
|
|
267
|
+
"filesCreated": 2,
|
|
268
|
+
"filesModified": 1,
|
|
269
|
+
"decisionsCount": 2,
|
|
270
|
+
"errorsEncountered": 1,
|
|
271
|
+
"errorsResolved": 1
|
|
272
|
+
},
|
|
273
|
+
"files": [
|
|
274
|
+
{ "path": "src/auth/jwt.ts", "action": "create", "summary": "JWTモジュール" }
|
|
275
|
+
],
|
|
276
|
+
"decisions": [
|
|
237
277
|
{
|
|
238
|
-
"id": "
|
|
239
|
-
"topic": "
|
|
240
|
-
"timestamp": "2026-01-27T10:15:00Z",
|
|
241
|
-
"request": "認証機能を実装したい",
|
|
242
|
-
"thinking": "JWTとセッションCookieを比較...",
|
|
243
|
-
"webLinks": ["https://jwt.io/introduction"],
|
|
244
|
-
"proposals": [
|
|
245
|
-
{ "option": "JWT", "description": "ステートレス、スケーラブル" },
|
|
246
|
-
{ "option": "セッションCookie", "description": "シンプル" }
|
|
247
|
-
],
|
|
278
|
+
"id": "dec-001",
|
|
279
|
+
"topic": "認証方式",
|
|
248
280
|
"choice": "JWT",
|
|
281
|
+
"alternatives": ["セッションCookie"],
|
|
249
282
|
"reasoning": "マイクロサービス間の認証共有が容易",
|
|
250
|
-
"
|
|
251
|
-
{ "type": "create", "path": "src/auth/jwt.ts", "summary": "JWTモジュール" }
|
|
252
|
-
],
|
|
253
|
-
"filesModified": ["src/auth/jwt.ts"]
|
|
283
|
+
"timestamp": "2026-01-27T10:15:00Z"
|
|
254
284
|
}
|
|
255
|
-
]
|
|
285
|
+
],
|
|
286
|
+
"errors": [
|
|
287
|
+
{
|
|
288
|
+
"id": "err-001",
|
|
289
|
+
"message": "secretOrPrivateKey must be asymmetric",
|
|
290
|
+
"type": "runtime",
|
|
291
|
+
"resolved": true,
|
|
292
|
+
"solution": "RS256用にPEM形式に変更"
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
"webLinks": ["https://jwt.io/introduction"],
|
|
296
|
+
"tags": ["auth", "jwt", "backend"],
|
|
297
|
+
"sessionType": "implementation",
|
|
298
|
+
"status": "complete"
|
|
256
299
|
}
|
|
257
300
|
```
|
|
258
301
|
|
|
@@ -262,17 +305,18 @@ Claude Code は意味のある変化があった時にセッションJSONを更
|
|
|
262
305
|
|
|
263
306
|
| トリガー | 更新内容 |
|
|
264
307
|
|---------|---------|
|
|
265
|
-
| セッションの目的が明確になった | `title`, `goal`, `sessionType` |
|
|
266
|
-
|
|
|
267
|
-
| 技術的決定を下した | `
|
|
268
|
-
| エラーに遭遇・解決した | `
|
|
269
|
-
| ファイルを変更した | `actions`, `filesModified` |
|
|
308
|
+
| セッションの目的が明確になった | `summary.title`, `summary.goal`, `sessionType` |
|
|
309
|
+
| ファイルを変更した | `files` に追加、`metrics` 更新 |
|
|
310
|
+
| 技術的決定を下した | `decisions` に追加 |
|
|
311
|
+
| エラーに遭遇・解決した | `errors` に追加 |
|
|
270
312
|
| URLを参照した | `webLinks` |
|
|
271
313
|
| 新しいキーワードが出現 | `tags`(tags.jsonを参照) |
|
|
272
314
|
|
|
315
|
+
**注意**: 明示的に `/memoria:save` を実行するか、APIフォールバックが発動するまでセッションは保存されません。
|
|
316
|
+
|
|
273
317
|
### セッションタイプ
|
|
274
318
|
|
|
275
|
-
`sessionType` フィールドはセッションの種類を分類します。
|
|
319
|
+
`sessionType` フィールドはセッションの種類を分類します。
|
|
276
320
|
|
|
277
321
|
| タイプ | 説明 |
|
|
278
322
|
|--------|------|
|
package/README.md
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Long-term memory plugin for Claude Code
|
|
4
4
|
|
|
5
|
-
Provides
|
|
5
|
+
Provides explicit session saving with API fallback, technical decision recording, and web dashboard management.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
### Core Features
|
|
10
|
-
- **
|
|
10
|
+
- **Explicit Session Saving**: Save with `/memoria:save` or "save session" request
|
|
11
|
+
- **API Fallback**: Auto-save via OpenAI API before Auto-Compact and at session end
|
|
11
12
|
- **Session Resume**: Resume past sessions with `/memoria:resume`
|
|
12
13
|
- **Technical Decision Recording**: Record decisions with `/memoria:decision`
|
|
13
14
|
- **Rule-based Review**: Code review based on `dev-rules.json` / `review-guidelines.json`
|
|
@@ -95,22 +96,47 @@ Restart Claude Code.
|
|
|
95
96
|
|
|
96
97
|
This will auto-update on Claude Code startup.
|
|
97
98
|
|
|
99
|
+
## Configuration (Optional)
|
|
100
|
+
|
|
101
|
+
To enable API fallback for automatic session saving, run `/memoria:init` or manually create `~/.claude/memoria.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"openai_api_key": "sk-...",
|
|
106
|
+
"model": "gpt-5-mini"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| Field | Required | Default | Description |
|
|
111
|
+
|-------|----------|---------|-------------|
|
|
112
|
+
| `openai_api_key` | No | - | Enables auto-save at PreCompact/SessionEnd |
|
|
113
|
+
| `model` | No | `gpt-5-mini` | Model to use (gpt-5-mini, gpt-5, gpt-5.2, etc.) |
|
|
114
|
+
|
|
115
|
+
**Without `openai_api_key`**: Sessions are only saved when you explicitly use `/memoria:save`.
|
|
116
|
+
|
|
117
|
+
**With `openai_api_key`**: Auto-save is enabled. Sessions are automatically saved:
|
|
118
|
+
- Before Auto-Compact (status: `draft`)
|
|
119
|
+
- At session end (status: `complete`)
|
|
120
|
+
|
|
98
121
|
## Usage
|
|
99
122
|
|
|
100
|
-
###
|
|
123
|
+
### Session Saving
|
|
101
124
|
|
|
102
|
-
|
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
| Session
|
|
125
|
+
| Method | When | Status |
|
|
126
|
+
|--------|------|--------|
|
|
127
|
+
| **Explicit** | `/memoria:save` or "save session" | `complete` |
|
|
128
|
+
| **PreCompact** | Before Auto-Compact (API) | `draft` |
|
|
129
|
+
| **SessionEnd** | Session end (API) | `complete` |
|
|
130
|
+
|
|
131
|
+
**Note**: PreCompact/SessionEnd API fallback requires `~/.claude/memoria.json` configuration.
|
|
107
132
|
|
|
108
133
|
### Commands
|
|
109
134
|
|
|
110
135
|
| Command | Description |
|
|
111
136
|
|---------|-------------|
|
|
137
|
+
| `/memoria:init` | Initialize config file (`~/.claude/memoria.json`) |
|
|
112
138
|
| `/memoria:resume [id]` | Resume session (show list if ID omitted) |
|
|
113
|
-
| `/memoria:save` |
|
|
139
|
+
| `/memoria:save` | Save session + extract rules from conversation |
|
|
114
140
|
| `/memoria:decision "title"` | Record a technical decision |
|
|
115
141
|
| `/memoria:search "query"` | Search sessions and decisions |
|
|
116
142
|
| `/memoria:review [--staged\|--all\|--diff=branch\|--full]` | Rule-based code review (--full for two-stage) |
|
|
@@ -217,42 +243,59 @@ Git-manageable. Add to `.gitignore` based on your project needs.
|
|
|
217
243
|
|
|
218
244
|
### Session JSON Schema
|
|
219
245
|
|
|
220
|
-
Sessions use an **
|
|
246
|
+
Sessions use an **analysis-friendly** schema. Quantitative data (metrics) and qualitative data (summary) are separated, with files/decisions/errors as independent arrays for easy aggregation.
|
|
221
247
|
|
|
222
248
|
```json
|
|
223
249
|
{
|
|
224
|
-
"id": "
|
|
250
|
+
"id": "abc12345",
|
|
225
251
|
"sessionId": "full-uuid-from-claude-code",
|
|
226
252
|
"createdAt": "2026-01-27T10:00:00Z",
|
|
253
|
+
"endedAt": "2026-01-27T12:00:00Z",
|
|
227
254
|
"context": {
|
|
228
255
|
"branch": "feature/auth",
|
|
229
256
|
"projectDir": "/path/to/project",
|
|
230
257
|
"user": { "name": "tanaka", "email": "tanaka@example.com" }
|
|
231
258
|
},
|
|
232
|
-
"
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
259
|
+
"summary": {
|
|
260
|
+
"title": "JWT authentication implementation",
|
|
261
|
+
"goal": "Implement JWT-based auth with refresh token support",
|
|
262
|
+
"outcome": "success",
|
|
263
|
+
"description": "Implemented JWT auth with RS256 signing"
|
|
264
|
+
},
|
|
265
|
+
"metrics": {
|
|
266
|
+
"durationMinutes": 120,
|
|
267
|
+
"filesCreated": 2,
|
|
268
|
+
"filesModified": 1,
|
|
269
|
+
"decisionsCount": 2,
|
|
270
|
+
"errorsEncountered": 1,
|
|
271
|
+
"errorsResolved": 1
|
|
272
|
+
},
|
|
273
|
+
"files": [
|
|
274
|
+
{ "path": "src/auth/jwt.ts", "action": "create", "summary": "JWT module" }
|
|
275
|
+
],
|
|
276
|
+
"decisions": [
|
|
237
277
|
{
|
|
238
|
-
"id": "
|
|
239
|
-
"topic": "Auth method
|
|
240
|
-
"timestamp": "2026-01-27T10:15:00Z",
|
|
241
|
-
"request": "Implement authentication",
|
|
242
|
-
"thinking": "Comparing JWT vs session cookies...",
|
|
243
|
-
"webLinks": ["https://jwt.io/introduction"],
|
|
244
|
-
"proposals": [
|
|
245
|
-
{ "option": "JWT", "description": "Stateless, scalable" },
|
|
246
|
-
{ "option": "Session Cookie", "description": "Simple" }
|
|
247
|
-
],
|
|
278
|
+
"id": "dec-001",
|
|
279
|
+
"topic": "Auth method",
|
|
248
280
|
"choice": "JWT",
|
|
281
|
+
"alternatives": ["Session Cookie"],
|
|
249
282
|
"reasoning": "Easy auth sharing between microservices",
|
|
250
|
-
"
|
|
251
|
-
{ "type": "create", "path": "src/auth/jwt.ts", "summary": "JWT module" }
|
|
252
|
-
],
|
|
253
|
-
"filesModified": ["src/auth/jwt.ts"]
|
|
283
|
+
"timestamp": "2026-01-27T10:15:00Z"
|
|
254
284
|
}
|
|
255
|
-
]
|
|
285
|
+
],
|
|
286
|
+
"errors": [
|
|
287
|
+
{
|
|
288
|
+
"id": "err-001",
|
|
289
|
+
"message": "secretOrPrivateKey must be asymmetric",
|
|
290
|
+
"type": "runtime",
|
|
291
|
+
"resolved": true,
|
|
292
|
+
"solution": "Changed to PEM format for RS256"
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
"webLinks": ["https://jwt.io/introduction"],
|
|
296
|
+
"tags": ["auth", "jwt", "backend"],
|
|
297
|
+
"sessionType": "implementation",
|
|
298
|
+
"status": "complete"
|
|
256
299
|
}
|
|
257
300
|
```
|
|
258
301
|
|
|
@@ -262,17 +305,16 @@ Claude Code updates session JSON when meaningful changes occur:
|
|
|
262
305
|
|
|
263
306
|
| Trigger | Update |
|
|
264
307
|
|---------|--------|
|
|
265
|
-
| Session purpose becomes clear | `title`, `goal`, `sessionType` |
|
|
266
|
-
|
|
|
267
|
-
| Technical decision made |
|
|
268
|
-
| Error encountered/resolved |
|
|
269
|
-
| File modified | `actions`, `filesModified` |
|
|
308
|
+
| Session purpose becomes clear | `summary.title`, `summary.goal`, `sessionType` |
|
|
309
|
+
| File modified | Add to `files`, update `metrics` |
|
|
310
|
+
| Technical decision made | Add to `decisions` |
|
|
311
|
+
| Error encountered/resolved | Add to `errors` |
|
|
270
312
|
| URL referenced | `webLinks` |
|
|
271
313
|
| New keyword appears | `tags` (reference tags.json) |
|
|
272
314
|
|
|
273
315
|
### Session Types
|
|
274
316
|
|
|
275
|
-
The `sessionType` field classifies the session type.
|
|
317
|
+
The `sessionType` field classifies the session type.
|
|
276
318
|
|
|
277
319
|
| Type | Description |
|
|
278
320
|
|------|-------------|
|
package/hooks/hooks.json
CHANGED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# pre-compact.sh - PreCompact hook for memoria plugin
|
|
4
|
+
#
|
|
5
|
+
# Triggered before auto-compact. If session JSON is empty (template state),
|
|
6
|
+
# uses OpenAI API to summarize transcript and save as draft.
|
|
7
|
+
#
|
|
8
|
+
# Input (stdin): JSON with session_id, transcript_path, cwd, trigger
|
|
9
|
+
# Output: JSON with continue (boolean)
|
|
10
|
+
#
|
|
11
|
+
# Requires: ~/.claude/memoria.json with openai_api_key
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# Read stdin
|
|
16
|
+
input_json=$(cat)
|
|
17
|
+
|
|
18
|
+
# Extract fields
|
|
19
|
+
session_id=$(echo "$input_json" | jq -r '.session_id // empty')
|
|
20
|
+
transcript_path=$(echo "$input_json" | jq -r '.transcript_path // empty')
|
|
21
|
+
cwd=$(echo "$input_json" | jq -r '.cwd // empty')
|
|
22
|
+
trigger=$(echo "$input_json" | jq -r '.trigger // "auto"')
|
|
23
|
+
|
|
24
|
+
# Only process auto-trigger
|
|
25
|
+
if [ "$trigger" != "auto" ]; then
|
|
26
|
+
echo '{"continue": true}'
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# If no cwd, use PWD
|
|
31
|
+
if [ -z "$cwd" ]; then
|
|
32
|
+
cwd="${PWD}"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Find session file
|
|
36
|
+
if [ -z "$session_id" ]; then
|
|
37
|
+
echo '{"continue": true}'
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
session_short_id="${session_id:0:8}"
|
|
42
|
+
memoria_dir="${cwd}/.memoria"
|
|
43
|
+
sessions_dir="${memoria_dir}/sessions"
|
|
44
|
+
|
|
45
|
+
# Find session file (new format first, then old format for backwards compatibility)
|
|
46
|
+
session_file=$(find "$sessions_dir" -name "${session_short_id}.json" -type f 2>/dev/null | head -1)
|
|
47
|
+
if [ -z "$session_file" ]; then
|
|
48
|
+
session_file=$(find "$sessions_dir" -name "*_${session_short_id}.json" -type f 2>/dev/null | head -1)
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if [ -z "$session_file" ] || [ ! -f "$session_file" ]; then
|
|
52
|
+
echo '{"continue": true}'
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Check if session needs saving (template state)
|
|
57
|
+
title=$(jq -r '.summary.title // ""' "$session_file")
|
|
58
|
+
files_count=$(jq -r '.files | length' "$session_file")
|
|
59
|
+
status=$(jq -r '.status // "null"' "$session_file")
|
|
60
|
+
|
|
61
|
+
# Skip if already saved
|
|
62
|
+
if [ "$status" = "complete" ] || [ "$status" = "draft" ]; then
|
|
63
|
+
echo '{"continue": true}'
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Skip if already has content
|
|
68
|
+
if [ -n "$title" ] && [ "$files_count" -gt 0 ]; then
|
|
69
|
+
echo '{"continue": true}'
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Check for API key in ~/.claude/memoria.json
|
|
74
|
+
config_file="${HOME}/.claude/memoria.json"
|
|
75
|
+
if [ ! -f "$config_file" ]; then
|
|
76
|
+
echo "[memoria] No config file found at ${config_file}, skipping auto-save" >&2
|
|
77
|
+
echo '{"continue": true}'
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
api_key=$(jq -r '.openai_api_key // empty' "$config_file")
|
|
82
|
+
if [ -z "$api_key" ]; then
|
|
83
|
+
echo "[memoria] No openai_api_key in config, skipping auto-save" >&2
|
|
84
|
+
echo '{"continue": true}'
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
model=$(jq -r '.model // "gpt-5-mini"' "$config_file")
|
|
89
|
+
|
|
90
|
+
# Check transcript exists
|
|
91
|
+
if [ -z "$transcript_path" ] || [ ! -f "$transcript_path" ]; then
|
|
92
|
+
echo "[memoria] No transcript found, skipping auto-save" >&2
|
|
93
|
+
echo '{"continue": true}'
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Run summarization in background (don't block compaction)
|
|
98
|
+
(
|
|
99
|
+
# Read transcript (JSONL format) - extract user messages, assistant text, and tool usage
|
|
100
|
+
transcript_content=$(cat "$transcript_path" | jq -r '
|
|
101
|
+
select(.type == "user" or .type == "assistant") |
|
|
102
|
+
if .type == "user" then
|
|
103
|
+
if (.message.content | type) == "string" then
|
|
104
|
+
"User: " + .message.content
|
|
105
|
+
else
|
|
106
|
+
empty
|
|
107
|
+
end
|
|
108
|
+
elif .type == "assistant" then
|
|
109
|
+
.message.content[]? |
|
|
110
|
+
if .type == "text" then
|
|
111
|
+
"Assistant: " + .text
|
|
112
|
+
elif .type == "tool_use" then
|
|
113
|
+
"Tool: " + .name + (if .input.file_path then " - " + .input.file_path elif .input.command then " - " + (.input.command | split("\n")[0]) else "" end)
|
|
114
|
+
else
|
|
115
|
+
empty
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
empty
|
|
119
|
+
end
|
|
120
|
+
' 2>/dev/null)
|
|
121
|
+
|
|
122
|
+
if [ -z "$transcript_content" ]; then
|
|
123
|
+
echo "[memoria] Empty transcript, skipping" >&2
|
|
124
|
+
exit 0
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Get git branch
|
|
128
|
+
branch=$(jq -r '.context.branch // ""' "$session_file")
|
|
129
|
+
|
|
130
|
+
# Prepare prompt for summarization (analysis-friendly structure)
|
|
131
|
+
prompt="Summarize this Claude Code session as JSON with analysis-friendly structure. Extract:
|
|
132
|
+
|
|
133
|
+
1. summary: {
|
|
134
|
+
title: Brief title (max 50 chars),
|
|
135
|
+
goal: What the user wanted to accomplish,
|
|
136
|
+
outcome: \"success\" | \"partial\" | \"abandoned\",
|
|
137
|
+
description: Brief summary (1-2 sentences)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
2. metrics: {
|
|
141
|
+
durationMinutes: estimated session duration,
|
|
142
|
+
filesCreated: count,
|
|
143
|
+
filesModified: count,
|
|
144
|
+
filesDeleted: count,
|
|
145
|
+
decisionsCount: count,
|
|
146
|
+
errorsEncountered: count,
|
|
147
|
+
errorsResolved: count
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
3. files: Array of file changes, each with:
|
|
151
|
+
- path: file path
|
|
152
|
+
- action: \"create\" | \"edit\" | \"delete\"
|
|
153
|
+
- summary: what was changed
|
|
154
|
+
|
|
155
|
+
4. decisions: Array of technical decisions, each with:
|
|
156
|
+
- id: dec-001, dec-002, etc.
|
|
157
|
+
- topic: decision topic
|
|
158
|
+
- choice: what was chosen
|
|
159
|
+
- alternatives: array of other options considered
|
|
160
|
+
- reasoning: why this choice
|
|
161
|
+
- timestamp: ISO8601
|
|
162
|
+
|
|
163
|
+
5. errors: Array of errors encountered, each with:
|
|
164
|
+
- id: err-001, err-002, etc.
|
|
165
|
+
- message: error message
|
|
166
|
+
- type: \"runtime\" | \"build\" | \"lint\" | \"test\" | \"other\"
|
|
167
|
+
- file: related file (if any)
|
|
168
|
+
- resolved: true/false
|
|
169
|
+
- solution: how it was fixed (if resolved)
|
|
170
|
+
- timestamp: ISO8601
|
|
171
|
+
|
|
172
|
+
6. webLinks: Array of URLs referenced
|
|
173
|
+
7. tags: Array of relevant tags (e.g., frontend, backend, auth)
|
|
174
|
+
8. sessionType: One of: decision, implementation, research, exploration, discussion, debug, review
|
|
175
|
+
|
|
176
|
+
Return ONLY valid JSON, no markdown or explanation.
|
|
177
|
+
|
|
178
|
+
Transcript:
|
|
179
|
+
${transcript_content}"
|
|
180
|
+
|
|
181
|
+
# Call OpenAI API
|
|
182
|
+
response=$(curl -s -X POST "https://api.openai.com/v1/chat/completions" \
|
|
183
|
+
-H "Content-Type: application/json" \
|
|
184
|
+
-H "Authorization: Bearer ${api_key}" \
|
|
185
|
+
-d "$(jq -n \
|
|
186
|
+
--arg model "$model" \
|
|
187
|
+
--arg prompt "$prompt" \
|
|
188
|
+
'{
|
|
189
|
+
model: $model,
|
|
190
|
+
messages: [
|
|
191
|
+
{role: "system", content: "You are a session summarizer. Return only valid JSON."},
|
|
192
|
+
{role: "user", content: $prompt}
|
|
193
|
+
],
|
|
194
|
+
temperature: 0.3,
|
|
195
|
+
max_tokens: 10000
|
|
196
|
+
}')" 2>/dev/null)
|
|
197
|
+
|
|
198
|
+
# Extract content from response
|
|
199
|
+
summary=$(echo "$response" | jq -r '.choices[0].message.content // empty' 2>/dev/null)
|
|
200
|
+
|
|
201
|
+
if [ -z "$summary" ]; then
|
|
202
|
+
echo "[memoria] API call failed or empty response" >&2
|
|
203
|
+
exit 0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# Parse and validate JSON
|
|
207
|
+
if ! echo "$summary" | jq -e . >/dev/null 2>&1; then
|
|
208
|
+
# Try to extract JSON from markdown code block
|
|
209
|
+
summary=$(echo "$summary" | sed -n '/^```json/,/^```$/p' | sed '1d;$d')
|
|
210
|
+
if ! echo "$summary" | jq -e . >/dev/null 2>&1; then
|
|
211
|
+
echo "[memoria] Invalid JSON response from API" >&2
|
|
212
|
+
exit 0
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Update session file with summary (analysis-friendly structure)
|
|
217
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
218
|
+
|
|
219
|
+
jq --argjson summary "$summary" --arg status "draft" --arg updatedAt "$now" '
|
|
220
|
+
.summary = ($summary.summary // .summary) |
|
|
221
|
+
.metrics = ($summary.metrics // .metrics) |
|
|
222
|
+
.files = ($summary.files // .files) |
|
|
223
|
+
.decisions = ($summary.decisions // .decisions) |
|
|
224
|
+
.errors = ($summary.errors // .errors) |
|
|
225
|
+
.webLinks = ($summary.webLinks // .webLinks) |
|
|
226
|
+
.tags = ($summary.tags // .tags) |
|
|
227
|
+
.sessionType = ($summary.sessionType // .sessionType) |
|
|
228
|
+
.status = $status |
|
|
229
|
+
.updatedAt = $updatedAt
|
|
230
|
+
' "$session_file" > "${session_file}.tmp" && mv "${session_file}.tmp" "$session_file"
|
|
231
|
+
|
|
232
|
+
echo "[memoria] Session saved as draft: ${session_file}" >&2
|
|
233
|
+
|
|
234
|
+
) &
|
|
235
|
+
|
|
236
|
+
# Return immediately (don't block compaction)
|
|
237
|
+
echo '{"continue": true}'
|