@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.
- package/.eslintrc.cjs +46 -0
- package/.github/workflows/ci.yml +74 -0
- package/.iflow/commands/opsx-apply.md +152 -0
- package/.iflow/commands/opsx-archive.md +157 -0
- package/.iflow/commands/opsx-explore.md +173 -0
- package/.iflow/commands/opsx-propose.md +106 -0
- package/.iflow/skills/openspec-apply-change/SKILL.md +156 -0
- package/.iflow/skills/openspec-archive-change/SKILL.md +114 -0
- package/.iflow/skills/openspec-explore/SKILL.md +288 -0
- package/.iflow/skills/openspec-propose/SKILL.md +110 -0
- package/.prettierrc +11 -0
- package/README.md +171 -0
- package/README.zh-CN.md +169 -0
- package/dist/src/contracts/index.d.ts +7 -0
- package/dist/src/contracts/index.d.ts.map +1 -0
- package/dist/src/contracts/index.js +10 -0
- package/dist/src/contracts/index.js.map +1 -0
- package/dist/src/contracts/mcp.d.ts +194 -0
- package/dist/src/contracts/mcp.d.ts.map +1 -0
- package/dist/src/contracts/mcp.js +112 -0
- package/dist/src/contracts/mcp.js.map +1 -0
- package/dist/src/contracts/schemas.d.ts +1153 -0
- package/dist/src/contracts/schemas.d.ts.map +1 -0
- package/dist/src/contracts/schemas.js +246 -0
- package/dist/src/contracts/schemas.js.map +1 -0
- package/dist/src/contracts/types.d.ts +328 -0
- package/dist/src/contracts/types.d.ts.map +1 -0
- package/dist/src/contracts/types.js +30 -0
- package/dist/src/contracts/types.js.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/server/index.d.ts +5 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +5 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/mcp-server.d.ts +80 -0
- package/dist/src/server/mcp-server.d.ts.map +1 -0
- package/dist/src/server/mcp-server.js +263 -0
- package/dist/src/server/mcp-server.js.map +1 -0
- package/dist/src/services/index.d.ts +5 -0
- package/dist/src/services/index.d.ts.map +1 -0
- package/dist/src/services/index.js +5 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/memory-service.d.ts +105 -0
- package/dist/src/services/memory-service.d.ts.map +1 -0
- package/dist/src/services/memory-service.js +447 -0
- package/dist/src/services/memory-service.js.map +1 -0
- package/dist/src/storage/frontmatter-parser.d.ts +69 -0
- package/dist/src/storage/frontmatter-parser.d.ts.map +1 -0
- package/dist/src/storage/frontmatter-parser.js +207 -0
- package/dist/src/storage/frontmatter-parser.js.map +1 -0
- package/dist/src/storage/index.d.ts +6 -0
- package/dist/src/storage/index.d.ts.map +1 -0
- package/dist/src/storage/index.js +6 -0
- package/dist/src/storage/index.js.map +1 -0
- package/dist/src/storage/markdown-storage.d.ts +76 -0
- package/dist/src/storage/markdown-storage.d.ts.map +1 -0
- package/dist/src/storage/markdown-storage.js +193 -0
- package/dist/src/storage/markdown-storage.js.map +1 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +5 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/slugify.d.ts +24 -0
- package/dist/src/utils/slugify.d.ts.map +1 -0
- package/dist/src/utils/slugify.js +56 -0
- package/dist/src/utils/slugify.js.map +1 -0
- package/docs/architecture.md +349 -0
- package/docs/contracts.md +119 -0
- package/docs/prompt-template.md +79 -0
- package/docs/proposal-close-gates.md +58 -0
- package/docs/tool-calling-policy.md +107 -0
- package/package.json +53 -0
- package/src/contracts/index.ts +12 -0
- package/src/contracts/mcp.ts +303 -0
- package/src/contracts/schemas.ts +311 -0
- package/src/contracts/types.ts +414 -0
- package/src/index.ts +8 -0
- package/src/server/index.ts +5 -0
- package/src/server/mcp-server.ts +352 -0
- package/src/services/index.ts +5 -0
- package/src/services/memory-service.ts +548 -0
- package/src/storage/frontmatter-parser.ts +243 -0
- package/src/storage/index.ts +6 -0
- package/src/storage/markdown-storage.ts +236 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/slugify.ts +63 -0
- package/test/contracts/schemas.test.ts +313 -0
- package/test/contracts/types.test.ts +21 -0
- package/test/frontmatter-parser-more.test.ts +94 -0
- package/test/server/mcp-server-internals.test.ts +257 -0
- package/test/server/mcp-server.test.ts +97 -0
- package/test/services/memory-service-edge.test.ts +248 -0
- package/test/services/memory-service.test.ts +279 -0
- package/test/storage/frontmatter-parser.test.ts +223 -0
- package/test/storage/markdown-storage.test.ts +217 -0
- package/test/storage/storage-edge.test.ts +238 -0
- package/test/utils/slugify-edge.test.ts +94 -0
- package/test/utils/slugify.test.ts +68 -0
- package/tsconfig.json +26 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify Edge Case Tests
|
|
3
|
+
* Additional tests for better coverage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { slugify, generateUniqueSlug } from '../../src/utils/slugify.js';
|
|
8
|
+
|
|
9
|
+
describe('slugify edge cases', () => {
|
|
10
|
+
it('should handle whitespace-only string', () => {
|
|
11
|
+
expect(slugify(' ')).toBe('untitled');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should handle multiple consecutive spaces', () => {
|
|
15
|
+
expect(slugify('hello world')).toBe('hello-world');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should handle string with only special characters', () => {
|
|
19
|
+
expect(slugify('!@#$%^&*()')).toBe('untitled');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle mixed alphanumeric with special chars', () => {
|
|
23
|
+
expect(slugify('hello123!@#world456')).toBe('hello123world456');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle string starting with numbers', () => {
|
|
27
|
+
expect(slugify('123hello')).toBe('123hello');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle very short string', () => {
|
|
31
|
+
expect(slugify('a')).toBe('a');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle string at exactly max length', () => {
|
|
35
|
+
const input = 'a'.repeat(100);
|
|
36
|
+
expect(slugify(input)).toBe(input);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should handle string just over max length', () => {
|
|
40
|
+
const input = 'a'.repeat(101);
|
|
41
|
+
expect(slugify(input).length).toBeLessThanOrEqual(100);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle string with hyphens at boundaries', () => {
|
|
45
|
+
expect(slugify('-hello-world-')).toBe('hello-world');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle string with multiple consecutive hyphens after cleaning', () => {
|
|
49
|
+
expect(slugify('hello---world')).toBe('hello-world');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle null-like empty string', () => {
|
|
53
|
+
expect(slugify('')).toBe('untitled');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should preserve numbers in slug', () => {
|
|
57
|
+
expect(slugify('version 2.0 release')).toBe('version-20-release');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('generateUniqueSlug edge cases', () => {
|
|
62
|
+
it('should handle empty existing slugs array', () => {
|
|
63
|
+
const result = generateUniqueSlug('test', []);
|
|
64
|
+
expect(result).toBe('test');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle when base slug plus counter would exceed limit', () => {
|
|
68
|
+
const longBase = 'a'.repeat(98);
|
|
69
|
+
const existing = [longBase];
|
|
70
|
+
const result = generateUniqueSlug(longBase, existing);
|
|
71
|
+
expect(result.length).toBeLessThanOrEqual(100);
|
|
72
|
+
expect(result).toMatch(/^a+-\d+$/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle multiple conflicts', () => {
|
|
76
|
+
const existing = ['test', 'test-1', 'test-2', 'test-3'];
|
|
77
|
+
const result = generateUniqueSlug('test', existing);
|
|
78
|
+
expect(result).toBe('test-4');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should handle slug that looks like a numbered conflict', () => {
|
|
82
|
+
const existing = ['test-1'];
|
|
83
|
+
// 'test' is not in existing, so it should return 'test'
|
|
84
|
+
const result = generateUniqueSlug('test', existing);
|
|
85
|
+
expect(result).toBe('test');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should handle case sensitivity', () => {
|
|
89
|
+
const existing = ['Test'];
|
|
90
|
+
const result = generateUniqueSlug('test', existing);
|
|
91
|
+
// Should not conflict because 'test' !== 'Test'
|
|
92
|
+
expect(result).toBe('test');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify Utility Tests
|
|
3
|
+
* Tests for the slug generation utility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { slugify, generateUniqueSlug } from '../../src/utils/slugify.js';
|
|
8
|
+
|
|
9
|
+
describe('slugify', () => {
|
|
10
|
+
it('should convert to lowercase', () => {
|
|
11
|
+
expect(slugify('HELLO')).toBe('hello');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should replace spaces with hyphens', () => {
|
|
15
|
+
expect(slugify('hello world')).toBe('hello-world');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should remove special characters', () => {
|
|
19
|
+
expect(slugify('hello!@#world')).toBe('helloworld');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should collapse multiple hyphens', () => {
|
|
23
|
+
expect(slugify('hello---world')).toBe('hello-world');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should trim leading/trailing hyphens', () => {
|
|
27
|
+
expect(slugify('-hello-world-')).toBe('hello-world');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle empty string', () => {
|
|
31
|
+
expect(slugify('')).toBe('untitled');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle strings that become empty after cleaning', () => {
|
|
35
|
+
expect(slugify('!@#$%')).toBe('untitled');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should truncate to max length', () => {
|
|
39
|
+
const long = 'a'.repeat(200);
|
|
40
|
+
const result = slugify(long);
|
|
41
|
+
expect(result.length).toBeLessThanOrEqual(100);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle Chinese characters', () => {
|
|
45
|
+
expect(slugify('你好世界')).toMatch(/^[a-z0-9-]*$/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle mixed content', () => {
|
|
49
|
+
expect(slugify('Hello 世界! World')).toMatch(/^[a-z0-9-]+$/);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('generateUniqueSlug', () => {
|
|
54
|
+
it('should return same slug when no conflicts', () => {
|
|
55
|
+
const result = generateUniqueSlug('hello world', []);
|
|
56
|
+
expect(result).toBe('hello-world');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should append counter when slug exists', () => {
|
|
60
|
+
const result = generateUniqueSlug('hello world', ['hello-world']);
|
|
61
|
+
expect(result).toBe('hello-world-1');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should increment counter until unique', () => {
|
|
65
|
+
const result = generateUniqueSlug('hello world', ['hello-world', 'hello-world-1', 'hello-world-2']);
|
|
66
|
+
expect(result).toBe('hello-world-3');
|
|
67
|
+
});
|
|
68
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"verbatimModuleSyntax": true,
|
|
22
|
+
"isolatedModules": true
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*"],
|
|
25
|
+
"exclude": ["node_modules", "dist"]
|
|
26
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: 'v8',
|
|
9
|
+
reporter: ['text', 'json', 'html', 'lcov'],
|
|
10
|
+
thresholds: {
|
|
11
|
+
lines: 80,
|
|
12
|
+
functions: 80,
|
|
13
|
+
branches: 80,
|
|
14
|
+
statements: 80,
|
|
15
|
+
},
|
|
16
|
+
exclude: [
|
|
17
|
+
'node_modules/',
|
|
18
|
+
'dist/',
|
|
19
|
+
'test/',
|
|
20
|
+
'**/*.d.ts',
|
|
21
|
+
'**/*.config.*',
|
|
22
|
+
'**/index.ts',
|
|
23
|
+
'.eslintrc.cjs',
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|