feat(services): add memory service and refactor CLI
This commit is contained in:
parent
fff8a61ad9
commit
e69e81135a
@ -1,9 +1,6 @@
|
|||||||
import { input, select } from '@inquirer/prompts';
|
import { input, select } from '@inquirer/prompts';
|
||||||
import { join } from 'path';
|
import { addMemory, searchMemory, listMemory } from '../../core/services/memoryService.js';
|
||||||
import { getEntityDir } from '../../core/paths.js';
|
import type { Memory } from '../../core/schema.js';
|
||||||
import { writeEntity } from '../../core/files.js';
|
|
||||||
import { MemorySchema, type Memory } from '../../core/schema.js';
|
|
||||||
import { Index } from '../../core/index.js';
|
|
||||||
|
|
||||||
export async function memoryAdd(cwd: string, options: Partial<Memory> = {}): Promise<void> {
|
export async function memoryAdd(cwd: string, options: Partial<Memory> = {}): Promise<void> {
|
||||||
const title = options.title ?? await input({ message: 'Memory title:' });
|
const title = options.title ?? await input({ message: 'Memory title:' });
|
||||||
@ -19,61 +16,27 @@ export async function memoryAdd(cwd: string, options: Partial<Memory> = {}): Pro
|
|||||||
});
|
});
|
||||||
const content = options.content ?? await input({ message: 'Content:' });
|
const content = options.content ?? await input({ message: 'Content:' });
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const memory = addMemory(cwd, { title, category, content });
|
||||||
const id = `MEM-${Date.now()}`;
|
|
||||||
const memory: Memory = MemorySchema.parse({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
category,
|
|
||||||
content,
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now,
|
|
||||||
});
|
|
||||||
|
|
||||||
const filePath = join(getEntityDir(cwd, 'memory'), `${id}.md`);
|
|
||||||
writeEntity(filePath, memory, `# ${memory.title}\n\n${memory.content}`);
|
|
||||||
|
|
||||||
const index = new Index(cwd);
|
|
||||||
index.upsert({
|
|
||||||
id: memory.id,
|
|
||||||
type: 'memory',
|
|
||||||
title: memory.title,
|
|
||||||
content: memory.content,
|
|
||||||
filePath,
|
|
||||||
createdAt: memory.createdAt,
|
|
||||||
updatedAt: memory.updatedAt,
|
|
||||||
tags: JSON.stringify(memory.tags),
|
|
||||||
});
|
|
||||||
index.close();
|
|
||||||
|
|
||||||
console.log(`Memory saved as ${memory.id}.`);
|
console.log(`Memory saved as ${memory.id}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function memorySearch(cwd: string, query: string): void {
|
export function memorySearch(cwd: string, query: string): void {
|
||||||
const index = new Index(cwd);
|
const results = searchMemory(cwd, query);
|
||||||
const results = index.search(query);
|
|
||||||
index.close();
|
|
||||||
|
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
console.log('No results found.');
|
console.log('No results found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const r of results) {
|
for (const r of results) {
|
||||||
console.log(`[${r.type}] ${r.id}: ${r.title}`);
|
console.log(`[${r.type}] ${r.id}: ${r.title}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function memoryList(cwd: string): void {
|
export function memoryList(cwd: string): void {
|
||||||
const index = new Index(cwd);
|
const memories = listMemory(cwd);
|
||||||
const memories = index.list('memory');
|
|
||||||
index.close();
|
|
||||||
|
|
||||||
if (memories.length === 0) {
|
if (memories.length === 0) {
|
||||||
console.log('No memory entries found.');
|
console.log('No memory entries found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const m of memories) {
|
for (const m of memories) {
|
||||||
console.log(`${m.id}: ${m.title}`);
|
console.log(`${m.id}: ${m.title}`);
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/core/services/memoryService.ts
Normal file
53
src/core/services/memoryService.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
import { getEntityDir } from '../paths.js';
|
||||||
|
import { writeEntity } from '../files.js';
|
||||||
|
import { MemorySchema, type Memory } from '../schema.js';
|
||||||
|
import { Index } from '../index.js';
|
||||||
|
|
||||||
|
export function addMemory(cwd: string, options: Partial<Memory> = {}): Memory {
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const id = `MEM-${Date.now()}`;
|
||||||
|
const memory: Memory = MemorySchema.parse({
|
||||||
|
id,
|
||||||
|
title: options.title ?? 'Memory',
|
||||||
|
category: options.category ?? 'technical',
|
||||||
|
content: options.content ?? '',
|
||||||
|
tags: options.tags ?? [],
|
||||||
|
relatedTasks: options.relatedTasks ?? [],
|
||||||
|
relatedDecisions: options.relatedDecisions ?? [],
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filePath = join(getEntityDir(cwd, 'memory'), `${memory.id}.md`);
|
||||||
|
writeEntity(filePath, memory, `# ${memory.title}\n\n${memory.content}`);
|
||||||
|
|
||||||
|
const index = new Index(cwd);
|
||||||
|
index.upsert({
|
||||||
|
id: memory.id,
|
||||||
|
type: 'memory',
|
||||||
|
title: memory.title,
|
||||||
|
content: memory.content,
|
||||||
|
filePath,
|
||||||
|
createdAt: memory.createdAt,
|
||||||
|
updatedAt: memory.updatedAt,
|
||||||
|
tags: JSON.stringify(memory.tags),
|
||||||
|
});
|
||||||
|
index.close();
|
||||||
|
|
||||||
|
return memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function searchMemory(cwd: string, query: string): ReturnType<Index['search']> {
|
||||||
|
const index = new Index(cwd);
|
||||||
|
const results = index.search(query);
|
||||||
|
index.close();
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listMemory(cwd: string): ReturnType<Index['list']> {
|
||||||
|
const index = new Index(cwd);
|
||||||
|
const memories = index.list('memory');
|
||||||
|
index.close();
|
||||||
|
return memories;
|
||||||
|
}
|
||||||
19
tests/memoryService.test.ts
Normal file
19
tests/memoryService.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { mkdtempSync, rmSync } from 'fs';
|
||||||
|
import { tmpdir } from 'os';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { addMemory, searchMemory, listMemory } from '../src/core/services/memoryService.js';
|
||||||
|
|
||||||
|
describe('memoryService', () => {
|
||||||
|
let cwd: string;
|
||||||
|
|
||||||
|
beforeEach(() => { cwd = mkdtempSync(join(tmpdir(), 'ah-mem-')); });
|
||||||
|
afterEach(() => { rmSync(cwd, { recursive: true, force: true }); });
|
||||||
|
|
||||||
|
it('creates and searches memory', () => {
|
||||||
|
const m = addMemory(cwd, { title: 'DNS cache', category: 'technical', content: 'Use TTL' });
|
||||||
|
expect(m.id.startsWith('MEM-')).toBe(true);
|
||||||
|
expect(searchMemory(cwd, 'TTL')).toHaveLength(1);
|
||||||
|
expect(listMemory(cwd)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user