@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.
@@ -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.10.1",
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
- - **リアルタイムセッション更新**: 意味のある変化があった時にセッションJSONを自動更新
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
- | セッション開始時 | セッションJSONを初期化、関連セッションの提案 |
105
- | 会話中 | 意味のある変化があった時にセッションJSONを更新 |
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
- セッションは **interactions ベース** のスキーマを使用します。各 interaction は決定サイクル(リクエスト → 思考 → 提案 → 選択 → 実装)を表します。
246
+ セッションは **分析向け** のスキーマを使用します。定量データ(metrics)と定性データ(summary)を分離し、files/decisions/errorsを独立配列として集計可能にしています。
221
247
 
222
248
  ```json
223
249
  {
224
- "id": "2026-01-27_abc123",
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
- "title": "JWT認証機能の実装",
233
- "goal": "JWTベースの認証機能を実装し、リフレッシュトークンにも対応する",
234
- "tags": ["auth", "jwt", "backend"],
235
- "sessionType": "implementation",
236
- "interactions": [
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": "int-001",
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
- "actions": [
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
- | ユーザーの指示に対応した | `interactions` に追加 |
267
- | 技術的決定を下した | `proposals`, `choice`, `reasoning` |
268
- | エラーに遭遇・解決した | `problem`, `choice`, `reasoning` |
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` フィールドはセッションの種類を分類します。interactions が空でも**必ず設定**してください。
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 automatic session saving, technical decision recording, and web dashboard management.
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
- - **Real-time Session Updates**: Session JSON is updated when meaningful changes occur
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
- ### Automatic Behavior
123
+ ### Session Saving
101
124
 
102
- | Timing | Action |
103
- |--------|--------|
104
- | Session Start | Suggest related sessions, initialize session JSON |
105
- | During Session | Claude Code updates session JSON on meaningful changes |
106
- | Session End | Log completion |
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` | Force flush current session |
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 **interactions-based** schema. Each interaction represents a decision cycle (request thinking proposals choice implementation).
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": "2026-01-27_abc123",
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
- "title": "JWT authentication implementation",
233
- "goal": "Implement JWT-based auth with refresh token support",
234
- "tags": ["auth", "jwt", "backend"],
235
- "sessionType": "implementation",
236
- "interactions": [
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": "int-001",
239
- "topic": "Auth method selection",
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
- "actions": [
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
- | User instruction handled | Add to `interactions` |
267
- | Technical decision made | `proposals`, `choice`, `reasoning` |
268
- | Error encountered/resolved | `problem`, `choice`, `reasoning` |
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. **Always set this** even if interactions is empty.
317
+ The `sessionType` field classifies the session type.
276
318
 
277
319
  | Type | Description |
278
320
  |------|-------------|
package/hooks/hooks.json CHANGED
@@ -11,6 +11,17 @@
11
11
  ]
12
12
  }
13
13
  ],
14
+ "PreCompact": [
15
+ {
16
+ "matcher": "auto",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre-compact.sh"
21
+ }
22
+ ]
23
+ }
24
+ ],
14
25
  "SessionEnd": [
15
26
  {
16
27
  "matcher": "",
@@ -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}'