Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 13 additions & 36 deletions packages/agents/scripts/testing/smoke-test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,35 +183,16 @@ export function makeKbEditSmokeTest(): SmokeTestInvocation {
}

/**
* Stands up an event store carrying a single seed event plus an isolated home registering it as `default_kb`, then
* returns a `SmokeTestInvocation` that recalls the event by a body term scoped to that store. Exercises the full scope →
* ripgrep recall → note-set scoping → event projection pipeline, the only path that wires the bundled search primitive
* and the event projection together. `ripgrep` (`rg`) must be on PATH for recall to find the seed event.
* Stands up an event store plus an isolated home registering it, then returns a `SmokeTestInvocation` that scopes to a
* store name the registry does not carry. Exercises the bundled resolver from home discovery through registry parse to
* the scope verdict and the JSON result shape, stopping short of recall.
*
* Keep the invocation off the recall path: recalling here would put ripgrep on the critical path of every build.
*/
export function makeKbRetrieveEventsSmokeTest(): SmokeTestInvocation {
const storePath = mkdtempSync(path.join(tmpdir(), 'kb-retrieve-events-store-'));
mkdirSync(resolveKbDir(storePath), { recursive: true });

mkdirSync(resolveEventsDir(storePath), { recursive: true });
writeFileSync(
resolveEventPath({ storePath, id: 'smoke-event' }),
[
'---',
'recordType: event',
'id: smoke-event',
'captured-at: 2026-06-18T09:41:02Z',
'session: smoke',
'cwd: /tmp/smoke',
'summary: Smoke retrieve event',
'repo: owner/repo-smoke',
'---',
'',
'A smoke note mentioning retrievesmokequux.',
'',
].join('\n'),
'utf8',
);

const home = mkdtempSync(path.join(tmpdir(), 'kb-retrieve-events-home-'));
mkdirSync(path.join(home, '.agents'), { recursive: true });
writeFileSync(
Expand All @@ -221,7 +202,7 @@ export function makeKbRetrieveEventsSmokeTest(): SmokeTestInvocation {
);

return {
args: ['retrievesmokequux', '--store', 'codeassembly'],
args: ['retrievesmokequux', '--store', 'no-such-store'],
env: { ...process.env, HOME: home },
assertResult: assertKbRetrieveEventsSmokeResult,
};
Expand Down Expand Up @@ -466,23 +447,19 @@ function assertKbEditSmokeResult(result: unknown): void {
}
}

/** Assert the kb-retrieve-events smoke recalled the seed event and projected it with its summary and capture timestamp. */
/** Assert the kb-retrieve-events smoke resolved the registry and reported the requested store as unregistered. */
function assertKbRetrieveEventsSmokeResult(result: unknown): void {
if (!isRecord(result)) {
throw new TypeError('expected object result from kb-retrieve-events');
}
if (!Array.isArray(result.candidates) || result.candidates.length === 0) {
throw new Error(`expected at least one event candidate, got ${JSON.stringify(result)}`);
}
const candidate: unknown = result.candidates[0];
if (!isRecord(candidate)) {
throw new TypeError('expected a candidate object');
if (!Array.isArray(result.candidates) || result.candidates.length > 0) {
throw new Error(`expected an empty candidate table, got ${JSON.stringify(result)}`);
}
if (candidate.summary !== 'Smoke retrieve event') {
throw new Error(`expected summary 'Smoke retrieve event', got ${JSON.stringify(candidate.summary)}`);
if (!Array.isArray(result.scopedKbs) || result.scopedKbs.length > 0) {
throw new Error(`expected an empty scope, got ${JSON.stringify(result.scopedKbs)}`);
}
if (typeof candidate.capturedAt !== 'string' || !candidate.capturedAt.includes('2026-06-18')) {
throw new Error(`expected an ISO capturedAt, got ${JSON.stringify(candidate.capturedAt)}`);
if (typeof result.diagnostic !== 'string' || !result.diagnostic.includes('is not registered in kb.yaml')) {
throw new Error(`expected an unregistered-store diagnostic, got ${JSON.stringify(result.diagnostic)}`);
}
}

Expand Down
55 changes: 49 additions & 6 deletions packages/agents/src/kb-retrieve-events/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { join } from 'node:path';

import { describe, expect, it } from 'vitest';

import { buildRecallStub } from '../../kb-search/test-utils/build-recall-stub.ts';
import { parseArgs, runRetrieveEvents } from '../cli.ts';

// The vault and registry fixtures live with the shared search primitive (kb-search), which owns scope and recall.
Expand Down Expand Up @@ -58,7 +59,12 @@ describe(parseArgs, () => {

describe(runRetrieveEvents, () => {
it('returns an event candidate carrying its recurrence signals', async () => {
const result = await runRetrieveEvents({ argv: ['phantomwidget'], startDir: NOTES_VAULT, home: FIXTURES });
const result = await runRetrieveEvents({
argv: ['phantomwidget'],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub({ hits: [eventNote('01HZCEVENTAAAAAAAAAAAAAAAA.md')] }),
});

expect(result.candidates).toHaveLength(1);
expect(result.candidates[0]).toMatchObject({
Expand All @@ -70,15 +76,25 @@ describe(runRetrieveEvents, () => {
});

it('excludes assertion records, pointing the reader at assertion recall', async () => {
const result = await runRetrieveEvents({ argv: ['backpressure'], startDir: NOTES_VAULT, home: FIXTURES });
const result = await runRetrieveEvents({
argv: ['backpressure'],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub({ hits: [join(NOTES_VAULT, 'streams.md')] }),
});

// backpressure matches only an assertion, so the event table is empty and the diagnostic routes to assertion recall.
// The only recalled note is an assertion, so the event table is empty and the diagnostic routes to assertion recall.
expect(result.candidates).toEqual([]);
expect(result.diagnostic).toMatch(/kb-retrieve/);
});

it('reports a no-match diagnostic when nothing matches', async () => {
const result = await runRetrieveEvents({ argv: ['zzzznomatch'], startDir: NOTES_VAULT, home: FIXTURES });
const result = await runRetrieveEvents({
argv: ['zzzznomatch'],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub(),
});

expect(result.candidates).toEqual([]);
expect(result.diagnostic).toBe('no notes matched the query');
Expand All @@ -89,6 +105,7 @@ describe(runRetrieveEvents, () => {
argv: ['phantomwidget', '--store', 'no-such-store'],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub(),
});

expect(result.candidates).toEqual([]);
Expand All @@ -97,14 +114,24 @@ describe(runRetrieveEvents, () => {
});

it('reports a diagnostic when the query is blank', async () => {
const result = await runRetrieveEvents({ argv: [], startDir: NOTES_VAULT, home: FIXTURES });
const result = await runRetrieveEvents({
argv: [],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub(),
});

expect(result.candidates).toEqual([]);
expect(result.diagnostic).toBe('no query provided');
});

it('surfaces a declared impact on a candidate and omits it on an unrated one', async () => {
const result = await runRetrieveEvents({ argv: ['snorkleweft'], startDir: NOTES_VAULT, home: FIXTURES });
const result = await runRetrieveEvents({
argv: ['snorkleweft'],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub({ hits: snorkleweftEvents() }),
});

const byImpact = new Map(result.candidates.map((candidate) => [candidate.summary, candidate.impact]));
expect(byImpact.get('A snorkleweft outage rated high')).toBe('high');
Expand All @@ -116,6 +143,7 @@ describe(runRetrieveEvents, () => {
argv: ['snorkleweft', '--min-impact', 'high'],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub({ hits: snorkleweftEvents() }),
});

expect(result.candidates).toHaveLength(1);
Expand All @@ -127,9 +155,24 @@ describe(runRetrieveEvents, () => {
argv: ['snorkleweft', '--min-impact', 'critical'],
startDir: NOTES_VAULT,
home: FIXTURES,
recall: buildRecallStub({ hits: snorkleweftEvents() }),
});

expect(result.candidates).toEqual([]);
expect(result.diagnostic).toBe('all matches were below the --min-impact threshold of critical');
});
});

/** Resolves an event record's filename to its absolute path in the shared fixture vault. */
function eventNote(name: string): string {
return join(NOTES_VAULT, 'content', 'events', name);
}

/** The fixture events mentioning `snorkleweft`: one rated high, one rated lower, one left unrated. */
function snorkleweftEvents(): string[] {
return [
eventNote('01HZCEVENTHGHAAAAAAAAAAAAA.md'),
eventNote('01HZCEVENTMNRAAAAAAAAAAAAA.md'),
eventNote('01HZCEVENTNRTAAAAAAAAAAAAA.md'),
];
}
3 changes: 3 additions & 0 deletions packages/agents/src/kb-retrieve-events/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';

import { EVENT_IMPACT_LEVELS, type EventImpact, isEventImpact } from '@codeassembly/kb/records';

import type { RecallFn } from '../kb-search/recall.ts';
import { recordTypeOf, searchNotes } from '../kb-search/search.ts';
import type { RecallFilters } from '../kb-search/types.ts';
import { type FlagSpec, scanFlags, valueFlagMap } from '../lib/parse-flags.ts';
Expand Down Expand Up @@ -117,6 +118,7 @@ export async function runRetrieveEvents(input: {
argv: readonly string[];
startDir: string;
home?: string;
recall?: RecallFn;
}): Promise<EventRetrieveResult> {
const { query, allKbs, storeName, filters, minImpact } = parseArgs(input.argv);

Expand All @@ -131,6 +133,7 @@ export async function runRetrieveEvents(input: {
startDir: input.startDir,
...(storeName !== null && { storeName }),
...(input.home !== undefined && { home: input.home }),
...(input.recall !== undefined && { recall: input.recall }),
});

if (search.emptyScopeDiagnostic !== undefined) {
Expand Down
Loading
Loading