@synth-coder/memhub 0.2.3 → 0.2.5

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 (118) hide show
  1. package/.github/workflows/ci.yml +48 -12
  2. package/.github/workflows/release.yml +70 -0
  3. package/AGENTS.md +71 -73
  4. package/README.md +284 -195
  5. package/README.zh-CN.md +90 -30
  6. package/dist/src/cli/agents/claude-code.d.ts +5 -0
  7. package/dist/src/cli/agents/claude-code.d.ts.map +1 -0
  8. package/dist/src/cli/agents/claude-code.js +14 -0
  9. package/dist/src/cli/agents/claude-code.js.map +1 -0
  10. package/dist/src/cli/agents/cline.d.ts +5 -0
  11. package/dist/src/cli/agents/cline.d.ts.map +1 -0
  12. package/dist/src/cli/agents/cline.js +14 -0
  13. package/dist/src/cli/agents/cline.js.map +1 -0
  14. package/dist/src/cli/agents/cursor.d.ts +5 -0
  15. package/dist/src/cli/agents/cursor.d.ts.map +1 -0
  16. package/dist/src/cli/agents/cursor.js +14 -0
  17. package/dist/src/cli/agents/cursor.js.map +1 -0
  18. package/dist/src/cli/agents/factory-droid.d.ts +5 -0
  19. package/dist/src/cli/agents/factory-droid.d.ts.map +1 -0
  20. package/dist/src/cli/agents/factory-droid.js +14 -0
  21. package/dist/src/cli/agents/factory-droid.js.map +1 -0
  22. package/dist/src/cli/agents/gemini-cli.d.ts +5 -0
  23. package/dist/src/cli/agents/gemini-cli.d.ts.map +1 -0
  24. package/dist/src/cli/agents/gemini-cli.js +14 -0
  25. package/dist/src/cli/agents/gemini-cli.js.map +1 -0
  26. package/dist/src/cli/agents/index.d.ts +13 -0
  27. package/dist/src/cli/agents/index.d.ts.map +1 -0
  28. package/dist/src/cli/agents/index.js +27 -0
  29. package/dist/src/cli/agents/index.js.map +1 -0
  30. package/dist/src/cli/agents/windsurf.d.ts +5 -0
  31. package/dist/src/cli/agents/windsurf.d.ts.map +1 -0
  32. package/dist/src/cli/agents/windsurf.js +14 -0
  33. package/dist/src/cli/agents/windsurf.js.map +1 -0
  34. package/dist/src/cli/index.d.ts +8 -0
  35. package/dist/src/cli/index.d.ts.map +1 -0
  36. package/dist/src/cli/index.js +168 -0
  37. package/dist/src/cli/index.js.map +1 -0
  38. package/dist/src/cli/init.d.ts +34 -0
  39. package/dist/src/cli/init.d.ts.map +1 -0
  40. package/dist/src/cli/init.js +140 -0
  41. package/dist/src/cli/init.js.map +1 -0
  42. package/dist/src/cli/instructions.d.ts +29 -0
  43. package/dist/src/cli/instructions.d.ts.map +1 -0
  44. package/dist/src/cli/instructions.js +141 -0
  45. package/dist/src/cli/instructions.js.map +1 -0
  46. package/dist/src/cli/types.d.ts +22 -0
  47. package/dist/src/cli/types.d.ts.map +1 -0
  48. package/dist/src/cli/types.js +75 -0
  49. package/dist/src/cli/types.js.map +1 -0
  50. package/dist/src/contracts/mcp.js +34 -34
  51. package/dist/src/contracts/schemas.js.map +1 -1
  52. package/dist/src/server/mcp-server.d.ts.map +1 -1
  53. package/dist/src/server/mcp-server.js +7 -14
  54. package/dist/src/server/mcp-server.js.map +1 -1
  55. package/dist/src/services/embedding-service.d.ts.map +1 -1
  56. package/dist/src/services/embedding-service.js +1 -1
  57. package/dist/src/services/embedding-service.js.map +1 -1
  58. package/dist/src/services/memory-service.d.ts.map +1 -1
  59. package/dist/src/services/memory-service.js.map +1 -1
  60. package/dist/src/storage/markdown-storage.d.ts.map +1 -1
  61. package/dist/src/storage/markdown-storage.js +1 -1
  62. package/dist/src/storage/markdown-storage.js.map +1 -1
  63. package/dist/src/storage/vector-index.d.ts.map +1 -1
  64. package/dist/src/storage/vector-index.js +4 -5
  65. package/dist/src/storage/vector-index.js.map +1 -1
  66. package/docs/README.md +21 -0
  67. package/docs/mcp-tools.md +136 -0
  68. package/docs/user-guide.md +184 -0
  69. package/package.json +61 -59
  70. package/src/cli/agents/claude-code.ts +14 -0
  71. package/src/cli/agents/cline.ts +14 -0
  72. package/src/cli/agents/codex.ts +14 -0
  73. package/src/cli/agents/cursor.ts +14 -0
  74. package/src/cli/agents/factory-droid.ts +14 -0
  75. package/src/cli/agents/gemini-cli.ts +14 -0
  76. package/src/cli/agents/index.ts +36 -0
  77. package/src/cli/agents/windsurf.ts +14 -0
  78. package/src/cli/index.ts +192 -0
  79. package/src/cli/init.ts +218 -0
  80. package/src/cli/instructions.ts +156 -0
  81. package/src/cli/types.ts +112 -0
  82. package/src/contracts/index.ts +12 -12
  83. package/src/contracts/mcp.ts +223 -223
  84. package/src/contracts/schemas.ts +307 -307
  85. package/src/contracts/types.ts +410 -410
  86. package/src/index.ts +8 -8
  87. package/src/server/index.ts +5 -5
  88. package/src/server/mcp-server.ts +169 -186
  89. package/src/services/embedding-service.ts +114 -114
  90. package/src/services/index.ts +5 -5
  91. package/src/services/memory-service.ts +656 -663
  92. package/src/storage/frontmatter-parser.ts +243 -243
  93. package/src/storage/index.ts +6 -6
  94. package/src/storage/markdown-storage.ts +228 -236
  95. package/src/storage/vector-index.ts +159 -160
  96. package/src/utils/index.ts +5 -5
  97. package/src/utils/slugify.ts +63 -63
  98. package/test/cli/init.test.ts +402 -0
  99. package/test/contracts/schemas.test.ts +313 -313
  100. package/test/contracts/types.test.ts +21 -21
  101. package/test/frontmatter-parser-more.test.ts +94 -94
  102. package/test/server/mcp-server.test.ts +211 -210
  103. package/test/services/memory-service-edge.test.ts +248 -248
  104. package/test/services/memory-service.test.ts +291 -279
  105. package/test/storage/frontmatter-parser.test.ts +223 -223
  106. package/test/storage/markdown-storage.test.ts +226 -217
  107. package/test/storage/storage-edge.test.ts +238 -238
  108. package/test/storage/vector-index.test.ts +149 -153
  109. package/test/utils/slugify-edge.test.ts +94 -94
  110. package/test/utils/slugify.test.ts +72 -68
  111. package/docs/architecture-diagrams.md +0 -368
  112. package/docs/architecture.md +0 -381
  113. package/docs/contracts.md +0 -190
  114. package/docs/prompt-template.md +0 -33
  115. package/docs/proposals/mcp-typescript-sdk-refactor.md +0 -568
  116. package/docs/proposals/proposal-close-gates.md +0 -58
  117. package/docs/tool-calling-policy.md +0 -101
  118. package/docs/vector-search.md +0 -306
