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.
Files changed (205) hide show
  1. package/.env.example +58 -0
  2. package/Claude/CLAUDE.md +92 -0
  3. package/Claude/hooks/lib/__init__.py +1 -0
  4. package/Claude/hooks/lib/hook_client.py +207 -0
  5. package/Claude/hooks/log-event.py +97 -0
  6. package/Claude/hooks/pre-compact.py +46 -0
  7. package/Claude/hooks/session-end.py +26 -0
  8. package/Claude/hooks/session-start.py +35 -0
  9. package/Claude/hooks/stop-extract.py +40 -0
  10. package/Claude/rules/frontmatter.md +54 -0
  11. package/Claude/rules/markdown.md +43 -0
  12. package/Claude/rules/note-organization.md +33 -0
  13. package/Claude/settings.json +54 -0
  14. package/Claude/skills/README.md +136 -0
  15. package/Claude/skills/_lib/__init__.py +1 -0
  16. package/Claude/skills/_lib/api.py +164 -0
  17. package/Claude/skills/_lib/output.py +95 -0
  18. package/Claude/skills/environment/SKILL.md +73 -0
  19. package/Claude/skills/environment/environment.py +239 -0
  20. package/Claude/skills/memory/SKILL.md +153 -0
  21. package/Claude/skills/memory/memory.py +270 -0
  22. package/Claude/skills/project/SKILL.md +105 -0
  23. package/Claude/skills/project/project.py +203 -0
  24. package/Claude/skills/skill-creator/SKILL.md +261 -0
  25. package/Claude/skills/task/SKILL.md +135 -0
  26. package/Claude/skills/task/task.py +310 -0
  27. package/LICENSE +21 -0
  28. package/README.md +176 -0
  29. package/app/app.config.ts +8 -0
  30. package/app/app.vue +39 -0
  31. package/app/assets/css/main.css +10 -0
  32. package/app/components/AppLogo.vue +40 -0
  33. package/app/components/AssistantPanel.client.vue +518 -0
  34. package/app/components/ConfirmModal.vue +84 -0
  35. package/app/components/TemplateMenu.vue +49 -0
  36. package/app/components/agents/AgentActivityChart.client.vue +105 -0
  37. package/app/components/agents/AgentActivityChart.server.vue +25 -0
  38. package/app/components/agents/AgentForm.vue +304 -0
  39. package/app/components/agents/AgentRunModal.vue +154 -0
  40. package/app/components/agents/AgentStatsCards.vue +98 -0
  41. package/app/components/chat/ChatInput.vue +85 -0
  42. package/app/components/chat/ConversationList.vue +78 -0
  43. package/app/components/chat/MessageBubble.vue +81 -0
  44. package/app/components/chat/StreamingMessage.vue +36 -0
  45. package/app/components/chat/ToolCallBlock.vue +77 -0
  46. package/app/components/editor/CodeEditor.client.vue +212 -0
  47. package/app/components/editor/CodeEditorFallback.vue +12 -0
  48. package/app/components/editor/DocumentEditor.vue +326 -0
  49. package/app/components/editor/DocumentMetadata.vue +140 -0
  50. package/app/components/editor/MarkdownEditor.vue +146 -0
  51. package/app/components/files/FileTree.vue +436 -0
  52. package/app/components/hooks/HookActivityChart.client.vue +117 -0
  53. package/app/components/hooks/HookActivityChart.server.vue +25 -0
  54. package/app/components/hooks/HookStatsCards.vue +63 -0
  55. package/app/components/hooks/RecentEventsTable.vue +123 -0
  56. package/app/components/hooks/ToolBreakdownTable.vue +72 -0
  57. package/app/components/search/DashboardSearch.vue +122 -0
  58. package/app/components/tasks/ProjectSelect.vue +35 -0
  59. package/app/components/tasks/TaskCard.vue +182 -0
  60. package/app/components/tasks/TaskDetail.vue +160 -0
  61. package/app/components/tasks/TaskForm.vue +280 -0
  62. package/app/components/tasks/TaskList.vue +69 -0
  63. package/app/components/view/ViewToc.vue +85 -0
  64. package/app/composables/useAgents.ts +153 -0
  65. package/app/composables/useAuth.ts +73 -0
  66. package/app/composables/useChat.ts +298 -0
  67. package/app/composables/useDocument.ts +141 -0
  68. package/app/composables/useEditor.ts +100 -0
  69. package/app/composables/useFileTree.ts +220 -0
  70. package/app/composables/useHookEvents.ts +68 -0
  71. package/app/composables/useMemories.ts +83 -0
  72. package/app/composables/useNotificationBus.ts +154 -0
  73. package/app/composables/usePreferences.ts +131 -0
  74. package/app/composables/useProjects.ts +97 -0
  75. package/app/composables/useSearch.ts +52 -0
  76. package/app/composables/useTasks.ts +201 -0
  77. package/app/composables/useTerminal.ts +135 -0
  78. package/app/layouts/auth.vue +20 -0
  79. package/app/layouts/dashboard.vue +186 -0
  80. package/app/layouts/view.vue +60 -0
  81. package/app/middleware/auth.ts +9 -0
  82. package/app/pages/agents/[id].vue +602 -0
  83. package/app/pages/agents/index.vue +412 -0
  84. package/app/pages/chat.vue +146 -0
  85. package/app/pages/dashboard.vue +80 -0
  86. package/app/pages/docs.vue +131 -0
  87. package/app/pages/hooks.vue +163 -0
  88. package/app/pages/index.vue +249 -0
  89. package/app/pages/login.vue +60 -0
  90. package/app/pages/memories.vue +282 -0
  91. package/app/pages/settings.vue +625 -0
  92. package/app/pages/tasks.vue +312 -0
  93. package/app/pages/view/[uuid].vue +376 -0
  94. package/dist/cli/index.js +2711 -0
  95. package/drizzle.config.ts +10 -0
  96. package/nuxt.config.ts +98 -0
  97. package/package.json +107 -0
  98. package/server/api/agents/[id]/cancel.post.ts +27 -0
  99. package/server/api/agents/[id]/run.post.ts +34 -0
  100. package/server/api/agents/[id]/runs.get.ts +45 -0
  101. package/server/api/agents/[id]/stats.get.ts +94 -0
  102. package/server/api/agents/[id].delete.ts +29 -0
  103. package/server/api/agents/[id].get.ts +25 -0
  104. package/server/api/agents/[id].patch.ts +55 -0
  105. package/server/api/agents/index.get.ts +15 -0
  106. package/server/api/agents/index.post.ts +48 -0
  107. package/server/api/agents/stats.get.ts +86 -0
  108. package/server/api/auth/[...all].ts +5 -0
  109. package/server/api/conversations/[id].delete.ts +16 -0
  110. package/server/api/conversations/[id].get.ts +34 -0
  111. package/server/api/conversations/index.get.ts +17 -0
  112. package/server/api/documents/[id]/index.delete.ts +47 -0
  113. package/server/api/documents/[id]/index.put.ts +102 -0
  114. package/server/api/documents/[id]/public.get.ts +60 -0
  115. package/server/api/documents/[id]/restore.post.ts +65 -0
  116. package/server/api/documents/by-path.post.ts +168 -0
  117. package/server/api/documents/index.get.ts +48 -0
  118. package/server/api/fs/delete.post.ts +41 -0
  119. package/server/api/fs/list.get.ts +99 -0
  120. package/server/api/fs/mkdir.post.ts +44 -0
  121. package/server/api/fs/move.post.ts +68 -0
  122. package/server/api/fs/read.post.ts +48 -0
  123. package/server/api/fs/rename.post.ts +55 -0
  124. package/server/api/fs/write.post.ts +51 -0
  125. package/server/api/health.get.ts +40 -0
  126. package/server/api/home.get.ts +26 -0
  127. package/server/api/hooks/events/index.get.ts +56 -0
  128. package/server/api/hooks/events/index.post.ts +36 -0
  129. package/server/api/hooks/stats.get.ts +99 -0
  130. package/server/api/memory/[id].delete.ts +26 -0
  131. package/server/api/memory/context.get.ts +83 -0
  132. package/server/api/memory/extract.post.ts +42 -0
  133. package/server/api/memory/search.get.ts +70 -0
  134. package/server/api/memory/store.post.ts +31 -0
  135. package/server/api/projects/[id]/index.delete.ts +40 -0
  136. package/server/api/projects/[id]/index.get.ts +25 -0
  137. package/server/api/projects/[id]/index.put.ts +50 -0
  138. package/server/api/projects/index.get.ts +20 -0
  139. package/server/api/projects/index.post.ts +34 -0
  140. package/server/api/secrets/[key].delete.ts +31 -0
  141. package/server/api/secrets/[key].get.ts +30 -0
  142. package/server/api/secrets/[key].put.ts +52 -0
  143. package/server/api/secrets/index.get.ts +20 -0
  144. package/server/api/secrets/index.post.ts +58 -0
  145. package/server/api/tasks/[id]/index.delete.ts +46 -0
  146. package/server/api/tasks/[id]/index.get.ts +24 -0
  147. package/server/api/tasks/[id]/index.put.ts +70 -0
  148. package/server/api/tasks/[id]/restore.post.ts +49 -0
  149. package/server/api/tasks/index.get.ts +53 -0
  150. package/server/api/tasks/index.post.ts +47 -0
  151. package/server/api/tasks/tags.get.ts +21 -0
  152. package/server/api/user/email.patch.ts +56 -0
  153. package/server/db/index.ts +76 -0
  154. package/server/db/migrate.ts +41 -0
  155. package/server/db/schema.ts +345 -0
  156. package/server/db/seed.ts +46 -0
  157. package/server/db/types.ts +28 -0
  158. package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
  159. package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
  160. package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
  161. package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
  162. package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
  163. package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
  164. package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
  165. package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
  166. package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
  167. package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
  168. package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
  169. package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
  170. package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
  171. package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
  172. package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
  173. package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
  174. package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
  175. package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
  176. package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
  177. package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
  178. package/server/drizzle/migrations/meta/_journal.json +76 -0
  179. package/server/middleware/auth.ts +79 -0
  180. package/server/plugins/00.env-validate.ts +38 -0
  181. package/server/plugins/01.api-token.ts +31 -0
  182. package/server/plugins/02.database.ts +54 -0
  183. package/server/plugins/03.file-watcher.ts +65 -0
  184. package/server/plugins/04.cron-agents.ts +26 -0
  185. package/server/routes/_ws/chat.ts +252 -0
  186. package/server/routes/notifications.ts +47 -0
  187. package/server/routes/terminal.ts +98 -0
  188. package/server/services/agent-executor.ts +218 -0
  189. package/server/services/cron-scheduler.ts +78 -0
  190. package/server/services/memory-extractor.ts +120 -0
  191. package/server/utils/agent-cleanup.ts +91 -0
  192. package/server/utils/agent-registry.ts +95 -0
  193. package/server/utils/auth.ts +33 -0
  194. package/server/utils/chat-session-manager.ts +59 -0
  195. package/server/utils/crypto.ts +40 -0
  196. package/server/utils/db-guard.ts +12 -0
  197. package/server/utils/db-state.ts +63 -0
  198. package/server/utils/document-sync.ts +207 -0
  199. package/server/utils/frontmatter.ts +84 -0
  200. package/server/utils/notification-bus.ts +60 -0
  201. package/server/utils/path-validator.ts +55 -0
  202. package/server/utils/pty-manager.ts +130 -0
  203. package/shared/types/index.ts +604 -0
  204. package/shared/utils/language-detection.ts +87 -0
  205. 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()