moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
import { vi } from 'vitest';
import type { CliBridge, MemoryHit } from '@waggle/hive-mind-shim-core';
export interface MockBridgeOverrides {
saveMemoryResult?: { id: string; success: boolean; workspace: string };
recallMemoryHits?: MemoryHit[];
cleanupFramesResult?: { pruned: number };
saveMemoryThrows?: Error;
}
export interface MockBridge extends CliBridge {
saveMemory: ReturnType<typeof vi.fn>;
recallMemory: ReturnType<typeof vi.fn>;
cleanupFrames: ReturnType<typeof vi.fn>;
callMcpTool: ReturnType<typeof vi.fn>;
setWorkspaceById: ReturnType<typeof vi.fn>;
getActiveWorkspaceId: ReturnType<typeof vi.fn>;
}
/**
* Mirrors the codex sibling test helper (tests/hooks/_test-helpers.ts). The
* cursor hooks drive the SAME shared `runHook` contract, so the same
* injectable CliBridge mock + stdout/exit captures apply unchanged.
*/
export function makeMockBridge(overrides: MockBridgeOverrides = {}): MockBridge {
let activeWorkspaceId: string | undefined;
const saveMemory = overrides.saveMemoryThrows
? vi.fn(async () => { throw overrides.saveMemoryThrows; })
: vi.fn(async () => overrides.saveMemoryResult ?? { id: 'frame-1', success: true, workspace: 'personal' });
const recallMemory = vi.fn(async () => overrides.recallMemoryHits ?? []);
const cleanupFrames = vi.fn(async () => overrides.cleanupFramesResult ?? { pruned: 0 });
const callMcpTool = vi.fn(async () => ({}));
const setWorkspaceById = vi.fn((id?: string) => { activeWorkspaceId = id; });
const getActiveWorkspaceId = vi.fn(() => activeWorkspaceId);
return {
saveMemory,
recallMemory,
cleanupFrames,
callMcpTool,
setWorkspaceById,
getActiveWorkspaceId,
} as unknown as MockBridge;
}
export interface CapturedHookOutput {
stdout: string[];
exits: number[];
}
export function makeHookCaptures(): CapturedHookOutput & {
writeStdout: (s: string) => void;
exit: (code: number) => void;
} {
const stdout: string[] = [];
const exits: number[] = [];
return {
stdout,
exits,
writeStdout: (s) => stdout.push(s),
exit: (c) => exits.push(c),
};
}

View File