@@ -1,243 +1,243 @@
1
- /**
2
- * FrontMatter Parser - Handles YAML Front Matter and Markdown content
3
- */
4
-
5
- import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
6
- import type { Memory, MemoryFrontMatter } from '../contracts/types.js';
7
- import { slugify } from '../utils/slugify.js';
8
-
9
- /**
10
- * Result of parsing a markdown file
11
- */
12
- export interface ParseResult {
13
- frontMatter: MemoryFrontMatter;
14
- title: string;
15
- content: string;
16
- }
17
-
18
- /**
19
- * Custom error for front matter parsing
20
- */
21
- export class FrontMatterError extends Error {
22
- constructor(
23
- message: string,
24
- public readonly cause?: unknown
25
- ) {
26
- super(message);
27
- this.name = 'FrontMatterError';
28
- }
29
- }
30
-
31
- /**
32
- * Parses a markdown file with YAML front matter
33
- *
34
- * Expected format:
35
- * ---
36
- * id: "uuid"
37
- * created_at: "ISO8601"
38
- * updated_at: "ISO8601"
39
- * tags: ["tag1", "tag2"]
40
- * category: "category"
41
- * importance: 3
42
- * ---
43
- *
44
- * # Title
45
- *
46
- * Content...
47
- *
48
- * @param markdown - The markdown content to parse
49
- * @returns Parsed front matter, title, and content
50
- * @throws FrontMatterError if parsing fails
51
- */
52
- export function parseFrontMatter(markdown: string): ParseResult {
53
- // Check for front matter delimiter
54
- if (!markdown.startsWith('---')) {
55
- throw new FrontMatterError('Missing front matter delimiter');
56
- }
57
-
58
- // Find the end of front matter
59
- const endMatch = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
60
- if (!endMatch) {
61
- throw new FrontMatterError('Invalid front matter format');
62
- }
63
-
64
- const frontMatterYaml = endMatch[1];
65
- const restOfContent = markdown.slice(endMatch[0].length);
66
-
67
- // Parse YAML front matter
68
- let frontMatter: unknown;
69
- try {
70
- frontMatter = parseYaml(frontMatterYaml);
71
- } catch (error) {
72
- throw new FrontMatterError('Invalid YAML in front matter', error);
73
- }
74
-
75
- // Validate required fields
76
- if (!isValidFrontMatter(frontMatter)) {
77
- throw new FrontMatterError('Missing required fields in front matter');
78
- }
79
-
80
- // Parse title and content from markdown body
81
- const { title, content } = parseMarkdownBody(restOfContent);
82
-
83
- return {
84
- frontMatter,
85
- title,
86
- content,
87
- };
88
- }
89
-
90
- /**
91
- * Converts front matter and content to markdown string
92
- *
93
- * @param frontMatter - The front matter data
94
- * @param title - The title (H1 heading)
95
- * @param content - The markdown content
96
- * @returns Complete markdown string
97
- */
98
- export function stringifyFrontMatter(
99
- frontMatter: MemoryFrontMatter,
100
- title: string,
101
- content: string
102
- ): string {
103
- // Convert camelCase to snake_case for YAML
104
- const yamlData = {
105
- id: frontMatter.id,
106
- created_at: frontMatter.created_at,
107
- updated_at: frontMatter.updated_at,
108
- ...(frontMatter.session_id ? { session_id: frontMatter.session_id } : {}),
109
- ...(frontMatter.entry_type ? { entry_type: frontMatter.entry_type } : {}),
110
- tags: frontMatter.tags.length > 0 ? frontMatter.tags : [],
111
- category: frontMatter.category,
112
- importance: frontMatter.importance,
113
- };
114
-
115
- // Stringify YAML with specific options for consistent formatting
116
- const yamlString = stringifyYaml(yamlData, {
117
- indent: 2,
118
- defaultKeyType: 'PLAIN',
119
- defaultStringType: 'QUOTE_DOUBLE',
120
- });
121
-
122
- // Build the complete markdown
123
- const parts: string[] = ['---', yamlString.trim(), '---', ''];
124
-
125
- // Add title if provided
126
- if (title) {
127
- parts.push(`# ${title}`, '');
128
- }
129
-
130
- // Add content if provided
131
- if (content) {
132
- parts.push(content);
133
- }
134
-
135
- // Ensure content ends with a single newline
136
- let result = parts.join('\n');
137
- if (!result.endsWith('\n')) {
138
- result += '\n';
139
- }
140
-
141
- return result;
142
- }
143
-
144
- /**
145
- * Parses the markdown body to extract title and content
146
- *
147
- * @param body - The markdown body (after front matter)
148
- * @returns Title and content
149
- */
150
- function parseMarkdownBody(body: string): { title: string; content: string } {
151
- const trimmed = body.trim();
152
-
153
- if (!trimmed) {
154
- return { title: '', content: '' };
155
- }
156
-
157
- // Try to extract H1 title
158
- const h1Match = trimmed.match(/^#\s+(.+)$/m);
159
- if (h1Match) {
160
- const title = h1Match[1].trim();
161
- // Remove the H1 line from content
162
- const content = trimmed.replace(/^#\s+.+$/m, '').trim();
163
- return { title, content };
164
- }
165
-
166
- // No H1 found, treat entire body as content
167
- return { title: '', content: trimmed };
168
- }
169
-
170
- /**
171
- * Type guard to validate front matter structure
172
- *
173
- * @param value - The value to check
174
- * @returns True if valid front matter
175
- */
176
- function isValidFrontMatter(value: unknown): value is MemoryFrontMatter {
177
- if (typeof value !== 'object' || value === null) {
178
- return false;
179
- }
180
-
181
- const fm = value as Record<string, unknown>;
182
-
183
- // Check required fields
184
- if (typeof fm.id !== 'string') return false;
185
- if (typeof fm.created_at !== 'string') return false;
186
- if (typeof fm.updated_at !== 'string') return false;
187
- if (fm.session_id !== undefined && typeof fm.session_id !== 'string') return false;
188
- if (fm.entry_type !== undefined && typeof fm.entry_type !== 'string') return false;
189
- if (!Array.isArray(fm.tags)) return false;
190
- if (typeof fm.category !== 'string') return false;
191
- if (typeof fm.importance !== 'number') return false;
192
-
193
- return true;
194
- }
195
-
196
- /**
197
- * Converts a Memory object to front matter format
198
- *
199
- * @param memory - The memory object
200
- * @returns Memory in front matter format
201
- */
202
- export function memoryToFrontMatter(memory: Memory): MemoryFrontMatter {
203
- return {
204
- id: memory.id,
205
- created_at: memory.createdAt,
206
- updated_at: memory.updatedAt,
207
- session_id: memory.sessionId,
208
- entry_type: memory.entryType,
209
- tags: memory.tags,
210
- category: memory.category,
211
- importance: memory.importance,
212
- };
213
- }
214
-
215
- /**
216
- * Converts front matter format to Memory object
217
- *
218
- * @param frontMatter - The front matter data
219
- * @param title - The memory title
220
- * @param content - The memory content
221
- * @returns Complete Memory object
222
- */
223
- export function frontMatterToMemory(
224
- frontMatter: MemoryFrontMatter,
225
- title: string,
226
- content: string
227
- ): Memory {
228
- return {
229
- id: frontMatter.id,
230
- createdAt: frontMatter.created_at,
231
- updatedAt: frontMatter.updated_at,
232
- sessionId: frontMatter.session_id,
233
- entryType: frontMatter.entry_type,
234
- tags: frontMatter.tags,
235
- category: frontMatter.category,
236
- importance: frontMatter.importance,
237
- title,
238
- content,
239
- };
240
- }
241
-
242
- // Re-export slugify for convenience
243
- export { slugify };
1
+ /**
2
+ * FrontMatter Parser - Handles YAML Front Matter and Markdown content
3
+ */
4
+
5
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
6
+ import type { Memory, MemoryFrontMatter } from '../contracts/types.js';
7
+ import { slugify } from '../utils/slugify.js';
8
+
9
+ /**
10
+ * Result of parsing a markdown file
11
+ */
12
+ export interface ParseResult {
13
+ frontMatter: MemoryFrontMatter;
14
+ title: string;
15
+ content: string;
16
+ }
17
+
18
+ /**
19
+ * Custom error for front matter parsing
20
+ */
21
+ export class FrontMatterError extends Error {
22
+ constructor(
23
+ message: string,
24
+ public readonly cause?: unknown
25
+ ) {
26
+ super(message);
27
+ this.name = 'FrontMatterError';
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Parses a markdown file with YAML front matter
33
+ *
34
+ * Expected format:
35
+ * ---
36
+ * id: "uuid"
37
+ * created_at: "ISO8601"
38
+ * updated_at: "ISO8601"
39
+ * tags: ["tag1", "tag2"]
40
+ * category: "category"
41
+ * importance: 3
42
+ * ---
43
+ *
44
+ * # Title
45
+ *
46
+ * Content...
47
+ *
48
+ * @param markdown - The markdown content to parse
49
+ * @returns Parsed front matter, title, and content
50
+ * @throws FrontMatterError if parsing fails
51
+ */
52
+ export function parseFrontMatter(markdown: string): ParseResult {
53
+ // Check for front matter delimiter
54
+ if (!markdown.startsWith('---')) {
55
+ throw new FrontMatterError('Missing front matter delimiter');
56
+ }
57
+
58
+ // Find the end of front matter
59
+ const endMatch = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
60
+ if (!endMatch) {
61
+ throw new FrontMatterError('Invalid front matter format');
62
+ }
63
+
64
+ const frontMatterYaml = endMatch[1];
65
+ const restOfContent = markdown.slice(endMatch[0].length);
66
+
67
+ // Parse YAML front matter
68
+ let frontMatter: unknown;
69
+ try {
70
+ frontMatter = parseYaml(frontMatterYaml);
71
+ } catch (error) {
72
+ throw new FrontMatterError('Invalid YAML in front matter', error);
73
+ }
74
+
75
+ // Validate required fields
76
+ if (!isValidFrontMatter(frontMatter)) {
77
+ throw new FrontMatterError('Missing required fields in front matter');
78
+ }
79
+
80
+ // Parse title and content from markdown body
81
+ const { title, content } = parseMarkdownBody(restOfContent);
82
+
83
+ return {
84
+ frontMatter,
85
+ title,
86
+ content,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Converts front matter and content to markdown string
92
+ *
93
+ * @param frontMatter - The front matter data
94
+ * @param title - The title (H1 heading)
95
+ * @param content - The markdown content
96
+ * @returns Complete markdown string
97
+ */
98
+ export function stringifyFrontMatter(
99
+ frontMatter: MemoryFrontMatter,
100
+ title: string,
101
+ content: string
102
+ ): string {
103
+ // Convert camelCase to snake_case for YAML
104
+ const yamlData = {
105
+ id: frontMatter.id,
106
+ created_at: frontMatter.created_at,
107
+ updated_at: frontMatter.updated_at,
108
+ ...(frontMatter.session_id ? { session_id: frontMatter.session_id } : {}),
109
+ ...(frontMatter.entry_type ? { entry_type: frontMatter.entry_type } : {}),
110
+ tags: frontMatter.tags.length > 0 ? frontMatter.tags : [],
111
+ category: frontMatter.category,
112
+ importance: frontMatter.importance,
113
+ };
114
+
115
+ // Stringify YAML with specific options for consistent formatting
116
+ const yamlString = stringifyYaml(yamlData, {
117
+ indent: 2,
118
+ defaultKeyType: 'PLAIN',
119
+ defaultStringType: 'QUOTE_DOUBLE',
120
+ });
121
+
122
+ // Build the complete markdown
123
+ const parts: string[] = ['---', yamlString.trim(), '---', ''];
124
+
125
+ // Add title if provided
126
+ if (title) {
127
+ parts.push(`# ${title}`, '');
128
+ }
129
+
130
+ // Add content if provided
131
+ if (content) {
132
+ parts.push(content);
133
+ }
134
+
135
+ // Ensure content ends with a single newline
136
+ let result = parts.join('\n');
137
+ if (!result.endsWith('\n')) {
138
+ result += '\n';
139
+ }
140
+
141
+ return result;
142
+ }
143
+
144
+ /**
145
+ * Parses the markdown body to extract title and content
146
+ *
147
+ * @param body - The markdown body (after front matter)
148
+ * @returns Title and content
149
+ */
150
+ function parseMarkdownBody(body: string): { title: string; content: string } {
151
+ const trimmed = body.trim();
152
+
153
+ if (!trimmed) {
154
+ return { title: '', content: '' };
155
+ }
156
+
157
+ // Try to extract H1 title
158
+ const h1Match = trimmed.match(/^#\s+(.+)$/m);
159
+ if (h1Match) {
160
+ const title = h1Match[1].trim();
161
+ // Remove the H1 line from content
162
+ const content = trimmed.replace(/^#\s+.+$/m, '').trim();
163
+ return { title, content };
164
+ }
165
+
166
+ // No H1 found, treat entire body as content
167
+ return { title: '', content: trimmed };
168
+ }
169
+
170
+ /**
171
+ * Type guard to validate front matter structure
172
+ *
173
+ * @param value - The value to check
174
+ * @returns True if valid front matter
175
+ */
176
+ function isValidFrontMatter(value: unknown): value is MemoryFrontMatter {
177
+ if (typeof value !== 'object' || value === null) {
178
+ return false;
179
+ }
180
+
181
+ const fm = value as Record<string, unknown>;
182
+
183
+ // Check required fields
184
+ if (typeof fm.id !== 'string') return false;
185
+ if (typeof fm.created_at !== 'string') return false;
186
+ if (typeof fm.updated_at !== 'string') return false;
187
+ if (fm.session_id !== undefined && typeof fm.session_id !== 'string') return false;
188
+ if (fm.entry_type !== undefined && typeof fm.entry_type !== 'string') return false;
189
+ if (!Array.isArray(fm.tags)) return false;
190
+ if (typeof fm.category !== 'string') return false;
191
+ if (typeof fm.importance !== 'number') return false;
192
+
193
+ return true;
194
+ }
195
+
196
+ /**
197
+ * Converts a Memory object to front matter format
198
+ *
199
+ * @param memory - The memory object
200
+ * @returns Memory in front matter format
201
+ */
202
+ export function memoryToFrontMatter(memory: Memory): MemoryFrontMatter {
203
+ return {
204
+ id: memory.id,
205
+ created_at: memory.createdAt,
206
+ updated_at: memory.updatedAt,
207
+ session_id: memory.sessionId,
208
+ entry_type: memory.entryType,
209
+ tags: memory.tags,
210
+ category: memory.category,
211
+ importance: memory.importance,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Converts front matter format to Memory object
217
+ *
218
+ * @param frontMatter - The front matter data
219
+ * @param title - The memory title
220
+ * @param content - The memory content
221
+ * @returns Complete Memory object
222
+ */
223
+ export function frontMatterToMemory(
224
+ frontMatter: MemoryFrontMatter,
225
+ title: string,
226
+ content: string
227
+ ): Memory {
228
+ return {
229
+ id: frontMatter.id,
230
+ createdAt: frontMatter.created_at,
231
+ updatedAt: frontMatter.updated_at,
232
+ sessionId: frontMatter.session_id,
233
+ entryType: frontMatter.entry_type,
234
+ tags: frontMatter.tags,
235
+ category: frontMatter.category,
236
+ importance: frontMatter.importance,
237
+ title,
238
+ content,
239
+ };
240
+ }
241
+
242
+ // Re-export slugify for convenience
243
+ export { slugify };
@@ -1,6 +1,6 @@
1
- /**
2
- * Storage layer exports
3
- */
4
-
5
- export * from './frontmatter-parser.js';
6
- export * from './markdown-storage.js';
1
+ /**
2
+ * Storage layer exports
3
+ */
4
+
5
+ export * from './frontmatter-parser.js';
6
+ export * from './markdown-storage.js';