ultra-memory 3.0.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.
@@ -0,0 +1,207 @@
1
+ [
2
+ {
3
+ "type": "function",
4
+ "function": {
5
+ "name": "memory_init",
6
+ "description": "Initialize ultra-memory session. Call at conversation start to create three-layer memory structure and inject historical context.",
7
+ "parameters": {
8
+ "type": "object",
9
+ "properties": {
10
+ "project": {
11
+ "type": "string",
12
+ "description": "Project name (default: 'default')"
13
+ },
14
+ "resume": {
15
+ "type": "boolean",
16
+ "description": "Try to resume the most recent session for this project"
17
+ }
18
+ },
19
+ "required": []
20
+ }
21
+ }
22
+ },
23
+ {
24
+ "type": "function",
25
+ "function": {
26
+ "name": "memory_status",
27
+ "description": "Query current session status: operation count, last milestone, context pressure level (low/medium/high/critical). Use to decide when to compress.",
28
+ "parameters": {
29
+ "type": "object",
30
+ "properties": {
31
+ "session_id": {
32
+ "type": "string",
33
+ "description": "Session ID returned by memory_init"
34
+ }
35
+ },
36
+ "required": ["session_id"]
37
+ }
38
+ }
39
+ },
40
+ {
41
+ "type": "function",
42
+ "function": {
43
+ "name": "memory_log",
44
+ "description": "Record an operation to the current session log (Layer 1). Call after every significant action: file write, bash command, tool call, decision, error, or milestone.",
45
+ "parameters": {
46
+ "type": "object",
47
+ "properties": {
48
+ "session_id": {
49
+ "type": "string",
50
+ "description": "Current session ID"
51
+ },
52
+ "op_type": {
53
+ "type": "string",
54
+ "enum": ["tool_call", "file_write", "file_read", "bash_exec", "reasoning", "user_instruction", "decision", "error", "milestone"],
55
+ "description": "Operation type"
56
+ },
57
+ "summary": {
58
+ "type": "string",
59
+ "description": "Operation summary (under 50 words)"
60
+ },
61
+ "detail": {
62
+ "type": "object",
63
+ "description": "Additional detail fields (optional)"
64
+ },
65
+ "tags": {
66
+ "type": "array",
67
+ "items": { "type": "string" },
68
+ "description": "Tag list (optional, auto-generated if omitted)"
69
+ }
70
+ },
71
+ "required": ["session_id", "op_type", "summary"]
72
+ }
73
+ }
74
+ },
75
+ {
76
+ "type": "function",
77
+ "function": {
78
+ "name": "memory_recall",
79
+ "description": "Retrieve relevant records from all memory layers (ops log, summary, semantic, entity index). Use when you need to recall past work, decisions, or errors.",
80
+ "parameters": {
81
+ "type": "object",
82
+ "properties": {
83
+ "session_id": {
84
+ "type": "string",
85
+ "description": "Current session ID"
86
+ },
87
+ "query": {
88
+ "type": "string",
89
+ "description": "Search keywords (Chinese or English)"
90
+ },
91
+ "top_k": {
92
+ "type": "number",
93
+ "description": "Number of results to return (default: 5)"
94
+ }
95
+ },
96
+ "required": ["session_id", "query"]
97
+ }
98
+ }
99
+ },
100
+ {
101
+ "type": "function",
102
+ "function": {
103
+ "name": "memory_summarize",
104
+ "description": "Trigger summary compression of the current session (compresses ops log into summary.md). Call when context pressure is high or critical, or every ~50 operations.",
105
+ "parameters": {
106
+ "type": "object",
107
+ "properties": {
108
+ "session_id": {
109
+ "type": "string",
110
+ "description": "Current session ID"
111
+ },
112
+ "force": {
113
+ "type": "boolean",
114
+ "description": "Force compression even if operation count is low"
115
+ }
116
+ },
117
+ "required": ["session_id"]
118
+ }
119
+ }
120
+ },
121
+ {
122
+ "type": "function",
123
+ "function": {
124
+ "name": "memory_restore",
125
+ "description": "Restore the last session context for a project. Use at conversation start to continue a previous task across days.",
126
+ "parameters": {
127
+ "type": "object",
128
+ "properties": {
129
+ "project": {
130
+ "type": "string",
131
+ "description": "Project name (default: 'default')"
132
+ },
133
+ "verbose": {
134
+ "type": "boolean",
135
+ "description": "Show detailed operation records"
136
+ }
137
+ },
138
+ "required": []
139
+ }
140
+ }
141
+ },
142
+ {
143
+ "type": "function",
144
+ "function": {
145
+ "name": "memory_profile",
146
+ "description": "Read or update user profile (tech stack, preferences, project list). Persist user-level preferences across all sessions.",
147
+ "parameters": {
148
+ "type": "object",
149
+ "properties": {
150
+ "action": {
151
+ "type": "string",
152
+ "enum": ["read", "update"],
153
+ "description": "Operation type"
154
+ },
155
+ "updates": {
156
+ "type": "object",
157
+ "description": "Fields to update (required when action=update)"
158
+ }
159
+ },
160
+ "required": ["action"]
161
+ }
162
+ }
163
+ },
164
+ {
165
+ "type": "function",
166
+ "function": {
167
+ "name": "memory_entities",
168
+ "description": "Query structured entity index (functions, files, dependencies, decisions, errors). Use for precise questions like 'which functions were used', 'which packages were installed', 'what decisions were made'.",
169
+ "parameters": {
170
+ "type": "object",
171
+ "properties": {
172
+ "entity_type": {
173
+ "type": "string",
174
+ "enum": ["function", "file", "dependency", "decision", "error", "class", "all"],
175
+ "description": "Entity type filter (all = no filter)"
176
+ },
177
+ "query": {
178
+ "type": "string",
179
+ "description": "Search keyword (optional, empty returns all of that type)"
180
+ },
181
+ "top_k": {
182
+ "type": "number",
183
+ "description": "Number of results to return (default: 10)"
184
+ }
185
+ },
186
+ "required": []
187
+ }
188
+ }
189
+ },
190
+ {
191
+ "type": "function",
192
+ "function": {
193
+ "name": "memory_extract_entities",
194
+ "description": "Re-extract all structured entities from a session's ops.jsonl. Use to repair or initialize the entity index.",
195
+ "parameters": {
196
+ "type": "object",
197
+ "properties": {
198
+ "session_id": {
199
+ "type": "string",
200
+ "description": "Session ID"
201
+ }
202
+ },
203
+ "required": ["session_id"]
204
+ }
205
+ }
206
+ }
207
+ ]
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ultra-memory: 会话清理脚本
4
+ 清理 N 天前的历史会话,支持 --archive-only 模式(只归档不删除)
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import shutil
11
+ import argparse
12
+ from datetime import datetime, timezone, timedelta
13
+ from pathlib import Path
14
+
15
+ if sys.stdout.encoding != "utf-8":
16
+ sys.stdout.reconfigure(encoding="utf-8")
17
+ if sys.stderr.encoding != "utf-8":
18
+ sys.stderr.reconfigure(encoding="utf-8")
19
+
20
+ ULTRA_MEMORY_HOME = Path(os.environ.get("ULTRA_MEMORY_HOME", Path.home() / ".ultra-memory"))
21
+
22
+
23
+ def parse_session_date(meta: dict) -> datetime:
24
+ """从 meta 中解析会话开始时间"""
25
+ ts_str = meta.get("started_at", "")
26
+ try:
27
+ return datetime.fromisoformat(ts_str.rstrip("Z")).replace(tzinfo=timezone.utc)
28
+ except Exception:
29
+ return datetime.min.replace(tzinfo=timezone.utc)
30
+
31
+
32
+ def cleanup(days: int = 30, archive_only: bool = False, dry_run: bool = False, project: str = None):
33
+ """
34
+ 清理或归档 N 天前的会话。
35
+
36
+ Args:
37
+ days: 清理多少天前的会话(默认 30 天)
38
+ archive_only: True 时只将旧会话移动到 archive/,不删除
39
+ dry_run: True 时只打印将要操作的会话,不实际执行
40
+ project: 只清理指定项目的会话(None 表示所有项目)
41
+ """
42
+ sessions_dir = ULTRA_MEMORY_HOME / "sessions"
43
+ archive_dir = ULTRA_MEMORY_HOME / "archive"
44
+
45
+ if not sessions_dir.exists():
46
+ print("[ultra-memory] 会话目录不存在,无需清理")
47
+ return
48
+
49
+ cutoff = datetime.now(timezone.utc) - timedelta(days=days)
50
+ print(f"[ultra-memory] 清理 {cutoff.strftime('%Y-%m-%d')} 之前的会话")
51
+ if archive_only:
52
+ print(f"[ultra-memory] 模式: 归档(不删除)")
53
+ if dry_run:
54
+ print(f"[ultra-memory] 模式: 演习(dry-run,不实际执行)")
55
+
56
+ removed = 0
57
+ archived = 0
58
+ skipped = 0
59
+
60
+ for session_dir in sessions_dir.iterdir():
61
+ if not session_dir.is_dir():
62
+ continue
63
+
64
+ meta_file = session_dir / "meta.json"
65
+ if not meta_file.exists():
66
+ continue
67
+
68
+ with open(meta_file, encoding="utf-8") as f:
69
+ meta = json.load(f)
70
+
71
+ # 过滤项目
72
+ if project and meta.get("project") != project:
73
+ skipped += 1
74
+ continue
75
+
76
+ session_date = parse_session_date(meta)
77
+ session_id = meta.get("session_id", session_dir.name)
78
+
79
+ if session_date >= cutoff:
80
+ skipped += 1
81
+ continue
82
+
83
+ age_days = (datetime.now(timezone.utc) - session_date).days
84
+ print(f" {'[DRY]' if dry_run else ''} 处理: {session_id} ({age_days} 天前, 项目: {meta.get('project', '?')})")
85
+
86
+ if dry_run:
87
+ continue
88
+
89
+ if archive_only:
90
+ # 移动到 archive 目录
91
+ archive_dir.mkdir(parents=True, exist_ok=True)
92
+ dest = archive_dir / session_dir.name
93
+ if not dest.exists():
94
+ shutil.move(str(session_dir), str(dest))
95
+ archived += 1
96
+ print(f" → 已归档到 {dest}")
97
+ else:
98
+ print(f" ⚠️ 归档目标已存在,跳过: {dest}")
99
+ else:
100
+ # 直接删除
101
+ shutil.rmtree(session_dir)
102
+ removed += 1
103
+ print(f" → 已删除")
104
+
105
+ # 同步更新 session_index.json(移除已清理的条目)
106
+ if not dry_run and (removed > 0 or archived > 0):
107
+ _update_session_index(cutoff, project, archive_only)
108
+
109
+ print(f"\n[ultra-memory] 清理完成:")
110
+ print(f" 删除: {removed} 个会话")
111
+ print(f" 归档: {archived} 个会话")
112
+ print(f" 跳过: {skipped} 个会话(未到期或不匹配项目)")
113
+
114
+
115
+ def _update_session_index(cutoff: datetime, project: str, archive_only: bool):
116
+ """从 session_index.json 中移除已清理的会话条目"""
117
+ index_file = ULTRA_MEMORY_HOME / "semantic" / "session_index.json"
118
+ if not index_file.exists():
119
+ return
120
+
121
+ with open(index_file, encoding="utf-8") as f:
122
+ index = json.load(f)
123
+
124
+ original_count = len(index.get("sessions", []))
125
+ kept = []
126
+ for s in index.get("sessions", []):
127
+ ts_str = s.get("started_at", "")
128
+ try:
129
+ ts = datetime.fromisoformat(ts_str.rstrip("Z")).replace(tzinfo=timezone.utc)
130
+ except Exception:
131
+ kept.append(s)
132
+ continue
133
+ if ts >= cutoff:
134
+ kept.append(s)
135
+ elif project and s.get("project") != project:
136
+ kept.append(s)
137
+ elif archive_only:
138
+ # 归档模式:标记为 archived,保留索引条目
139
+ s["archived"] = True
140
+ kept.append(s)
141
+
142
+ index["sessions"] = kept
143
+ with open(index_file, "w", encoding="utf-8") as f:
144
+ json.dump(index, f, ensure_ascii=False, indent=2)
145
+
146
+ print(f"[ultra-memory] session_index.json: {original_count} → {len(kept)} 条记录")
147
+
148
+
149
+ if __name__ == "__main__":
150
+ parser = argparse.ArgumentParser(description="清理历史会话")
151
+ parser.add_argument("--days", type=int, default=30, help="清理多少天前的会话(默认 30 天)")
152
+ parser.add_argument("--archive-only", action="store_true", help="只归档到 archive/ 目录,不删除")
153
+ parser.add_argument("--dry-run", action="store_true", help="演习模式,只打印不执行")
154
+ parser.add_argument("--project", default=None, help="只清理指定项目(默认所有项目)")
155
+ args = parser.parse_args()
156
+ cleanup(args.days, args.archive_only, args.dry_run, args.project)
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ultra-memory: 记忆导出脚本
4
+ 将所有记忆(会话日志、摘要、语义层)打包为 zip 备份文件
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import shutil
11
+ import argparse
12
+ import zipfile
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+
16
+ if sys.stdout.encoding != "utf-8":
17
+ sys.stdout.reconfigure(encoding="utf-8")
18
+ if sys.stderr.encoding != "utf-8":
19
+ sys.stderr.reconfigure(encoding="utf-8")
20
+
21
+ ULTRA_MEMORY_HOME = Path(os.environ.get("ULTRA_MEMORY_HOME", Path.home() / ".ultra-memory"))
22
+
23
+
24
+ def export_memory(
25
+ output_path: str = None,
26
+ project: str = None,
27
+ include_archive: bool = False,
28
+ days: int = None,
29
+ ):
30
+ """
31
+ 导出 ultra-memory 数据为 zip 备份。
32
+
33
+ Args:
34
+ output_path: 输出 zip 文件路径(默认 ~/ultra-memory-backup-<date>.zip)
35
+ project: 只导出指定项目(None 表示全部)
36
+ include_archive: 是否包含 archive/ 目录中的归档会话
37
+ days: 只导出最近 N 天的会话(None 表示全部)
38
+ """
39
+ if not ULTRA_MEMORY_HOME.exists():
40
+ print(f"[ultra-memory] 记忆目录不存在: {ULTRA_MEMORY_HOME}")
41
+ return
42
+
43
+ # 默认输出路径
44
+ if not output_path:
45
+ date_str = datetime.now().strftime("%Y%m%d_%H%M%S")
46
+ output_path = str(Path.home() / f"ultra-memory-backup-{date_str}.zip")
47
+
48
+ output_path = Path(output_path)
49
+ output_path.parent.mkdir(parents=True, exist_ok=True)
50
+
51
+ now = datetime.now(timezone.utc)
52
+ cutoff = None
53
+ if days is not None:
54
+ from datetime import timedelta
55
+ cutoff = now - timedelta(days=days)
56
+
57
+ added_files = 0
58
+ skipped_files = 0
59
+
60
+ print(f"[ultra-memory] 开始导出到: {output_path}")
61
+ if project:
62
+ print(f"[ultra-memory] 过滤项目: {project}")
63
+ if cutoff:
64
+ print(f"[ultra-memory] 只导出 {cutoff.strftime('%Y-%m-%d')} 之后的数据")
65
+
66
+ with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
67
+ # 1. 导出 semantic 目录(用户画像、知识库、会话索引)
68
+ semantic_dir = ULTRA_MEMORY_HOME / "semantic"
69
+ if semantic_dir.exists():
70
+ for file in semantic_dir.rglob("*"):
71
+ if file.is_file():
72
+ arcname = file.relative_to(ULTRA_MEMORY_HOME)
73
+ zf.write(file, arcname)
74
+ added_files += 1
75
+ print(f"[ultra-memory] ✅ 导出语义层: {semantic_dir}")
76
+
77
+ # 2. 导出 sessions 目录
78
+ sessions_dir = ULTRA_MEMORY_HOME / "sessions"
79
+ if sessions_dir.exists():
80
+ for session_dir in sessions_dir.iterdir():
81
+ if not session_dir.is_dir():
82
+ continue
83
+
84
+ meta_file = session_dir / "meta.json"
85
+ if not meta_file.exists():
86
+ continue
87
+
88
+ with open(meta_file) as f:
89
+ meta = json.load(f)
90
+
91
+ # 过滤项目
92
+ if project and meta.get("project") != project:
93
+ skipped_files += 1
94
+ continue
95
+
96
+ # 过滤时间
97
+ if cutoff:
98
+ ts_str = meta.get("started_at", "")
99
+ try:
100
+ ts = datetime.fromisoformat(ts_str.rstrip("Z")).replace(tzinfo=timezone.utc)
101
+ if ts < cutoff:
102
+ skipped_files += 1
103
+ continue
104
+ except Exception:
105
+ pass
106
+
107
+ # 将整个会话目录打包
108
+ for file in session_dir.rglob("*"):
109
+ if file.is_file():
110
+ arcname = file.relative_to(ULTRA_MEMORY_HOME)
111
+ zf.write(file, arcname)
112
+ added_files += 1
113
+
114
+ print(f" + {session_dir.name} (项目: {meta.get('project', '?')}, "
115
+ f"操作数: {meta.get('op_count', 0)})")
116
+
117
+ # 3. 可选:导出 archive 目录
118
+ if include_archive:
119
+ archive_dir = ULTRA_MEMORY_HOME / "archive"
120
+ if archive_dir.exists():
121
+ for file in archive_dir.rglob("*"):
122
+ if file.is_file():
123
+ arcname = file.relative_to(ULTRA_MEMORY_HOME)
124
+ zf.write(file, arcname)
125
+ added_files += 1
126
+ print(f"[ultra-memory] ✅ 导出归档层: {archive_dir}")
127
+
128
+ # 4. 写入导出元信息
129
+ export_meta = {
130
+ "exported_at": now.isoformat(),
131
+ "ultra_memory_home": str(ULTRA_MEMORY_HOME),
132
+ "filter_project": project,
133
+ "filter_days": days,
134
+ "include_archive": include_archive,
135
+ "total_files": added_files,
136
+ }
137
+ zf.writestr("export_meta.json", json.dumps(export_meta, ensure_ascii=False, indent=2))
138
+
139
+ # 统计 zip 大小
140
+ zip_size_kb = output_path.stat().st_size / 1024
141
+
142
+ print(f"\n[ultra-memory] ✅ 导出完成")
143
+ print(f" 输出文件: {output_path}")
144
+ print(f" 文件大小: {zip_size_kb:.1f} KB")
145
+ print(f" 已打包: {added_files} 个文件")
146
+ if skipped_files:
147
+ print(f" 已跳过: {skipped_files} 个会话(不符合过滤条件)")
148
+ print(f"\n 恢复方式: unzip {output_path} -d ~/.ultra-memory/")
149
+
150
+
151
+ if __name__ == "__main__":
152
+ parser = argparse.ArgumentParser(description="导出 ultra-memory 数据为 zip 备份")
153
+ parser.add_argument("--output", default=None, help="输出 zip 路径(默认 ~/ultra-memory-backup-<date>.zip)")
154
+ parser.add_argument("--project", default=None, help="只导出指定项目(默认全部)")
155
+ parser.add_argument("--include-archive", action="store_true", help="同时导出归档会话")
156
+ parser.add_argument("--days", type=int, default=None, help="只导出最近 N 天的会话")
157
+ args = parser.parse_args()
158
+ export_memory(args.output, args.project, args.include_archive, args.days)