@synth-coder/memhub 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 (104) hide show
  1. package/.eslintrc.cjs +46 -0
  2. package/.github/workflows/ci.yml +74 -0
  3. package/.iflow/commands/opsx-apply.md +152 -0
  4. package/.iflow/commands/opsx-archive.md +157 -0
  5. package/.iflow/commands/opsx-explore.md +173 -0
  6. package/.iflow/commands/opsx-propose.md +106 -0
  7. package/.iflow/skills/openspec-apply-change/SKILL.md +156 -0
  8. package/.iflow/skills/openspec-archive-change/SKILL.md +114 -0
  9. package/.iflow/skills/openspec-explore/SKILL.md +288 -0
  10. package/.iflow/skills/openspec-propose/SKILL.md +110 -0
  11. package/.prettierrc +11 -0
  12. package/README.md +171 -0
  13. package/README.zh-CN.md +169 -0
  14. package/dist/src/contracts/index.d.ts +7 -0
  15. package/dist/src/contracts/index.d.ts.map +1 -0
  16. package/dist/src/contracts/index.js +10 -0
  17. package/dist/src/contracts/index.js.map +1 -0
  18. package/dist/src/contracts/mcp.d.ts +194 -0
  19. package/dist/src/contracts/mcp.d.ts.map +1 -0
  20. package/dist/src/contracts/mcp.js +112 -0
  21. package/dist/src/contracts/mcp.js.map +1 -0
  22. package/dist/src/contracts/schemas.d.ts +1153 -0
  23. package/dist/src/contracts/schemas.d.ts.map +1 -0
  24. package/dist/src/contracts/schemas.js +246 -0
  25. package/dist/src/contracts/schemas.js.map +1 -0
  26. package/dist/src/contracts/types.d.ts +328 -0
  27. package/dist/src/contracts/types.d.ts.map +1 -0
  28. package/dist/src/contracts/types.js +30 -0
  29. package/dist/src/contracts/types.js.map +1 -0
  30. package/dist/src/index.d.ts +8 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +8 -0
  33. package/dist/src/index.js.map +1 -0
  34. package/dist/src/server/index.d.ts +5 -0
  35. package/dist/src/server/index.d.ts.map +1 -0
  36. package/dist/src/server/index.js +5 -0
  37. package/dist/src/server/index.js.map +1 -0
  38. package/dist/src/server/mcp-server.d.ts +80 -0
  39. package/dist/src/server/mcp-server.d.ts.map +1 -0
  40. package/dist/src/server/mcp-server.js +263 -0
  41. package/dist/src/server/mcp-server.js.map +1 -0
  42. package/dist/src/services/index.d.ts +5 -0
  43. package/dist/src/services/index.d.ts.map +1 -0
  44. package/dist/src/services/index.js +5 -0
  45. package/dist/src/services/index.js.map +1 -0
  46. package/dist/src/services/memory-service.d.ts +105 -0
  47. package/dist/src/services/memory-service.d.ts.map +1 -0
  48. package/dist/src/services/memory-service.js +447 -0
  49. package/dist/src/services/memory-service.js.map +1 -0
  50. package/dist/src/storage/frontmatter-parser.d.ts +69 -0
  51. package/dist/src/storage/frontmatter-parser.d.ts.map +1 -0
  52. package/dist/src/storage/frontmatter-parser.js +207 -0
  53. package/dist/src/storage/frontmatter-parser.js.map +1 -0
  54. package/dist/src/storage/index.d.ts +6 -0
  55. package/dist/src/storage/index.d.ts.map +1 -0
  56. package/dist/src/storage/index.js +6 -0
  57. package/dist/src/storage/index.js.map +1 -0
  58. package/dist/src/storage/markdown-storage.d.ts +76 -0
  59. package/dist/src/storage/markdown-storage.d.ts.map +1 -0
  60. package/dist/src/storage/markdown-storage.js +193 -0
  61. package/dist/src/storage/markdown-storage.js.map +1 -0
  62. package/dist/src/utils/index.d.ts +5 -0
  63. package/dist/src/utils/index.d.ts.map +1 -0
  64. package/dist/src/utils/index.js +5 -0
  65. package/dist/src/utils/index.js.map +1 -0
  66. package/dist/src/utils/slugify.d.ts +24 -0
  67. package/dist/src/utils/slugify.d.ts.map +1 -0
  68. package/dist/src/utils/slugify.js +56 -0
  69. package/dist/src/utils/slugify.js.map +1 -0
  70. package/docs/architecture.md +349 -0
  71. package/docs/contracts.md +119 -0
  72. package/docs/prompt-template.md +79 -0
  73. package/docs/proposal-close-gates.md +58 -0
  74. package/docs/tool-calling-policy.md +107 -0
  75. package/package.json +53 -0
  76. package/src/contracts/index.ts +12 -0
  77. package/src/contracts/mcp.ts +303 -0
  78. package/src/contracts/schemas.ts +311 -0
  79. package/src/contracts/types.ts +414 -0
  80. package/src/index.ts +8 -0
  81. package/src/server/index.ts +5 -0
  82. package/src/server/mcp-server.ts +352 -0
  83. package/src/services/index.ts +5 -0
  84. package/src/services/memory-service.ts +548 -0
  85. package/src/storage/frontmatter-parser.ts +243 -0
  86. package/src/storage/index.ts +6 -0
  87. package/src/storage/markdown-storage.ts +236 -0
  88. package/src/utils/index.ts +5 -0
  89. package/src/utils/slugify.ts +63 -0
  90. package/test/contracts/schemas.test.ts +313 -0
  91. package/test/contracts/types.test.ts +21 -0
  92. package/test/frontmatter-parser-more.test.ts +94 -0
  93. package/test/server/mcp-server-internals.test.ts +257 -0
  94. package/test/server/mcp-server.test.ts +97 -0
  95. package/test/services/memory-service-edge.test.ts +248 -0
  96. package/test/services/memory-service.test.ts +279 -0
  97. package/test/storage/frontmatter-parser.test.ts +223 -0
  98. package/test/storage/markdown-storage.test.ts +217 -0
  99. package/test/storage/storage-edge.test.ts +238 -0
  100. package/test/utils/slugify-edge.test.ts +94 -0
  101. package/test/utils/slugify.test.ts +68 -0
  102. package/tsconfig.json +26 -0
  103. package/tsconfig.test.json +8 -0
  104. package/vitest.config.ts +27 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * FrontMatter Parser - Handles YAML Front Matter and Markdown content
