runtime-memory 3.0.0__py3-none-any.whl

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 (54) hide show
  1. runtime_memory/__init__.py +28 -0
  2. runtime_memory/claude_code/__init__.py +48 -0
  3. runtime_memory/claude_code/commands.py +698 -0
  4. runtime_memory/claude_code/daemon.py +852 -0
  5. runtime_memory/claude_code/hooks.py +722 -0
  6. runtime_memory/cli/__init__.py +8 -0
  7. runtime_memory/cli/main.py +1936 -0
  8. runtime_memory/core/__init__.py +216 -0
  9. runtime_memory/core/config.py +473 -0
  10. runtime_memory/core/embeddings.py +908 -0
  11. runtime_memory/core/engine.py +1007 -0
  12. runtime_memory/core/exceptions.py +547 -0
  13. runtime_memory/core/legacy_env.py +39 -0
  14. runtime_memory/core/logging.py +160 -0
  15. runtime_memory/core/models.py +1051 -0
  16. runtime_memory/core/observability.py +725 -0
  17. runtime_memory/core/paths.py +30 -0
  18. runtime_memory/core/resilience.py +511 -0
  19. runtime_memory/core/retrieval.py +819 -0
  20. runtime_memory/core/storage.py +1105 -0
  21. runtime_memory/extraction/__init__.py +36 -0
  22. runtime_memory/extraction/extractor.py +1143 -0
  23. runtime_memory/hermes/__init__.py +39 -0
  24. runtime_memory/hermes/_base.py +154 -0
  25. runtime_memory/hermes/bridge.py +119 -0
  26. runtime_memory/hermes/plugin.yaml +13 -0
  27. runtime_memory/hermes/provider.py +536 -0
  28. runtime_memory/hermes/tools.py +230 -0
  29. runtime_memory/hermes/trace.py +177 -0
  30. runtime_memory/plugin/__init__.py +646 -0
  31. runtime_memory/sdk/__init__.py +97 -0
  32. runtime_memory/sdk/client.py +1577 -0
  33. runtime_memory/server/__init__.py +75 -0
  34. runtime_memory/server/api.py +1665 -0
  35. runtime_memory/server/mcp.py +1574 -0
  36. runtime_memory/server/static/css/styles.css +1110 -0
  37. runtime_memory/server/static/index.html +264 -0
  38. runtime_memory/server/static/js/api.js +294 -0
  39. runtime_memory/server/static/js/app.js +771 -0
  40. runtime_memory/tasks/__init__.py +114 -0
  41. runtime_memory/tasks/adapter.py +501 -0
  42. runtime_memory/tasks/claude_code_adapter.py +495 -0
  43. runtime_memory/tasks/claude_code_parser.py +339 -0
  44. runtime_memory/tasks/cli_bridge.py +415 -0
  45. runtime_memory/tasks/linking.py +397 -0
  46. runtime_memory/tasks/models.py +520 -0
  47. runtime_memory/tasks/outcomes.py +320 -0
  48. runtime_memory/tasks/parser.py +305 -0
  49. runtime_memory/tasks/unified_adapter.py +661 -0
  50. runtime_memory-3.0.0.dist-info/METADATA +497 -0
  51. runtime_memory-3.0.0.dist-info/RECORD +54 -0
  52. runtime_memory-3.0.0.dist-info/WHEEL +4 -0
  53. runtime_memory-3.0.0.dist-info/entry_points.txt +6 -0
  54. runtime_memory-3.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,698 @@
