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,57 @@
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>;
}
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,50 @@
import { describe, expect, it } from 'vitest';
import { preCompactHandler, runPreCompact } from '../../src/hooks/pre-compact.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
describe('pre-compact handler', () => {
it('extracts scope from session_id, sessionId, or scope', () => {
expect(preCompactHandler.parse({ session_id: 'a' }).scope).toBe('a');
expect(preCompactHandler.parse({ sessionId: 'b' }).scope).toBe('b');
expect(preCompactHandler.parse({ scope: 'c' }).scope).toBe('c');
expect(preCompactHandler.parse({}).scope).toBeUndefined();
});
it('calls cleanupFrames (Commit 1.4 renamed from compactMemory)', async () => {
const bridge = makeMockBridge({ cleanupFramesResult: { pruned: 4 } });
const cap = makeHookCaptures();
await runPreCompact({
readStdin: async () => JSON.stringify({ session_id: 'sess-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 present', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runPreCompact({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.cleanupFrames).toHaveBeenCalled();
});
it('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,84 @@
import { describe, expect, it } from 'vitest';
import { sessionStartHandler, 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:claude-code event:stop] past observation',
importance: 'important',
source: 'system',
score: 0.87,
created_at: '2026-04-28T10:00:00.000Z',
from: 'personal',
};
describe('session-start handler', () => {
it('parses cwd from payload and falls back to process.cwd()', () => {
expect(sessionStartHandler.parse({ cwd: '/proj/x' }).cwd).toBe('/proj/x');
expect(sessionStartHandler.parse({}).cwd).toBe(process.cwd());
});
it('parses recallLimit number with default 20', () => {
expect(sessionStartHandler.parse({}).recallLimit).toBe(20);
expect(sessionStartHandler.parse({ recall_limit: 5 }).recallLimit).toBe(5);
expect(sessionStartHandler.parse({ recallLimit: 'not-a-number' }).recallLimit).toBe(20);
});
it('calls recallMemory with personal scope and formats hits into context', 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,
});
// Commit 1.4: switchWorkspace removed — only recallMemory should fire.
expect(bridge.recallMemory).toHaveBeenCalledWith('', { limit: 1, scope: 'personal', workspace: null });
expect(cap.stdout).toHaveLength(1);
const parsed = JSON.parse(cap.stdout[0]) as { hookSpecificOutput: { additionalContext: string } };
expect(parsed.hookSpecificOutput.additionalContext).toContain('past observation');
expect(cap.exits).toEqual([0]);
});
it('handles empty recall result gracefully', 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 { hookSpecificOutput: { additionalContext: string } };
expect(parsed.hookSpecificOutput.additionalContext).toContain('no recalled frames');
expect(cap.exits).toEqual([0]);
});
it('exits 0 even when bridge throws (fail-open)', 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]);
});
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 { hookSpecificOutput: { additionalContext: string } };
expect(parsed.hookSpecificOutput.additionalContext).toContain('workspace:team-foo');
});
});

View File

@@ -0,0 +1,65 @@
import { describe, expect, it } from 'vitest';
import {
parseHookArgs,
pickStringField,
pickStringFromObject,
safeJsonParse,
} from '../../src/hooks/_shared.js';
describe('safeJsonParse', () => {
it('returns {} for empty / whitespace input', () => {
expect(safeJsonParse('')).toEqual({});
expect(safeJsonParse(' ')).toEqual({});
});
it('returns parsed JSON when valid', () => {
expect(safeJsonParse('{"a":1}')).toEqual({ a: 1 });
});
it('returns {} when JSON is malformed', () => {
expect(safeJsonParse('not json')).toEqual({});
expect(safeJsonParse('{')).toEqual({});
});
});
describe('parseHookArgs', () => {
it('extracts --cli-path value when present', () => {
expect(parseHookArgs(['--cli-path', '/abs/cli.js'])).toEqual({ cliPath: '/abs/cli.js' });
});
it('returns {} when --cli-path is absent', () => {
expect(parseHookArgs([])).toEqual({});
expect(parseHookArgs(['--other-flag', 'value'])).toEqual({});
});
it('returns {} when --cli-path has no following value', () => {
expect(parseHookArgs(['--cli-path'])).toEqual({});
});
it('rejects empty-string value as missing', () => {
expect(parseHookArgs(['--cli-path', ''])).toEqual({});
});
it('handles flag in the middle of argv', () => {
expect(parseHookArgs(['--foo', 'bar', '--cli-path', '/x.js', '--baz'])).toEqual({ cliPath: '/x.js' });
});
});
describe('pickStringField / pickStringFromObject', () => {
it('returns the first non-empty string match', () => {
expect(pickStringField({ a: 'x', b: 'y' }, 'a', 'b')).toBe('x');
expect(pickStringField({ a: '', b: 'y' }, 'a', 'b')).toBe('y');
});
it('returns undefined when no key resolves', () => {
expect(pickStringField({}, 'a')).toBeUndefined();
expect(pickStringField(null, 'a')).toBeUndefined();
expect(pickStringField(undefined, 'a')).toBeUndefined();
});
it('pickStringFromObject only treats strings as hits', () => {
expect(pickStringFromObject({ a: 1 } as Record<string, unknown>, 'a')).toBeUndefined();
expect(pickStringFromObject({ a: 'ok' }, 'a')).toBe('ok');
expect(pickStringFromObject({ a: '' }, 'a')).toBeUndefined();
});
});

