cognova 0.1.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/.env.example +58 -0
- package/Claude/CLAUDE.md +92 -0
- package/Claude/hooks/lib/__init__.py +1 -0
- package/Claude/hooks/lib/hook_client.py +207 -0
- package/Claude/hooks/log-event.py +97 -0
- package/Claude/hooks/pre-compact.py +46 -0
- package/Claude/hooks/session-end.py +26 -0
- package/Claude/hooks/session-start.py +35 -0
- package/Claude/hooks/stop-extract.py +40 -0
- package/Claude/rules/frontmatter.md +54 -0
- package/Claude/rules/markdown.md +43 -0
- package/Claude/rules/note-organization.md +33 -0
- package/Claude/settings.json +54 -0
- package/Claude/skills/README.md +136 -0
- package/Claude/skills/_lib/__init__.py +1 -0
- package/Claude/skills/_lib/api.py +164 -0
- package/Claude/skills/_lib/output.py +95 -0
- package/Claude/skills/environment/SKILL.md +73 -0
- package/Claude/skills/environment/environment.py +239 -0
- package/Claude/skills/memory/SKILL.md +153 -0
- package/Claude/skills/memory/memory.py +270 -0
- package/Claude/skills/project/SKILL.md +105 -0
- package/Claude/skills/project/project.py +203 -0
- package/Claude/skills/skill-creator/SKILL.md +261 -0
- package/Claude/skills/task/SKILL.md +135 -0
- package/Claude/skills/task/task.py +310 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +39 -0
- package/app/assets/css/main.css +10 -0
- package/app/components/AppLogo.vue +40 -0
- package/app/components/AssistantPanel.client.vue +518 -0
- package/app/components/ConfirmModal.vue +84 -0
- package/app/components/TemplateMenu.vue +49 -0
- package/app/components/agents/AgentActivityChart.client.vue +105 -0
- package/app/components/agents/AgentActivityChart.server.vue +25 -0
- package/app/components/agents/AgentForm.vue +304 -0
- package/app/components/agents/AgentRunModal.vue +154 -0
- package/app/components/agents/AgentStatsCards.vue +98 -0
- package/app/components/chat/ChatInput.vue +85 -0
- package/app/components/chat/ConversationList.vue +78 -0
- package/app/components/chat/MessageBubble.vue +81 -0
- package/app/components/chat/StreamingMessage.vue +36 -0
- package/app/components/chat/ToolCallBlock.vue +77 -0
- package/app/components/editor/CodeEditor.client.vue +212 -0
- package/app/components/editor/CodeEditorFallback.vue +12 -0
- package/app/components/editor/DocumentEditor.vue +326 -0
- package/app/components/editor/DocumentMetadata.vue +140 -0
- package/app/components/editor/MarkdownEditor.vue +146 -0
- package/app/components/files/FileTree.vue +436 -0
- package/app/components/hooks/HookActivityChart.client.vue +117 -0
- package/app/components/hooks/HookActivityChart.server.vue +25 -0
- package/app/components/hooks/HookStatsCards.vue +63 -0
- package/app/components/hooks/RecentEventsTable.vue +123 -0
- package/app/components/hooks/ToolBreakdownTable.vue +72 -0
- package/app/components/search/DashboardSearch.vue +122 -0
- package/app/components/tasks/ProjectSelect.vue +35 -0
- package/app/components/tasks/TaskCard.vue +182 -0
- package/app/components/tasks/TaskDetail.vue +160 -0
- package/app/components/tasks/TaskForm.vue +280 -0
- package/app/components/tasks/TaskList.vue +69 -0
- package/app/components/view/ViewToc.vue +85 -0
- package/app/composables/useAgents.ts +153 -0
- package/app/composables/useAuth.ts +73 -0
- package/app/composables/useChat.ts +298 -0
- package/app/composables/useDocument.ts +141 -0
- package/app/composables/useEditor.ts +100 -0
- package/app/composables/useFileTree.ts +220 -0
- package/app/composables/useHookEvents.ts +68 -0
- package/app/composables/useMemories.ts +83 -0
- package/app/composables/useNotificationBus.ts +154 -0
- package/app/composables/usePreferences.ts +131 -0
- package/app/composables/useProjects.ts +97 -0
- package/app/composables/useSearch.ts +52 -0
- package/app/composables/useTasks.ts +201 -0
- package/app/composables/useTerminal.ts +135 -0
- package/app/layouts/auth.vue +20 -0
- package/app/layouts/dashboard.vue +186 -0
- package/app/layouts/view.vue +60 -0
- package/app/middleware/auth.ts +9 -0
- package/app/pages/agents/[id].vue +602 -0
- package/app/pages/agents/index.vue +412 -0
- package/app/pages/chat.vue +146 -0
- package/app/pages/dashboard.vue +80 -0
- package/app/pages/docs.vue +131 -0
- package/app/pages/hooks.vue +163 -0
- package/app/pages/index.vue +249 -0
- package/app/pages/login.vue +60 -0
- package/app/pages/memories.vue +282 -0
- package/app/pages/settings.vue +625 -0
- package/app/pages/tasks.vue +312 -0
- package/app/pages/view/[uuid].vue +376 -0
- package/dist/cli/index.js +2711 -0
- package/drizzle.config.ts +10 -0
- package/nuxt.config.ts +98 -0
- package/package.json +107 -0
- package/server/api/agents/[id]/cancel.post.ts +27 -0
- package/server/api/agents/[id]/run.post.ts +34 -0
- package/server/api/agents/[id]/runs.get.ts +45 -0
- package/server/api/agents/[id]/stats.get.ts +94 -0
- package/server/api/agents/[id].delete.ts +29 -0
- package/server/api/agents/[id].get.ts +25 -0
- package/server/api/agents/[id].patch.ts +55 -0
- package/server/api/agents/index.get.ts +15 -0
- package/server/api/agents/index.post.ts +48 -0
- package/server/api/agents/stats.get.ts +86 -0
- package/server/api/auth/[...all].ts +5 -0
- package/server/api/conversations/[id].delete.ts +16 -0
- package/server/api/conversations/[id].get.ts +34 -0
- package/server/api/conversations/index.get.ts +17 -0
- package/server/api/documents/[id]/index.delete.ts +47 -0
- package/server/api/documents/[id]/index.put.ts +102 -0
- package/server/api/documents/[id]/public.get.ts +60 -0
- package/server/api/documents/[id]/restore.post.ts +65 -0
- package/server/api/documents/by-path.post.ts +168 -0
- package/server/api/documents/index.get.ts +48 -0
- package/server/api/fs/delete.post.ts +41 -0
- package/server/api/fs/list.get.ts +99 -0
- package/server/api/fs/mkdir.post.ts +44 -0
- package/server/api/fs/move.post.ts +68 -0
- package/server/api/fs/read.post.ts +48 -0
- package/server/api/fs/rename.post.ts +55 -0
- package/server/api/fs/write.post.ts +51 -0
- package/server/api/health.get.ts +40 -0
- package/server/api/home.get.ts +26 -0
- package/server/api/hooks/events/index.get.ts +56 -0
- package/server/api/hooks/events/index.post.ts +36 -0
- package/server/api/hooks/stats.get.ts +99 -0
- package/server/api/memory/[id].delete.ts +26 -0
- package/server/api/memory/context.get.ts +83 -0
- package/server/api/memory/extract.post.ts +42 -0
- package/server/api/memory/search.get.ts +70 -0
- package/server/api/memory/store.post.ts +31 -0
- package/server/api/projects/[id]/index.delete.ts +40 -0
- package/server/api/projects/[id]/index.get.ts +25 -0
- package/server/api/projects/[id]/index.put.ts +50 -0
- package/server/api/projects/index.get.ts +20 -0
- package/server/api/projects/index.post.ts +34 -0
- package/server/api/secrets/[key].delete.ts +31 -0
- package/server/api/secrets/[key].get.ts +30 -0
- package/server/api/secrets/[key].put.ts +52 -0
- package/server/api/secrets/index.get.ts +20 -0
- package/server/api/secrets/index.post.ts +58 -0
- package/server/api/tasks/[id]/index.delete.ts +46 -0
- package/server/api/tasks/[id]/index.get.ts +24 -0
- package/server/api/tasks/[id]/index.put.ts +70 -0
- package/server/api/tasks/[id]/restore.post.ts +49 -0
- package/server/api/tasks/index.get.ts +53 -0
- package/server/api/tasks/index.post.ts +47 -0
- package/server/api/tasks/tags.get.ts +21 -0
- package/server/api/user/email.patch.ts +56 -0
- package/server/db/index.ts +76 -0
- package/server/db/migrate.ts +41 -0
- package/server/db/schema.ts +345 -0
- package/server/db/seed.ts +46 -0
- package/server/db/types.ts +28 -0
- package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
- package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
- package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
- package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
- package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
- package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
- package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
- package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
- package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
- package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
- package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
- package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
- package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
- package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
- package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
- package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
- package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
- package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
- package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
- package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
- package/server/drizzle/migrations/meta/_journal.json +76 -0
- package/server/middleware/auth.ts +79 -0
- package/server/plugins/00.env-validate.ts +38 -0
- package/server/plugins/01.api-token.ts +31 -0
- package/server/plugins/02.database.ts +54 -0
- package/server/plugins/03.file-watcher.ts +65 -0
- package/server/plugins/04.cron-agents.ts +26 -0
- package/server/routes/_ws/chat.ts +252 -0
- package/server/routes/notifications.ts +47 -0
- package/server/routes/terminal.ts +98 -0
- package/server/services/agent-executor.ts +218 -0
- package/server/services/cron-scheduler.ts +78 -0
- package/server/services/memory-extractor.ts +120 -0
- package/server/utils/agent-cleanup.ts +91 -0
- package/server/utils/agent-registry.ts +95 -0
- package/server/utils/auth.ts +33 -0
- package/server/utils/chat-session-manager.ts +59 -0
- package/server/utils/crypto.ts +40 -0
- package/server/utils/db-guard.ts +12 -0
- package/server/utils/db-state.ts +63 -0
- package/server/utils/document-sync.ts +207 -0
- package/server/utils/frontmatter.ts +84 -0
- package/server/utils/notification-bus.ts +60 -0
- package/server/utils/path-validator.ts +55 -0
- package/server/utils/pty-manager.ts +130 -0
- package/shared/types/index.ts +604 -0
- package/shared/utils/language-detection.ts +87 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Environment Skill for Cognova
|
|
4
|
+
|
|
5
|
+
Provides system information, service status, and troubleshooting utilities.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python environment.py info
|
|
9
|
+
python environment.py status
|
|
10
|
+
python environment.py logs [--lines N]
|
|
11
|
+
python environment.py health
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import platform
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / '_lib'))
|
|
25
|
+
|
|
26
|
+
from api import api_request, API_BASE
|
|
27
|
+
|
|
28
|
+
INSTALL_DIR = os.environ.get('COGNOVA_PROJECT_DIR', '')
|
|
29
|
+
VAULT_PATH = os.environ.get('VAULT_PATH', '')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def cmd_info(args):
|
|
33
|
+
"""Show system and installation information."""
|
|
34
|
+
print("=== Cognova Environment ===\n")
|
|
35
|
+
|
|
36
|
+
# System
|
|
37
|
+
print(f"OS: {platform.system()} {platform.release()}")
|
|
38
|
+
print(f"Architecture: {platform.machine()}")
|
|
39
|
+
print(f"Python: {platform.python_version()}")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
node_v = subprocess.run(['node', '--version'], capture_output=True, text=True, timeout=5)
|
|
43
|
+
print(f"Node.js: {node_v.stdout.strip()}")
|
|
44
|
+
except Exception:
|
|
45
|
+
print("Node.js: not found")
|
|
46
|
+
|
|
47
|
+
print()
|
|
48
|
+
|
|
49
|
+
# Paths
|
|
50
|
+
print(f"Install Dir: {INSTALL_DIR or '(not set)'}")
|
|
51
|
+
print(f"Vault Path: {VAULT_PATH or '(not set)'}")
|
|
52
|
+
print(f"API URL: {API_BASE}")
|
|
53
|
+
print(f"Home Claude: {Path.home() / '.claude'}")
|
|
54
|
+
|
|
55
|
+
# Metadata
|
|
56
|
+
meta_path = Path.home() / '.cognova'
|
|
57
|
+
if meta_path.exists():
|
|
58
|
+
try:
|
|
59
|
+
meta = json.loads(meta_path.read_text())
|
|
60
|
+
print(f"Version: {meta.get('version', 'unknown')}")
|
|
61
|
+
print(f"Installed: {meta.get('installedAt', 'unknown')[:10]}")
|
|
62
|
+
if meta.get('updatedAt'):
|
|
63
|
+
print(f"Updated: {meta['updatedAt'][:10]}")
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
print()
|
|
68
|
+
|
|
69
|
+
# Disk
|
|
70
|
+
check_dir = INSTALL_DIR or str(Path.home())
|
|
71
|
+
if Path(check_dir).exists():
|
|
72
|
+
usage = shutil.disk_usage(check_dir)
|
|
73
|
+
total_gb = usage.total / (1024**3)
|
|
74
|
+
used_gb = usage.used / (1024**3)
|
|
75
|
+
free_gb = usage.free / (1024**3)
|
|
76
|
+
pct = (usage.used / usage.total) * 100
|
|
77
|
+
print(f"Disk Total: {total_gb:.1f} GB")
|
|
78
|
+
print(f"Disk Used: {used_gb:.1f} GB ({pct:.0f}%)")
|
|
79
|
+
print(f"Disk Free: {free_gb:.1f} GB")
|
|
80
|
+
|
|
81
|
+
# Memory
|
|
82
|
+
try:
|
|
83
|
+
if platform.system() == 'Linux':
|
|
84
|
+
with open('/proc/meminfo') as f:
|
|
85
|
+
meminfo = f.read()
|
|
86
|
+
for line in meminfo.split('\n'):
|
|
87
|
+
if line.startswith('MemTotal:'):
|
|
88
|
+
total_kb = int(line.split()[1])
|
|
89
|
+
print(f"\nRAM Total: {total_kb / (1024**2):.1f} GB")
|
|
90
|
+
elif line.startswith('MemAvailable:'):
|
|
91
|
+
avail_kb = int(line.split()[1])
|
|
92
|
+
print(f"RAM Available:{avail_kb / (1024**2):.1f} GB")
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
# Vault stats
|
|
97
|
+
if VAULT_PATH and Path(VAULT_PATH).exists():
|
|
98
|
+
vault = Path(VAULT_PATH)
|
|
99
|
+
md_files = list(vault.rglob('*.md'))
|
|
100
|
+
print(f"\nVault Files: {len(md_files)} markdown documents")
|
|
101
|
+
|
|
102
|
+
folders = ['inbox', 'projects', 'areas', 'resources', 'archive']
|
|
103
|
+
for folder in folders:
|
|
104
|
+
folder_path = vault / folder
|
|
105
|
+
if folder_path.exists():
|
|
106
|
+
count = len(list(folder_path.rglob('*.md')))
|
|
107
|
+
print(f" {folder}/: {count} files")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def cmd_status(args):
|
|
111
|
+
"""Check service status."""
|
|
112
|
+
print("=== Service Status ===\n")
|
|
113
|
+
|
|
114
|
+
# PM2
|
|
115
|
+
try:
|
|
116
|
+
result = subprocess.run(
|
|
117
|
+
['pm2', 'jlist'],
|
|
118
|
+
capture_output=True, text=True, timeout=10
|
|
119
|
+
)
|
|
120
|
+
if result.returncode == 0:
|
|
121
|
+
processes = json.loads(result.stdout)
|
|
122
|
+
sb = [p for p in processes if p.get('name') == 'cognova']
|
|
123
|
+
if sb:
|
|
124
|
+
proc = sb[0]
|
|
125
|
+
env = proc.get('pm2_env', {})
|
|
126
|
+
monit = proc.get('monit', {})
|
|
127
|
+
status = env.get('status', 'unknown')
|
|
128
|
+
restarts = env.get('restart_time', 0)
|
|
129
|
+
memory = monit.get('memory', 0)
|
|
130
|
+
cpu = monit.get('cpu', 0)
|
|
131
|
+
mem_mb = memory / (1024 * 1024)
|
|
132
|
+
|
|
133
|
+
print(f"PM2 Status: {status}")
|
|
134
|
+
print(f"Memory: {mem_mb:.0f} MB")
|
|
135
|
+
print(f"CPU: {cpu}%")
|
|
136
|
+
print(f"Restarts: {restarts}")
|
|
137
|
+
else:
|
|
138
|
+
print("PM2 Status: not found (process 'cognova' not registered)")
|
|
139
|
+
else:
|
|
140
|
+
print("PM2 Status: error running pm2")
|
|
141
|
+
except FileNotFoundError:
|
|
142
|
+
print("PM2 Status: pm2 not installed")
|
|
143
|
+
except Exception as e:
|
|
144
|
+
print(f"PM2 Status: error ({e})")
|
|
145
|
+
|
|
146
|
+
print()
|
|
147
|
+
|
|
148
|
+
# API health
|
|
149
|
+
try:
|
|
150
|
+
start = time.time()
|
|
151
|
+
ok, data = api_request('GET', '/health')
|
|
152
|
+
elapsed = (time.time() - start) * 1000
|
|
153
|
+
if ok:
|
|
154
|
+
print(f"API: healthy ({elapsed:.0f}ms)")
|
|
155
|
+
if isinstance(data, dict):
|
|
156
|
+
for k, v in data.items():
|
|
157
|
+
print(f" {k}: {v}")
|
|
158
|
+
else:
|
|
159
|
+
print(f"API: unhealthy — {data}")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"API: unreachable ({e})")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def cmd_logs(args):
|
|
165
|
+
"""Show recent PM2 logs."""
|
|
166
|
+
lines = args.lines or 30
|
|
167
|
+
try:
|
|
168
|
+
result = subprocess.run(
|
|
169
|
+
['pm2', 'logs', 'cognova', '--lines', str(lines), '--nostream'],
|
|
170
|
+
capture_output=True, text=True, timeout=15
|
|
171
|
+
)
|
|
172
|
+
if result.stdout:
|
|
173
|
+
print(result.stdout)
|
|
174
|
+
if result.stderr:
|
|
175
|
+
print(result.stderr)
|
|
176
|
+
except FileNotFoundError:
|
|
177
|
+
print("ERROR: pm2 not installed")
|
|
178
|
+
sys.exit(1)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
print(f"ERROR: {e}")
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def cmd_health(args):
|
|
185
|
+
"""Detailed API health check."""
|
|
186
|
+
endpoints = [
|
|
187
|
+
('GET', '/health', 'Health'),
|
|
188
|
+
('GET', '/tasks', 'Tasks'),
|
|
189
|
+
('GET', '/projects', 'Projects'),
|
|
190
|
+
('GET', '/documents', 'Documents'),
|
|
191
|
+
('GET', '/memory/search', 'Memory'),
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
print("=== API Health Check ===\n")
|
|
195
|
+
print(f"Base URL: {API_BASE}\n")
|
|
196
|
+
|
|
197
|
+
all_ok = True
|
|
198
|
+
for method, endpoint, label in endpoints:
|
|
199
|
+
start = time.time()
|
|
200
|
+
try:
|
|
201
|
+
ok, data = api_request(method, endpoint)
|
|
202
|
+
elapsed = (time.time() - start) * 1000
|
|
203
|
+
status = "OK" if ok else "FAIL"
|
|
204
|
+
if not ok:
|
|
205
|
+
all_ok = False
|
|
206
|
+
print(f" {status:4s} {label:15s} {elapsed:6.0f}ms")
|
|
207
|
+
except Exception as e:
|
|
208
|
+
all_ok = False
|
|
209
|
+
print(f" FAIL {label:15s} error: {e}")
|
|
210
|
+
|
|
211
|
+
print(f"\nOverall: {'All healthy' if all_ok else 'Some endpoints failing'}")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def main():
|
|
215
|
+
parser = argparse.ArgumentParser(description='Environment Skill')
|
|
216
|
+
subparsers = parser.add_subparsers(dest='command', required=True)
|
|
217
|
+
|
|
218
|
+
subparsers.add_parser('info', help='Show system information')
|
|
219
|
+
subparsers.add_parser('status', help='Check service status')
|
|
220
|
+
|
|
221
|
+
logs_p = subparsers.add_parser('logs', help='Show recent logs')
|
|
222
|
+
logs_p.add_argument('--lines', '-n', type=int, default=30, help='Number of lines')
|
|
223
|
+
|
|
224
|
+
subparsers.add_parser('health', help='API health check')
|
|
225
|
+
|
|
226
|
+
args = parser.parse_args()
|
|
227
|
+
|
|
228
|
+
commands = {
|
|
229
|
+
'info': cmd_info,
|
|
230
|
+
'status': cmd_status,
|
|
231
|
+
'logs': cmd_logs,
|
|
232
|
+
'health': cmd_health,
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
commands[args.command](args)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == '__main__':
|
|
239
|
+
main()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory
|
|
3
|
+
description: Access persistent memory across Claude sessions. Search past conversations, recall decisions, store key insights. Use when needing context from previous work or to save important information for future sessions.
|
|
4
|
+
allowed-tools: Bash, Read
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Memory Skill
|
|
8
|
+
|
|
9
|
+
Access and manage persistent memory from previous Claude sessions. Memories are automatically extracted from conversations and can be explicitly stored.
|
|
10
|
+
|
|
11
|
+
## Memory Types
|
|
12
|
+
|
|
13
|
+
| Type | Icon | Description |
|
|
14
|
+
|------|------|-------------|
|
|
15
|
+
| `decision` | [D] | Architectural or design decisions made |
|
|
16
|
+
| `fact` | [F] | Key facts or information learned |
|
|
17
|
+
| `solution` | [S] | Solutions to problems encountered |
|
|
18
|
+
| `pattern` | [P] | Patterns or conventions discovered |
|
|
19
|
+
| `preference` | [*] | User or project preferences |
|
|
20
|
+
| `summary` | [~] | Summarized context from sessions |
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
### Search memories
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python3 ~/.claude/skills/memory/memory.py search "<query>" [options]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
- `--type <type>` - Filter by memory type
|
|
32
|
+
- `--project <path>` - Filter by project path
|
|
33
|
+
- `--limit <n>` - Max results (default: 10)
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
```bash
|
|
37
|
+
python3 ~/.claude/skills/memory/memory.py search "authentication"
|
|
38
|
+
python3 ~/.claude/skills/memory/memory.py search "database" --type decision
|
|
39
|
+
python3 ~/.claude/skills/memory/memory.py search "API" --project "/Users/me/project"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Get recent memories
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python3 ~/.claude/skills/memory/memory.py recent [limit] [options]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
- `--type <type>` - Filter by memory type
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
```bash
|
|
53
|
+
python3 ~/.claude/skills/memory/memory.py recent
|
|
54
|
+
python3 ~/.claude/skills/memory/memory.py recent 5
|
|
55
|
+
python3 ~/.claude/skills/memory/memory.py recent --type decision
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Store a memory
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
python3 ~/.claude/skills/memory/memory.py store "<content>" [options]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Options:
|
|
65
|
+
- `--type <type>` - Memory type (default: fact)
|
|
66
|
+
- `--project <path>` - Associated project path
|
|
67
|
+
- `--relevance <0-1>` - Relevance score (default: 0.9)
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
```bash
|
|
71
|
+
python3 ~/.claude/skills/memory/memory.py store "We use PostgreSQL with Drizzle ORM"
|
|
72
|
+
python3 ~/.claude/skills/memory/memory.py store "API rate limit set to 100 req/min" --type decision
|
|
73
|
+
python3 ~/.claude/skills/memory/memory.py store "User prefers pnpm over npm" --type preference
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### List decisions
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python3 ~/.claude/skills/memory/memory.py decisions [options]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
- `--project <path>` - Filter by project
|
|
84
|
+
- `--limit <n>` - Max results (default: 20)
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
```bash
|
|
88
|
+
python3 ~/.claude/skills/memory/memory.py decisions
|
|
89
|
+
python3 ~/.claude/skills/memory/memory.py decisions --project "cognova"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Find memories about a topic
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python3 ~/.claude/skills/memory/memory.py about "<topic>" [options]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
- `--limit <n>` - Max results (default: 15)
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
```bash
|
|
103
|
+
python3 ~/.claude/skills/memory/memory.py about "error handling"
|
|
104
|
+
python3 ~/.claude/skills/memory/memory.py about "testing strategy"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Preview session context
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python3 ~/.claude/skills/memory/memory.py context [options]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Shows what context would be injected into a new session.
|
|
114
|
+
|
|
115
|
+
Options:
|
|
116
|
+
- `--project <path>` - Project path
|
|
117
|
+
- `--limit <n>` - Max memories (default: 5)
|
|
118
|
+
|
|
119
|
+
## Natural Language Patterns
|
|
120
|
+
|
|
121
|
+
When users say things like:
|
|
122
|
+
- "What did we decide about..." -> Use `search` or `decisions`
|
|
123
|
+
- "Remember that we..." -> Use `store`
|
|
124
|
+
- "What do you know about..." -> Use `about`
|
|
125
|
+
- "Show recent context" -> Use `recent` or `context`
|
|
126
|
+
- "Save this insight..." -> Use `store`
|
|
127
|
+
|
|
128
|
+
## How Memory Works
|
|
129
|
+
|
|
130
|
+
### Automatic Extraction
|
|
131
|
+
Memories are automatically extracted from conversations:
|
|
132
|
+
1. **PreCompact Hook** - Before context compaction, key insights are extracted
|
|
133
|
+
2. **Stop Hook** - After Claude responds, important facts are saved asynchronously
|
|
134
|
+
|
|
135
|
+
### Session Context
|
|
136
|
+
When a new session starts, relevant memories are automatically injected based on:
|
|
137
|
+
- Project path matching
|
|
138
|
+
- Recency
|
|
139
|
+
- Relevance score
|
|
140
|
+
- Access frequency
|
|
141
|
+
|
|
142
|
+
### Relevance Scoring
|
|
143
|
+
Each memory has a relevance score (0-1):
|
|
144
|
+
- Newly stored memories start at 0.9
|
|
145
|
+
- Frequently accessed memories maintain higher scores
|
|
146
|
+
- Old, unused memories gradually decay
|
|
147
|
+
|
|
148
|
+
## Best Practices
|
|
149
|
+
|
|
150
|
+
1. **Store key decisions** - When making architectural choices, use `store --type decision`
|
|
151
|
+
2. **Check before changes** - Before major refactors, use `about` to check relevant history
|
|
152
|
+
3. **Review decisions** - Use `decisions` to see past architectural choices
|
|
153
|
+
4. **Explicit storage** - For critical insights, explicitly store rather than relying on auto-extraction
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Memory Management Skill for Cognova
|
|
4
|
+
|
|
5
|
+
Provides access to persistent memory across Claude sessions.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python memory.py search <query>
|
|
9
|
+
python memory.py recent [limit]
|
|
10
|
+
python memory.py store <content> [options]
|
|
11
|
+
python memory.py decisions
|
|
12
|
+
python memory.py about <topic>
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / '_lib'))
|
|
20
|
+
|
|
21
|
+
from api import get, post
|
|
22
|
+
from output import success, error, info
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def format_memory(memory: dict) -> str:
|
|
26
|
+
"""Format a memory chunk for display."""
|
|
27
|
+
type_icons = {
|
|
28
|
+
'decision': '[D]',
|
|
29
|
+
'fact': '[F]',
|
|
30
|
+
'solution': '[S]',
|
|
31
|
+
'pattern': '[P]',
|
|
32
|
+
'preference': '[*]',
|
|
33
|
+
'summary': '[~]'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
chunk_type = memory.get('chunkType', 'fact')
|
|
37
|
+
icon = type_icons.get(chunk_type, '[?]')
|
|
38
|
+
content = memory.get('content', '')
|
|
39
|
+
relevance = memory.get('relevanceScore', 1.0)
|
|
40
|
+
|
|
41
|
+
# First line: icon and content
|
|
42
|
+
line1 = f"{icon} {content}"
|
|
43
|
+
|
|
44
|
+
# Second line: metadata
|
|
45
|
+
parts = [f"ID: {memory['id'][:8]}"]
|
|
46
|
+
|
|
47
|
+
if memory.get('projectPath'):
|
|
48
|
+
parts.append(f"Project: {memory['projectPath']}")
|
|
49
|
+
|
|
50
|
+
parts.append(f"Relevance: {relevance:.2f}")
|
|
51
|
+
|
|
52
|
+
if memory.get('accessCount'):
|
|
53
|
+
parts.append(f"Accessed: {memory['accessCount']}x")
|
|
54
|
+
|
|
55
|
+
created = memory.get('createdAt', '')
|
|
56
|
+
if created:
|
|
57
|
+
parts.append(f"Created: {created[:10]}")
|
|
58
|
+
|
|
59
|
+
line2 = " " + " | ".join(parts)
|
|
60
|
+
|
|
61
|
+
return f"{line1}\n{line2}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cmd_search(args):
|
|
65
|
+
"""Search memories by query."""
|
|
66
|
+
params = {
|
|
67
|
+
'query': args.query,
|
|
68
|
+
'limit': args.limit or 10
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if args.type:
|
|
72
|
+
params['chunkType'] = args.type
|
|
73
|
+
|
|
74
|
+
if args.project:
|
|
75
|
+
params['projectPath'] = args.project
|
|
76
|
+
|
|
77
|
+
ok, memories = get('/memory/search', params)
|
|
78
|
+
if not ok:
|
|
79
|
+
error(f"Failed to search memories: {memories}")
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
|
|
82
|
+
if not memories:
|
|
83
|
+
print(f"No memories found matching '{args.query}'")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
print(f"Found {len(memories)} memory/memories:\n")
|
|
87
|
+
for memory in memories:
|
|
88
|
+
print(format_memory(memory))
|
|
89
|
+
print()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def cmd_recent(args):
|
|
93
|
+
"""Get recent memories."""
|
|
94
|
+
params = {
|
|
95
|
+
'limit': args.limit or 10
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if args.type:
|
|
99
|
+
params['chunkType'] = args.type
|
|
100
|
+
|
|
101
|
+
ok, memories = get('/memory/search', params)
|
|
102
|
+
if not ok:
|
|
103
|
+
error(f"Failed to fetch recent memories: {memories}")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
|
|
106
|
+
if not memories:
|
|
107
|
+
print("No memories stored yet.")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
print(f"Recent {len(memories)} memory/memories:\n")
|
|
111
|
+
for memory in memories:
|
|
112
|
+
print(format_memory(memory))
|
|
113
|
+
print()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def cmd_store(args):
|
|
117
|
+
"""Store a new memory explicitly."""
|
|
118
|
+
data = {
|
|
119
|
+
'content': args.content,
|
|
120
|
+
'chunkType': args.type or 'fact',
|
|
121
|
+
'relevanceScore': args.relevance or 0.9
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if args.project:
|
|
125
|
+
data['projectPath'] = args.project
|
|
126
|
+
|
|
127
|
+
ok, result = post('/memory/store', data)
|
|
128
|
+
if ok:
|
|
129
|
+
success(f"Stored memory: {args.content[:50]}...")
|
|
130
|
+
print(f"\nID: {result['id'][:8]}")
|
|
131
|
+
print(f"Type: {result['chunkType']}")
|
|
132
|
+
else:
|
|
133
|
+
error(f"Failed to store memory: {result}")
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def cmd_decisions(args):
|
|
138
|
+
"""List all decision memories."""
|
|
139
|
+
params = {
|
|
140
|
+
'chunkType': 'decision',
|
|
141
|
+
'limit': args.limit or 20
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if args.project:
|
|
145
|
+
params['projectPath'] = args.project
|
|
146
|
+
|
|
147
|
+
ok, memories = get('/memory/search', params)
|
|
148
|
+
if not ok:
|
|
149
|
+
error(f"Failed to fetch decisions: {memories}")
|
|
150
|
+
sys.exit(1)
|
|
151
|
+
|
|
152
|
+
if not memories:
|
|
153
|
+
print("No decisions recorded yet.")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
print(f"Found {len(memories)} decision(s):\n")
|
|
157
|
+
for memory in memories:
|
|
158
|
+
print(format_memory(memory))
|
|
159
|
+
print()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def cmd_about(args):
|
|
163
|
+
"""Find memories about a specific topic."""
|
|
164
|
+
params = {
|
|
165
|
+
'query': args.topic,
|
|
166
|
+
'limit': args.limit or 15
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
ok, memories = get('/memory/search', params)
|
|
170
|
+
if not ok:
|
|
171
|
+
error(f"Failed to search memories: {memories}")
|
|
172
|
+
sys.exit(1)
|
|
173
|
+
|
|
174
|
+
if not memories:
|
|
175
|
+
print(f"No memories found about '{args.topic}'")
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
print(f"Memories about '{args.topic}':\n")
|
|
179
|
+
for memory in memories:
|
|
180
|
+
print(format_memory(memory))
|
|
181
|
+
print()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def cmd_context(args):
|
|
185
|
+
"""Get context that would be injected into a session."""
|
|
186
|
+
params = {
|
|
187
|
+
'limit': args.limit or 5
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if args.project:
|
|
191
|
+
params['projectPath'] = args.project
|
|
192
|
+
|
|
193
|
+
ok, response = get('/memory/context', params)
|
|
194
|
+
if not ok:
|
|
195
|
+
error(f"Failed to fetch context: {response}")
|
|
196
|
+
sys.exit(1)
|
|
197
|
+
|
|
198
|
+
if not response.get('memories'):
|
|
199
|
+
print("No relevant context available.")
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
print("Context that would be injected:\n")
|
|
203
|
+
print("-" * 50)
|
|
204
|
+
print(response.get('formattedContext', ''))
|
|
205
|
+
print("-" * 50)
|
|
206
|
+
print(f"\n({len(response['memories'])} memories included)")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def main():
|
|
210
|
+
parser = argparse.ArgumentParser(description='Memory Management Skill')
|
|
211
|
+
subparsers = parser.add_subparsers(dest='command', required=True)
|
|
212
|
+
|
|
213
|
+
# Search
|
|
214
|
+
search_p = subparsers.add_parser('search', help='Search memories')
|
|
215
|
+
search_p.add_argument('query', help='Search query')
|
|
216
|
+
search_p.add_argument('--type', '-t',
|
|
217
|
+
choices=['decision', 'fact', 'solution', 'pattern', 'preference', 'summary'],
|
|
218
|
+
help='Filter by memory type')
|
|
219
|
+
search_p.add_argument('--project', '-p', help='Filter by project path')
|
|
220
|
+
search_p.add_argument('--limit', '-l', type=int, default=10, help='Max results')
|
|
221
|
+
|
|
222
|
+
# Recent
|
|
223
|
+
recent_p = subparsers.add_parser('recent', help='Get recent memories')
|
|
224
|
+
recent_p.add_argument('limit', nargs='?', type=int, default=10, help='Number of memories')
|
|
225
|
+
recent_p.add_argument('--type', '-t',
|
|
226
|
+
choices=['decision', 'fact', 'solution', 'pattern', 'preference', 'summary'],
|
|
227
|
+
help='Filter by memory type')
|
|
228
|
+
|
|
229
|
+
# Store
|
|
230
|
+
store_p = subparsers.add_parser('store', help='Store a memory')
|
|
231
|
+
store_p.add_argument('content', help='Memory content')
|
|
232
|
+
store_p.add_argument('--type', '-t',
|
|
233
|
+
choices=['decision', 'fact', 'solution', 'pattern', 'preference', 'summary'],
|
|
234
|
+
default='fact',
|
|
235
|
+
help='Memory type (default: fact)')
|
|
236
|
+
store_p.add_argument('--project', '-p', help='Associated project path')
|
|
237
|
+
store_p.add_argument('--relevance', '-r', type=float, default=0.9,
|
|
238
|
+
help='Relevance score 0-1 (default: 0.9)')
|
|
239
|
+
|
|
240
|
+
# Decisions
|
|
241
|
+
decisions_p = subparsers.add_parser('decisions', help='List decision memories')
|
|
242
|
+
decisions_p.add_argument('--project', '-p', help='Filter by project')
|
|
243
|
+
decisions_p.add_argument('--limit', '-l', type=int, default=20, help='Max results')
|
|
244
|
+
|
|
245
|
+
# About
|
|
246
|
+
about_p = subparsers.add_parser('about', help='Find memories about a topic')
|
|
247
|
+
about_p.add_argument('topic', help='Topic to search for')
|
|
248
|
+
about_p.add_argument('--limit', '-l', type=int, default=15, help='Max results')
|
|
249
|
+
|
|
250
|
+
# Context
|
|
251
|
+
context_p = subparsers.add_parser('context', help='Preview session context')
|
|
252
|
+
context_p.add_argument('--project', '-p', help='Project path')
|
|
253
|
+
context_p.add_argument('--limit', '-l', type=int, default=5, help='Max memories')
|
|
254
|
+
|
|
255
|
+
args = parser.parse_args()
|
|
256
|
+
|
|
257
|
+
commands = {
|
|
258
|
+
'search': cmd_search,
|
|
259
|
+
'recent': cmd_recent,
|
|
260
|
+
'store': cmd_store,
|
|
261
|
+
'decisions': cmd_decisions,
|
|
262
|
+
'about': cmd_about,
|
|
263
|
+
'context': cmd_context,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
commands[args.command](args)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
if __name__ == '__main__':
|
|
270
|
+
main()
|