Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/mindcache/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mindcache",
"version": "3.7.0",
"version": "3.8.0",
"description": "A TypeScript library for managing short-term memory in AI agents through an LLM-friendly key-value repository",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
48 changes: 40 additions & 8 deletions packages/mindcache/src/core/MarkdownSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ export interface IMarkdownSerializable {
clear(): void;
}

/**
* Options for markdown export
*/
export interface MarkdownExportOptions {
/** Name/title for the export (defaults to 'MindCache Export') */
name?: string;
/** Description to include below the title */
description?: string;
}

/**
* Serializes and deserializes MindCache data to/from Markdown format.
*/
export class MarkdownSerializer {
/**
* Export MindCache data to Markdown format.
*/
static toMarkdown(mc: IMarkdownSerializable): string {
static toMarkdown(mc: IMarkdownSerializable, options?: MarkdownExportOptions): string {
const now = new Date();
const lines: string[] = [];
const appendixEntries: Array<{
Expand All @@ -33,13 +43,18 @@ export class MarkdownSerializer {
}> = [];
let appendixCounter = 0;

lines.push('# MindCache STM Export');
const name = options?.name || 'MindCache Export';
lines.push(`# ${name}`);
lines.push('');
if (options?.description) {
lines.push(options.description);
lines.push('');
}
lines.push(`Export Date: ${now.toISOString().split('T')[0]}`);
lines.push('');
lines.push('---');
lines.push('');
lines.push('## STM Entries');
lines.push('## Keys & Values');
lines.push('');

const sortedKeys = mc.getSortedKeys();
Expand All @@ -49,9 +64,20 @@ export class MarkdownSerializer {

lines.push(`### ${key}`);
const entryType = attributes?.type || 'text';
lines.push(`- **Type**: \`${entryType}\``);
lines.push(`- **System Tags**: \`${attributes?.systemTags?.join(', ') || 'none'}\``);
lines.push(`- **Z-Index**: \`${attributes?.zIndex ?? 0}\``);
// Only show type if not 'text' (the default)
if (entryType !== 'text') {
lines.push(`- **Type**: \`${entryType}\``);
}
// Only show system tags if there are any
const systemTags = attributes?.systemTags;
if (systemTags && systemTags.length > 0) {
lines.push(`- **System Tags**: \`${systemTags.join(', ')}\``);
}
// Only show z-index if non-zero
const zIndex = attributes?.zIndex ?? 0;
if (zIndex !== 0) {
lines.push(`- **Z-Index**: \`${zIndex}\``);
}

if (attributes?.contentTags && attributes.contentTags.length > 0) {
lines.push(`- **Tags**: \`${attributes.contentTags.join('`, `')}\``);
Expand Down Expand Up @@ -278,7 +304,10 @@ export class MarkdownSerializer {

// Check if we parsed any keys
const hasParsedKeys = lines.some(line => line.startsWith('### ') && !line.startsWith('### Appendix'));
const isSTMExport = markdown.includes('# MindCache STM Export') || markdown.includes('## STM Entries');
const isSTMExport = markdown.includes('# MindCache STM Export') ||
markdown.includes('## STM Entries') ||
markdown.includes('## Keys & Values') ||
markdown.includes('# MindCache Export');

if (!hasParsedKeys && !isSTMExport && markdown.trim().length > 0) {
mc.set_value('imported_content', markdown.trim(), {
Expand Down Expand Up @@ -416,7 +445,10 @@ export class MarkdownSerializer {

// Handle unstructured content
const hasParsedKeys = lines.some(line => line.startsWith('### ') && !line.startsWith('### Appendix'));
const isSTMExport = markdown.includes('# MindCache STM Export') || markdown.includes('## STM Entries');
const isSTMExport = markdown.includes('# MindCache STM Export') ||
markdown.includes('## STM Entries') ||
markdown.includes('## Keys & Values') ||
markdown.includes('# MindCache Export');

if (!hasParsedKeys && !isSTMExport && markdown.trim().length > 0) {
result['imported_content'] = {
Expand Down
7 changes: 4 additions & 3 deletions packages/mindcache/src/core/MindCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class MindCache {
private globalListeners: GlobalListener[] = [];

// Metadata
public readonly version = '3.6.0';
public readonly version = '3.8.0';

// Internal flag to prevent sync loops when receiving remote updates
// (Less critical with Yjs but kept for API compat)
Expand Down Expand Up @@ -1495,9 +1495,10 @@ export class MindCache {

/**
* Export to Markdown format.
* @param options Optional name and description for the export
*/
toMarkdown(): string {
return MarkdownSerializer.toMarkdown(this);
toMarkdown(options?: { name?: string; description?: string }): string {
return MarkdownSerializer.toMarkdown(this, options);
}

/**
Expand Down
65 changes: 59 additions & 6 deletions packages/mindcache/src/core/mindcache.markdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,49 @@ describe('MindCache Markdown Serialization', () => {
test('should export empty STM to markdown', () => {
const markdown = cache.toMarkdown();

expect(markdown).toContain('# MindCache STM Export');
expect(markdown).toContain('## STM Entries');
expect(markdown).toContain('# MindCache Export');
expect(markdown).toContain('## Keys & Values');
// No entries when empty
});

test('should export text values to markdown', () => {
test('should export with custom name and description', () => {
cache.set_value('test', 'value');
const markdown = cache.toMarkdown({
name: 'My Project',
description: 'This is my project description.'
});

expect(markdown).toContain('# My Project');
expect(markdown).toContain('This is my project description.');
expect(markdown).toContain('## Keys & Values');
});

test('should export text values to markdown without type line (default)', () => {
cache.set_value('username', 'john_doe');
cache.set_value('email', 'john@example.com');

const markdown = cache.toMarkdown();

expect(markdown).toContain('### username');
expect(markdown).toContain('- **Type**: `text`');
// Type 'text' should NOT be shown (it's the default)
expect(markdown).not.toContain('- **Type**: `text`');
expect(markdown).toContain('```\njohn_doe\n```');
expect(markdown).toContain('### email');
expect(markdown).toContain('```\njohn@example.com\n```');
});

test('should skip default values (z-index 0, no system tags)', () => {
cache.set_value('simple', 'value');

const markdown = cache.toMarkdown();

expect(markdown).toContain('### simple');
// These defaults should NOT appear
expect(markdown).not.toContain('- **Type**: `text`');
expect(markdown).not.toContain('- **System Tags**: `none`');
expect(markdown).not.toContain('- **Z-Index**: `0`');
});

test('should export multiline text with code blocks', () => {
cache.set_value('description', 'Line 1\nLine 2\nLine 3');

Expand All @@ -53,16 +78,18 @@ describe('MindCache Markdown Serialization', () => {
expect(markdown).toContain('"dark"');
});

test('should export all attributes', () => {
test('should export non-default attributes', () => {
cache.set_value('test_key', 'value', {
systemTags: ['ApplyTemplate'],
contentTags: ['tag1', 'tag2', 'tag3']
contentTags: ['tag1', 'tag2', 'tag3'],
zIndex: 5
});

const markdown = cache.toMarkdown();

expect(markdown).toContain('- **System Tags**: `ApplyTemplate`');
expect(markdown).toContain('- **Tags**: `tag1`, `tag2`, `tag3`');
expect(markdown).toContain('- **Z-Index**: `5`');
});

test('should export image to appendix', () => {
Expand Down Expand Up @@ -150,6 +177,32 @@ Export Date: 2025-10-01
expect(cache.size()).toBe(0);
});

test('should import new format (Keys & Values)', () => {
const markdown = `# My Project

This is my project.

Export Date: 2025-10-01

---

## Keys & Values

### username
- **Value**:
\`\`\`
john_doe
\`\`\`

`;

cache.fromMarkdown(markdown);

expect(cache.get_value('username')).toBe('john_doe');
// Should default to type 'text'
expect(cache.get_attributes('username')?.type).toBe('text');
});

test('should import text values', () => {
const markdown = `# MindCache STM Export

Expand Down
Loading