Skip to content

Commit 9c468de

Browse files
jack-arturoclaude
andauthored
fix(test): make the suite pass from any checkout directory (#199)
Two defects made a bare `npm test` unreliable outside a checkout literally named mcp-automem — the reason the marker-extraction work had to be run as `npx vitest run --exclude '**/.claude/**'` and still reported two failures. vitest.config.ts: setting `exclude` replaces vitest's defaults, so the list has to carry everything. It named 'node_modules/**' and 'dist/**' without the recursive '**/' form and said nothing about .claude/, so from the main checkout the glob walked every agent worktree under .claude/worktrees/ — 106 test files across two worktrees here — running each suite twice, the second time from a stale copy of the repo. hermes-real-host.test.ts: the provider derives its project tag from `basename(os.getcwd())`, and runHermesProviderPrefetchSequence defaulted its cwd to REPO_ROOT. The tag assertions therefore passed only where the checkout directory happened to be named mcp-automem, and failed in a worktree, a second clone, or any CI cache path. The prefetch now runs in a fixture directory named for the slug, which pins the contract the provider actually promises instead of the machine's directory layout. Python imports resolve from the Hermes venv, not cwd, so nothing else depended on REPO_ROOT here. Both verified load-bearing: dropping the .claude exclude re-globs a planted worktree fixture, and restoring the REPO_ROOT default fails exactly the two prefetch tests. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 2d1716f commit 9c468de

2 files changed

Lines changed: 42 additions & 8 deletions

File tree

‎tests/host-smoke/hermes-real-host.test.ts‎

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,24 @@ print(json.dumps({
322322
return JSON.parse(lastJsonLine);
323323
}
324324

325+
/**
326+
* Working directory the prefetch runs in. The provider derives its project tag from
327+
* `basename(os.getcwd())`, so this — not the repo — decides what the tag assertions see.
328+
*
329+
* It used to default to REPO_ROOT, which made the assertions pass only where the
330+
* checkout directory happened to be named `mcp-automem`: a git worktree, a second clone,
331+
* or a CI cache path under any other name failed on a tag the provider never promised.
332+
* A fixture directory named for the slug is deterministic everywhere, and it pins the
333+
* contract (tag == basename of cwd) instead of the machine's directory layout. Set per
334+
* test in beforeEach; the Python imports resolve from the Hermes venv, not from cwd.
335+
*/
336+
const PROJECT_SLUG = 'mcp-automem';
337+
let projectCwd: string;
338+
325339
async function runHermesProviderPrefetchSequence(
326340
home: string,
327341
prompts: string[],
328-
cwd: string = REPO_ROOT
342+
cwd: string = projectCwd
329343
): Promise<string[]> {
330344
if (!HERMES_PYTHON) {
331345
throw new Error('Hermes Python is not available');
@@ -481,13 +495,21 @@ describe.skipIf(!HERMES_PYTHON)('Hermes real host integration', () => {
481495
let tmpDir: string;
482496
let fakeApi: FakeAutoMemApi;
483497

498+
let projectRoot: string;
499+
484500
beforeEach(async () => {
485501
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'automem-hermes-host-'));
502+
// See PROJECT_SLUG: the prefetch runs here so the provider's project tag is the
503+
// slug under test rather than whatever this checkout's directory is called.
504+
projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'automem-hermes-project-'));
505+
projectCwd = path.join(projectRoot, PROJECT_SLUG);
506+
fs.mkdirSync(projectCwd, { recursive: true });
486507
fakeApi = await startFakeAutoMemApi();
487508
});
488509

489510
afterEach(async () => {
490511
fs.rmSync(tmpDir, { recursive: true, force: true });
512+
fs.rmSync(projectRoot, { recursive: true, force: true });
491513
await fakeApi.close();
492514
});
493515

@@ -637,7 +659,7 @@ describe.skipIf(!HERMES_PYTHON)('Hermes real host integration', () => {
637659
expect(firstContext?.searchParams.get('limit')).toBe('10');
638660
expect(firstContext?.searchParams.get('time_query')).toBe('last 90 days');
639661
expect(firstContext?.searchParams.get('format')).toBe('detailed');
640-
expect(firstContext?.searchParams.getAll('tags')).toContain('mcp-automem');
662+
expect(firstContext?.searchParams.getAll('tags')).toContain(PROJECT_SLUG);
641663

642664
const topicShift = recalls.find(
643665
(url) => url.searchParams.get('query') === 'Railway deployment status'
@@ -699,7 +721,8 @@ describe.skipIf(!HERMES_PYTHON)('Hermes real host integration', () => {
699721
});
700722

701723
await runHermesProviderPrefetchSequence(tmpDir, ['do we like Example Contact?']);
702-
await runHermesProviderPrefetchSequence(tmpDir, ['what do we know about mcp-automem Hermes?']);
724+
const projectPrompt = `what do we know about ${PROJECT_SLUG} Hermes?`;
725+
await runHermesProviderPrefetchSequence(tmpDir, [projectPrompt]);
703726

704727
const generalContext = recallRequests(fakeApi).find(
705728
(url) => url.searchParams.get('query') === 'do we like Example Contact?'
@@ -708,10 +731,10 @@ describe.skipIf(!HERMES_PYTHON)('Hermes real host integration', () => {
708731
expect(generalContext?.searchParams.getAll('tags')).toEqual([]);
709732

710733
const projectContext = recallRequests(fakeApi).find(
711-
(url) => url.searchParams.get('query') === 'what do we know about mcp-automem Hermes?'
734+
(url) => url.searchParams.get('query') === projectPrompt
712735
);
713736
expect(projectContext?.searchParams.get('limit')).toBe('10');
714-
expect(projectContext?.searchParams.getAll('tags')).toContain('mcp-automem');
737+
expect(projectContext?.searchParams.getAll('tags')).toContain(PROJECT_SLUG);
715738
}, 45_000);
716739

717740
it('provider explicit recall clamps large limits before calling AutoMem', async () => {

‎vitest.config.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@ export default defineConfig({
44
test: {
55
globals: true,
66
environment: 'node',
7-
// Default: exclude integration tests (they need real service)
8-
// Run integration tests with: npm run test:integration
9-
exclude: ['node_modules/**', 'dist/**', 'tests/integration/automem-service.test.ts'],
7+
// Setting `exclude` REPLACES vitest's defaults, so everything that must stay out of
8+
// the run has to be listed here — including the recursive `**/` forms, or a nested
9+
// copy of the repo brings its own node_modules and dist back into scope.
10+
exclude: [
11+
'**/node_modules/**',
12+
'**/dist/**',
13+
// Agent worktrees under .claude/worktrees/ are full checkouts of this repo. Without
14+
// this, `npm test` from the main checkout globs every suite twice — once from the
15+
// real tree and once from each worktree's (stale) copy — and reports failures for
16+
// code that is not the code under test.
17+
'**/.claude/**',
18+
// Integration tests need a real service; run them with `npm run test:integration`.
19+
'tests/integration/automem-service.test.ts',
20+
],
1021
coverage: {
1122
provider: 'v8',
1223
reporter: ['text', 'json', 'html'],

0 commit comments

Comments
 (0)