@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest';
import { runPreCompact } from '../../src/hooks/pre-compact.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
describe('cursor pre-compact handler (preCompact — observational, fire-and-forget)', () => {
it('calls cleanupFrames to merge superseded frames before host compaction', async () => {
const bridge = makeMockBridge({ cleanupFramesResult: { pruned: 4 } });
const cap = makeHookCaptures();
await runPreCompact({
readStdin: async () => JSON.stringify({ conversation_id: 'conv-3' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.cleanupFrames).toHaveBeenCalledTimes(1);
expect(cap.exits).toEqual([0]);
});
it('still calls cleanupFrames even when no scope/session present', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runPreCompact({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.cleanupFrames).toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
it('FAIL-OPEN: exits 0 when cleanupFrames rejects', async () => {
const bridge = makeMockBridge();
bridge.cleanupFrames.mockRejectedValueOnce(new Error('cli unreachable'));
const cap = makeHookCaptures();
await runPreCompact({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(cap.exits).toEqual([0]);
});
});

View File

@@ -0,0 +1,77 @@
import { describe, expect, it } from 'vitest';
import { runSessionStart } from '../../src/hooks/session-start.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
import type { MemoryHit } from '@waggle/hive-mind-shim-core';
const HIT_FIXTURE: MemoryHit = {
id: 1,
content: '[hm src:cursor event:stop] past observation',
importance: 'important',
source: 'system',
score: 0.87,
created_at: '2026-04-28T10:00:00.000Z',
from: 'personal',
};
describe('cursor session-start handler', () => {
it('recalls personal-scoped frames and injects them as { additional_context } (cursor rename)', async () => {
const bridge = makeMockBridge({ recallMemoryHits: [HIT_FIXTURE] });
const cap = makeHookCaptures();
await runSessionStart({
readStdin: async () => JSON.stringify({ cwd: '/proj/x', recall_limit: 1 }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.recallMemory).toHaveBeenCalledWith('', { limit: 1, scope: 'personal', workspace: null });
expect(cap.stdout).toHaveLength(1);
const parsed = JSON.parse(cap.stdout[0]) as Record<string, unknown>;
// Cursor's sessionStart inject shape is { additional_context }, a rename of
// CC's hookSpecificOutput.additionalContext — assert the rename, and that
// the default CC envelope is NOT used.
expect(parsed['hookSpecificOutput']).toBeUndefined();
expect(typeof parsed['additional_context']).toBe('string');
expect(parsed['additional_context'] as string).toContain('past observation');
expect(cap.exits).toEqual([0]);
});
it('handles an empty recall result gracefully (still { additional_context })', async () => {
const bridge = makeMockBridge({ recallMemoryHits: [] });
const cap = makeHookCaptures();
await runSessionStart({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
const parsed = JSON.parse(cap.stdout[0]) as { additional_context: string };
expect(parsed.additional_context).toContain('no recalled frames');
expect(cap.exits).toEqual([0]);
});
it('annotates hits with their workspace origin when from != personal', async () => {
const bridge = makeMockBridge({ recallMemoryHits: [{ ...HIT_FIXTURE, from: 'workspace:team-foo' }] });
const cap = makeHookCaptures();
await runSessionStart({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
const parsed = JSON.parse(cap.stdout[0]) as { additional_context: string };
expect(parsed.additional_context).toContain('workspace:team-foo');
});
it('FAIL-OPEN: exits 0 even when the bridge throws', async () => {
const bridge = makeMockBridge();
bridge.recallMemory.mockRejectedValueOnce(new Error('cli unreachable'));
const cap = makeHookCaptures();
await runSessionStart({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(cap.exits).toEqual([0]);
});
});

View File

@@ -0,0 +1,190 @@
import { describe, expect, it, afterEach, vi } from 'vitest';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { runStop } from '../../src/hooks/stop.js';
import { cursorAdapter } from '../../src/adapter.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
import type { HookFrame } from '@waggle/hive-mind-shim-core';
describe('cursor stop handler (turn read via transcript_path)', () => {
const dirs: string[] = [];
afterEach(async () => {
vi.unstubAllEnvs();
for (const d of dirs.splice(0)) await rm(d, { recursive: true, force: true });
});
async function writeTranscript(text: string): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), 'hmcur-stop-'));
dirs.push(dir);
const file = join(dir, 'transcript.txt');
await writeFile(file, text, 'utf-8');
return file;
}
it('reads the completed turn off transcript_path and saves an important frame', async () => {
const transcriptPath = await writeTranscript(
'Here is the answer to your question about X. It depends on the config.',
);
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
transcript_path: transcriptPath,
cwd: '/proj/foo',
conversation_id: 'conv-9',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame.source).toBe('cursor');
expect(frame.scope).toBe('conv-9');
expect(['important', 'critical']).toContain(frame.importance);
expect(frame.content.length).toBeGreaterThan(0);
expect(cap.exits).toEqual([0]);
});
it('TOLERATE-NULL: a missing transcript_path file → no save, NO throw, exits 0 (fail open)', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
transcript_path: join(tmpdir(), 'does-not-exist-hmcur', 'nope.txt'),
conversation_id: 'conv-1',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
// No readable response → shared body skips the save; the hook still exits 0.
expect(bridge.saveMemory).not.toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
it('TOLERATE-NULL: no transcript_path at all (transcripts disabled) → no save, exits 0', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({ conversation_id: 'conv-2' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).not.toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
it('prefers an inline response key over the transcript file (forward-compat)', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
response: 'inline assistant message wins',
transcript_path: '/should/not/be/read.txt',
conversation_id: 's',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame.content.length).toBeGreaterThan(0);
});
it('links the Stop frame to its parent prompt frame when known', async () => {
const transcriptPath = await writeTranscript('done with the task');
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
transcript_path: transcriptPath,
parent_frame_id: 'frame-prompt-1',
conversation_id: 's',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame.parent).toBe('frame-prompt-1');
});
it('does NOT emit a discovery signal by default (WAGGLE_SIGNAL_EMIT off)', async () => {
vi.stubEnv('WAGGLE_SIGNAL_EMIT', '');
const transcriptPath = await writeTranscript('hi there');
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({ transcript_path: transcriptPath, conversation_id: 's' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.callMcpTool).not.toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
it('FAIL-OPEN: exits 0 even when saveMemory rejects', async () => {
const transcriptPath = await writeTranscript('some answer');
const bridge = makeMockBridge({ saveMemoryThrows: new Error('cli unreachable') });
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({ transcript_path: transcriptPath, conversation_id: 's' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(cap.exits).toEqual([0]);
});
});
// Direct unit coverage of the async transcript reader, including the
// ctx.readFile preference path (which the runHook drive path never supplies).
describe('cursorAdapter.extractResponse (transcript reader)', () => {
it('prefers ctx.readFile over node fs when supplied', async () => {
const readFile = vi.fn(async () => ' injected transcript text ');
const out = await cursorAdapter.extractResponse(
{ transcript_path: '/whatever/path.jsonl' },
{ readFile },
);
expect(readFile).toHaveBeenCalledWith('/whatever/path.jsonl');
expect(out).toBe('injected transcript text'); // trimmed
});
it('returns the inline response key without touching the file reader', async () => {
const readFile = vi.fn(async () => 'should not be read');
const out = await cursorAdapter.extractResponse(
{ response: 'inline wins', transcript_path: '/x.txt' },
{ readFile },
);
expect(out).toBe('inline wins');
expect(readFile).not.toHaveBeenCalled();
});
it('returns undefined when there is no transcript_path and no inline response', async () => {
const out = await cursorAdapter.extractResponse({ conversation_id: 's' }, {});
expect(out).toBeUndefined();
});
it('FAILS OPEN: a throwing reader yields undefined, never rejects', async () => {
const readFile = vi.fn(async () => { throw new Error('EACCES'); });
const out = await cursorAdapter.extractResponse(
{ transcript_path: '/locked.txt' },
{ readFile },
);
expect(out).toBeUndefined();
});
it('treats an empty/whitespace transcript as undefined (not an empty save)', async () => {
const readFile = vi.fn(async () => ' \n ');
const out = await cursorAdapter.extractResponse(
{ transcript_path: '/empty.txt' },
{ readFile },
);
expect(out).toBeUndefined();
});
});

View File

@@ -0,0 +1,83 @@
import { describe, expect, it } from 'vitest';
import { runUserPromptSubmit } from '../../src/hooks/user-prompt-submit.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
import type { HookFrame } from '@waggle/hive-mind-shim-core';
describe('cursor user-prompt-submit handler (SAVE-ONLY — beforeSubmitPrompt)', () => {
it('saves a temporary, cursor-sourced frame containing the prompt', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runUserPromptSubmit({
readStdin: async () => JSON.stringify({
prompt: 'How do I X?',
cwd: '/proj/foo',
conversation_id: 'conv-7',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame).toMatchObject({
content: 'How do I X?',
importance: 'temporary',
scope: 'conv-7',
source: 'cursor',
});
expect(cap.exits).toEqual([0]);
});
it('SAVE-ONLY: emits NO stdout (beforeSubmitPrompt cannot inject)', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runUserPromptSubmit({
readStdin: async () => JSON.stringify({ prompt: 'hi', conversation_id: 'c1' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
// The shared UserPromptSubmit body returns undefined → runHook writes nothing.
expect(cap.stdout).toHaveLength(0);
expect(cap.exits).toEqual([0]);
});
it('reads the cursor user_message fallback when prompt is absent', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runUserPromptSubmit({
readStdin: async () => JSON.stringify({ user_message: 'hello from cursor', conversation_id: 's1' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame.content).toBe('hello from cursor');
expect(frame.source).toBe('cursor');
});
it('skips the save when no prompt is present', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runUserPromptSubmit({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).not.toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
it('FAIL-OPEN: exits 0 even if saveMemory rejects', async () => {
const bridge = makeMockBridge({ saveMemoryThrows: new Error('cli down') });
const cap = makeHookCaptures();
await runUserPromptSubmit({
readStdin: async () => JSON.stringify({ prompt: 'x' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(cap.exits).toEqual([0]);
});
});