@synth-coder/memhub 0.2.2 → 0.2.3

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 (71) hide show
  1. package/.eslintrc.cjs +45 -45
  2. package/.factory/commands/opsx-apply.md +150 -0
  3. package/.factory/commands/opsx-archive.md +155 -0
  4. package/.factory/commands/opsx-explore.md +171 -0
  5. package/.factory/commands/opsx-propose.md +104 -0
  6. package/.factory/skills/openspec-apply-change/SKILL.md +156 -0
  7. package/.factory/skills/openspec-archive-change/SKILL.md +114 -0
  8. package/.factory/skills/openspec-explore/SKILL.md +288 -0
  9. package/.factory/skills/openspec-propose/SKILL.md +110 -0
  10. package/.github/workflows/ci.yml +74 -74
  11. package/.iflow/commands/opsx-apply.md +152 -152
  12. package/.iflow/commands/opsx-archive.md +157 -157
  13. package/.iflow/commands/opsx-explore.md +173 -173
  14. package/.iflow/commands/opsx-propose.md +106 -106
  15. package/.iflow/skills/openspec-apply-change/SKILL.md +156 -156
  16. package/.iflow/skills/openspec-archive-change/SKILL.md +114 -114
  17. package/.iflow/skills/openspec-explore/SKILL.md +288 -288
  18. package/.iflow/skills/openspec-propose/SKILL.md +110 -110
  19. package/.prettierrc +11 -11
  20. package/AGENTS.md +169 -26
  21. package/README.md +195 -195
  22. package/README.zh-CN.md +193 -193
  23. package/dist/src/contracts/mcp.js +34 -34
  24. package/dist/src/server/mcp-server.d.ts +8 -0
  25. package/dist/src/server/mcp-server.d.ts.map +1 -1
  26. package/dist/src/server/mcp-server.js +23 -2
  27. package/dist/src/server/mcp-server.js.map +1 -1
  28. package/dist/src/services/memory-service.d.ts +1 -0
  29. package/dist/src/services/memory-service.d.ts.map +1 -1
  30. package/dist/src/services/memory-service.js +125 -82
  31. package/dist/src/services/memory-service.js.map +1 -1
  32. package/docs/architecture-diagrams.md +368 -0
  33. package/docs/architecture.md +381 -349
  34. package/docs/contracts.md +190 -119
  35. package/docs/prompt-template.md +33 -79
  36. package/docs/proposals/mcp-typescript-sdk-refactor.md +568 -568
  37. package/docs/proposals/proposal-close-gates.md +58 -58
  38. package/docs/tool-calling-policy.md +101 -107
  39. package/docs/vector-search.md +306 -0
  40. package/package.json +59 -58
  41. package/src/contracts/index.ts +12 -12
  42. package/src/contracts/mcp.ts +222 -222
  43. package/src/contracts/schemas.ts +307 -307
  44. package/src/contracts/types.ts +410 -410
  45. package/src/index.ts +8 -8
  46. package/src/server/index.ts +5 -5
  47. package/src/server/mcp-server.ts +185 -161
  48. package/src/services/embedding-service.ts +114 -114
  49. package/src/services/index.ts +5 -5
  50. package/src/services/memory-service.ts +663 -621
  51. package/src/storage/frontmatter-parser.ts +243 -243
  52. package/src/storage/index.ts +6 -6
  53. package/src/storage/markdown-storage.ts +236 -236
  54. package/src/storage/vector-index.ts +160 -160
  55. package/src/utils/index.ts +5 -5
  56. package/src/utils/slugify.ts +63 -63
  57. package/test/contracts/schemas.test.ts +313 -313
  58. package/test/contracts/types.test.ts +21 -21
  59. package/test/frontmatter-parser-more.test.ts +94 -94
  60. package/test/server/mcp-server.test.ts +210 -169
  61. package/test/services/memory-service-edge.test.ts +248 -248
  62. package/test/services/memory-service.test.ts +278 -278
  63. package/test/storage/frontmatter-parser.test.ts +222 -222
  64. package/test/storage/markdown-storage.test.ts +216 -216
  65. package/test/storage/storage-edge.test.ts +238 -238
  66. package/test/storage/vector-index.test.ts +153 -153
  67. package/test/utils/slugify-edge.test.ts +94 -94
  68. package/test/utils/slugify.test.ts +68 -68
  69. package/tsconfig.json +25 -25
  70. package/tsconfig.test.json +8 -8
  71. package/vitest.config.ts +29 -29