3
+ */
4
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
5
+ import { slugify } from '../utils/slugify.js';
6
+ /**
7
+ * Custom error for front matter parsing
8
+ */
9
+ export class FrontMatterError extends Error {
10
+ cause;
11
+ constructor(message, cause) {
12
+ super(message);
13
+ this.cause = cause;
14
+ this.name = 'FrontMatterError';
15
+ }
16
+ }
17
+ /**
18
+ * Parses a markdown file with YAML front matter
19
+ *
20
+ * Expected format:
21
+ * ---
22
+ * id: "uuid"
23
+ * created_at: "ISO8601"
24
+ * updated_at: "ISO8601"
25
+ * tags: ["tag1", "tag2"]
26
+ * category: "category"
27
+ * importance: 3
28
+ * ---
29
+ *
30
+ * # Title
31
+ *
32
+ * Content...
33
+ *
34
+ * @param markdown - The markdown content to parse
35
+ * @returns Parsed front matter, title, and content
36
+ * @throws FrontMatterError if parsing fails
37
+ */
38
+ export function parseFrontMatter(markdown) {
39
+ // Check for front matter delimiter
40
+ if (!markdown.startsWith('---')) {
41
+ throw new FrontMatterError('Missing front matter delimiter');
42
+ }
43
+ // Find the end of front matter
44
+ const endMatch = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
45
+ if (!endMatch) {
46
+ throw new FrontMatterError('Invalid front matter format');
47
+ }
48
+ const frontMatterYaml = endMatch[1];
49
+ const restOfContent = markdown.slice(endMatch[0].length);
50
+ // Parse YAML front matter
51
+ let frontMatter;
52
+ try {
53
+ frontMatter = parseYaml(frontMatterYaml);
54
+ }
55
+ catch (error) {
56
+ throw new FrontMatterError('Invalid YAML in front matter', error);
57
+ }
58
+ // Validate required fields
59
+ if (!isValidFrontMatter(frontMatter)) {
60
+ throw new FrontMatterError('Missing required fields in front matter');
61
+ }
62
+ // Parse title and content from markdown body
63
+ const { title, content } = parseMarkdownBody(restOfContent);
64
+ return {
65
+ frontMatter,
66
+ title,
67
+ content,
68
+ };
69
+ }
70
+ /**
71
+ * Converts front matter and content to markdown string
72
+ *
73
+ * @param frontMatter - The front matter data
74
+ * @param title - The title (H1 heading)
75
+ * @param content - The markdown content
76
+ * @returns Complete markdown string
77
+ */
78
+ export function stringifyFrontMatter(frontMatter, title, content) {
79
+ // Convert camelCase to snake_case for YAML
80
+ const yamlData = {
81
+ id: frontMatter.id,
82
+ created_at: frontMatter.created_at,
83
+ updated_at: frontMatter.updated_at,
84
+ ...(frontMatter.session_id ? { session_id: frontMatter.session_id } : {}),
85
+ ...(frontMatter.entry_type ? { entry_type: frontMatter.entry_type } : {}),
86
+ tags: frontMatter.tags.length > 0 ? frontMatter.tags : [],
87
+ category: frontMatter.category,
88
+ importance: frontMatter.importance,
89
+ };
90
+ // Stringify YAML with specific options for consistent formatting
91
+ const yamlString = stringifyYaml(yamlData, {
92
+ indent: 2,
93
+ defaultKeyType: 'PLAIN',
94
+ defaultStringType: 'QUOTE_DOUBLE',
95
+ });
96
+ // Build the complete markdown
97
+ const parts = ['---', yamlString.trim(), '---', ''];
98
+ // Add title if provided
99
+ if (title) {
100
+ parts.push(`# ${title}`, '');
101
+ }
102
+ // Add content if provided
103
+ if (content) {
104
+ parts.push(content);
105
+ }
106
+ // Ensure content ends with a single newline
107
+ let result = parts.join('\n');
108
+ if (!result.endsWith('\n')) {
109
+ result += '\n';
110
+ }
111
+ return result;
112
+ }
113
+ /**
114
+ * Parses the markdown body to extract title and content
115
+ *
116
+ * @param body - The markdown body (after front matter)
117
+ * @returns Title and content
118
+ */
119
+ function parseMarkdownBody(body) {
120
+ const trimmed = body.trim();
121
+ if (!trimmed) {
122
+ return { title: '', content: '' };
123
+ }
124
+ // Try to extract H1 title
125
+ const h1Match = trimmed.match(/^#\s+(.+)$/m);
126
+ if (h1Match) {
127
+ const title = h1Match[1].trim();
128
+ // Remove the H1 line from content
129
+ const content = trimmed.replace(/^#\s+.+$/m, '').trim();
130
+ return { title, content };
131
+ }
132
+ // No H1 found, treat entire body as content
133
+ return { title: '', content: trimmed };
134
+ }
135
+ /**
136
+ * Type guard to validate front matter structure
137
+ *
138
+ * @param value - The value to check
139
+ * @returns True if valid front matter
140
+ */
141
+ function isValidFrontMatter(value) {
142
+ if (typeof value !== 'object' || value === null) {
143
+ return false;
144
+ }
145
+ const fm = value;
146
+ // Check required fields
147
+ if (typeof fm.id !== 'string')
148
+ return false;
149
+ if (typeof fm.created_at !== 'string')
150
+ return false;
151
+ if (typeof fm.updated_at !== 'string')
152
+ return false;
153
+ if (fm.session_id !== undefined && typeof fm.session_id !== 'string')
154
+ return false;
155
+ if (fm.entry_type !== undefined && typeof fm.entry_type !== 'string')
156
+ return false;
157
+ if (!Array.isArray(fm.tags))
158
+ return false;
159
+ if (typeof fm.category !== 'string')
160
+ return false;
161
+ if (typeof fm.importance !== 'number')
162
+ return false;
163
+ return true;
164
+ }
165
+ /**
166
+ * Converts a Memory object to front matter format
167
+ *
168
+ * @param memory - The memory object
169
+ * @returns Memory in front matter format
170
+ */
171
+ export function memoryToFrontMatter(memory) {
172
+ return {
173
+ id: memory.id,
174
+ created_at: memory.createdAt,
175
+ updated_at: memory.updatedAt,
176
+ session_id: memory.sessionId,
177
+ entry_type: memory.entryType,
178
+ tags: memory.tags,
179
+ category: memory.category,
180
+ importance: memory.importance,
181
+ };
182
+ }
183
+ /**
184
+ * Converts front matter format to Memory object
185
+ *
186
+ * @param frontMatter - The front matter data
187
+ * @param title - The memory title
188
+ * @param content - The memory content
189
+ * @returns Complete Memory object
190
+ */
191
+ export function frontMatterToMemory(frontMatter, title, content) {
192
+ return {
193
+ id: frontMatter.id,
194
+ createdAt: frontMatter.created_at,
195
+ updatedAt: frontMatter.updated_at,
196
+ sessionId: frontMatter.session_id,
197
+ entryType: frontMatter.entry_type,
198
+ tags: frontMatter.tags,
199
+ category: frontMatter.category,
200
+ importance: frontMatter.importance,
201
+ title,
202
+ content,
203
+ };
204
+ }
205
+ // Re-export slugify for convenience
206
+ export { slugify };
207
+ //# sourceMappingURL=frontmatter-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-parser.js","sourceRoot":"","sources":["../../../src/storage/frontmatter-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Storage layer exports
3
+ */
4
+ export * from './frontmatter-parser.js';
5
+ export * from './markdown-storage.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Storage layer exports
3
+ */
4
+ export * from './frontmatter-parser.js';
5
+ export * from './markdown-storage.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/storage/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Markdown Storage - Handles file system operations for memory storage
3
+ */
4
+ import type { Memory, MemoryFile } from '../contracts/types.js';
5
+ /**
6
+ * Custom error for storage operations
7
+ */
8
+ export declare class StorageError extends Error {
9
+ readonly cause?: unknown | undefined;
10
+ constructor(message: string, cause?: unknown | undefined);
11
+ }
12
+ /**
13
+ * Storage configuration
14
+ */
15
+ export interface StorageConfig {
16
+ /** Root directory for memory storage */
17
+ storagePath: string;
18
+ }
19
+ /**
20
+ * Markdown file storage implementation
21
+ */
22
+ export declare class MarkdownStorage {
23
+ private readonly storagePath;
24
+ constructor(config: StorageConfig);
25
+ /**
26
+ * Ensures the storage directory exists
27
+ */
28
+ initialize(): Promise<void>;
29
+ /**
30
+ * Writes a memory to a markdown file
31
+ *
32
+ * @param memory - The memory to write
33
+ * @returns The file path where the memory was stored
34
+ * @throws StorageError if write fails
35
+ */
36
+ write(memory: Memory): Promise<string>;
37
+ /**
38
+ * Reads a memory by its ID
39
+ *
40
+ * @param id - The memory ID
41
+ * @returns The memory object
42
+ * @throws StorageError if memory not found or read fails
43
+ */
44
+ read(id: string): Promise<Memory>;
45
+ /**
46
+ * Deletes a memory by its ID
47
+ *
48
+ * @param id - The memory ID
49
+ * @returns The file path of the deleted memory
50
+ * @throws StorageError if memory not found or delete fails
51
+ */
52
+ delete(id: string): Promise<string>;
53
+ /**
54
+ * Lists all memory files
55
+ *
56
+ * @returns Array of memory file information
57
+ * @throws StorageError if listing fails
58
+ */
59
+ list(): Promise<MemoryFile[]>;
60
+ /**
61
+ * Finds a memory file by ID
62
+ *
63
+ * @param id - The memory ID to find
64
+ * @returns The file path or null if not found
65
+ * @throws StorageError if search fails
66
+ */
67
+ findById(id: string): Promise<string | null>;
68
+ /**
69
+ * Generates nested path parts for a memory
70
+ *
71
+ * Format: {storage}/{YYYY-MM-DD}/{session_uuid}/{timestamp}-{slug}.md
72
+ */
73
+ private generatePathParts;
74
+ private collectMarkdownFiles;
75
+ }
76
+ //# sourceMappingURL=markdown-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-storage.d.ts","sourceRoot":"","sources":["../../../src/storage/markdown-storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAUhE;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;aAGnB,KAAK,CAAC,EAAE,OAAO;gBAD/B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,OAAO,YAAA;CAKlC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,EAAE,aAAa;IAIjC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC;;;;;;OAMG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB5C;;;;;;OAMG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBvC;;;;;;OAMG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAezC;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAyBnC;;;;;;OAMG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAyBlD;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YAWX,oBAAoB;CAgBnC"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Markdown Storage - Handles file system operations for memory storage
3
+ */
4
+ import { readFile, writeFile, unlink, readdir, stat, access, mkdir, } from 'fs/promises';
5
+ import { join, extname } from 'path';
6
+ import { constants } from 'fs';
7
+ import { parseFrontMatter, stringifyFrontMatter, memoryToFrontMatter, frontMatterToMemory, FrontMatterError, } from './frontmatter-parser.js';
8
+ import { slugify } from '../utils/slugify.js';
9
+ /**
10
+ * Custom error for storage operations
11
+ */
12
+ export class StorageError extends Error {
13
+ cause;
14
+ constructor(message, cause) {
15
+ super(message);
16
+ this.cause = cause;
17
+ this.name = 'StorageError';
18
+ }
19
+ }
20
+ /**
21
+ * Markdown file storage implementation
22
+ */
23
+ export class MarkdownStorage {
24
+ storagePath;
25
+ constructor(config) {
26
+ this.storagePath = config.storagePath;
27
+ }
28
+ /**
29
+ * Ensures the storage directory exists
30
+ */
31
+ async initialize() {
32
+ try {
33
+ await access(this.storagePath, constants.F_OK);
34
+ }
35
+ catch {
36
+ // Directory doesn't exist, create it
37
+ await mkdir(this.storagePath, { recursive: true });
38
+ }
39
+ }
40
+ /**
41
+ * Writes a memory to a markdown file
42
+ *
43
+ * @param memory - The memory to write
44
+ * @returns The file path where the memory was stored
45
+ * @throws StorageError if write fails
46
+ */
47
+ async write(memory) {
48
+ await this.initialize();
49
+ const { directoryPath, filename } = this.generatePathParts(memory);
50
+ const filePath = join(directoryPath, filename);
51
+ const frontMatter = memoryToFrontMatter(memory);
52
+ const content = stringifyFrontMatter(frontMatter, memory.title, memory.content);
53
+ try {
54
+ await mkdir(directoryPath, { recursive: true });
55
+ await writeFile(filePath, content, 'utf-8');
56
+ return filePath;
57
+ }
58
+ catch (error) {
59
+ throw new StorageError(`Failed to write memory file: ${filename}`, error);
60
+ }
61
+ }
62
+ /**
63
+ * Reads a memory by its ID
64
+ *
65
+ * @param id - The memory ID
66
+ * @returns The memory object
67
+ * @throws StorageError if memory not found or read fails
68
+ */
69
+ async read(id) {
70
+ const filePath = await this.findById(id);
71
+ if (!filePath) {
72
+ throw new StorageError(`Memory not found: ${id}`);
73
+ }
74
+ try {
75
+ const content = await readFile(filePath, 'utf-8');
76
+ const { frontMatter, title, content: bodyContent } = parseFrontMatter(content);
77
+ return frontMatterToMemory(frontMatter, title, bodyContent);
78
+ }
79
+ catch (error) {
80
+ if (error instanceof FrontMatterError) {
81
+ throw new StorageError(`Invalid memory file format: ${filePath}`, error);
82
+ }
83
+ throw new StorageError(`Failed to read memory file: ${filePath}`, error);
84
+ }
85
+ }
86
+ /**
87
+ * Deletes a memory by its ID
88
+ *
89
+ * @param id - The memory ID
90
+ * @returns The file path of the deleted memory
91
+ * @throws StorageError if memory not found or delete fails
92
+ */
93
+ async delete(id) {
94
+ const filePath = await this.findById(id);
95
+ if (!filePath) {
96
+ throw new StorageError(`Memory not found: ${id}`);
97
+ }
98
+ try {
99
+ await unlink(filePath);
100
+ return filePath;
101
+ }
102
+ catch (error) {
103
+ throw new StorageError(`Failed to delete memory file: ${filePath}`, error);
104
+ }
105
+ }
106
+ /**
107
+ * Lists all memory files
108
+ *
109
+ * @returns Array of memory file information
110
+ * @throws StorageError if listing fails
111
+ */
112
+ async list() {
113
+ await this.initialize();
114
+ try {
115
+ const markdownPaths = await this.collectMarkdownFiles(this.storagePath);
116
+ const files = [];
117
+ for (const filePath of markdownPaths) {
118
+ const stats = await stat(filePath);
119
+ const content = await readFile(filePath, 'utf-8');
120
+ files.push({
121
+ path: filePath,
122
+ filename: filePath.split(/[/\\]/).pop() ?? filePath,
123
+ content,
124
+ modifiedAt: stats.mtime.toISOString(),
125
+ });
126
+ }
127
+ return files;
128
+ }
129
+ catch (error) {
130
+ throw new StorageError('Failed to list memory files', error);
131
+ }
132
+ }
133
+ /**
134
+ * Finds a memory file by ID
135
+ *
136
+ * @param id - The memory ID to find
137
+ * @returns The file path or null if not found
138
+ * @throws StorageError if search fails
139
+ */
140
+ async findById(id) {
141
+ await this.initialize();
142
+ try {
143
+ const markdownPaths = await this.collectMarkdownFiles(this.storagePath);
144
+ for (const filePath of markdownPaths) {
145
+ try {
146
+ const content = await readFile(filePath, 'utf-8');
147
+ const { frontMatter } = parseFrontMatter(content);
148
+ if (frontMatter.id === id) {
149
+ return filePath;
150
+ }
151
+ }
152
+ catch {
153
+ // Skip files that can't be parsed
154
+ continue;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ catch (error) {
160
+ throw new StorageError('Failed to search for memory file', error);
161
+ }
162
+ }
163
+ /**
164
+ * Generates nested path parts for a memory
165
+ *
166
+ * Format: {storage}/{YYYY-MM-DD}/{session_uuid}/{timestamp}-{slug}.md
167
+ */
168
+ generatePathParts(memory) {
169
+ const date = memory.createdAt.split('T')[0];
170
+ const sessionId = memory.sessionId ?? 'default-session';
171
+ const titleSlug = slugify(memory.title) || 'untitled';
172
+ const timestamp = memory.createdAt.replace(/[:.]/g, '-');
173
+ const filename = `${timestamp}-${titleSlug}.md`;
174
+ const directoryPath = join(this.storagePath, date, sessionId);
175
+ return { directoryPath, filename };
176
+ }
177
+ async collectMarkdownFiles(rootDir) {
178
+ const entries = await readdir(rootDir, { withFileTypes: true });
179
+ const files = [];
180
+ for (const entry of entries) {
181
+ const entryPath = join(rootDir, entry.name);
182
+ if (entry.isDirectory()) {
183
+ const nested = await this.collectMarkdownFiles(entryPath);
184
+ files.push(...nested);
185
+ }
186
+ else if (entry.isFile() && extname(entry.name) === '.md') {
187
+ files.push(entryPath);
188
+ }
189
+ }
190
+ return files;
191
+ }
192
+ }
193
+ //# sourceMappingURL=markdown-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-storage.js","sourceRoot":"","sources":["../../../src/storage/markdown-storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Utility exports
3
+ */
4
+ export * from './slugify.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Utility exports
3
+ */
4
+ export * from './slugify.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Slugify utility - Converts strings to URL-friendly slugs
3
+ */
4
+ /**
5
+ * Converts a string to a URL-friendly slug
6
+ * - Converts to lowercase
7
+ * - Replaces spaces with hyphens
8
+ * - Removes special characters
9
+ * - Collapses multiple hyphens
10
+ * - Trims leading/trailing hyphens
11
+ *
12
+ * @param input - The string to convert
13
+ * @returns The slugified string
14
+ */
15
+ export declare function slugify(input: string): string;
16
+ /**
17
+ * Generates a unique slug by appending a timestamp or counter if needed
18
+ *
19
+ * @param title - The title to slugify
20
+ * @param existingSlugs - Array of existing slugs to check against
21
+ * @returns A unique slug
22
+ */
23
+ export declare function generateUniqueSlug(title: string, existingSlugs?: readonly string[]): string;
24
+ //# sourceMappingURL=slugify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.d.ts","sourceRoot":"","sources":["../../../src/utils/slugify.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAwB7C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAc/F"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Slugify utility - Converts strings to URL-friendly slugs
3
+ */
4
+ /**
5
+ * Converts a string to a URL-friendly slug
6
+ * - Converts to lowercase
7
+ * - Replaces spaces with hyphens
8
+ * - Removes special characters
9
+ * - Collapses multiple hyphens
10
+ * - Trims leading/trailing hyphens
11
+ *
12
+ * @param input - The string to convert
13
+ * @returns The slugified string
14
+ */
15
+ export function slugify(input) {
16
+ if (!input || input.trim().length === 0) {
17
+ return 'untitled';
18
+ }
19
+ // Convert to lowercase and replace non-alphanumeric characters with hyphens
20
+ const slug = input
21
+ .toLowerCase()
22
+ .replace(/[^a-z0-9\s-]/g, '') // Remove special characters
23
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
24
+ .replace(/-+/g, '-') // Collapse multiple hyphens
25
+ .replace(/^-+|-+$/g, ''); // Trim leading/trailing hyphens
26
+ // If empty after cleaning (e.g., only special characters), return 'untitled'
27
+ if (slug.length === 0) {
28
+ return 'untitled';
29
+ }
30
+ // Truncate to max 100 characters
31
+ if (slug.length > 100) {
32
+ return slug.substring(0, 100).replace(/-+$/, ''); // Don't end with hyphen
33
+ }
34
+ return slug;
35
+ }
36
+ /**
37
+ * Generates a unique slug by appending a timestamp or counter if needed
38
+ *
39
+ * @param title - The title to slugify
40
+ * @param existingSlugs - Array of existing slugs to check against
41
+ * @returns A unique slug
42
+ */
43
+ export function generateUniqueSlug(title, existingSlugs = []) {
44
+ const slug = slugify(title);
45
+ let counter = 1;
46
+ let uniqueSlug = slug;
47
+ while (existingSlugs.includes(uniqueSlug)) {
48
+ const suffix = `-${counter}`;
49
+ const maxBaseLength = 100 - suffix.length;
50
+ const baseSlug = slug.substring(0, maxBaseLength).replace(/-+$/, '');
51
+ uniqueSlug = `${baseSlug}${suffix}`;
52
+ counter++;
53
+ }
54
+ return uniqueSlug;
55
+ }
56
+ //# sourceMappingURL=slugify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.js","sourceRoot":"","sources":["../../../src/utils/slugify.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,4EAA4E;IAC5E,MAAM,IAAI,GAAG,KAAK;SACf,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,4BAA4B;SACzD,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,8BAA8B;SACnD,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,4BAA4B;SAChD,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,gCAAgC;IAE5D,6EAA6E;IAC7E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,iCAAiC;IACjC,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,wBAAwB;IAC5E,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,gBAAmC,EAAE;IACrF,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,UAAU,GAAG,IAAI,CAAC;IAEtB,OAAO,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,aAAa,GAAG,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACrE,UAAU,GAAG,GAAG,QAAQ,GAAG,MAAM,EAAE,CAAC;QACpC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC"}