View File

@@ -0,0 +1,262 @@
import { describe, expect, it } from 'vitest';
import { runStop, stopHandler } from '../../src/hooks/stop.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
describe('stop handler', () => {
it('extracts response from payload.response or payload.assistant_message', () => {
expect(stopHandler.parse({ response: 'r' }).response).toBe('r');
expect(stopHandler.parse({ assistant_message: 'a' }).response).toBe('a');
expect(stopHandler.parse({}).response).toBe('');
});
it('summarizes long responses and saves an important frame', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
const longResp = 'First sentence. ' + 'X'.repeat(2000) + '.';
await runStop({
readStdin: async () => JSON.stringify({
response: longResp,
cwd: '/proj',
session_id: 'sess-2',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
const arg = bridge.saveMemory.mock.calls[0][0];
expect(['important', 'critical']).toContain(arg.importance);
expect(typeof arg.content).toBe('string');
expect(arg.content.length).toBeLessThanOrEqual(401); // budget + ellipsis
expect(cap.exits).toEqual([0]);
});
it('promotes to critical when response contains a "never" directive', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
response: 'never commit secrets to the public repo.',
cwd: '/proj',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
const arg = bridge.saveMemory.mock.calls[0][0];
expect(arg.importance).toBe('critical');
});
it('attaches parent frame id when supplied', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
response: 'something happened.',
parent_frame_id: 'frame-99',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
const arg = bridge.saveMemory.mock.calls[0][0];
expect(arg.parent).toBe('frame-99');
});
it('skips save when response is empty', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).not.toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
});
// ── AI-OS Phase 1E — opt-in v2 signal emission ─────────────────────
describe('stop handler — WAGGLE_SIGNAL_EMIT (Phase 1E)', () => {
// Capture the global fetch so we can assert on the emitter call.
// maybeEmitDiscovery uses globalThis.fetch when no fetchImpl is
// passed — the production hook does not pass one.
function withCapturedFetch<T>(
fetchImpl: typeof globalThis.fetch,
fn: () => Promise<T>,
): Promise<T> {
const original = globalThis.fetch;
globalThis.fetch = fetchImpl;
return fn().finally(() => {
globalThis.fetch = original;
});
}
function makeOkFetch(): typeof globalThis.fetch & {
calls: Array<{ url: string; body: unknown }>;
} {
const calls: Array<{ url: string; body: unknown }> = [];
const impl = (async (url: string | URL | Request, init?: RequestInit) => {
const body = init?.body ? JSON.parse(String(init.body)) : null;
calls.push({ url: String(url), body });
return new Response(
JSON.stringify({
dispatched: true,
message: {
id: 'srv-1',
teamId: 'personal::claude-code-hook',
senderId: 'claude-code-hook',
type: 'broadcast',
subtype: 'discovery',
content: body?.content ?? {},
referenceId: null,
routing: null,
createdAt: new Date().toISOString(),
},
}),
{ status: 201, headers: { 'content-type': 'application/json' } },
);
}) as typeof globalThis.fetch & { calls: typeof calls };
impl.calls = calls;
return impl;
}
function withEnv<T>(
key: string,
value: string | undefined,
fn: () => Promise<T>,
): Promise<T> {
const prev = process.env[key];
if (value === undefined) delete process.env[key];
else process.env[key] = value;
return fn().finally(() => {
if (prev === undefined) delete process.env[key];
else process.env[key] = prev;
});
}
it('does not emit when WAGGLE_SIGNAL_EMIT is unset', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
const f = makeOkFetch();
await withEnv('WAGGLE_SIGNAL_EMIT', undefined, () =>
withCapturedFetch(f, () =>
runStop({
readStdin: async () => JSON.stringify({
response: 'never commit secrets to the public repo.',
cwd: '/proj',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
}),
),
);
expect(f.calls).toHaveLength(0);
});
it('does not emit when WAGGLE_SIGNAL_EMIT=0', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
const f = makeOkFetch();
await withEnv('WAGGLE_SIGNAL_EMIT', '0', () =>
withCapturedFetch(f, () =>
runStop({
readStdin: async () => JSON.stringify({
response: 'never commit secrets to the public repo.',
cwd: '/proj',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
}),
),
);
expect(f.calls).toHaveLength(0);
});
it('emits on critical importance when WAGGLE_SIGNAL_EMIT=1', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
const f = makeOkFetch();
await withEnv('WAGGLE_SIGNAL_EMIT', '1', () =>
withCapturedFetch(f, () =>
runStop({
readStdin: async () => JSON.stringify({
response: 'never commit secrets to the public repo.',
cwd: '/proj',
session_id: 'sess-cc',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
}),
),
);
expect(f.calls).toHaveLength(1);
expect(f.calls[0].url).toContain('/api/waggle-dance/signal');
const body = f.calls[0].body as Record<string, unknown>;
expect(body.type).toBe('broadcast');
expect(body.subtype).toBe('discovery');
expect(body.senderId).toBe('claude-code-hook');
const content = body.content as Record<string, unknown>;
expect(content.tool).toBe('claude-code');
expect(content.eventType).toBe('stop');
// Critical "never" sentence → critical importance → high-or-critical
// emission per the Importance→emit mapping (critical → critical).
expect(content.importance).toBe('critical');
expect(content.sessionId).toBe('sess-cc');
expect(content.frameId).toBe('frame-1');
expect(content.memoryWorkspace).toBe('personal');
expect(content.summary).toContain('never commit secrets');
});
it('does not emit on a normal-importance turn (emission policy floor)', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
const f = makeOkFetch();
// A short benign response → classifyImportance returns 'normal',
// which our mapping bumps to 'normal' (not high/critical) → the
// maybeEmitDiscovery policy skips emission.
await withEnv('WAGGLE_SIGNAL_EMIT', '1', () =>
withCapturedFetch(f, () =>
runStop({
readStdin: async () => JSON.stringify({
response: 'Hello.',
cwd: '/proj',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
}),
),
);
expect(f.calls).toHaveLength(0);
});
it('saves frame even when the signal endpoint is unreachable (fail-open)', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
const unreachable = (async () => {
throw new Error('ECONNREFUSED');
}) as typeof globalThis.fetch;
await withEnv('WAGGLE_SIGNAL_EMIT', 'true', () =>
withCapturedFetch(unreachable, () =>
runStop({
readStdin: async () => JSON.stringify({
response: 'never commit secrets to the public repo.',
cwd: '/proj',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
}),
),
);
// Frame save still happened — emitter failure does not block.
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
expect(cap.exits).toEqual([0]);
});
});

View File

@@ -0,0 +1,60 @@
import { describe, expect, it } from 'vitest';
import { runUserPromptSubmit, userPromptSubmitHandler } from '../../src/hooks/user-prompt-submit.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
describe('user-prompt-submit handler', () => {
it('extracts prompt from payload.prompt or payload.user_message', () => {
expect(userPromptSubmitHandler.parse({ prompt: 'hi' }).prompt).toBe('hi');
expect(userPromptSubmitHandler.parse({ user_message: 'hello' }).prompt).toBe('hello');
expect(userPromptSubmitHandler.parse({}).prompt).toBe('');
});
it('saves a temporary 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',
session_id: 'sess-7',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
const arg = bridge.saveMemory.mock.calls[0][0];
expect(arg).toMatchObject({
content: 'How do I X?',
importance: 'temporary',
scope: 'sess-7',
source: 'claude-code',
});
expect(cap.exits).toEqual([0]);
});
it('skips 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('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]);
});
});