@@ -1,223 +1,223 @@
1
- /**
2
- * FrontMatter Parser Tests
3
- * Tests for the FrontMatter parser
4
- */
5
-
6
- import { describe, it, expect } from 'vitest';
7
- import {
8
- parseFrontMatter,
9
- stringifyFrontMatter,
10
- memoryToFrontMatter,
11
- frontMatterToMemory,
12
- FrontMatterError,
13
- } from '../../src/storage/frontmatter-parser.js';
14
- import type { Memory, MemoryFrontMatter } from '../../src/contracts/types.js';
15
-
16
- describe('parseFrontMatter', () => {
17
- it('should parse valid front matter and content', () => {
18
- const markdown = `---
19
- id: "550e8400-e29b-41d4-a716-446655440000"
20
- created_at: "2024-03-15T10:30:00Z"
21
- updated_at: "2024-03-15T14:20:00Z"
22
- tags:
23
- - project
24
- - meeting
25
- category: "work"
26
- importance: 4
27
- ---
28
-
29
- # Project Meeting
30
-
31
- This is the meeting content.
32
-
33
- ## Action Items
34
-
35
- - Item 1
36
- - Item 2
37
- `;
38
-
39
- const result = parseFrontMatter(markdown);
40
- expect(result.frontMatter.id).toBe('550e8400-e29b-41d4-a716-446655440000');
41
- expect(result.frontMatter.category).toBe('work');
42
- expect(result.frontMatter.importance).toBe(4);
43
- expect(result.title).toBe('Project Meeting');
44
- expect(result.content).toContain('Action Items');
45
- });
46
-
47
- it('should parse front matter with empty tags', () => {
48
- const markdown = `---
49
- id: "550e8400-e29b-41d4-a716-446655440000"
50
- created_at: "2024-03-15T10:30:00Z"
51
- updated_at: "2024-03-15T10:30:00Z"
52
- tags: []
53
- category: "general"
54
- importance: 3
55
- ---
56
-
57
- # Empty Tags Test
58
-
59
- Content here.
60
- `;
61
-
62
- const result = parseFrontMatter(markdown);
63
- expect(result.frontMatter.tags).toEqual([]);
64
- });
65
-
66
- it('should throw error for missing front matter', () => {
67
- const markdown = `# No Front Matter
68
-
69
- This markdown has no front matter.
70
- `;
71
-
72
- expect(() => parseFrontMatter(markdown)).toThrow(FrontMatterError);
73
- expect(() => parseFrontMatter(markdown)).toThrow('Missing front matter delimiter');
74
- });
75
-
76
- it('should throw error for invalid YAML', () => {
77
- const markdown = `---
78
- id: "550e8400-e29b-41d4-a716-446655440000"
79
- created_at: [invalid yaml
80
- ---
81
-
82
- # Title
83
-
84
- Content.
85
- `;
86
-
87
- expect(() => parseFrontMatter(markdown)).toThrow(FrontMatterError);
88
- });
89
-
90
- it('should throw error for missing required fields', () => {
91
- const markdown = `---
92
- id: "550e8400-e29b-41d4-a716-446655440000"
93
- ---
94
-
95
- # Title
96
-
97
- Content.
98
- `;
99
-
100
- expect(() => parseFrontMatter(markdown)).toThrow(FrontMatterError);
101
- expect(() => parseFrontMatter(markdown)).toThrow('Missing required fields');
102
- });
103
-
104
- it('should handle multiline content correctly', () => {
105
- const markdown = `---
106
- id: "550e8400-e29b-41d4-a716-446655440000"
107
- created_at: "2024-03-15T10:30:00Z"
108
- updated_at: "2024-03-15T10:30:00Z"
109
- tags: []
110
- category: "general"
111
- importance: 3
112
- ---
113
-
114
- # Title
115
-
116
- Line 1
117
- Line 2
118
-
119
- Line 3 after blank
120
- `;
121
-
122
- const result = parseFrontMatter(markdown);
123
- expect(result.content).toContain('Line 1');
124
- expect(result.content).toContain('Line 3 after blank');
125
- });
126
- });
127
-
128
- describe('stringifyFrontMatter', () => {
129
- const frontMatter: MemoryFrontMatter = {
130
- id: '550e8400-e29b-41d4-a716-446655440000',
131
- created_at: '2024-03-15T10:30:00Z',
132
- updated_at: '2024-03-15T14:20:00Z',
133
- tags: ['project', 'meeting'],
134
- category: 'work',
135
- importance: 4,
136
- };
137
-
138
- it('should stringify front matter and content', () => {
139
- const result = stringifyFrontMatter(frontMatter, 'Title', 'Content here');
140
- expect(result).toContain('---');
141
- expect(result).toContain('id: "550e8400-e29b-41d4-a716-446655440000"');
142
- expect(result).toContain('# Title');
143
- expect(result).toContain('Content here');
144
- });
145
-
146
- it('should format tags as YAML array', () => {
147
- const result = stringifyFrontMatter(frontMatter, 'Title', 'Content');
148
- expect(result).toContain('tags:');
149
- expect(result).toContain('project');
150
- expect(result).toContain('meeting');
151
- });
152
-
153
- it('should use LF line endings', () => {
154
- const result = stringifyFrontMatter(frontMatter, 'Title', 'Content');
155
- expect(result).not.toContain('\r\n');
156
- expect(result).toContain('\n');
157
- });
158
-
159
- it('should add blank line between front matter and content', () => {
160
- const result = stringifyFrontMatter(frontMatter, 'Title', 'Content');
161
- expect(result).toMatch(/---\n\n# Title/);
162
- });
163
-
164
- it('should handle empty tags', () => {
165
- const fmWithEmptyTags: MemoryFrontMatter = { ...frontMatter, tags: [] };
166
- const result = stringifyFrontMatter(fmWithEmptyTags, 'Title', 'Content');
167
- expect(result).toContain('tags: []');
168
- });
169
-
170
- it('should handle multiline content', () => {
171
- const content = 'Line 1\n\nLine 2\n\nLine 3';
172
- const result = stringifyFrontMatter(frontMatter, 'Title', content);
173
- expect(result).toContain('Line 1');
174
- expect(result).toContain('Line 2');
175
- expect(result).toContain('Line 3');
176
- });
177
- });
178
-
179
- describe('memoryToFrontMatter', () => {
180
- it('should convert Memory to MemoryFrontMatter', () => {
181
- const memory: Memory = {
182
- id: '550e8400-e29b-41d4-a716-446655440000',
183
- createdAt: '2024-03-15T10:30:00Z',
184
- updatedAt: '2024-03-15T14:20:00Z',
185
- tags: ['test'],
186
- category: 'work',
187
- importance: 3,
188
- title: 'Test',
189
- content: 'Content',
190
- };
191
-
192
- const result = memoryToFrontMatter(memory);
193
- expect(result.id).toBe(memory.id);
194
- expect(result.created_at).toBe(memory.createdAt);
195
- expect(result.updated_at).toBe(memory.updatedAt);
196
- expect(result.tags).toEqual(memory.tags);
197
- expect(result.category).toBe(memory.category);
198
- expect(result.importance).toBe(memory.importance);
199
- });
200
- });
201
-
202
- describe('frontMatterToMemory', () => {
203
- it('should convert MemoryFrontMatter to Memory', () => {
204
- const fm: MemoryFrontMatter = {
205
- id: '550e8400-e29b-41d4-a716-446655440000',
206
- created_at: '2024-03-15T10:30:00Z',
207
- updated_at: '2024-03-15T14:20:00Z',
208
- tags: ['test'],
209
- category: 'work',
210
- importance: 3,
211
- };
212
-
213
- const result = frontMatterToMemory(fm, 'Title', 'Content');
214
- expect(result.id).toBe(fm.id);
215
- expect(result.createdAt).toBe(fm.created_at);
216
- expect(result.updatedAt).toBe(fm.updated_at);
217
- expect(result.tags).toEqual(fm.tags);
218
- expect(result.category).toBe(fm.category);
219
- expect(result.importance).toBe(fm.importance);
220
- expect(result.title).toBe('Title');
221
- expect(result.content).toBe('Content');
222
- });
1
+ /**
2
+ * FrontMatter Parser Tests
3
+ * Tests for the FrontMatter parser
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import {
8
+ parseFrontMatter,
9
+ stringifyFrontMatter,
10
+ memoryToFrontMatter,
11
+ frontMatterToMemory,
12
+ FrontMatterError,
13
+ } from '../../src/storage/frontmatter-parser.js';
14
+ import type { Memory, MemoryFrontMatter } from '../../src/contracts/types.js';
15
+
16
+ describe('parseFrontMatter', () => {
17
+ it('should parse valid front matter and content', () => {
18
+ const markdown = `---
19
+ id: "550e8400-e29b-41d4-a716-446655440000"
20
+ created_at: "2024-03-15T10:30:00Z"
21
+ updated_at: "2024-03-15T14:20:00Z"
22
+ tags:
23
+ - project
24
+ - meeting
25
+ category: "work"
26
+ importance: 4
27
+ ---
28
+
29
+ # Project Meeting
30
+
31
+ This is the meeting content.
32
+
33
+ ## Action Items
34
+
35
+ - Item 1
36
+ - Item 2
37
+ `;
38
+
39
+ const result = parseFrontMatter(markdown);
40
+ expect(result.frontMatter.id).toBe('550e8400-e29b-41d4-a716-446655440000');
41
+ expect(result.frontMatter.category).toBe('work');
42
+ expect(result.frontMatter.importance).toBe(4);
43
+ expect(result.title).toBe('Project Meeting');
44
+ expect(result.content).toContain('Action Items');
45
+ });
46
+
47
+ it('should parse front matter with empty tags', () => {
48
+ const markdown = `---
49
+ id: "550e8400-e29b-41d4-a716-446655440000"
50
+ created_at: "2024-03-15T10:30:00Z"
51
+ updated_at: "2024-03-15T10:30:00Z"
52
+ tags: []
53
+ category: "general"
54
+ importance: 3
55
+ ---
56
+
57
+ # Empty Tags Test
58
+
59
+ Content here.
60
+ `;
61
+
62
+ const result = parseFrontMatter(markdown);
63
+ expect(result.frontMatter.tags).toEqual([]);
64
+ });
65
+
66
+ it('should throw error for missing front matter', () => {
67
+ const markdown = `# No Front Matter
68
+
69
+ This markdown has no front matter.
70
+ `;
71
+
72
+ expect(() => parseFrontMatter(markdown)).toThrow(FrontMatterError);
73
+ expect(() => parseFrontMatter(markdown)).toThrow('Missing front matter delimiter');
74
+ });
75
+
76
+ it('should throw error for invalid YAML', () => {
77
+ const markdown = `---
78
+ id: "550e8400-e29b-41d4-a716-446655440000"
79
+ created_at: [invalid yaml
80
+ ---
81
+
82
+ # Title
83
+
84
+ Content.
85
+ `;
86
+
87
+ expect(() => parseFrontMatter(markdown)).toThrow(FrontMatterError);
88
+ });
89
+
90
+ it('should throw error for missing required fields', () => {
91
+ const markdown = `---
92
+ id: "550e8400-e29b-41d4-a716-446655440000"
93
+ ---
94
+
95
+ # Title
96
+
97
+ Content.
98
+ `;
99
+
100
+ expect(() => parseFrontMatter(markdown)).toThrow(FrontMatterError);
101
+ expect(() => parseFrontMatter(markdown)).toThrow('Missing required fields');
102
+ });
103
+
104
+ it('should handle multiline content correctly', () => {
105
+ const markdown = `---
106
+ id: "550e8400-e29b-41d4-a716-446655440000"
107
+ created_at: "2024-03-15T10:30:00Z"
108
+ updated_at: "2024-03-15T10:30:00Z"
109
+ tags: []
110
+ category: "general"
111
+ importance: 3
112
+ ---
113
+
114
+ # Title
115
+
116
+ Line 1
117
+ Line 2
118
+
119
+ Line 3 after blank
120
+ `;
121
+
122
+ const result = parseFrontMatter(markdown);
123
+ expect(result.content).toContain('Line 1');
124
+ expect(result.content).toContain('Line 3 after blank');
125
+ });
126
+ });
127
+
128
+ describe('stringifyFrontMatter', () => {
129
+ const frontMatter: MemoryFrontMatter = {
130
+ id: '550e8400-e29b-41d4-a716-446655440000',
131
+ created_at: '2024-03-15T10:30:00Z',
132
+ updated_at: '2024-03-15T14:20:00Z',
133
+ tags: ['project', 'meeting'],
134
+ category: 'work',
135
+ importance: 4,
136
+ };
137
+
138
+ it('should stringify front matter and content', () => {
139
+ const result = stringifyFrontMatter(frontMatter, 'Title', 'Content here');
140
+ expect(result).toContain('---');
141
+ expect(result).toContain('id: "550e8400-e29b-41d4-a716-446655440000"');
142
+ expect(result).toContain('# Title');
143
+ expect(result).toContain('Content here');
144
+ });
145
+
146
+ it('should format tags as YAML array', () => {
147
+ const result = stringifyFrontMatter(frontMatter, 'Title', 'Content');
148
+ expect(result).toContain('tags:');
149
+ expect(result).toContain('project');
150
+ expect(result).toContain('meeting');
151
+ });
152
+
153
+ it('should use LF line endings', () => {
154
+ const result = stringifyFrontMatter(frontMatter, 'Title', 'Content');
155
+ expect(result).not.toContain('\r\n');
156
+ expect(result).toContain('\n');
157
+ });
158
+
159
+ it('should add blank line between front matter and content', () => {
160
+ const result = stringifyFrontMatter(frontMatter, 'Title', 'Content');
161
+ expect(result).toMatch(/---\n\n# Title/);
162
+ });
163
+
164
+ it('should handle empty tags', () => {
165
+ const fmWithEmptyTags: MemoryFrontMatter = { ...frontMatter, tags: [] };
166
+ const result = stringifyFrontMatter(fmWithEmptyTags, 'Title', 'Content');
167
+ expect(result).toContain('tags: []');
168
+ });
169
+
170
+ it('should handle multiline content', () => {
171
+ const content = 'Line 1\n\nLine 2\n\nLine 3';
172
+ const result = stringifyFrontMatter(frontMatter, 'Title', content);
173
+ expect(result).toContain('Line 1');
174
+ expect(result).toContain('Line 2');
175
+ expect(result).toContain('Line 3');
176
+ });
177
+ });
178
+
179
+ describe('memoryToFrontMatter', () => {
180
+ it('should convert Memory to MemoryFrontMatter', () => {
181
+ const memory: Memory = {
182
+ id: '550e8400-e29b-41d4-a716-446655440000',
183
+ createdAt: '2024-03-15T10:30:00Z',
184
+ updatedAt: '2024-03-15T14:20:00Z',
185
+ tags: ['test'],
186
+ category: 'work',
187
+ importance: 3,
188
+ title: 'Test',
189
+ content: 'Content',
190
+ };
191
+
192
+ const result = memoryToFrontMatter(memory);
193
+ expect(result.id).toBe(memory.id);
194
+ expect(result.created_at).toBe(memory.createdAt);
195
+ expect(result.updated_at).toBe(memory.updatedAt);
196
+ expect(result.tags).toEqual(memory.tags);
197
+ expect(result.category).toBe(memory.category);
198
+ expect(result.importance).toBe(memory.importance);
199
+ });
200
+ });
201
+
202
+ describe('frontMatterToMemory', () => {
203
+ it('should convert MemoryFrontMatter to Memory', () => {
204
+ const fm: MemoryFrontMatter = {
205
+ id: '550e8400-e29b-41d4-a716-446655440000',
206
+ created_at: '2024-03-15T10:30:00Z',
207
+ updated_at: '2024-03-15T14:20:00Z',
208
+ tags: ['test'],
209
+ category: 'work',
210
+ importance: 3,
211
+ };
212
+
213
+ const result = frontMatterToMemory(fm, 'Title', 'Content');
214
+ expect(result.id).toBe(fm.id);
215
+ expect(result.createdAt).toBe(fm.created_at);
216
+ expect(result.updatedAt).toBe(fm.updated_at);
217
+ expect(result.tags).toEqual(fm.tags);
218
+ expect(result.category).toBe(fm.category);
219
+ expect(result.importance).toBe(fm.importance);
220
+ expect(result.title).toBe('Title');
221
+ expect(result.content).toBe('Content');
222
+ });
223
223
  });