Local-mode branch of 'memory add' omitted category when calling memoryAdd, so it always fell through to an interactive select() prompt — which throws in non-TTY agent contexts and breaks the reporting loop. Forward category; add tests for the non-interactive flag path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
/**
|
|
* Regression tests for `memory add` non-interactive path.
|
|
*
|
|
* Before the fix, `memoryAdd` called from the local-mode CLI branch omitted
|
|
* `category` in the options object. Because `memoryAdd` resolves category via
|
|
* `options.category ?? await select(...)`, the missing field caused it to open
|
|
* an @inquirer/prompts interactive select even when --category was supplied —
|
|
* which throws "User force closed the prompt with 0 null" in any non-TTY
|
|
* (agent/background) context.
|
|
*
|
|
* These tests call `memoryAdd` directly with all fields provided and assert
|
|
* that it completes without prompting. If prompting is triggered the test will
|
|
* throw in the non-TTY vitest environment, reproducing the original failure.
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { mkdtempSync, rmSync, readdirSync } from 'fs';
|
|
import { tmpdir } from 'os';
|
|
import { join } from 'path';
|
|
import { memoryAdd } from '../src/cli/commands/memory.js';
|
|
import { init } from '../src/cli/commands/init.js';
|
|
import { listMemory } from '../src/core/services/memoryService.js';
|
|
import { readEntity } from '../src/core/files.js';
|
|
|
|
describe('memoryAdd — non-interactive (all flags supplied)', () => {
|
|
let cwd: string;
|
|
|
|
beforeEach(async () => {
|
|
cwd = mkdtempSync(join(tmpdir(), 'ah-mem-cmd-'));
|
|
await init(cwd, { yes: true, projectName: 'test' });
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(cwd, { recursive: true, force: true });
|
|
});
|
|
|
|
it('succeeds without prompting when title, category and content are provided', async () => {
|
|
// This must not throw — if it prompts it throws in non-TTY vitest.
|
|
await memoryAdd(cwd, {
|
|
title: 'Session summary',
|
|
category: 'implementation',
|
|
content: 'Completed TSK-0010 DNS cache work.',
|
|
});
|
|
|
|
const memories = listMemory(cwd);
|
|
expect(memories).toHaveLength(1);
|
|
expect(memories[0].title).toBe('Session summary');
|
|
|
|
// Verify category was persisted in the markdown frontmatter (index does
|
|
// not carry category, so we read the file directly).
|
|
const memDir = join(cwd, '.agenthub', 'memory');
|
|
const files = readdirSync(memDir).filter((f) => f.endsWith('.md'));
|
|
expect(files).toHaveLength(1);
|
|
const { frontmatter } = readEntity(join(memDir, files[0]));
|
|
expect(frontmatter.category).toBe('implementation');
|
|
});
|
|
|
|
it('succeeds with all activity-timeline flags (the exact agent invocation pattern)', async () => {
|
|
// Reproduces the exact flags agents pass after completing a task.
|
|
await memoryAdd(cwd, {
|
|
title: 'TSK-0010 implementation done',
|
|
category: 'implementation',
|
|
content: 'Implemented activity timeline. All tests green.',
|
|
relatedTasks: ['TSK-0010'],
|
|
tokens: 63576,
|
|
duration: 384987,
|
|
by: 'ops',
|
|
});
|
|
|
|
const memories = listMemory(cwd);
|
|
expect(memories).toHaveLength(1);
|
|
expect(memories[0].title).toBe('TSK-0010 implementation done');
|
|
|
|
// Verify category, relatedTasks, tokens, duration, by in the frontmatter.
|
|
const memDir = join(cwd, '.agenthub', 'memory');
|
|
const files = readdirSync(memDir).filter((f) => f.endsWith('.md'));
|
|
expect(files).toHaveLength(1);
|
|
const { frontmatter } = readEntity(join(memDir, files[0]));
|
|
expect(frontmatter.category).toBe('implementation');
|
|
expect(frontmatter.tokens).toBe(63576);
|
|
expect(frontmatter.duration).toBe(384987);
|
|
expect(frontmatter.by).toBe('ops');
|
|
// relatedTasks is a YAML sequence
|
|
expect(frontmatter.relatedTasks).toContain('TSK-0010');
|
|
});
|
|
});
|