1
+ """Custom commands for Claude Code integration.
2
+
3
+ This module implements slash commands that can be used within Claude Code
4
+ to interact with the memory layer.
5
+
6
+ Commands:
7
+ - /remember: Store a new memory
8
+ - /recall: Search for memories
9
+ - /forget: Delete a memory
10
+ - /outcome: Record outcome feedback
11
+ - /context: Get formatted context
12
+ - /memories: List memories
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import re
19
+ from dataclasses import dataclass, field
20
+ from enum import Enum
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ from runtime_memory.core.engine import MemoryEngine
25
+ from runtime_memory.core.models import (
26
+ Memory,
27
+ MemoryCategory,
28
+ MemoryScope,
29
+ MemorySource,
30
+ Outcome,
31
+ SearchResult,
32
+ )
33
+
34
+
35
+ class CommandType(str, Enum):
36
+ """Types of custom commands."""
37
+
38
+ REMEMBER = "remember"
39
+ RECALL = "recall"
40
+ FORGET = "forget"
41
+ OUTCOME = "outcome"
42
+ CONTEXT = "context"
43
+ MEMORIES = "memories"
44
+
45
+
46
+ @dataclass
47
+ class CommandResult:
48
+ """Result from executing a command."""
49
+
50
+ success: bool
51
+ command: CommandType
52
+ message: str
53
+ data: dict[str, Any] = field(default_factory=dict)
54
+ error: str | None = None
55
+
56
+ def to_dict(self) -> dict[str, Any]:
57
+ """Convert to dictionary."""
58
+ result = {
59
+ "success": self.success,
60
+ "command": self.command.value,
61
+ "message": self.message,
62
+ }
63
+ if self.data:
64
+ result["data"] = self.data
65
+ if self.error:
66
+ result["error"] = self.error
67
+ return result
68
+
69
+ def to_json(self) -> str:
70
+ """Convert to JSON string."""
71
+ return json.dumps(self.to_dict(), indent=2)
72
+
73
+ def to_markdown(self) -> str:
74
+ """Convert to markdown format for display."""
75
+ if not self.success:
76
+ return f"❌ **Error**: {self.error or self.message}"
77
+
78
+ lines = [f"✅ **{self.command.value.title()}**: {self.message}"]
79
+
80
+ if self.data:
81
+ if "memories" in self.data:
82
+ lines.append("")
83
+ for mem in self.data["memories"]:
84
+ score_str = f" (score: {mem.get('score', 0):.2f})" if "score" in mem else ""
85
+ lines.append(f"- **{mem.get('category', 'unknown')}**{score_str}: {mem.get('content', '')[:100]}...")
86
+ elif "memory" in self.data:
87
+ mem = self.data["memory"]
88
+ lines.append(f"\n**ID**: `{mem.get('id', 'unknown')}`")
89
+ lines.append(f"**Category**: {mem.get('category', 'unknown')}")
90
+ lines.append(f"**Content**: {mem.get('content', '')}")
91
+ elif "context" in self.data:
92
+ lines.append("")
93
+ lines.append(self.data["context"])
94
+ elif "stats" in self.data:
95
+ stats = self.data["stats"]
96
+ lines.append(f"\n**Total**: {stats.get('total', 0)} memories")
97
+ lines.append(f"**Active**: {stats.get('active', 0)}")
98
+ lines.append(f"**Archived**: {stats.get('archived', 0)}")
99
+
100
+ return "\n".join(lines)
101
+
102
+
103
+ @dataclass
104
+ class CommandConfig:
105
+ """Configuration for command handler."""
106
+
107
+ default_project: str | None = None
108
+ default_scope: MemoryScope = MemoryScope.PROJECT
109
+ max_results: int = 10
110
+ include_archived: bool = False
111
+ output_format: str = "markdown" # markdown, json, plain
112
+
113
+
114
+ class CommandHandler:
115
+ """Handler for custom slash commands.
116
+
117
+ This class processes slash commands and interacts with the memory engine
118
+ to perform operations like storing, searching, and managing memories.
119
+ """
120
+
121
+ def __init__(
122
+ self,
123
+ engine: MemoryEngine,
124
+ config: CommandConfig | None = None,
125
+ ) -> None:
126
+ """Initialize command handler.
127
+
128
+ Args:
129
+ engine: Memory engine instance
130
+ config: Command configuration
131
+ """
132
+ self.engine = engine
133
+ self.config = config or CommandConfig()
134
+
135
+ async def execute(self, command: str, args: str = "", project: str | None = None) -> CommandResult:
136
+ """Execute a slash command.
137
+
138
+ Args:
139
+ command: Command name (with or without leading slash)
140
+ args: Command arguments as string
141
+ project: Project context (optional)
142
+
143
+ Returns:
144
+ CommandResult with operation outcome
145
+ """
146
+ # Normalize command name
147
+ cmd = command.lstrip("/").lower().strip()
148
+
149
+ # Use project from config if not specified
150
+ project = project or self.config.default_project
151
+
152
+ try:
153
+ if cmd == CommandType.REMEMBER.value:
154
+ return await self._remember(args, project)
155
+ elif cmd == CommandType.RECALL.value:
156
+ return await self._recall(args, project)
157
+ elif cmd == CommandType.FORGET.value:
158
+ return await self._forget(args, project)
159
+ elif cmd == CommandType.OUTCOME.value:
160
+ return await self._outcome(args, project)
161
+ elif cmd == CommandType.CONTEXT.value:
162
+ return await self._context(args, project)
163
+ elif cmd == CommandType.MEMORIES.value:
164
+ return await self._memories(args, project)
165
+ else:
166
+ return CommandResult(
167
+ success=False,
168
+ command=CommandType.REMEMBER, # Default
169
+ message="Unknown command",
170
+ error=f"Unknown command: /{cmd}. Available: /remember, /recall, /forget, /outcome, /context, /memories",
171
+ )
172
+ except Exception as e:
173
+ return CommandResult(
174
+ success=False,
175
+ command=CommandType(cmd) if cmd in [c.value for c in CommandType] else CommandType.REMEMBER,
176
+ message="Command execution failed",
177
+ error=str(e),
178
+ )
179
+
180
+ async def _remember(self, args: str, project: str | None) -> CommandResult:
181
+ """Store a new memory.
182
+
183
+ Format: /remember [category:CATEGORY] content
184
+ Example: /remember category:gotcha Always use --no-cache with pip install
185
+ """
186
+ # Parse category from args
187
+ category = MemoryCategory.PATTERN
188
+ content = args.strip()
189
+
190
+ # Check for category prefix
191
+ category_match = re.match(r"category:(\w+)\s+(.+)", content, re.IGNORECASE | re.DOTALL)
192
+ if category_match:
193
+ cat_str = category_match.group(1).upper()
194
+ content = category_match.group(2).strip()
195
+ try:
196
+ category = MemoryCategory(cat_str.lower())
197
+ except ValueError:
198
+ # Try to match partial category name
199
+ for cat in MemoryCategory:
200
+ if cat.value.upper().startswith(cat_str):
201
+ category = cat
202
+ break
203
+
204
+ if not content:
205
+ return CommandResult(
206
+ success=False,
207
+ command=CommandType.REMEMBER,
208
+ message="No content provided",
209
+ error="Usage: /remember [category:CATEGORY] content",
210
+ )
211
+
212
+ # Add the memory
213
+ memory = await self.engine.add(
214
+ content=content,
215
+ category=category,
216
+ scope=self.config.default_scope,
217
+ source=MemorySource.EXPLICIT,
218
+ project=project,
219
+ )
220
+
221
+ return CommandResult(
222
+ success=True,
223
+ command=CommandType.REMEMBER,
224
+ message=f"Memory stored with ID: {memory.id}",
225
+ data={
226
+ "memory": {
227
+ "id": memory.id,
228
+ "content": memory.content,
229
+ "category": memory.category.value,
230
+ "scope": memory.scope.value,
231
+ "project": memory.project,
232
+ }
233
+ },
234
+ )
235
+
236
+ async def _recall(self, args: str, project: str | None) -> CommandResult:
237
+ """Search for memories.
238
+
239
+ Format: /recall query [limit:N]
240
+ Example: /recall docker memory issues limit:5
241
+ """
242
+ query = args.strip()
243
+ limit = self.config.max_results
244
+
245
+ # Check for limit suffix
246
+ limit_match = re.search(r"\s+limit:(\d+)$", query)
247
+ if limit_match:
248
+ limit = min(int(limit_match.group(1)), 50)
249
+ query = query[: limit_match.start()].strip()
250
+
251
+ if not query:
252
+ return CommandResult(
253
+ success=False,
254
+ command=CommandType.RECALL,
255
+ message="No search query provided",
256
+ error="Usage: /recall query [limit:N]",
257
+ )
258
+
259
+ # Search memories
260
+ results = await self.engine.search(
261
+ query=query,
262
+ project=project,
263
+ limit=limit,
264
+ )
265
+
266
+ if not results:
267
+ return CommandResult(
268
+ success=True,
269
+ command=CommandType.RECALL,
270
+ message="No memories found matching your query",
271
+ data={"memories": [], "query": query},
272
+ )
273
+
274
+ memories_data = []
275
+ for result in results:
276
+ memories_data.append({
277
+ "id": result.memory.id,
278
+ "content": result.memory.content,
279
+ "category": result.memory.category.value,
280
+ "score": result.score,
281
+ "project": result.memory.project,
282
+ })
283
+
284
+ return CommandResult(
285
+ success=True,
286
+ command=CommandType.RECALL,
287
+ message=f"Found {len(results)} memories",
288
+ data={"memories": memories_data, "query": query},
289
+ )
290
+
291
+ async def _forget(self, args: str, project: str | None) -> CommandResult:
292
+ """Delete/archive a memory.
293
+
294
+ Format: /forget memory_id [--permanent]
295
+ Example: /forget abc123
296
+ """
297
+ args = args.strip()
298
+ permanent = False
299
+
300
+ if "--permanent" in args:
301
+ permanent = True
302
+ args = args.replace("--permanent", "").strip()
303
+
304
+ memory_id = args.strip()
305
+
306
+ if not memory_id:
307
+ return CommandResult(
308
+ success=False,
309
+ command=CommandType.FORGET,
310
+ message="No memory ID provided",
311
+ error="Usage: /forget memory_id [--permanent]",
312
+ )
313
+
314
+ # Check if memory exists
315
+ memory = await self.engine.get(memory_id)
316
+ if not memory:
317
+ return CommandResult(
318
+ success=False,
319
+ command=CommandType.FORGET,
320
+ message="Memory not found",
321
+ error=f"No memory found with ID: {memory_id}",
322
+ )
323
+
324
+ # Delete/archive the memory
325
+ await self.engine.delete(memory_id, permanent=permanent)
326
+
327
+ action = "deleted" if permanent else "archived"
328
+ return CommandResult(
329
+ success=True,
330
+ command=CommandType.FORGET,
331
+ message=f"Memory {action}: {memory_id}",
332
+ data={"memory_id": memory_id, "permanent": permanent},
333
+ )
334
+
335
+ async def _outcome(self, args: str, project: str | None) -> CommandResult:
336
+ """Record outcome feedback for a memory.
337
+
338
+ Format: /outcome memory_id worked|failed|partial [notes]
339
+ Example: /outcome abc123 worked The solution fixed the issue
340
+ """
341
+ parts = args.strip().split(None, 2)
342
+
343
+ if len(parts) < 2:
344
+ return CommandResult(
345
+ success=False,
346
+ command=CommandType.OUTCOME,
347
+ message="Invalid arguments",
348
+ error="Usage: /outcome memory_id worked|failed|partial [notes]",
349
+ )
350
+
351
+ memory_id = parts[0]
352
+ outcome_str = parts[1].lower()
353
+ notes = parts[2] if len(parts) > 2 else None
354
+
355
+ # Parse outcome
356
+ try:
357
+ outcome = Outcome(outcome_str)
358
+ except ValueError:
359
+ return CommandResult(
360
+ success=False,
361
+ command=CommandType.OUTCOME,
362
+ message="Invalid outcome value",
363
+ error=f"Outcome must be one of: worked, failed, partial. Got: {outcome_str}",
364
+ )
365
+
366
+ # Check if memory exists
367
+ memory = await self.engine.get(memory_id)
368
+ if not memory:
369
+ return CommandResult(
370
+ success=False,
371
+ command=CommandType.OUTCOME,
372
+ message="Memory not found",
373
+ error=f"No memory found with ID: {memory_id}",
374
+ )
375
+
376
+ # Record the outcome
377
+ old_score = memory.outcome_score
378
+ updated_memory = await self.engine.record_outcome(
379
+ memory_id=memory_id,
380
+ outcome=outcome,
381
+ context=notes,
382
+ )
383
+
384
+ return CommandResult(
385
+ success=True,
386
+ command=CommandType.OUTCOME,
387
+ message=f"Outcome recorded for memory {memory_id}",
388
+ data={
389
+ "memory_id": memory_id,
390
+ "outcome": outcome.value,
391
+ "old_score": old_score,
392
+ "new_score": updated_memory.outcome_score,
393
+ "notes": notes,
394
+ },
395
+ )
396
+
397
+ async def _context(self, args: str, project: str | None) -> CommandResult:
398
+ """Get formatted context for current project.
399
+
400
+ Format: /context [project_name] [limit:N]
401
+ Example: /context my-project limit:10
402
+ """
403
+ args = args.strip()
404
+ limit = self.config.max_results
405
+
406
+ # Check for limit suffix
407
+ limit_match = re.search(r"\s+limit:(\d+)$", args)
408
+ if limit_match:
409
+ limit = min(int(limit_match.group(1)), 50)
410
+ args = args[: limit_match.start()].strip()
411
+
412
+ # Use args as project name if provided
413
+ target_project = args or project
414
+
415
+ # Get context
416
+ context = await self.engine.get_context(
417
+ project=target_project,
418
+ limit=limit,
419
+ )
420
+
421
+ return CommandResult(
422
+ success=True,
423
+ command=CommandType.CONTEXT,
424
+ message=f"Context for project: {target_project or 'all'}",
425
+ data={
426
+ "context": context.formatted,
427
+ "memory_count": context.memory_count,
428
+ "token_estimate": context.token_estimate,
429
+ "project": target_project,
430
+ },
431
+ )
432
+
433
+ async def _memories(self, args: str, project: str | None) -> CommandResult:
434
+ """List memories with optional filtering.
435
+
436
+ Format: /memories [category:CATEGORY] [scope:SCOPE] [limit:N]
437
+ Example: /memories category:gotcha limit:20
438
+ """
439
+ category: MemoryCategory | None = None
440
+ scope: MemoryScope | None = None
441
+ limit = self.config.max_results
442
+ include_archived = self.config.include_archived
443
+
444
+ # Parse arguments
445
+ args_str = args.strip()
446
+
447
+ # Category filter
448
+ cat_match = re.search(r"category:(\w+)", args_str, re.IGNORECASE)
449
+ if cat_match:
450
+ cat_str = cat_match.group(1).lower()
451
+ try:
452
+ category = MemoryCategory(cat_str)
453
+ except ValueError:
454
+ for cat in MemoryCategory:
455
+ if cat.value.startswith(cat_str):
456
+ category = cat
457
+ break
458
+
459
+ # Scope filter
460
+ scope_match = re.search(r"scope:(\w+)", args_str, re.IGNORECASE)
461
+ if scope_match:
462
+ scope_str = scope_match.group(1).lower()
463
+ try:
464
+ scope = MemoryScope(scope_str)
465
+ except ValueError:
466
+ pass
467
+
468
+ # Limit
469
+ limit_match = re.search(r"limit:(\d+)", args_str)
470
+ if limit_match:
471
+ limit = min(int(limit_match.group(1)), 100)
472
+
473
+ # Include archived flag
474
+ if "--archived" in args_str or "-a" in args_str:
475
+ include_archived = True
476
+
477
+ # List memories
478
+ memories = await self.engine.list(
479
+ project=project,
480
+ category=category,
481
+ scope=scope,
482
+ include_archived=include_archived,
483
+ limit=limit,
484
+ )
485
+
486
+ # Get stats
487
+ stats = await self.engine.stats(project=project)
488
+
489
+ memories_data = []
490
+ for memory in memories:
491
+ memories_data.append({
492
+ "id": memory.id,
493
+ "content": memory.content[:100] + ("..." if len(memory.content) > 100 else ""),
494
+ "category": memory.category.value,
495
+ "scope": memory.scope.value,
496
+ "outcome_score": memory.outcome_score,
497
+ "use_count": memory.use_count,
498
+ "archived": memory.archived,
499
+ })
500
+
501
+ return CommandResult(
502
+ success=True,
503
+ command=CommandType.MEMORIES,
504
+ message=f"Listing {len(memories)} memories",
505
+ data={
506
+ "memories": memories_data,
507
+ "stats": {
508
+ "total": stats.total_memories,
509
+ "active": stats.active_memories,
510
+ "archived": stats.archived_memories,
511
+ "by_category": {k.value: v for k, v in stats.by_category.items()},
512
+ },
513
+ "filters": {
514
+ "category": category.value if category else None,
515
+ "scope": scope.value if scope else None,
516
+ "project": project,
517
+ "include_archived": include_archived,
518
+ },
519
+ },
520
+ )
521
+
522
+
523
+ def get_command_schemas() -> dict[str, dict[str, Any]]:
524
+ """Generate JSON schemas for all commands.
525
+
526
+ Returns:
527
+ Dictionary mapping command names to their JSON schemas
528
+ """
529
+ schemas = {
530
+ "remember": {
531
+ "name": "remember",
532
+ "description": "Store a new memory in the memory layer",
533
+ "parameters": {
534
+ "type": "object",
535
+ "properties": {
536
+ "content": {
537
+ "type": "string",
538
+ "description": "The content to remember",
539
+ },
540
+ "category": {
541
+ "type": "string",
542
+ "enum": [c.value for c in MemoryCategory],
543
+ "description": "Category of the memory",
544
+ "default": "pattern",
545
+ },
546
+ },
547
+ "required": ["content"],
548
+ },
549
+ "examples": [
550
+ "/remember Always run migrations before deploying",
551
+ "/remember category:gotcha Docker needs --shm-size for PyTorch",
552
+ ],
553
+ },
554
+ "recall": {
555
+ "name": "recall",
556
+ "description": "Search for memories matching a query",
557
+ "parameters": {
558
+ "type": "object",
559
+ "properties": {
560
+ "query": {
561
+ "type": "string",
562
+ "description": "Search query",
563
+ },
564
+ "limit": {
565
+ "type": "integer",
566
+ "description": "Maximum number of results",
567
+ "default": 10,
568
+ },
569
+ },
570
+ "required": ["query"],
571
+ },
572
+ "examples": [
573
+ "/recall docker memory issues",
574
+ "/recall database connection limit:5",
575
+ ],
576
+ },
577
+ "forget": {
578
+ "name": "forget",
579
+ "description": "Delete or archive a memory",
580
+ "parameters": {
581
+ "type": "object",
582
+ "properties": {
583
+ "memory_id": {
584
+ "type": "string",
585
+ "description": "ID of the memory to forget",
586
+ },
587
+ "permanent": {
588
+ "type": "boolean",
589
+ "description": "Permanently delete instead of archive",
590
+ "default": False,
591
+ },
592
+ },
593
+ "required": ["memory_id"],
594
+ },
595
+ "examples": [
596
+ "/forget abc123",
597
+ "/forget abc123 --permanent",
598
+ ],
599
+ },
600
+ "outcome": {
601
+ "name": "outcome",
602
+ "description": "Record whether a memory was helpful",
603
+ "parameters": {
604
+ "type": "object",
605
+ "properties": {
606
+ "memory_id": {
607
+ "type": "string",
608
+ "description": "ID of the memory",
609
+ },
610
+ "outcome": {
611
+ "type": "string",
612
+ "enum": ["worked", "failed", "partial"],
613
+ "description": "The outcome of using this memory",
614
+ },
615
+ "notes": {
616
+ "type": "string",
617
+ "description": "Optional notes about the outcome",
618
+ },
619
+ },
620
+ "required": ["memory_id", "outcome"],
621
+ },
622
+ "examples": [
623
+ "/outcome abc123 worked",
624
+ "/outcome abc123 failed The solution was outdated",
625
+ ],
626
+ },
627
+ "context": {
628
+ "name": "context",
629
+ "description": "Get formatted context for the current project",
630
+ "parameters": {
631
+ "type": "object",
632
+ "properties": {
633
+ "project": {
634
+ "type": "string",
635
+ "description": "Project name (optional, uses current project)",
636
+ },
637
+ "limit": {
638
+ "type": "integer",
639
+ "description": "Maximum number of memories to include",
640
+ "default": 10,
641
+ },
642
+ },
643
+ "required": [],
644
+ },
645
+ "examples": [
646
+ "/context",
647
+ "/context my-project limit:20",
648
+ ],
649
+ },
650
+ "memories": {
651
+ "name": "memories",
652
+ "description": "List memories with optional filtering",
653
+ "parameters": {
654
+ "type": "object",
655
+ "properties": {
656
+ "category": {
657
+ "type": "string",
658
+ "enum": [c.value for c in MemoryCategory],
659
+ "description": "Filter by category",
660
+ },
661
+ "scope": {
662
+ "type": "string",
663
+ "enum": [s.value for s in MemoryScope],
664
+ "description": "Filter by scope",
665
+ },
666
+ "limit": {
667
+ "type": "integer",
668
+ "description": "Maximum number of results",
669
+ "default": 10,
670
+ },
671
+ "include_archived": {
672
+ "type": "boolean",
673
+ "description": "Include archived memories",
674
+ "default": False,
675
+ },
676
+ },
677
+ "required": [],
678
+ },
679
+ "examples": [
680
+ "/memories",
681
+ "/memories category:gotcha limit:20",
682
+ "/memories scope:global --archived",
683
+ ],
684
+ },
685
+ }
686
+
687
+ return schemas
688
+
689
+
690
+ def export_command_schemas(output_path: Path) -> None:
691
+ """Export command schemas to a JSON file.
692
+
693
+ Args:
694
+ output_path: Path to write the JSON schema file
695
+ """
696
+ schemas = get_command_schemas()
697
+ with open(output_path, "w") as f:
698
+ json.dump(schemas, f, indent=2)