@kb-labs/shared-testing 2.94.0 → 2.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,68 +1,5 @@
1
- import { platform } from '@kb-labs/core-runtime';
2
- import { ILLM, ICache, IEmbeddings, IVectorStore, IStorage, IAnalytics, ILogger, IEventBus, LLMOptions, LLMResponse, LLMMessage, LLMToolCallOptions, LLMToolCallResponse, LLMToolCall } from '@kb-labs/core-platform';
3
- import { HostContext, PlatformServices, UIFacade, PluginContextV3, PluginAPI, EnvironmentAPI, RuntimeAPI, SnapshotAPI, TraceContext, WorkspaceAPI } from '@kb-labs/plugin-contracts';
4
-
5
- /**
6
- * @module @kb-labs/shared-testing/setup-platform
7
- *
8
- * Solves the "singleton gap" problem: composables (useLLM, useCache, etc.)
9
- * read from the global platform singleton, but createTestContext() only
10
- * populates ctx.platform. This module bridges the gap by setting test mocks
11
- * directly into the global singleton.
12
- */
13
-
14
- /**
15
- * Options for setting up the test platform.
16
- * Only adapters that are provided will be registered.
17
- * Omitted adapters will use the PlatformContainer's built-in fallbacks.
18
- */
19
- interface TestPlatformOptions {
20
- llm?: ILLM;
21
- cache?: ICache;
22
- embeddings?: IEmbeddings;
23
- vectorStore?: IVectorStore;
24
- storage?: IStorage;
25
- analytics?: IAnalytics;
26
- logger?: ILogger;
27
- eventBus?: IEventBus;
28
- }
29
- interface TestPlatformResult {
30
- /** The global platform singleton (with test mocks applied) */
31
- platform: typeof platform;
32
- /** Call in afterEach() to reset the singleton to a clean state */
33
- cleanup: () => void;
34
- }
35
- /**
36
- * Setup the global platform singleton with test mocks.
37
- *
38
- * This ensures that useLLM(), useCache() and other composables
39
- * return the test mocks instead of uninitialized/stale adapters.
40
- *
41
- * @example
42
- * ```typescript
43
- * import { setupTestPlatform, mockLLM } from '@kb-labs/shared-testing';
44
- *
45
- * describe('my handler', () => {
46
- * let cleanup: () => void;
47
- *
48
- * beforeEach(() => {
49
- * const result = setupTestPlatform({
50
- * llm: mockLLM().onAnyComplete().respondWith('hello'),
51
- * });
52
- * cleanup = result.cleanup;
53
- * });
54
- *
55
- * afterEach(() => cleanup());
56
- *
57
- * it('uses LLM', async () => {
58
- * const llm = useLLM(); // Returns the test mock!
59
- * const res = await llm!.complete('test');
60
- * expect(res.content).toBe('hello');
61
- * });
62
- * });
63
- * ```
64
- */
65
- declare function setupTestPlatform(options?: TestPlatformOptions): TestPlatformResult;
1
+ import { LLMOptions, LLMResponse, LLMMessage, LLMToolCallOptions, LLMToolCallResponse, ILLM, LLMToolCall, ICache, IStorage, ILogger } from '@kb-labs/core-platform';
2
+ import { IEmbeddings } from '@kb-labs/core-platform/adapters';
66
3
 
67
4
  /**
68
5
  * @module @kb-labs/shared-testing/mock-llm
@@ -315,231 +252,29 @@ interface MockLoggerInstance extends ILogger {
315
252
  declare function mockLogger(sharedMessages?: LogEntry[]): MockLoggerInstance;
316
253
 
317
254
  /**
318
- * @module @kb-labs/shared-testing/create-test-context
255
+ * @module @kb-labs/shared-testing/mock-embeddings
319
256
  *
320
- * Enhanced test context factory that bridges ctx.platform and the global singleton.
257
+ * Deterministic in-memory `IEmbeddings` test double.
321
258
  *
322
- * Unlike the original createTestContext() from SDK, this version:
323
- * - Uses mockLLM/mockCache/mockLogger with vi.fn() spies (not noop functions)
324
- * - Syncs ctx.platform adapters with the global singleton via setupTestPlatform()
325
- * - Provides a cleanup function to reset the singleton in afterEach()
259
+ * Generates stable, normalised vectors from a text hash so tests that
260
+ * need real math (cosine similarity, nearest-neighbour lookups) produce
261
+ * reproducible results across runs — without any network call or model.
326
262
  *
327
263
  * @example
328
264
  * ```typescript
329
- * import { createTestContext, mockLLM } from '@kb-labs/shared-testing';
330
- *
331
- * const llm = mockLLM().onAnyComplete().respondWith('hello');
332
- * const { ctx, cleanup } = createTestContext({ platform: { llm } });
333
- *
334
- * // Both work — ctx.platform and useLLM() return the same mock
335
- * await handler.execute(ctx, args);
336
- * expect(llm.complete).toHaveBeenCalled();
337
- *
338
- * cleanup(); // Reset singleton in afterEach
265
+ * import { MockEmbeddings } from '@kb-labs/shared-testing';
266
+ * const embed = new MockEmbeddings();
267
+ * const vec = await embed.embed('hello'); // length === 1536, normalised
339
268
  * ```
340
269
  */
341
270
 
342
- interface CreateTestContextOptions {
343
- pluginId?: string;
344
- pluginVersion?: string;
345
- host?: 'cli' | 'rest' | 'workflow' | 'webhook';
346
- hostContext?: HostContext;
347
- config?: unknown;
348
- cwd?: string;
349
- outdir?: string;
350
- tenantId?: string;
351
- signal?: AbortSignal;
352
- /** Override platform services. Also synced to global singleton. */
353
- platform?: Partial<PlatformServices>;
354
- /** Override UI facade */
355
- ui?: Partial<UIFacade>;
356
- /**
357
- * If true, sync platform adapters to the global singleton
358
- * so that useLLM(), useCache(), etc. return the test mocks.
359
- * @default true
360
- */
361
- syncSingleton?: boolean;
362
- }
363
- interface TestContextResult<TConfig = unknown> {
364
- /** The plugin context with test mocks */
365
- ctx: PluginContextV3<TConfig>;
366
- /** Call in afterEach() to reset the global singleton */
367
- cleanup: () => void;
271
+ declare class MockEmbeddings implements IEmbeddings {
272
+ readonly dimensions = 1536;
273
+ private hash;
274
+ private generateVector;
275
+ embed(text: string): Promise<number[]>;
276
+ embedBatch(texts: string[]): Promise<number[][]>;
277
+ getDimensions(): Promise<number>;
368
278
  }
369
- declare function createMockTrace(): TraceContext;
370
- declare function createMockUI(): UIFacade;
371
- declare function createMockRuntime(): RuntimeAPI;
372
- declare function createMockEnvironmentAPI(): EnvironmentAPI;
373
- declare function createMockWorkspaceAPI(): WorkspaceAPI;
374
- declare function createMockSnapshotAPI(): SnapshotAPI;
375
- declare function createInfraApiMocks(): Pick<PluginAPI, 'environment' | 'workspace' | 'snapshot'>;
376
- declare function createMockPluginAPI(): PluginAPI;
377
- declare const createMockPlatformApi: typeof createMockPluginAPI;
378
- declare function createMockPluginContextV3<TConfig = unknown>(options?: CreateTestContextOptions): TestContextResult<TConfig>;
379
- /**
380
- * Create a test context for plugin development.
381
- *
382
- * Unlike the original SDK version, this:
383
- * - Uses mock builders with vi.fn() spies (not noop functions)
384
- * - Syncs platform adapters to the global singleton by default
385
- * - Returns a cleanup function for afterEach()
386
- *
387
- * @example
388
- * ```typescript
389
- * import { createTestContext, mockLLM } from '@kb-labs/shared-testing';
390
- *
391
- * describe('my handler', () => {
392
- * let cleanup: () => void;
393
- *
394
- * beforeEach(() => {
395
- * const llm = mockLLM().onAnyComplete().respondWith('ok');
396
- * const result = createTestContext({ platform: { llm } });
397
- * cleanup = result.cleanup;
398
- * // Use result.ctx in your tests
399
- * });
400
- *
401
- * afterEach(() => cleanup());
402
- * });
403
- * ```
404
- */
405
- declare function createTestContext<TConfig = unknown>(options?: CreateTestContextOptions): TestContextResult<TConfig>;
406
-
407
- /**
408
- * @module @kb-labs/shared-testing/test-command
409
- *
410
- * Single-function test runner for plugin command handlers.
411
- *
412
- * Eliminates boilerplate: no manual context creation, no descriptor setup,
413
- * no mock wiring. Import your handler, call testCommand(), assert on result.
414
- *
415
- * @example
416
- * ```typescript
417
- * import { testCommand, mockLLM } from '@kb-labs/shared-testing';
418
- * import handler from '../src/commands/greet.js';
419
- *
420
- * it('greets the user', async () => {
421
- * const result = await testCommand(handler, {
422
- * flags: { name: 'Alice' },
423
- * });
424
- *
425
- * expect(result.exitCode).toBe(0);
426
- * expect(result.result).toEqual({ message: 'Hello, Alice!' });
427
- * expect(result.ui.success).toHaveBeenCalledWith('Hello, Alice!');
428
- * });
429
- * ```
430
- */
431
-
432
- /**
433
- * Any handler that has an execute(ctx, input) method.
434
- * Covers CommandHandlerV3, RouteHandler, Handler, and raw objects.
435
- */
436
- interface TestableHandler<TConfig = unknown, TInput = unknown, TResult = unknown> {
437
- execute(context: PluginContextV3<TConfig>, input: TInput): Promise<TResult> | TResult;
438
- cleanup?(): Promise<void> | void;
439
- }
440
- /**
441
- * Options for testCommand().
442
- *
443
- * Provide only what you need — everything else gets sensible defaults.
444
- */
445
- interface TestCommandOptions<TConfig = unknown> {
446
- /** CLI flags (sugar for input: { flags, argv }) */
447
- flags?: Record<string, unknown>;
448
- /** CLI positional arguments (default: []) */
449
- argv?: string[];
450
- /** REST query params (sugar for input: { query }) */
451
- query?: Record<string, unknown>;
452
- /** REST request body (sugar for input: { body }) */
453
- body?: unknown;
454
- /** REST route params (sugar for input: { params }) */
455
- params?: Record<string, unknown>;
456
- /**
457
- * Raw input object. If provided, flags/argv/query/body/params are ignored.
458
- * Use this when your handler expects a custom input shape.
459
- */
460
- input?: unknown;
461
- /** Host type (default: 'cli') */
462
- host?: 'cli' | 'rest' | 'workflow' | 'webhook';
463
- /** Plugin config passed as ctx.config */
464
- config?: TConfig;
465
- /** Working directory (default: process.cwd()) */
466
- cwd?: string;
467
- /** Tenant ID */
468
- tenantId?: string;
469
- /** Abort signal */
470
- signal?: AbortSignal;
471
- /** Override platform services (e.g., { llm: mockLLM().onAnyComplete().respondWith('ok') }) */
472
- platform?: Partial<PlatformServices>;
473
- /** Override UI facade (individual methods are merged with default spy UI) */
474
- ui?: Partial<UIFacade>;
475
- /**
476
- * Sync platform mocks to global singleton for composables (useLLM, useCache, etc.)
477
- * @default true
478
- */
479
- syncSingleton?: boolean;
480
- }
481
- /**
482
- * Result of testCommand() — everything you need for assertions.
483
- */
484
- interface TestCommandResult<TResult = unknown> {
485
- /** Exit code from CommandResult (0 = success). Defaults to 0 if handler returns raw data. */
486
- exitCode: number;
487
- /** The result data from the handler */
488
- result: TResult | undefined;
489
- /** Custom metadata from CommandResult.meta */
490
- meta: Record<string, unknown> | undefined;
491
- /** The raw return value from handler.execute() — useful for non-CommandResult handlers */
492
- raw: unknown;
493
- /** UI facade with vi.fn() spies — assert on ui.success, ui.error, etc. */
494
- ui: UIFacade;
495
- /** The full context that was passed to the handler */
496
- ctx: PluginContextV3;
497
- /** Call in afterEach() to reset the global platform singleton */
498
- cleanup: () => void;
499
- }
500
- /**
501
- * Test a plugin command handler with minimal boilerplate.
502
- *
503
- * Creates a test context, builds the input object, calls handler.execute(),
504
- * and returns a result with UI spies for assertions.
505
- *
506
- * @example CLI command
507
- * ```typescript
508
- * import handler from '../src/commands/greet.js';
509
- *
510
- * const result = await testCommand(handler, {
511
- * flags: { name: 'Alice' },
512
- * });
513
- * expect(result.exitCode).toBe(0);
514
- * expect(result.ui.success).toHaveBeenCalledWith('Hello, Alice!');
515
- * ```
516
- *
517
- * @example REST handler
518
- * ```typescript
519
- * import handler from '../src/routes/create-plan.js';
520
- *
521
- * const result = await testCommand(handler, {
522
- * host: 'rest',
523
- * body: { name: 'v2.0' },
524
- * query: { workspace: 'root' },
525
- * });
526
- * expect(result.result).toMatchObject({ name: 'v2.0' });
527
- * ```
528
- *
529
- * @example With LLM mock
530
- * ```typescript
531
- * import { testCommand, mockLLM } from '@kb-labs/shared-testing';
532
- * import handler from '../src/commands/analyze.js';
533
- *
534
- * const llm = mockLLM().onAnyComplete().respondWith('looks good');
535
- * const result = await testCommand(handler, {
536
- * flags: { file: 'index.ts' },
537
- * platform: { llm },
538
- * });
539
- * expect(llm.complete).toHaveBeenCalled();
540
- * expect(result.exitCode).toBe(0);
541
- * ```
542
- */
543
- declare function testCommand<TResult = unknown, TConfig = unknown>(handler: TestableHandler<TConfig, any, any>, options?: TestCommandOptions<TConfig>): Promise<TestCommandResult<TResult>>;
544
279
 
545
- export { type CreateTestContextOptions, type LLMCall, type LLMToolCallRecord, type LogEntry, type MockCacheInstance, type MockLLM, type MockLLMInstance, type MockLoggerInstance, type MockStorageInstance, type TestCommandOptions, type TestCommandResult, type TestContextResult, type TestPlatformOptions, type TestPlatformResult, type TestableHandler, createInfraApiMocks, createMockEnvironmentAPI, createMockPlatformApi, createMockPluginAPI, createMockPluginContextV3, createMockRuntime, createMockSnapshotAPI, createMockTrace, createMockUI, createMockWorkspaceAPI, createTestContext, mockCache, mockLLM, mockLogger, mockStorage, setupTestPlatform, testCommand };
280
+ export { type LLMCall, type LLMToolCallRecord, type LogEntry, type MockCacheInstance, MockEmbeddings, type MockLLM, type MockLLMInstance, type MockLoggerInstance, type MockStorageInstance, mockCache, mockLLM, mockLogger, mockStorage };
package/dist/index.js CHANGED
@@ -1,29 +1,6 @@
1
- import { resetPlatform, platform } from '@kb-labs/core-runtime';
2
1
  import { vi } from 'vitest';
3
2
 
4
- // src/setup-platform.ts
5
- function setupTestPlatform(options = {}) {
6
- resetPlatform();
7
- const adapterMap = [
8
- ["llm", options.llm],
9
- ["cache", options.cache],
10
- ["embeddings", options.embeddings],
11
- ["vectorStore", options.vectorStore],
12
- ["storage", options.storage],
13
- ["analytics", options.analytics],
14
- ["logger", options.logger],
15
- ["eventBus", options.eventBus]
16
- ];
17
- for (const [key, instance] of adapterMap) {
18
- if (instance !== void 0) {
19
- platform.setAdapter(key, instance);
20
- }
21
- }
22
- return {
23
- platform,
24
- cleanup: () => resetPlatform()
25
- };
26
- }
3
+ // src/mock-llm.ts
27
4
  var MockLLMBuilder = class {
28
5
  rules = [];
29
6
  defaultResponse = "mock response";
@@ -401,543 +378,38 @@ function mockLogger(sharedMessages) {
401
378
  };
402
379
  return instance;
403
380
  }
404
- function createMockTrace() {
405
- return {
406
- traceId: "test-trace-id",
407
- spanId: "test-span-id",
408
- parentSpanId: void 0,
409
- addEvent: () => {
410
- },
411
- setAttribute: () => {
412
- },
413
- recordError: () => {
414
- }
415
- };
416
- }
417
- function createMockUI() {
418
- const messages = [];
419
- const mockColor = (text) => text;
420
- const mockColors = {
421
- success: mockColor,
422
- error: mockColor,
423
- warning: mockColor,
424
- info: mockColor,
425
- primary: mockColor,
426
- accent: mockColor,
427
- highlight: mockColor,
428
- secondary: mockColor,
429
- emphasis: mockColor,
430
- muted: mockColor,
431
- foreground: mockColor,
432
- dim: mockColor,
433
- bold: mockColor,
434
- underline: mockColor,
435
- inverse: mockColor
436
- };
437
- return {
438
- colors: mockColors,
439
- symbols: {
440
- success: "+",
441
- error: "x",
442
- warning: "!",
443
- info: "i",
444
- bullet: "-",
445
- clock: "T",
446
- folder: "D",
447
- package: "P",
448
- pointer: ">",
449
- section: "#",
450
- separator: "-",
451
- border: "|",
452
- topLeft: "+",
453
- topRight: "+",
454
- bottomLeft: "+",
455
- bottomRight: "+",
456
- leftT: "+",
457
- rightT: "+"
458
- },
459
- write: vi.fn((text) => messages.push(`WRITE: ${text}`)),
460
- info: vi.fn((msg) => messages.push(`INFO: ${msg}`)),
461
- success: vi.fn((msg) => messages.push(`SUCCESS: ${msg}`)),
462
- warn: vi.fn((msg) => messages.push(`WARN: ${msg}`)),
463
- error: vi.fn((err) => messages.push(`ERROR: ${err instanceof Error ? err.message : err}`)),
464
- debug: vi.fn((msg) => messages.push(`DEBUG: ${msg}`)),
465
- spinner: vi.fn((msg) => {
466
- messages.push(`SPINNER: ${msg}`);
467
- return {
468
- update: vi.fn((m) => messages.push(`SPINNER UPDATE: ${m}`)),
469
- succeed: vi.fn((m) => messages.push(`SPINNER SUCCEED: ${m ?? msg}`)),
470
- fail: vi.fn((m) => messages.push(`SPINNER FAIL: ${m ?? msg}`)),
471
- stop: vi.fn()
472
- };
473
- }),
474
- table: vi.fn((data) => messages.push(`TABLE: ${JSON.stringify(data)}`)),
475
- json: vi.fn((data) => messages.push(`JSON: ${JSON.stringify(data)}`)),
476
- newline: vi.fn(() => messages.push("")),
477
- divider: vi.fn(() => messages.push("-".repeat(40))),
478
- box: vi.fn((content, title) => {
479
- if (title) {
480
- messages.push(`+- ${title} -+`);
481
- }
482
- messages.push(content);
483
- if (title) {
484
- messages.push(`+${"-".repeat(title.length + 4)}+`);
485
- }
486
- }),
487
- sideBox: vi.fn((options) => {
488
- messages.push(`+- ${options.title} -+`);
489
- if (options.summary) {
490
- Object.entries(options.summary).forEach(([key, value]) => {
491
- messages.push(` ${key}: ${value}`);
492
- });
493
- }
494
- if (options.sections) {
495
- options.sections.forEach((section) => {
496
- if (section.header) {
497
- messages.push(` ${section.header}:`);
498
- }
499
- section.items.forEach((item) => messages.push(` ${item}`));
500
- });
501
- }
502
- if (options.timing) {
503
- messages.push(` Timing: ${options.timing}ms`);
504
- }
505
- messages.push("+" + "-".repeat(options.title.length + 4) + "+");
506
- }),
507
- confirm: vi.fn(async () => true),
508
- prompt: vi.fn(async () => "")
509
- };
510
- }
511
- function createMockRuntime() {
512
- const mockFS = {
513
- readFile: vi.fn(async () => ""),
514
- readFileBuffer: vi.fn(async () => new Uint8Array()),
515
- writeFile: vi.fn(async () => {
516
- }),
517
- readdir: vi.fn(async () => []),
518
- readdirWithStats: vi.fn(async () => []),
519
- mkdir: vi.fn(async () => {
520
- }),
521
- rm: vi.fn(async () => {
522
- }),
523
- copy: vi.fn(async () => {
524
- }),
525
- move: vi.fn(async () => {
526
- }),
527
- glob: vi.fn(async () => []),
528
- stat: vi.fn(async () => ({
529
- isFile: () => false,
530
- isDirectory: () => false,
531
- size: 0,
532
- mtime: Date.now(),
533
- ctime: Date.now()
534
- })),
535
- exists: vi.fn(async () => false),
536
- resolve: (path) => path,
537
- relative: (path) => path,
538
- join: (...segments) => segments.join("/"),
539
- dirname: (path) => path.split("/").slice(0, -1).join("/"),
540
- basename: (path) => path.split("/").pop() ?? "",
541
- extname: (path) => {
542
- const base = path.split("/").pop() ?? "";
543
- const idx = base.lastIndexOf(".");
544
- return idx > 0 ? base.slice(idx) : "";
545
- }
546
- };
547
- const mockFetch = vi.fn(async () => new Response("mock"));
548
- const mockEnv = vi.fn((_key) => void 0);
549
- return {
550
- fs: mockFS,
551
- fetch: mockFetch,
552
- env: mockEnv
553
- };
554
- }
555
- function createMockEnvironmentAPI() {
556
- return {
557
- create: vi.fn(async () => ({
558
- environmentId: "env_mock_1",
559
- provider: "mock",
560
- status: "ready",
561
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
562
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
563
- })),
564
- status: vi.fn(async (environmentId) => ({
565
- environmentId,
566
- status: "ready",
567
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
568
- })),
569
- destroy: vi.fn(async () => {
570
- }),
571
- renewLease: vi.fn(async () => ({
572
- leaseId: "lease_mock_1",
573
- acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
574
- expiresAt: new Date(Date.now() + 10 * 60 * 1e3).toISOString()
575
- }))
576
- };
577
- }
578
- function createMockWorkspaceAPI() {
579
- return {
580
- materialize: vi.fn(async () => ({
581
- workspaceId: "ws_mock_1",
582
- provider: "mock",
583
- status: "ready",
584
- rootPath: "/tmp/ws_mock_1",
585
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
586
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
587
- })),
588
- attach: vi.fn(async (request) => ({
589
- workspaceId: request.workspaceId,
590
- environmentId: request.environmentId,
591
- mountPath: request.mountPath ?? "/workspace",
592
- attachedAt: (/* @__PURE__ */ new Date()).toISOString()
593
- })),
594
- release: vi.fn(async () => {
595
- }),
596
- status: vi.fn(async (workspaceId) => ({
597
- workspaceId,
598
- status: "ready",
599
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
600
- }))
601
- };
602
- }
603
- function createMockSnapshotAPI() {
604
- return {
605
- capture: vi.fn(async (request) => ({
606
- snapshotId: request.snapshotId ?? "snap_mock_1",
607
- provider: "mock",
608
- status: "ready",
609
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
610
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
611
- workspaceId: request.workspaceId,
612
- environmentId: request.environmentId
613
- })),
614
- restore: vi.fn(async (request) => ({
615
- snapshotId: request.snapshotId,
616
- restoredAt: (/* @__PURE__ */ new Date()).toISOString(),
617
- workspaceId: request.workspaceId,
618
- environmentId: request.environmentId,
619
- targetPath: request.targetPath
620
- })),
621
- status: vi.fn(async (snapshotId) => ({
622
- snapshotId,
623
- status: "ready",
624
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
625
- })),
626
- delete: vi.fn(async () => {
627
- }),
628
- gc: vi.fn(async (request) => ({
629
- scanned: 0,
630
- deleted: 0,
631
- dryRun: request?.dryRun ?? false
632
- }))
633
- };
634
- }
635
- function createInfraApiMocks() {
636
- return {
637
- environment: createMockEnvironmentAPI(),
638
- workspace: createMockWorkspaceAPI(),
639
- snapshot: createMockSnapshotAPI()
640
- };
641
- }
642
- function createMockPluginAPI() {
643
- const infra = createInfraApiMocks();
644
- return {
645
- lifecycle: {
646
- onCleanup: vi.fn()
647
- },
648
- state: {
649
- get: vi.fn(async () => void 0),
650
- set: vi.fn(async () => {
651
- }),
652
- delete: vi.fn(async () => {
653
- }),
654
- has: vi.fn(async () => false),
655
- getMany: vi.fn(async () => /* @__PURE__ */ new Map()),
656
- setMany: vi.fn(async () => {
657
- })
658
- },
659
- artifacts: {
660
- write: vi.fn(async () => "/mock/path"),
661
- list: vi.fn(async () => []),
662
- read: vi.fn(async () => ""),
663
- readBuffer: vi.fn(async () => new Uint8Array()),
664
- exists: vi.fn(async () => false),
665
- path: vi.fn(() => "/mock/path")
666
- },
667
- shell: {
668
- exec: vi.fn(async () => ({ code: 0, stdout: "", stderr: "", ok: true }))
669
- },
670
- events: {
671
- emit: vi.fn(async () => {
672
- })
673
- },
674
- invoke: {
675
- call: vi.fn(async (_pluginId, _input, _options) => void 0)
676
- },
677
- workflows: {
678
- run: vi.fn(async () => "mock-run-id"),
679
- wait: vi.fn(async () => void 0),
680
- status: vi.fn(async () => null),
681
- cancel: vi.fn(async () => {
682
- }),
683
- list: vi.fn(async () => [])
684
- },
685
- jobs: {
686
- submit: vi.fn(async () => "mock-job-id"),
687
- schedule: vi.fn(async () => "mock-scheduled-job-id"),
688
- wait: vi.fn(async () => void 0),
689
- status: vi.fn(async () => null),
690
- cancel: vi.fn(async () => false),
691
- list: vi.fn(async () => [])
692
- },
693
- cron: {
694
- register: vi.fn(async () => {
695
- }),
696
- unregister: vi.fn(async () => {
697
- }),
698
- list: vi.fn(async () => []),
699
- pause: vi.fn(async () => {
700
- }),
701
- resume: vi.fn(async () => {
702
- }),
703
- trigger: vi.fn(async () => {
704
- })
705
- },
706
- environment: infra.environment,
707
- workspace: infra.workspace,
708
- snapshot: infra.snapshot
709
- };
710
- }
711
- var createMockPlatformApi = createMockPluginAPI;
712
- function createMockPluginContextV3(options = {}) {
713
- return createTestContext(options);
714
- }
715
- function createTestContext(options = {}) {
716
- const {
717
- pluginId = "test-plugin",
718
- pluginVersion = "0.0.0",
719
- host = "cli",
720
- hostContext,
721
- config,
722
- cwd = process.cwd(),
723
- outdir,
724
- tenantId,
725
- signal,
726
- platform: platformOverrides,
727
- ui: uiOverrides,
728
- syncSingleton = true
729
- } = options;
730
- const resolvedOutdir = outdir ?? `${cwd}/.kb/output`;
731
- const defaultHostContext = hostContext ?? (() => {
732
- switch (host) {
733
- case "cli":
734
- return { host: "cli", argv: ["test"], flags: {} };
735
- case "rest":
736
- return {
737
- host: "rest",
738
- method: "GET",
739
- path: "/test",
740
- requestId: "test-req-001",
741
- traceId: "test-trace-001"
742
- };
743
- case "workflow":
744
- return { host: "workflow", workflowId: "test-wf", runId: "test-run", stepId: "test-step" };
745
- case "webhook":
746
- return { host: "webhook", event: "test:event" };
381
+
382
+ // src/mock-embeddings.ts
383
+ var MockEmbeddings = class {
384
+ dimensions = 1536;
385
+ hash(text) {
386
+ let h = 0;
387
+ for (let i = 0; i < text.length; i++) {
388
+ h = (h << 5) - h + text.charCodeAt(i);
389
+ h = h & h;
747
390
  }
748
- })();
749
- const defaultLogger = mockLogger();
750
- const defaultLLM = mockLLM();
751
- const defaultCache = mockCache();
752
- const defaultStorage = mockStorage();
753
- const defaultPlatform = {
754
- logger: defaultLogger,
755
- llm: defaultLLM,
756
- embeddings: {
757
- embed: vi.fn(async () => []),
758
- embedBatch: vi.fn(async () => [[]]),
759
- dimensions: 1536,
760
- getDimensions: vi.fn(async () => 1536)
761
- },
762
- vectorStore: {
763
- search: vi.fn(async () => []),
764
- upsert: vi.fn(async () => {
765
- }),
766
- delete: vi.fn(async () => {
767
- }),
768
- count: vi.fn(async () => 0)
769
- },
770
- cache: defaultCache,
771
- storage: defaultStorage,
772
- analytics: {
773
- track: vi.fn(async () => {
774
- }),
775
- identify: vi.fn(async () => {
776
- }),
777
- flush: vi.fn(async () => {
778
- })
779
- },
780
- eventBus: {
781
- publish: vi.fn(async () => {
782
- }),
783
- subscribe: vi.fn(() => () => {
784
- })
785
- },
786
- config: {
787
- getConfig: vi.fn(async () => ({})),
788
- getRawConfig: vi.fn(async () => ({}))
789
- },
790
- invoke: {
791
- call: vi.fn(async () => ({ success: true, data: null })),
792
- isAvailable: vi.fn(async () => false)
793
- },
794
- sqlDatabase: {
795
- query: vi.fn(async () => ({ rows: [], rowCount: 0 })),
796
- transaction: vi.fn(async () => ({ query: vi.fn(), commit: vi.fn(), rollback: vi.fn() })),
797
- close: vi.fn(async () => {
798
- })
799
- },
800
- documentDatabase: {
801
- find: vi.fn(async () => []),
802
- findById: vi.fn(async () => null),
803
- insertOne: vi.fn(async () => ({ id: "mock", createdAt: 0, updatedAt: 0 })),
804
- updateMany: vi.fn(async () => 0),
805
- updateById: vi.fn(async () => null),
806
- deleteMany: vi.fn(async () => 0),
807
- deleteById: vi.fn(async () => false),
808
- count: vi.fn(async () => 0),
809
- close: vi.fn(async () => {
810
- })
811
- },
812
- logs: {
813
- query: vi.fn(async () => ({ logs: [], total: 0, hasMore: false, source: "buffer" })),
814
- getById: vi.fn(async () => null),
815
- search: vi.fn(async () => ({ logs: [], total: 0, hasMore: false })),
816
- subscribe: vi.fn(() => () => {
817
- }),
818
- getStats: vi.fn(async () => ({})),
819
- getCapabilities: vi.fn(() => ({ hasBuffer: false, hasPersistence: false, hasSearch: false, hasStreaming: false }))
391
+ return h;
392
+ }
393
+ generateVector(text) {
394
+ const seed = this.hash(text);
395
+ const vec = [];
396
+ for (let i = 0; i < this.dimensions; i++) {
397
+ vec.push(Math.sin(seed * (i + 1)) * 0.5);
820
398
  }
821
- };
822
- const finalPlatform = {
823
- ...defaultPlatform,
824
- ...platformOverrides
825
- };
826
- let cleanupFn = () => {
827
- };
828
- if (syncSingleton) {
829
- const result = setupTestPlatform({
830
- llm: finalPlatform.llm,
831
- cache: finalPlatform.cache,
832
- storage: finalPlatform.storage,
833
- logger: finalPlatform.logger,
834
- analytics: finalPlatform.analytics,
835
- embeddings: finalPlatform.embeddings,
836
- vectorStore: finalPlatform.vectorStore,
837
- eventBus: finalPlatform.eventBus
838
- });
839
- cleanupFn = result.cleanup;
399
+ const mag = Math.sqrt(vec.reduce((s, v) => s + v * v, 0));
400
+ return vec.map((v) => v / mag);
840
401
  }
841
- const defaultUI = createMockUI();
842
- const finalUI = {
843
- ...defaultUI,
844
- ...uiOverrides
845
- };
846
- const ctx = {
847
- host,
848
- requestId: "test-trace:test-span",
849
- pluginId,
850
- pluginVersion,
851
- tenantId,
852
- cwd,
853
- outdir: resolvedOutdir,
854
- config,
855
- signal,
856
- trace: createMockTrace(),
857
- hostContext: defaultHostContext,
858
- ui: finalUI,
859
- platform: finalPlatform,
860
- runtime: createMockRuntime(),
861
- api: createMockPluginAPI()
862
- };
863
- return { ctx, cleanup: cleanupFn };
864
- }
865
-
866
- // src/test-command.ts
867
- async function testCommand(handler, options = {}) {
868
- const {
869
- flags,
870
- argv = [],
871
- query,
872
- body,
873
- params,
874
- input: rawInput,
875
- host = "cli",
876
- config,
877
- cwd,
878
- tenantId,
879
- signal,
880
- platform: platform2,
881
- ui,
882
- syncSingleton = true
883
- } = options;
884
- const ctxOptions = {
885
- host,
886
- config,
887
- cwd,
888
- tenantId,
889
- signal,
890
- platform: platform2,
891
- ui,
892
- syncSingleton
893
- };
894
- const { ctx, cleanup } = createTestContext(ctxOptions);
895
- let input;
896
- if (rawInput !== void 0) {
897
- input = rawInput;
898
- } else if (host === "rest") {
899
- input = {
900
- ...query !== void 0 ? { query } : {},
901
- ...body !== void 0 ? { body } : {},
902
- ...params !== void 0 ? { params } : {}
903
- };
904
- } else {
905
- input = { flags: flags ?? {}, argv };
402
+ async embed(text) {
403
+ return this.generateVector(text);
906
404
  }
907
- let raw;
908
- try {
909
- raw = await handler.execute(ctx, input);
910
- } finally {
911
- await handler.cleanup?.();
405
+ async embedBatch(texts) {
406
+ return texts.map((t) => this.generateVector(t));
912
407
  }
913
- let exitCode = 0;
914
- let result;
915
- let meta;
916
- if (raw != null && typeof raw === "object" && "exitCode" in raw) {
917
- const cmdResult = raw;
918
- exitCode = cmdResult.exitCode;
919
- result = cmdResult.result;
920
- meta = cmdResult.meta;
921
- } else if (raw === void 0 || raw === null) {
922
- exitCode = 0;
923
- result = void 0;
924
- meta = void 0;
925
- } else {
926
- exitCode = 0;
927
- result = raw;
928
- meta = void 0;
408
+ async getDimensions() {
409
+ return this.dimensions;
929
410
  }
930
- return {
931
- exitCode,
932
- result,
933
- meta,
934
- raw,
935
- ui: ctx.ui,
936
- ctx,
937
- cleanup
938
- };
939
- }
411
+ };
940
412
 
941
- export { createInfraApiMocks, createMockEnvironmentAPI, createMockPlatformApi, createMockPluginAPI, createMockPluginContextV3, createMockRuntime, createMockSnapshotAPI, createMockTrace, createMockUI, createMockWorkspaceAPI, createTestContext, mockCache, mockLLM, mockLogger, mockStorage, setupTestPlatform, testCommand };
413
+ export { MockEmbeddings, mockCache, mockLLM, mockLogger, mockStorage };
942
414
  //# sourceMappingURL=index.js.map
943
415
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/setup-platform.ts","../src/mock-llm.ts","../src/mock-cache.ts","../src/mock-storage.ts","../src/mock-logger.ts","../src/create-test-context.ts","../src/test-command.ts"],"names":["vi","platform"],"mappings":";;;;AA0EO,SAAS,iBAAA,CAAkB,OAAA,GAA+B,EAAC,EAAuB;AAEvF,EAAA,aAAA,EAAc;AAGd,EAAA,MAAM,UAAA,GAAuC;AAAA,IAC3C,CAAC,KAAA,EAAO,OAAA,CAAQ,GAAG,CAAA;AAAA,IACnB,CAAC,OAAA,EAAS,OAAA,CAAQ,KAAK,CAAA;AAAA,IACvB,CAAC,YAAA,EAAc,OAAA,CAAQ,UAAU,CAAA;AAAA,IACjC,CAAC,aAAA,EAAe,OAAA,CAAQ,WAAW,CAAA;AAAA,IACnC,CAAC,SAAA,EAAW,OAAA,CAAQ,OAAO,CAAA;AAAA,IAC3B,CAAC,WAAA,EAAa,OAAA,CAAQ,SAAS,CAAA;AAAA,IAC/B,CAAC,QAAA,EAAU,OAAA,CAAQ,MAAM,CAAA;AAAA,IACzB,CAAC,UAAA,EAAY,OAAA,CAAQ,QAAQ;AAAA,GAC/B;AAEA,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,QAAQ,CAAA,IAAK,UAAA,EAAY;AACxC,IAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,MAAA,QAAA,CAAS,UAAA,CAAW,KAAK,QAAQ,CAAA;AAAA,IACnC;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,OAAA,EAAS,MAAM,aAAA;AAAc,GAC/B;AACF;AClBA,IAAM,iBAAN,MAAqB;AAAA,EACX,QAA0B,EAAC;AAAA,EAC3B,eAAA,GAAqF,eAAA;AAAA,EACrF,YAAA,GAAyB,CAAC,MAAM,CAAA;AAAA,EAChC,YAAA,GAA6B,IAAA;AAAA,EAC7B,oBAAmC,EAAC;AAAA,EACpC,uBAAA,GAA0B,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlC,WAAW,OAAA,EAA0I;AACnJ,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,CAAC,QAAA,KAAa;AACzB,QAAA,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,EAAE,OAAA,EAAS,UAAU,CAAA;AACrC,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAA,GAAkI;AAChI,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,CAAC,QAAA,KAAa;AACzB,QAAA,IAAA,CAAK,eAAA,GAAkB,QAAA;AACvB,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,MAAA,EAAkC;AAC1C,IAAA,IAAA,CAAK,YAAA,GAAe,MAAA;AACpB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,KAAA,EAA8B;AACpC,IAAA,IAAA,CAAK,YAAA,GAAe,KAAA;AACpB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAA,CAAc,KAAA,EAAsB,OAAA,GAAU,EAAA,EAAoB;AAChE,IAAA,IAAA,CAAK,iBAAA,GAAoB,KAAA;AACzB,IAAA,IAAA,CAAK,uBAAA,GAA0B,OAAA;AAC/B,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAyB;AACvB,IAAA,MAAM,QAAmB,EAAC;AAC1B,IAAA,MAAM,kBAAuC,EAAC;AAC9C,IAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AACnB,IAAA,MAAM,kBAAkB,IAAA,CAAK,eAAA;AAC7B,IAAA,MAAM,eAAe,IAAA,CAAK,YAAA;AAC1B,IAAA,MAAM,eAAe,IAAA,CAAK,YAAA;AAC1B,IAAA,MAAM,oBAAoB,IAAA,CAAK,iBAAA;AAC/B,IAAA,MAAM,0BAA0B,IAAA,CAAK,uBAAA;AAErC,IAAA,SAAS,gBAAgB,MAAA,EAA6B;AAEpD,MAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,QAAA,IAAI,aAAA,CAAc,IAAA,CAAK,OAAA,EAAS,MAAM,CAAA,EAAG;AACvC,UAAA,OAAO,UAAA,CAAW,IAAA,CAAK,QAAA,EAAU,MAAM,CAAA;AAAA,QACzC;AAAA,MACF;AAEA,MAAA,OAAO,UAAA,CAAW,iBAAiB,MAAM,CAAA;AAAA,IAC3C;AAEA,IAAA,MAAM,UAAA,GAAa,EAAA,CAAG,EAAA,CAAG,OAAO,QAAgB,OAAA,KAA+C;AAC7F,MAAA,IAAI,YAAA,EAAc;AAAC,QAAA,MAAM,YAAA;AAAA,MAAa;AACtC,MAAA,MAAM,QAAA,GAAW,gBAAgB,MAAM,CAAA;AACvC,MAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,OAAA,EAAS,UAAU,CAAA;AACxC,MAAA,OAAO,QAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,MAAM,QAAA,GAAW,EAAA,CAAG,EAAA,CAAG,iBAAiB,SAAiB,QAAA,EAA8C;AACrG,MAAA,IAAI,YAAA,EAAc;AAAC,QAAA,MAAM,YAAA;AAAA,MAAa;AACtC,MAAA,KAAA,MAAW,SAAS,YAAA,EAAc;AAChC,QAAA,MAAM,KAAA;AAAA,MACR;AAAA,IACF,CAAC,CAAA;AAED,IAAA,MAAM,eAAA,GAAkB,EAAA,CAAG,EAAA,CAAG,OAAO,UAAwB,OAAA,KAA8D;AACzH,MAAA,IAAI,YAAA,EAAc;AAAC,QAAA,MAAM,YAAA;AAAA,MAAa;AAEtC,MAAA,MAAM,WAAA,GAAc,SAAS,MAAA,CAAO,CAAA,CAAA,KAAK,EAAE,IAAA,KAAS,MAAM,EAAE,GAAA,EAAI;AAChE,MAAA,MAAM,MAAA,GAAS,aAAa,OAAA,IAAW,EAAA;AACvC,MAAA,MAAM,YAAA,GAAe,gBAAgB,MAAM,CAAA;AAE3C,MAAA,MAAM,QAAA,GAAgC;AAAA,QACpC,GAAG,YAAA;AAAA,QACH,OAAA,EAAS,iBAAA,CAAkB,MAAA,GAAS,CAAA,GAAI,0BAA0B,YAAA,CAAa,OAAA;AAAA,QAC/E,SAAA,EAAW,iBAAA,CAAkB,MAAA,GAAS,CAAA,GAAI,iBAAA,GAAoB;AAAA,OAChE;AAEA,MAAA,eAAA,CAAgB,IAAA,CAAK,EAAE,QAAA,EAAU,OAAA,EAAS,UAAU,CAAA;AACpD,MAAA,OAAO,QAAA;AAAA,IACT,CAAC,CAAA;AAED,IAAA,MAAM,QAAA,GAA4B;AAAA,MAChC,QAAA,EAAU,UAAA;AAAA,MACV,MAAA,EAAQ,QAAA;AAAA,MACR,aAAA,EAAe,eAAA;AAAA,MACf,KAAA;AAAA,MACA,SAAA,EAAW,eAAA;AAAA,MACX,IAAI,QAAA,GAAW;AACb,QAAA,OAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AAAA,MAC/B,CAAA;AAAA,MACA,YAAY,MAAM;AAChB,QAAA,KAAA,CAAM,MAAA,GAAS,CAAA;AACf,QAAA,eAAA,CAAgB,MAAA,GAAS,CAAA;AACzB,QAAA,UAAA,CAAW,SAAA,EAAU;AACrB,QAAA,QAAA,CAAS,SAAA,EAAU;AACnB,QAAA,eAAA,CAAgB,SAAA,EAAU;AAAA,MAC5B;AAAA,KACF;AAEA,IAAA,OAAO,QAAA;AAAA,EACT;AACF,CAAA;AAMA,SAAS,aAAA,CAAc,SAAwB,MAAA,EAAyB;AACtE,EAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAAC,IAAA,OAAO,MAAA,CAAO,SAAS,OAAO,CAAA;AAAA,EAAE;AAClE,EAAA,IAAI,mBAAmB,MAAA,EAAQ;AAAC,IAAA,OAAO,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,EAAE;AAC5D,EAAA,OAAO,QAAQ,MAAM,CAAA;AACvB;AAEA,SAAS,UAAA,CACP,OACA,MAAA,EACa;AACb,EAAA,MAAM,WAAW,OAAO,KAAA,KAAU,UAAA,GAAa,KAAA,CAAM,MAAM,CAAA,GAAI,KAAA;AAC/D,EAAA,IAAI,OAAO,aAAa,QAAA,EAAU;AAChC,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,QAAA;AAAA,MACT,KAAA,EAAO,EAAE,YAAA,EAAc,CAAA,EAAG,kBAAkB,CAAA,EAAE;AAAA,MAC9C,KAAA,EAAO;AAAA,KACT;AAAA,EACF;AACA,EAAA,OAAO,QAAA;AACT;AAgCO,SAAS,OAAA,GAAmB;AACjC,EAAA,MAAM,OAAA,GAAU,IAAI,cAAA,EAAe;AAKnC,EAAA,IAAI,KAAA,GAAgC,IAAA;AAEpC,EAAA,SAAS,WAAA,GAA+B;AACtC,IAAA,IAAI,CAAC,KAAA,EAAO;AAAC,MAAA,KAAA,GAAQ,QAAQ,KAAA,EAAM;AAAA,IAAE;AACrC,IAAA,OAAO,KAAA;AAAA,EACT;AAOA,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,OAAA,EAA0B;AAAA,IAChD,GAAA,CAAI,MAAA,EAAuB,IAAA,EAAuB,SAAA,EAA6B;AAE7E,MAAA,IAAI,QAAQ,MAAA,IAAU,OAAO,MAAA,CAAO,IAAc,MAAM,UAAA,EAAY;AAClE,QAAA,MAAM,MAAA,GAAU,MAAA,CAAO,IAAc,CAAA,CAAsC,KAAK,MAAM,CAAA;AAEtF,QAAA,OAAO,IAAI,IAAA,KAA6B;AACtC,UAAA,KAAA,GAAQ,IAAA;AACR,UAAA,MAAM,MAAA,GAAS,MAAA,CAAO,GAAG,IAAI,CAAA;AAE7B,UAAA,IAAI,MAAA,KAAW,UAAW,MAAA,KAAW,IAAA,IAAQ,OAAO,MAAA,KAAW,QAAA,IAAY,iBAAiB,MAAA,EAAS;AACnG,YAAA,IAAI,WAAW,MAAA,EAAQ;AAAC,cAAA,OAAO,KAAA;AAAA,YAAM;AAErC,YAAA,MAAM,iBAAA,GAAoB,MAAA;AAC1B,YAAA,OAAO;AAAA,cACL,WAAA,EAAa,IAAI,KAAA,KAA8B;AAC7C,gBAAA,KAAA,GAAQ,IAAA;AACR,gBAAA,iBAAA,CAAkB,WAAA,CAAY,GAAG,KAAK,CAAA;AACtC,gBAAA,OAAO,KAAA;AAAA,cACT;AAAA,aACF;AAAA,UACF;AACA,UAAA,OAAO,MAAA;AAAA,QACT,CAAA;AAAA,MACF;AAGA,MAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,MAAA,OAAO,SAAS,IAAc,CAAA;AAAA,IAChC;AAAA,GACD,CAAA;AAED,EAAA,OAAO,KAAA;AACT;ACrQO,SAAS,UAAU,OAAA,EAAsD;AAC9E,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAwB;AAC1C,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAA+B;AAGtD,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AAClD,MAAA,KAAA,CAAM,IAAI,GAAA,EAAK,EAAE,KAAA,EAAO,SAAA,EAAW,MAAM,CAAA;AAAA,IAC3C;AAAA,EACF;AAEA,EAAA,SAAS,UAAU,KAAA,EAA4B;AAC7C,IAAA,OAAO,MAAM,SAAA,KAAc,IAAA,IAAQ,IAAA,CAAK,GAAA,KAAQ,KAAA,CAAM,SAAA;AAAA,EACxD;AAIA,EAAA,MAAM,QAAoC,EAAC;AAG3C,EAAA,SAAS,IAA2C,EAAA,EAAU;AAC5D,IAAA,MAAM,CAAA,GAAIA,EAAAA,CAAG,EAAA,CAAG,EAAE,CAAA;AAClB,IAAA,KAAA,CAAM,KAAK,CAAwC,CAAA;AACnD,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,OAAO,GAAA,KAAkC;AACzD,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC3B,IAAA,IAAI,CAAC,KAAA,IAAS,SAAA,CAAU,KAAK,CAAA,EAAG;AAC9B,MAAA,IAAI,KAAA,EAAO;AAAC,QAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MAAE;AAC9B,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACf,CAAC,CAAA;AAED,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,OAAO,GAAA,EAAa,OAAgB,GAAA,KAAgC;AACpF,IAAA,KAAA,CAAM,IAAI,GAAA,EAAK;AAAA,MACb,KAAA;AAAA,MACA,SAAA,EAAW,GAAA,GAAM,IAAA,CAAK,GAAA,KAAQ,GAAA,GAAM;AAAA,KACrC,CAAA;AAAA,EACH,CAAC,CAAA;AAED,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,OAAO,GAAA,KAA+B;AACzD,IAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,EAClB,CAAC,CAAA;AAED,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAO,OAAA,KAAoC;AAC7D,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,KAAA,CAAM,KAAA,EAAM;AACZ,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACxC,IAAA,KAAA,MAAW,GAAA,IAAO,KAAA,CAAM,IAAA,EAAK,EAAG;AAC9B,MAAA,IAAI,GAAA,CAAI,UAAA,CAAW,MAAM,CAAA,EAAG;AAC1B,QAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,MAClB;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAO,GAAA,EAAa,OAAe,MAAA,KAAkC;AACtF,IAAA,IAAI,CAAC,UAAA,CAAW,GAAA,CAAI,GAAG,CAAA,EAAG;AAAC,MAAA,UAAA,CAAW,GAAA,CAAI,GAAA,EAAK,EAAE,CAAA;AAAA,IAAE;AACnD,IAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,CAAI,GAAG,CAAA;AAE9B,IAAA,MAAM,MAAM,GAAA,CAAI,SAAA,CAAU,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,MAAM,CAAA;AAClD,IAAA,IAAI,OAAO,CAAA,EAAG;AAAC,MAAA,GAAA,CAAI,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAAE;AAElC,IAAA,GAAA,CAAI,IAAA,CAAK,EAAE,KAAA,EAAO,MAAA,EAAQ,CAAA;AAE1B,IAAA,GAAA,CAAI,KAAK,CAAC,CAAA,EAAG,MAAM,CAAA,CAAE,KAAA,GAAQ,EAAE,KAAK,CAAA;AAAA,EACtC,CAAC,CAAA;AAED,EAAA,MAAM,eAAA,GAAkB,GAAA,CAAI,OAAO,GAAA,EAAa,KAAa,GAAA,KAAmC;AAC9F,IAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,GAAA,EAAK;AAAC,MAAA,OAAO,EAAC;AAAA,IAAE;AACrB,IAAA,OAAO,GAAA,CACJ,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,KAAA,IAAS,GAAA,IAAO,CAAA,CAAE,KAAA,IAAS,GAAG,CAAA,CAC5C,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,MAAM,CAAA;AAAA,EACtB,CAAC,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,OAAO,GAAA,EAAa,MAAA,KAAkC;AACvE,IAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,GAAA,EAAK;AAAC,MAAA;AAAA,IAAO;AAClB,IAAA,MAAM,MAAM,GAAA,CAAI,SAAA,CAAU,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,MAAM,CAAA;AAClD,IAAA,IAAI,OAAO,CAAA,EAAG;AAAC,MAAA,GAAA,CAAI,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAAE;AAAA,EACpC,CAAC,CAAA;AAED,EAAA,MAAM,gBAAA,GAAmB,GAAA,CAAI,OAAO,GAAA,EAAa,OAAgB,GAAA,KAAmC;AAClG,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,QAAA,IAAY,CAAC,SAAA,CAAU,QAAQ,CAAA,EAAG;AAAC,MAAA,OAAO,KAAA;AAAA,IAAM;AACpD,IAAA,KAAA,CAAM,IAAI,GAAA,EAAK;AAAA,MACb,KAAA;AAAA,MACA,SAAA,EAAW,GAAA,GAAM,IAAA,CAAK,GAAA,KAAQ,GAAA,GAAM;AAAA,KACrC,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA;AAED,EAAA,MAAM,QAAA,GAA8B;AAAA,IAClC,GAAA,EAAK,KAAA;AAAA,IACL,GAAA,EAAK,KAAA;AAAA,IACL,MAAA,EAAQ,QAAA;AAAA,IACR,KAAA,EAAO,OAAA;AAAA,IACP,IAAA,EAAM,MAAA;AAAA,IACN,aAAA,EAAe,eAAA;AAAA,IACf,IAAA,EAAM,MAAA;AAAA,IACN,cAAA,EAAgB,gBAAA;AAAA,IAChB,IAAI,KAAA,GAAQ;AAAE,MAAA,OAAO,KAAA;AAAA,IAAO,CAAA;AAAA,IAC5B,IAAI,UAAA,GAAa;AAAE,MAAA,OAAO,UAAA;AAAA,IAAY,CAAA;AAAA,IACtC,OAAO,MAAM;AACX,MAAA,KAAA,CAAM,KAAA,EAAM;AACZ,MAAA,UAAA,CAAW,KAAA,EAAM;AACjB,MAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AAAC,QAAA,CAAA,CAAE,SAAA,EAAU;AAAA,MAAE;AAAA,IACxC;AAAA,GACF;AAEA,EAAA,OAAO,QAAA;AACT;AC9HO,SAAS,YAAY,OAAA,EAAgE;AAC1F,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAoB;AAGtC,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,OAAO,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AACrD,MAAA,KAAA,CAAM,GAAA,CAAI,MAAM,OAAO,OAAA,KAAY,WAAW,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,GAAI,OAAO,CAAA;AAAA,IAC9E;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAASA,EAAAA,CAAG,EAAA,CAAG,OAAO,IAAA,KAAyC;AACnE,IAAA,OAAO,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA,IAAK,IAAA;AAAA,EAC5B,CAAC,CAAA;AAED,EAAA,MAAM,OAAA,GAAUA,EAAAA,CAAG,EAAA,CAAG,OAAO,MAAc,IAAA,KAAgC;AACzE,IAAA,KAAA,CAAM,GAAA,CAAI,MAAM,IAAI,CAAA;AAAA,EACtB,CAAC,CAAA;AAED,EAAA,MAAM,QAAA,GAAWA,EAAAA,CAAG,EAAA,CAAG,OAAO,IAAA,KAAgC;AAC5D,IAAA,KAAA,CAAM,OAAO,IAAI,CAAA;AAAA,EACnB,CAAC,CAAA;AAED,EAAA,MAAM,MAAA,GAASA,EAAAA,CAAG,EAAA,CAAG,OAAO,MAAA,KAAsC;AAChE,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,IAAA,EAAK,EAAG;AAC/B,MAAA,IAAI,IAAA,CAAK,UAAA,CAAW,MAAM,CAAA,EAAG;AAC3B,QAAA,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,MAClB;AAAA,IACF;AACA,IAAA,OAAO,OAAO,IAAA,EAAK;AAAA,EACrB,CAAC,CAAA;AAED,EAAA,MAAM,QAAA,GAAWA,EAAAA,CAAG,EAAA,CAAG,OAAO,IAAA,KAAmC;AAC/D,IAAA,OAAO,KAAA,CAAM,IAAI,IAAI,CAAA;AAAA,EACvB,CAAC,CAAA;AAED,EAAA,MAAM,QAAA,GAAgC;AAAA,IACpC,IAAA,EAAM,MAAA;AAAA,IACN,KAAA,EAAO,OAAA;AAAA,IACP,MAAA,EAAQ,QAAA;AAAA,IACR,IAAA,EAAM,MAAA;AAAA,IACN,MAAA,EAAQ,QAAA;AAAA,IACR,IAAI,KAAA,GAAQ;AAAE,MAAA,OAAO,KAAA;AAAA,IAAO,CAAA;AAAA,IAC5B,OAAO,MAAM;AACX,MAAA,KAAA,CAAM,KAAA,EAAM;AACZ,MAAA,MAAA,CAAO,SAAA,EAAU;AACjB,MAAA,OAAA,CAAQ,SAAA,EAAU;AAClB,MAAA,QAAA,CAAS,SAAA,EAAU;AACnB,MAAA,MAAA,CAAO,SAAA,EAAU;AACjB,MAAA,QAAA,CAAS,SAAA,EAAU;AAAA,IACrB;AAAA,GACF;AAEA,EAAA,OAAO,QAAA;AACT;ACjDO,SAAS,WAAW,cAAA,EAAiD;AAC1E,EAAA,MAAM,QAAA,GAAuB,kBAAkB,EAAC;AAGhD,EAAA,SAAS,sBAAsB,KAAA,EAA0B;AACvD,IAAA,OAAOA,EAAAA,CAAG,EAAA,CAAG,CAAC,GAAA,EAAa,IAAA,KAAmC;AAC5D,MAAA,QAAA,CAAS,IAAA,CAAK,EAAE,KAAA,EAAO,GAAA,EAAK,MAAM,CAAA;AAAA,IACpC,CAAC,CAAA;AAAA,EACH;AAGA,EAAA,SAAS,qBAAqB,KAAA,EAA0B;AACtD,IAAA,OAAOA,EAAAA,CAAG,EAAA,CAAG,CAAC,GAAA,EAAa,OAAe,IAAA,KAAmC;AAC3E,MAAA,QAAA,CAAS,KAAK,EAAE,KAAA,EAAO,GAAA,EAAK,KAAA,EAAO,MAAM,CAAA;AAAA,IAC3C,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,QAAA,GAA+B;AAAA,IACnC,KAAA,EAAO,sBAAsB,OAAO,CAAA;AAAA,IACpC,KAAA,EAAO,sBAAsB,OAAO,CAAA;AAAA,IACpC,IAAA,EAAM,sBAAsB,MAAM,CAAA;AAAA,IAClC,IAAA,EAAM,sBAAsB,MAAM,CAAA;AAAA,IAClC,KAAA,EAAO,qBAAqB,OAAO,CAAA;AAAA,IACnC,KAAA,EAAO,qBAAqB,OAAO,CAAA;AAAA,IACnC,KAAA,EAAO,CAAC,SAAA,KAAwC,UAAA,CAAW,QAAQ,CAAA;AAAA,IACnE,IAAI,QAAA,GAAW;AAAE,MAAA,OAAO,QAAA;AAAA,IAAU,CAAA;AAAA,IAClC,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,MAAA,GAAS,CAAA;AAClB,MAAC,QAAA,CAAS,MAAmC,SAAA,EAAU;AACvD,MAAC,QAAA,CAAS,MAAmC,SAAA,EAAU;AACvD,MAAC,QAAA,CAAS,KAAkC,SAAA,EAAU;AACtD,MAAC,QAAA,CAAS,KAAkC,SAAA,EAAU;AACtD,MAAC,QAAA,CAAS,MAAmC,SAAA,EAAU;AACvD,MAAC,QAAA,CAAS,MAAmC,SAAA,EAAU;AAAA,IACzD;AAAA,GACF;AAEA,EAAA,OAAO,QAAA;AACT;ACVO,SAAS,eAAA,GAAgC;AAC9C,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,eAAA;AAAA,IACT,MAAA,EAAQ,cAAA;AAAA,IACR,YAAA,EAAc,MAAA;AAAA,IACd,UAAU,MAAM;AAAA,IAAC,CAAA;AAAA,IACjB,cAAc,MAAM;AAAA,IAAC,CAAA;AAAA,IACrB,aAAa,MAAM;AAAA,IAAC;AAAA,GACtB;AACF;AAEO,SAAS,YAAA,GAAyB;AACvC,EAAA,MAAM,WAAqB,EAAC;AAE5B,EAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KAAiB,IAAA;AACpC,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,OAAA,EAAS,SAAA;AAAA,IACT,KAAA,EAAO,SAAA;AAAA,IACP,OAAA,EAAS,SAAA;AAAA,IACT,IAAA,EAAM,SAAA;AAAA,IACN,OAAA,EAAS,SAAA;AAAA,IACT,MAAA,EAAQ,SAAA;AAAA,IACR,SAAA,EAAW,SAAA;AAAA,IACX,SAAA,EAAW,SAAA;AAAA,IACX,QAAA,EAAU,SAAA;AAAA,IACV,KAAA,EAAO,SAAA;AAAA,IACP,UAAA,EAAY,SAAA;AAAA,IACZ,GAAA,EAAK,SAAA;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,SAAA,EAAW,SAAA;AAAA,IACX,OAAA,EAAS;AAAA,GACX;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,UAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,OAAA,EAAS,GAAA;AAAA,MACT,KAAA,EAAO,GAAA;AAAA,MACP,OAAA,EAAS,GAAA;AAAA,MACT,IAAA,EAAM,GAAA;AAAA,MACN,MAAA,EAAQ,GAAA;AAAA,MACR,KAAA,EAAO,GAAA;AAAA,MACP,MAAA,EAAQ,GAAA;AAAA,MACR,OAAA,EAAS,GAAA;AAAA,MACT,OAAA,EAAS,GAAA;AAAA,MACT,OAAA,EAAS,GAAA;AAAA,MACT,SAAA,EAAW,GAAA;AAAA,MACX,MAAA,EAAQ,GAAA;AAAA,MACR,OAAA,EAAS,GAAA;AAAA,MACT,QAAA,EAAU,GAAA;AAAA,MACV,UAAA,EAAY,GAAA;AAAA,MACZ,WAAA,EAAa,GAAA;AAAA,MACb,KAAA,EAAO,GAAA;AAAA,MACP,MAAA,EAAQ;AAAA,KACV;AAAA,IACA,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,CAAC,IAAA,KAAS,SAAS,IAAA,CAAK,CAAA,OAAA,EAAU,IAAI,CAAA,CAAE,CAAC,CAAA;AAAA,IACtD,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,CAAC,GAAA,KAAQ,SAAS,IAAA,CAAK,CAAA,MAAA,EAAS,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,IAClD,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,CAAC,GAAA,KAAQ,SAAS,IAAA,CAAK,CAAA,SAAA,EAAY,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,IACxD,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,CAAC,GAAA,KAAQ,SAAS,IAAA,CAAK,CAAA,MAAA,EAAS,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,IAClD,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,CAAC,QAAQ,QAAA,CAAS,IAAA,CAAK,CAAA,OAAA,EAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,GAAG,EAAE,CAAC,CAAA;AAAA,IACzF,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,CAAC,GAAA,KAAQ,SAAS,IAAA,CAAK,CAAA,OAAA,EAAU,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,IACpD,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,CAAC,GAAA,KAAQ;AACtB,MAAA,QAAA,CAAS,IAAA,CAAK,CAAA,SAAA,EAAY,GAAG,CAAA,CAAE,CAAA;AAC/B,MAAA,OAAO;AAAA,QACL,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,CAAC,CAAA,KAAc,SAAS,IAAA,CAAK,CAAA,gBAAA,EAAmB,CAAC,CAAA,CAAE,CAAC,CAAA;AAAA,QAClE,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,CAAC,CAAA,KAAe,QAAA,CAAS,IAAA,CAAK,CAAA,iBAAA,EAAoB,CAAA,IAAK,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,QAC5E,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,CAAC,CAAA,KAAe,QAAA,CAAS,IAAA,CAAK,CAAA,cAAA,EAAiB,CAAA,IAAK,GAAG,CAAA,CAAE,CAAC,CAAA;AAAA,QACtE,IAAA,EAAMA,GAAG,EAAA;AAAG,OACd;AAAA,IACF,CAAC,CAAA;AAAA,IACD,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,CAAC,IAAA,KAAS,QAAA,CAAS,IAAA,CAAK,CAAA,OAAA,EAAU,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,EAAE,CAAC,CAAA;AAAA,IACtE,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,CAAC,IAAA,KAAS,QAAA,CAAS,IAAA,CAAK,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,EAAE,CAAC,CAAA;AAAA,IACpE,SAASA,EAAAA,CAAG,EAAA,CAAG,MAAM,QAAA,CAAS,IAAA,CAAK,EAAE,CAAC,CAAA;AAAA,IACtC,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,MAAM,QAAA,CAAS,KAAK,GAAA,CAAI,MAAA,CAAO,EAAE,CAAC,CAAC,CAAA;AAAA,IAClD,GAAA,EAAKA,EAAAA,CAAG,EAAA,CAAG,CAAC,SAAS,KAAA,KAAU;AAC7B,MAAA,IAAI,KAAA,EAAO;AAAC,QAAA,QAAA,CAAS,IAAA,CAAK,CAAA,GAAA,EAAM,KAAK,CAAA,GAAA,CAAK,CAAA;AAAA,MAAE;AAC5C,MAAA,QAAA,CAAS,KAAK,OAAO,CAAA;AACrB,MAAA,IAAI,KAAA,EAAO;AAAC,QAAA,QAAA,CAAS,IAAA,CAAK,IAAI,GAAA,CAAI,MAAA,CAAO,MAAM,MAAA,GAAS,CAAC,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,MAAE;AAAA,IACjE,CAAC,CAAA;AAAA,IACD,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,CAAC,OAAA,KAAY;AAC1B,MAAA,QAAA,CAAS,IAAA,CAAK,CAAA,GAAA,EAAM,OAAA,CAAQ,KAAK,CAAA,GAAA,CAAK,CAAA;AACtC,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,MAAA,CAAO,OAAA,CAAQ,QAAQ,OAAO,CAAA,CAAE,QAAQ,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AACxD,UAAA,QAAA,CAAS,IAAA,CAAK,CAAA,EAAA,EAAK,GAAG,CAAA,EAAA,EAAK,KAAK,CAAA,CAAE,CAAA;AAAA,QACpC,CAAC,CAAA;AAAA,MACH;AACA,MAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,QAAA,OAAA,CAAQ,QAAA,CAAS,OAAA,CAAQ,CAAC,OAAA,KAAkD;AAC1E,UAAA,IAAI,QAAQ,MAAA,EAAQ;AAAC,YAAA,QAAA,CAAS,IAAA,CAAK,CAAA,EAAA,EAAK,OAAA,CAAQ,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,UAAE;AAC3D,UAAA,OAAA,CAAQ,KAAA,CAAM,QAAQ,CAAC,IAAA,KAAiB,SAAS,IAAA,CAAK,CAAA,IAAA,EAAO,IAAI,CAAA,CAAE,CAAC,CAAA;AAAA,QACtE,CAAC,CAAA;AAAA,MACH;AACA,MAAA,IAAI,QAAQ,MAAA,EAAQ;AAClB,QAAA,QAAA,CAAS,IAAA,CAAK,CAAA,UAAA,EAAa,OAAA,CAAQ,MAAM,CAAA,EAAA,CAAI,CAAA;AAAA,MAC/C;AACA,MAAA,QAAA,CAAS,IAAA,CAAK,MAAM,GAAA,CAAI,MAAA,CAAO,QAAQ,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,GAAI,GAAG,CAAA;AAAA,IAChE,CAAC,CAAA;AAAA,IACD,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,CAAA;AAAA,IAC/B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE;AAAA,GAC9B;AACF;AAEO,SAAS,iBAAA,GAAgC;AAC9C,EAAA,MAAM,MAAA,GAAiB;AAAA,IACrB,QAAA,EAAUA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,IAC9B,gBAAgBA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,YAAY,CAAA;AAAA,IAClD,SAAA,EAAWA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IAC/B,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,IAC7B,gBAAA,EAAkBA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,IACtC,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IAC3B,EAAA,EAAIA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IACxB,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IAC1B,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IAC1B,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,IAC1B,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,aAAa;AAAA,MACvB,QAAQ,MAAM,KAAA;AAAA,MACd,aAAa,MAAM,KAAA;AAAA,MACnB,IAAA,EAAM,CAAA;AAAA,MACN,KAAA,EAAO,KAAK,GAAA,EAAI;AAAA,MAChB,KAAA,EAAO,KAAK,GAAA;AAAI,KAClB,CAAE,CAAA;AAAA,IACF,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA,IAC/B,OAAA,EAAS,CAAC,IAAA,KAAiB,IAAA;AAAA,IAC3B,QAAA,EAAU,CAAC,IAAA,KAAiB,IAAA;AAAA,IAC5B,IAAA,EAAM,CAAA,GAAI,QAAA,KAAuB,QAAA,CAAS,KAAK,GAAG,CAAA;AAAA,IAClD,OAAA,EAAS,CAAC,IAAA,KAAiB,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAAA,IAChE,QAAA,EAAU,CAAC,IAAA,KAAiB,IAAA,CAAK,MAAM,GAAG,CAAA,CAAE,KAAI,IAAK,EAAA;AAAA,IACrD,OAAA,EAAS,CAAC,IAAA,KAAiB;AACzB,MAAA,MAAM,OAAO,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,KAAI,IAAK,EAAA;AACtC,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,WAAA,CAAY,GAAG,CAAA;AAChC,MAAA,OAAO,GAAA,GAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI,EAAA;AAAA,IACrC;AAAA,GACF;AAEA,EAAA,MAAM,YAAuBA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,QAAA,CAAS,MAAM,CAAC,CAAA;AACnE,EAAA,MAAM,OAAA,GAAmBA,EAAAA,CAAG,EAAA,CAAG,CAAC,SAAiB,MAAS,CAAA;AAE1D,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,MAAA;AAAA,IACJ,KAAA,EAAO,SAAA;AAAA,IACP,GAAA,EAAK;AAAA,GACP;AACF;AAEO,SAAS,wBAAA,GAA2C;AACzD,EAAA,OAAO;AAAA,IACL,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,aAAa;AAAA,MACzB,aAAA,EAAe,YAAA;AAAA,MACf,QAAA,EAAU,MAAA;AAAA,MACV,MAAA,EAAQ,OAAA;AAAA,MACR,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MAClC,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC,CAAE,CAAA;AAAA,IACF,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,OAAO,aAAA,MAA2B;AAAA,MAC9C,aAAA;AAAA,MACA,MAAA,EAAQ,OAAA;AAAA,MACR,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC,CAAE,CAAA;AAAA,IACF,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IAC7B,UAAA,EAAYA,EAAAA,CAAG,EAAA,CAAG,aAAa;AAAA,MAC7B,OAAA,EAAS,cAAA;AAAA,MACT,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MACnC,SAAA,EAAW,IAAI,IAAA,CAAK,IAAA,CAAK,GAAA,KAAQ,EAAA,GAAK,EAAA,GAAK,GAAI,CAAA,CAAE,WAAA;AAAY,KAC/D,CAAE;AAAA,GACJ;AACF;AAEO,SAAS,sBAAA,GAAuC;AACrD,EAAA,OAAO;AAAA,IACL,WAAA,EAAaA,EAAAA,CAAG,EAAA,CAAG,aAAa;AAAA,MAC9B,WAAA,EAAa,WAAA;AAAA,MACb,QAAA,EAAU,MAAA;AAAA,MACV,MAAA,EAAQ,OAAA;AAAA,MACR,QAAA,EAAU,gBAAA;AAAA,MACV,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MAClC,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC,CAAE,CAAA;AAAA,IACF,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,OAAO,OAAA,MAAa;AAAA,MAChC,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,eAAe,OAAA,CAAQ,aAAA;AAAA,MACvB,SAAA,EAAW,QAAQ,SAAA,IAAa,YAAA;AAAA,MAChC,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACrC,CAAE,CAAA;AAAA,IACF,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IAC7B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,OAAO,WAAA,MAAyB;AAAA,MAC5C,WAAA;AAAA,MACA,MAAA,EAAQ,OAAA;AAAA,MACR,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC,CAAE;AAAA,GACJ;AACF;AAEO,SAAS,qBAAA,GAAqC;AACnD,EAAA,OAAO;AAAA,IACL,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,OAAO,OAAA,MAAa;AAAA,MACjC,UAAA,EAAY,QAAQ,UAAA,IAAc,aAAA;AAAA,MAClC,QAAA,EAAU,MAAA;AAAA,MACV,MAAA,EAAQ,OAAA;AAAA,MACR,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MAClC,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MAClC,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,eAAe,OAAA,CAAQ;AAAA,KACzB,CAAE,CAAA;AAAA,IACF,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,OAAO,OAAA,MAAa;AAAA,MACjC,YAAY,OAAA,CAAQ,UAAA;AAAA,MACpB,UAAA,EAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MACnC,aAAa,OAAA,CAAQ,WAAA;AAAA,MACrB,eAAe,OAAA,CAAQ,aAAA;AAAA,MACvB,YAAY,OAAA,CAAQ;AAAA,KACtB,CAAE,CAAA;AAAA,IACF,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,OAAO,UAAA,MAAwB;AAAA,MAC3C,UAAA;AAAA,MACA,MAAA,EAAQ,OAAA;AAAA,MACR,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACpC,CAAE,CAAA;AAAA,IACF,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,IAAC,CAAC,CAAA;AAAA,IAC5B,EAAA,EAAIA,EAAAA,CAAG,EAAA,CAAG,OAAO,OAAA,MAAa;AAAA,MAC5B,OAAA,EAAS,CAAA;AAAA,MACT,OAAA,EAAS,CAAA;AAAA,MACT,MAAA,EAAQ,SAAS,MAAA,IAAU;AAAA,KAC7B,CAAE;AAAA,GACJ;AACF;AAEO,SAAS,mBAAA,GAAiF;AAC/F,EAAA,OAAO;AAAA,IACL,aAAa,wBAAA,EAAyB;AAAA,IACtC,WAAW,sBAAA,EAAuB;AAAA,IAClC,UAAU,qBAAA;AAAsB,GAClC;AACF;AAEO,SAAS,mBAAA,GAAiC;AAC/C,EAAA,MAAM,QAAQ,mBAAA,EAAoB;AAClC,EAAA,OAAO;AAAA,IACL,SAAA,EAAW;AAAA,MACT,SAAA,EAAWA,GAAG,EAAA;AAAG,KACnB;AAAA,IACA,KAAA,EAAO;AAAA,MACL,GAAA,EAAKA,EAAAA,CAAG,EAAA,CAAG,YAAY,MAAS,CAAA;AAAA,MAChC,GAAA,EAAKA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MACzB,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC5B,GAAA,EAAKA,EAAAA,CAAG,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA,MAC5B,SAASA,EAAAA,CAAG,EAAA,CAAG,4BAAY,IAAI,KAAK,CAAA;AAAA,MACpC,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC;AAAA,KAC/B;AAAA,IACA,SAAA,EAAW;AAAA,MACT,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY,YAAY,CAAA;AAAA,MACrC,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,MAC1B,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,MAC1B,YAAYA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,YAAY,CAAA;AAAA,MAC9C,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA,MAC/B,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,MAAM,YAAY;AAAA,KAChC;AAAA,IACA,KAAA,EAAO;AAAA,MACL,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAE,IAAA,EAAM,CAAA,EAAG,MAAA,EAAQ,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,EAAA,EAAI,MAAK,CAAE;AAAA,KACzE;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC;AAAA,KAC5B;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,MAAMA,EAAAA,CAAG,EAAA,CAAG,OAAO,SAAA,EAAmB,MAAA,EAAkB,aAAuB,MAAS;AAAA,KAC1F;AAAA,IACA,SAAA,EAAW;AAAA,MACT,GAAA,EAAKA,EAAAA,CAAG,EAAA,CAAG,YAAY,aAAa,CAAA;AAAA,MACpC,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,MAAS,CAAA;AAAA,MACjC,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,CAAA;AAAA,MAC9B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC5B,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE;AAAA,KAC5B;AAAA,IACA,IAAA,EAAM;AAAA,MACJ,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,aAAa,CAAA;AAAA,MACvC,QAAA,EAAUA,EAAAA,CAAG,EAAA,CAAG,YAAY,uBAAuB,CAAA;AAAA,MACnD,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,MAAS,CAAA;AAAA,MACjC,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,CAAA;AAAA,MAC9B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA,MAC/B,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE;AAAA,KAC5B;AAAA,IACA,IAAA,EAAM;AAAA,MACJ,QAAA,EAAUA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC9B,UAAA,EAAYA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAChC,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,MAC1B,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC3B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC5B,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC;AAAA,KAC/B;AAAA,IACA,aAAa,KAAA,CAAM,WAAA;AAAA,IACnB,WAAW,KAAA,CAAM,SAAA;AAAA,IACjB,UAAU,KAAA,CAAM;AAAA,GAClB;AACF;AAEO,IAAM,qBAAA,GAAwB;AAE9B,SAAS,yBAAA,CACd,OAAA,GAAoC,EAAC,EACT;AAC5B,EAAA,OAAO,kBAA2B,OAAO,CAAA;AAC3C;AAgCO,SAAS,iBAAA,CACd,OAAA,GAAoC,EAAC,EACT;AAC5B,EAAA,MAAM;AAAA,IACJ,QAAA,GAAW,aAAA;AAAA,IACX,aAAA,GAAgB,OAAA;AAAA,IAChB,IAAA,GAAO,KAAA;AAAA,IACP,WAAA;AAAA,IACA,MAAA;AAAA,IACA,GAAA,GAAM,QAAQ,GAAA,EAAI;AAAA,IAClB,MAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA,EAAU,iBAAA;AAAA,IACV,EAAA,EAAI,WAAA;AAAA,IACJ,aAAA,GAAgB;AAAA,GAClB,GAAI,OAAA;AAEJ,EAAA,MAAM,cAAA,GAAiB,MAAA,IAAU,CAAA,EAAG,GAAG,CAAA,WAAA,CAAA;AAGvC,EAAA,MAAM,kBAAA,GACJ,gBACC,MAAM;AACL,IAAA,QAAQ,IAAA;AAAM,MACZ,KAAK,KAAA;AACH,QAAA,OAAO,EAAE,MAAM,KAAA,EAAO,IAAA,EAAM,CAAC,MAAM,CAAA,EAAG,KAAA,EAAO,EAAC,EAAE;AAAA,MAClD,KAAK,MAAA;AACH,QAAA,OAAO;AAAA,UACL,IAAA,EAAM,MAAA;AAAA,UACN,MAAA,EAAQ,KAAA;AAAA,UACR,IAAA,EAAM,OAAA;AAAA,UACN,SAAA,EAAW,cAAA;AAAA,UACX,OAAA,EAAS;AAAA,SACX;AAAA,MACF,KAAK,UAAA;AACH,QAAA,OAAO,EAAE,MAAM,UAAA,EAAY,UAAA,EAAY,WAAW,KAAA,EAAO,UAAA,EAAY,QAAQ,WAAA,EAAY;AAAA,MAC3F,KAAK,SAAA;AACH,QAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,YAAA,EAAa;AAAA;AAClD,EACF,CAAA,GAAG;AAGL,EAAA,MAAM,gBAAgB,UAAA,EAAW;AACjC,EAAA,MAAM,aAAa,OAAA,EAAQ;AAC3B,EAAA,MAAM,eAAe,SAAA,EAAU;AAC/B,EAAA,MAAM,iBAAiB,WAAA,EAAY;AAEnC,EAAA,MAAM,eAAA,GAAoC;AAAA,IACxC,MAAA,EAAQ,aAAA;AAAA,IACR,GAAA,EAAK,UAAA;AAAA,IACL,UAAA,EAAY;AAAA,MACV,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,MAC3B,YAAYA,EAAAA,CAAG,EAAA,CAAG,YAAY,CAAC,EAAE,CAAC,CAAA;AAAA,MAClC,UAAA,EAAY,IAAA;AAAA,MACZ,aAAA,EAAeA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI;AAAA,KACvC;AAAA,IACA,WAAA,EAAa;AAAA,MACX,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,MAC5B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC5B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC5B,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY,CAAC;AAAA,KAC5B;AAAA,IACA,KAAA,EAAO,YAAA;AAAA,IACP,OAAA,EAAS,cAAA;AAAA,IACT,SAAA,EAAW;AAAA,MACT,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC3B,QAAA,EAAUA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC9B,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC;AAAA,KAC7B;AAAA,IACA,QAAA,EAAU;AAAA,MACR,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC,CAAA;AAAA,MAC7B,SAAA,EAAWA,EAAAA,CAAG,EAAA,CAAG,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,KACjC;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,SAAA,EAAWA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAC,CAAE,CAAA;AAAA,MACjC,YAAA,EAAcA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAC,CAAE;AAAA,KACtC;AAAA,IACA,MAAA,EAAQ;AAAA,MACN,IAAA,EAAMA,GAAG,EAAA,CAAG,aAAa,EAAE,OAAA,EAAS,IAAA,EAAM,IAAA,EAAM,IAAA,EAAK,CAAE,CAAA;AAAA,MACvD,WAAA,EAAaA,EAAAA,CAAG,EAAA,CAAG,YAAY,KAAK;AAAA,KACtC;AAAA,IACA,WAAA,EAAa;AAAA,MACX,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAE,MAAM,EAAC,EAAG,QAAA,EAAU,CAAA,EAAE,CAAE,CAAA;AAAA,MACpD,aAAaA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAE,OAAOA,EAAAA,CAAG,EAAA,EAAG,EAAG,MAAA,EAAQA,GAAG,EAAA,EAAG,EAAG,UAAUA,EAAAA,CAAG,EAAA,IAAK,CAAE,CAAA;AAAA,MACvF,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC;AAAA,KAC7B;AAAA,IACA,gBAAA,EAAkB;AAAA,MAChB,IAAA,EAAMA,EAAAA,CAAG,EAAA,CAAG,YAAY,EAAE,CAAA;AAAA,MAC1B,QAAA,EAAUA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,CAAA;AAAA,MAChC,SAAA,EAAWA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAE,EAAA,EAAI,MAAA,EAAQ,SAAA,EAAW,CAAA,EAAG,SAAA,EAAW,CAAA,EAAE,CAAE,CAAA;AAAA,MACzE,UAAA,EAAYA,EAAAA,CAAG,EAAA,CAAG,YAAY,CAAC,CAAA;AAAA,MAC/B,UAAA,EAAYA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,CAAA;AAAA,MAClC,UAAA,EAAYA,EAAAA,CAAG,EAAA,CAAG,YAAY,CAAC,CAAA;AAAA,MAC/B,UAAA,EAAYA,EAAAA,CAAG,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA,MACnC,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY,CAAC,CAAA;AAAA,MAC1B,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,YAAY;AAAA,MAAC,CAAC;AAAA,KAC7B;AAAA,IACA,IAAA,EAAM;AAAA,MACJ,KAAA,EAAOA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAE,IAAA,EAAM,EAAC,EAAG,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,UAAkB,CAAE,CAAA;AAAA,MAC5F,OAAA,EAASA,EAAAA,CAAG,EAAA,CAAG,YAAY,IAAI,CAAA;AAAA,MAC/B,MAAA,EAAQA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAE,IAAA,EAAM,EAAC,EAAG,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,KAAA,EAAM,CAAE,CAAA;AAAA,MAClE,SAAA,EAAWA,EAAAA,CAAG,EAAA,CAAG,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,MAC/B,QAAA,EAAUA,EAAAA,CAAG,EAAA,CAAG,aAAa,EAAC,CAAE,CAAA;AAAA,MAChC,eAAA,EAAiBA,EAAAA,CAAG,EAAA,CAAG,OAAO,EAAE,SAAA,EAAW,KAAA,EAAO,cAAA,EAAgB,KAAA,EAAO,SAAA,EAAW,KAAA,EAAO,YAAA,EAAc,OAAM,CAAE;AAAA;AACnH,GACF;AAGA,EAAA,MAAM,aAAA,GAAkC;AAAA,IACtC,GAAG,eAAA;AAAA,IACH,GAAG;AAAA,GACL;AAGA,EAAA,IAAI,YAAY,MAAM;AAAA,EAAC,CAAA;AACvB,EAAA,IAAI,aAAA,EAAe;AACjB,IAAA,MAAM,SAAS,iBAAA,CAAkB;AAAA,MAC/B,KAAK,aAAA,CAAc,GAAA;AAAA,MACnB,OAAO,aAAA,CAAc,KAAA;AAAA,MACrB,SAAS,aAAA,CAAc,OAAA;AAAA,MACvB,QAAQ,aAAA,CAAc,MAAA;AAAA,MACtB,WAAW,aAAA,CAAc,SAAA;AAAA,MACzB,YAAY,aAAA,CAAc,UAAA;AAAA,MAC1B,aAAa,aAAA,CAAc,WAAA;AAAA,MAC3B,UAAU,aAAA,CAAc;AAAA,KACzB,CAAA;AACD,IAAA,SAAA,GAAY,MAAA,CAAO,OAAA;AAAA,EACrB;AAGA,EAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,EAAA,MAAM,OAAA,GAAoB;AAAA,IACxB,GAAG,SAAA;AAAA,IACH,GAAG;AAAA,GACL;AAGA,EAAA,MAAM,GAAA,GAAgC;AAAA,IACpC,IAAA;AAAA,IACA,SAAA,EAAW,sBAAA;AAAA,IACX,QAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA,EAAQ,cAAA;AAAA,IACR,MAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAO,eAAA,EAAgB;AAAA,IACvB,WAAA,EAAa,kBAAA;AAAA,IACb,EAAA,EAAI,OAAA;AAAA,IACJ,QAAA,EAAU,aAAA;AAAA,IACV,SAAS,iBAAA,EAAkB;AAAA,IAC3B,KAAK,mBAAA;AAAoB,GAC3B;AAEA,EAAA,OAAO,EAAE,GAAA,EAAK,OAAA,EAAS,SAAA,EAAU;AACnC;;;AClYA,eAAsB,WAAA,CACpB,OAAA,EACA,OAAA,GAAuC,EAAC,EACH;AACrC,EAAA,MAAM;AAAA,IACJ,KAAA;AAAA,IACA,OAAO,EAAC;AAAA,IACR,KAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA,EAAO,QAAA;AAAA,IACP,IAAA,GAAO,KAAA;AAAA,IACP,MAAA;AAAA,IACA,GAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA,EAAAC,SAAAA;AAAA,IACA,EAAA;AAAA,IACA,aAAA,GAAgB;AAAA,GAClB,GAAI,OAAA;AAIJ,EAAA,MAAM,UAAA,GAAuC;AAAA,IAC3C,IAAA;AAAA,IACA,MAAA;AAAA,IACA,GAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA,EAAAA,SAAAA;AAAA,IACA,EAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,MAAM,EAAE,GAAA,EAAK,OAAA,EAAQ,GAAI,kBAA2B,UAAU,CAAA;AAI9D,EAAA,IAAI,KAAA;AAEJ,EAAA,IAAI,aAAa,MAAA,EAAW;AAE1B,IAAA,KAAA,GAAQ,QAAA;AAAA,EACV,CAAA,MAAA,IAAW,SAAS,MAAA,EAAQ;AAE1B,IAAA,KAAA,GAAQ;AAAA,MACN,GAAI,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,KAAU,EAAC;AAAA,MACvC,GAAI,IAAA,KAAS,MAAA,GAAY,EAAE,IAAA,KAAS,EAAC;AAAA,MACrC,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW;AAAC,KAC3C;AAAA,EACF,CAAA,MAAO;AAEL,IAAA,KAAA,GAAQ,EAAE,KAAA,EAAO,KAAA,IAAS,IAAI,IAAA,EAAK;AAAA,EACrC;AAIA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,MAAM,OAAA,CAAQ,OAAA,CAAQ,GAAA,EAAiC,KAAK,CAAA;AAAA,EACpE,CAAA,SAAE;AAEA,IAAA,MAAM,QAAQ,OAAA,IAAU;AAAA,EAC1B;AAMA,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,IAAA;AAEJ,EAAA,IAAI,OAAO,IAAA,IAAQ,OAAO,GAAA,KAAQ,QAAA,IAAY,cAAc,GAAA,EAAK;AAE/D,IAAA,MAAM,SAAA,GAAY,GAAA;AAClB,IAAA,QAAA,GAAW,SAAA,CAAU,QAAA;AACrB,IAAA,MAAA,GAAS,SAAA,CAAU,MAAA;AACnB,IAAA,IAAA,GAAO,SAAA,CAAU,IAAA;AAAA,EACnB,CAAA,MAAA,IAAW,GAAA,KAAQ,MAAA,IAAa,GAAA,KAAQ,IAAA,EAAM;AAE5C,IAAA,QAAA,GAAW,CAAA;AACX,IAAA,MAAA,GAAS,MAAA;AACT,IAAA,IAAA,GAAO,MAAA;AAAA,EACT,CAAA,MAAO;AAEL,IAAA,QAAA,GAAW,CAAA;AACX,IAAA,MAAA,GAAS,GAAA;AACT,IAAA,IAAA,GAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA;AAAA,IACA,GAAA;AAAA,IACA,IAAI,GAAA,CAAI,EAAA;AAAA,IACR,GAAA;AAAA,IACA;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/shared-testing/setup-platform\n *\n * Solves the \"singleton gap\" problem: composables (useLLM, useCache, etc.)\n * read from the global platform singleton, but createTestContext() only\n * populates ctx.platform. This module bridges the gap by setting test mocks\n * directly into the global singleton.\n */\n\nimport { platform, resetPlatform } from '@kb-labs/core-runtime';\nimport type {\n ILLM,\n ICache,\n IEmbeddings,\n IVectorStore,\n IStorage,\n IAnalytics,\n ILogger,\n IEventBus,\n} from '@kb-labs/core-platform';\n\n/**\n * Options for setting up the test platform.\n * Only adapters that are provided will be registered.\n * Omitted adapters will use the PlatformContainer's built-in fallbacks.\n */\nexport interface TestPlatformOptions {\n llm?: ILLM;\n cache?: ICache;\n embeddings?: IEmbeddings;\n vectorStore?: IVectorStore;\n storage?: IStorage;\n analytics?: IAnalytics;\n logger?: ILogger;\n eventBus?: IEventBus;\n}\n\nexport interface TestPlatformResult {\n /** The global platform singleton (with test mocks applied) */\n platform: typeof platform;\n /** Call in afterEach() to reset the singleton to a clean state */\n cleanup: () => void;\n}\n\n/**\n * Setup the global platform singleton with test mocks.\n *\n * This ensures that useLLM(), useCache() and other composables\n * return the test mocks instead of uninitialized/stale adapters.\n *\n * @example\n * ```typescript\n * import { setupTestPlatform, mockLLM } from '@kb-labs/shared-testing';\n *\n * describe('my handler', () => {\n * let cleanup: () => void;\n *\n * beforeEach(() => {\n * const result = setupTestPlatform({\n * llm: mockLLM().onAnyComplete().respondWith('hello'),\n * });\n * cleanup = result.cleanup;\n * });\n *\n * afterEach(() => cleanup());\n *\n * it('uses LLM', async () => {\n * const llm = useLLM(); // Returns the test mock!\n * const res = await llm!.complete('test');\n * expect(res.content).toBe('hello');\n * });\n * });\n * ```\n */\nexport function setupTestPlatform(options: TestPlatformOptions = {}): TestPlatformResult {\n // Reset everything first — clean slate\n resetPlatform();\n\n // Register provided adapters into the global singleton\n const adapterMap: Array<[string, unknown]> = [\n ['llm', options.llm],\n ['cache', options.cache],\n ['embeddings', options.embeddings],\n ['vectorStore', options.vectorStore],\n ['storage', options.storage],\n ['analytics', options.analytics],\n ['logger', options.logger],\n ['eventBus', options.eventBus],\n ];\n\n for (const [key, instance] of adapterMap) {\n if (instance !== undefined) {\n platform.setAdapter(key, instance);\n }\n }\n\n return {\n platform,\n cleanup: () => resetPlatform(),\n };\n}\n","/**\n * @module @kb-labs/shared-testing/mock-llm\n *\n * LLM mock builder with fluent API and call tracking.\n *\n * @example\n * ```typescript\n * const llm = mockLLM()\n * .onComplete('Generate commit').respondWith('feat: add login')\n * .onComplete(/explain/i).respondWith('This function does X')\n * .onAnyComplete().respondWith('default answer');\n *\n * const res = await llm.complete('Generate commit message');\n * expect(res.content).toBe('feat: add login');\n * expect(llm.complete).toHaveBeenCalledOnce();\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type {\n ILLM,\n LLMOptions,\n LLMResponse,\n LLMMessage,\n LLMToolCallOptions,\n LLMToolCallResponse,\n LLMToolCall,\n} from '@kb-labs/core-platform';\n\n// ────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────\n\n/** Recorded call to complete() */\nexport interface LLMCall {\n prompt: string;\n options?: LLMOptions;\n response: LLMResponse;\n}\n\n/** Recorded call to chatWithTools() */\nexport interface LLMToolCallRecord {\n messages: LLMMessage[];\n options: LLMToolCallOptions;\n response: LLMToolCallResponse;\n}\n\ntype PromptMatcher = string | RegExp | ((prompt: string) => boolean);\n\ninterface CompletionRule {\n matcher: PromptMatcher;\n response: string | LLMResponse | ((prompt: string) => string | LLMResponse);\n}\n\n/**\n * Mock LLM instance with call tracking and fluent API.\n * All methods are vi.fn() spies.\n */\nexport interface MockLLMInstance extends ILLM {\n /** All recorded complete() calls */\n calls: LLMCall[];\n /** Last complete() call (or undefined) */\n lastCall: LLMCall | undefined;\n /** All recorded chatWithTools() calls */\n toolCalls: LLMToolCallRecord[];\n /** Reset all recorded calls and spies */\n resetCalls: () => void;\n}\n\n/** Public type returned by mockLLM() — builder fluent API + ILLM spy instance */\nexport interface MockLLM extends MockLLMInstance {\n onComplete(matcher: PromptMatcher): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLM };\n onAnyComplete(): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLM };\n streaming(chunks: string[]): MockLLM;\n failing(error: Error): MockLLM;\n withToolCalls(calls: LLMToolCall[], content?: string): MockLLM;\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Builder\n// ────────────────────────────────────────────────────────────────────\n\nclass MockLLMBuilder {\n private rules: CompletionRule[] = [];\n private defaultResponse: string | LLMResponse | ((prompt: string) => string | LLMResponse) = 'mock response';\n private streamChunks: string[] = ['mock'];\n private errorToThrow: Error | null = null;\n private toolCallsToReturn: LLMToolCall[] = [];\n private toolCallResponseContent = '';\n\n /**\n * Match a specific prompt (exact string or regex).\n * Returns a handler to set the response.\n */\n onComplete(matcher: PromptMatcher): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLMBuilder } {\n return {\n respondWith: (response) => {\n this.rules.push({ matcher, response });\n return this;\n },\n };\n }\n\n /**\n * Set the default response for any prompt that doesn't match a rule.\n */\n onAnyComplete(): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLMBuilder } {\n return {\n respondWith: (response) => {\n this.defaultResponse = response;\n return this;\n },\n };\n }\n\n /**\n * Configure streaming to yield specific chunks.\n */\n streaming(chunks: string[]): MockLLMBuilder {\n this.streamChunks = chunks;\n return this;\n }\n\n /**\n * Make the LLM throw an error on every call.\n */\n failing(error: Error): MockLLMBuilder {\n this.errorToThrow = error;\n return this;\n }\n\n /**\n * Configure chatWithTools() to return specific tool calls.\n */\n withToolCalls(calls: LLMToolCall[], content = ''): MockLLMBuilder {\n this.toolCallsToReturn = calls;\n this.toolCallResponseContent = content;\n return this;\n }\n\n /**\n * Build the mock ILLM instance. Called automatically by mockLLM().\n */\n build(): MockLLMInstance {\n const calls: LLMCall[] = [];\n const toolCallRecords: LLMToolCallRecord[] = [];\n const rules = this.rules;\n const defaultResponse = this.defaultResponse;\n const streamChunks = this.streamChunks;\n const errorToThrow = this.errorToThrow;\n const toolCallsToReturn = this.toolCallsToReturn;\n const toolCallResponseContent = this.toolCallResponseContent;\n\n function resolveResponse(prompt: string): LLMResponse {\n // Check rules in order\n for (const rule of rules) {\n if (matchesPrompt(rule.matcher, prompt)) {\n return toResponse(rule.response, prompt);\n }\n }\n // Fallback\n return toResponse(defaultResponse, prompt);\n }\n\n const completeFn = vi.fn(async (prompt: string, options?: LLMOptions): Promise<LLMResponse> => {\n if (errorToThrow) {throw errorToThrow;}\n const response = resolveResponse(prompt);\n calls.push({ prompt, options, response });\n return response;\n });\n\n const streamFn = vi.fn(async function* (_prompt: string, _options?: LLMOptions): AsyncIterable<string> {\n if (errorToThrow) {throw errorToThrow;}\n for (const chunk of streamChunks) {\n yield chunk;\n }\n });\n\n const chatWithToolsFn = vi.fn(async (messages: LLMMessage[], options: LLMToolCallOptions): Promise<LLMToolCallResponse> => {\n if (errorToThrow) {throw errorToThrow;}\n\n const lastUserMsg = messages.filter(m => m.role === 'user').pop();\n const prompt = lastUserMsg?.content ?? '';\n const baseResponse = resolveResponse(prompt);\n\n const response: LLMToolCallResponse = {\n ...baseResponse,\n content: toolCallsToReturn.length > 0 ? toolCallResponseContent : baseResponse.content,\n toolCalls: toolCallsToReturn.length > 0 ? toolCallsToReturn : undefined,\n };\n\n toolCallRecords.push({ messages, options, response });\n return response;\n });\n\n const instance: MockLLMInstance = {\n complete: completeFn,\n stream: streamFn,\n chatWithTools: chatWithToolsFn,\n calls,\n toolCalls: toolCallRecords,\n get lastCall() {\n return calls[calls.length - 1];\n },\n resetCalls: () => {\n calls.length = 0;\n toolCallRecords.length = 0;\n completeFn.mockClear();\n streamFn.mockClear();\n chatWithToolsFn.mockClear();\n },\n };\n\n return instance;\n }\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────────────\n\nfunction matchesPrompt(matcher: PromptMatcher, prompt: string): boolean {\n if (typeof matcher === 'string') {return prompt.includes(matcher);}\n if (matcher instanceof RegExp) {return matcher.test(prompt);}\n return matcher(prompt);\n}\n\nfunction toResponse(\n value: string | LLMResponse | ((prompt: string) => string | LLMResponse),\n prompt: string,\n): LLMResponse {\n const resolved = typeof value === 'function' ? value(prompt) : value;\n if (typeof resolved === 'string') {\n return {\n content: resolved,\n usage: { promptTokens: 0, completionTokens: 0 },\n model: 'mock',\n };\n }\n return resolved;\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Public API\n// ────────────────────────────────────────────────────────────────────\n\n/**\n * Create a mock LLM with fluent builder API.\n *\n * @example\n * ```typescript\n * // Simple: always returns same response\n * const llm = mockLLM();\n *\n * // With specific responses\n * const llm = mockLLM()\n * .onComplete('generate commit').respondWith('feat: add feature')\n * .onComplete(/explain/i).respondWith('This code does...')\n * .onAnyComplete().respondWith('default');\n *\n * // Streaming\n * const llm = mockLLM().streaming(['chunk1', 'chunk2', 'chunk3']);\n *\n * // Error simulation\n * const llm = mockLLM().failing(new Error('rate limit exceeded'));\n *\n * // Tool calling\n * const llm = mockLLM().withToolCalls([\n * { id: 'call-1', name: 'search', input: { query: 'test' } },\n * ]);\n * ```\n */\nexport function mockLLM(): MockLLM {\n const builder = new MockLLMBuilder();\n\n // Create a Proxy that acts as both builder and built instance.\n // Builder methods are forwarded to the builder.\n // ILLM methods (complete, stream, etc.) trigger auto-build on first access.\n let built: MockLLMInstance | null = null;\n\n function ensureBuilt(): MockLLMInstance {\n if (!built) {built = builder.build();}\n return built;\n }\n\n // Use Record<string, unknown> index signatures so we can introspect builder / instance\n // properties without falling back to `any`. The Proxy target is the builder.\n type BuilderRecord = MockLLMBuilder & Record<string, unknown>;\n type InstanceRecord = MockLLMInstance & Record<string, unknown>;\n\n const proxy = new Proxy(builder as BuilderRecord, {\n get(target: BuilderRecord, prop: string | symbol, _receiver: unknown): unknown {\n // Builder methods — return from builder\n if (prop in target && typeof target[prop as string] === 'function') {\n const method = (target[prop as string] as (...args: unknown[]) => unknown).bind(target);\n // After calling a builder method, invalidate the built instance\n return (...args: unknown[]): unknown => {\n built = null;\n const result = method(...args);\n // If the result is the builder itself (chaining), return the proxy\n if (result === target || (result !== null && typeof result === 'object' && 'respondWith' in result)) {\n if (result === target) {return proxy;}\n // Wrap respondWith to return proxy\n const resultWithRespond = result as { respondWith: (...rArgs: unknown[]) => unknown };\n return {\n respondWith: (...rArgs: unknown[]): unknown => {\n built = null;\n resultWithRespond.respondWith(...rArgs);\n return proxy;\n },\n };\n }\n return result;\n };\n }\n\n // ILLM instance properties — auto-build\n const instance = ensureBuilt() as InstanceRecord;\n // Return functions directly (not bound) to preserve vi.fn() spy identity\n return instance[prop as string];\n },\n });\n\n return proxy as unknown as MockLLM;\n}\n","/**\n * @module @kb-labs/shared-testing/mock-cache\n *\n * In-memory cache mock that actually stores data (unlike noop mocks).\n * All methods are vi.fn() spies for assertion.\n *\n * @example\n * ```typescript\n * const cache = mockCache();\n * await cache.set('key', { data: 42 }, 5000);\n * expect(await cache.get('key')).toEqual({ data: 42 });\n * expect(cache.set).toHaveBeenCalledWith('key', { data: 42 }, 5000);\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type { ICache } from '@kb-labs/core-platform';\n\ninterface CacheEntry {\n value: unknown;\n expiresAt: number | null; // null = no TTL\n}\n\ninterface SortedSetMember {\n score: number;\n member: string;\n}\n\n/**\n * Mock cache instance with working in-memory storage.\n * All methods are vi.fn() spies.\n */\nexport interface MockCacheInstance extends ICache {\n /** Direct access to the internal store (for assertions) */\n readonly store: Map<string, CacheEntry>;\n /** Direct access to sorted sets (for assertions) */\n readonly sortedSets: Map<string, SortedSetMember[]>;\n /** Reset all data and spy call history */\n reset: () => void;\n}\n\n/**\n * Create a mock cache with working in-memory storage.\n *\n * Unlike noop mocks, this cache actually stores and retrieves data,\n * respects TTL, and supports sorted set operations.\n *\n * @param initial - Optional initial data to populate the cache\n *\n * @example\n * ```typescript\n * // Empty cache\n * const cache = mockCache();\n *\n * // Pre-populated cache\n * const cache = mockCache({ 'user:1': { name: 'Alice' } });\n *\n * // TTL works\n * await cache.set('temp', 'value', 100); // expires in 100ms\n * await new Promise(r => setTimeout(r, 150));\n * expect(await cache.get('temp')).toBeNull(); // expired\n * ```\n */\nexport function mockCache(initial?: Record<string, unknown>): MockCacheInstance {\n const store = new Map<string, CacheEntry>();\n const sortedSets = new Map<string, SortedSetMember[]>();\n\n // Populate initial data\n if (initial) {\n for (const [key, value] of Object.entries(initial)) {\n store.set(key, { value, expiresAt: null });\n }\n }\n\n function isExpired(entry: CacheEntry): boolean {\n return entry.expiresAt !== null && Date.now() > entry.expiresAt;\n }\n\n // Keep raw vi.fn() references for mockClear() in reset.\n // The ICache-typed versions are cast below for the public interface.\n const spies: ReturnType<typeof vi.fn>[] = [];\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function spy<T extends (...args: any[]) => unknown>(fn: T): T {\n const s = vi.fn(fn) as unknown as T;\n spies.push(s as unknown as ReturnType<typeof vi.fn>);\n return s;\n }\n\n const getFn = spy(async (key: string): Promise<unknown> => {\n const entry = store.get(key);\n if (!entry || isExpired(entry)) {\n if (entry) {store.delete(key);} // cleanup expired\n return null;\n }\n return entry.value;\n });\n\n const setFn = spy(async (key: string, value: unknown, ttl?: number): Promise<void> => {\n store.set(key, {\n value,\n expiresAt: ttl ? Date.now() + ttl : null,\n });\n });\n\n const deleteFn = spy(async (key: string): Promise<void> => {\n store.delete(key);\n });\n\n const clearFn = spy(async (pattern?: string): Promise<void> => {\n if (!pattern) {\n store.clear();\n return;\n }\n // Simple glob matching: only support trailing *\n const prefix = pattern.replace(/\\*$/, '');\n for (const key of store.keys()) {\n if (key.startsWith(prefix)) {\n store.delete(key);\n }\n }\n });\n\n const zaddFn = spy(async (key: string, score: number, member: string): Promise<void> => {\n if (!sortedSets.has(key)) {sortedSets.set(key, []);}\n const set = sortedSets.get(key)!;\n // Remove existing member\n const idx = set.findIndex(m => m.member === member);\n if (idx >= 0) {set.splice(idx, 1);}\n // Add with new score\n set.push({ score, member });\n // Keep sorted by score\n set.sort((a, b) => a.score - b.score);\n });\n\n const zrangebyscoreFn = spy(async (key: string, min: number, max: number): Promise<string[]> => {\n const set = sortedSets.get(key);\n if (!set) {return [];}\n return set\n .filter(m => m.score >= min && m.score <= max)\n .map(m => m.member);\n });\n\n const zremFn = spy(async (key: string, member: string): Promise<void> => {\n const set = sortedSets.get(key);\n if (!set) {return;}\n const idx = set.findIndex(m => m.member === member);\n if (idx >= 0) {set.splice(idx, 1);}\n });\n\n const setIfNotExistsFn = spy(async (key: string, value: unknown, ttl?: number): Promise<boolean> => {\n const existing = store.get(key);\n if (existing && !isExpired(existing)) {return false;}\n store.set(key, {\n value,\n expiresAt: ttl ? Date.now() + ttl : null,\n });\n return true;\n });\n\n const instance: MockCacheInstance = {\n get: getFn as unknown as ICache['get'],\n set: setFn as unknown as ICache['set'],\n delete: deleteFn,\n clear: clearFn,\n zadd: zaddFn,\n zrangebyscore: zrangebyscoreFn,\n zrem: zremFn,\n setIfNotExists: setIfNotExistsFn as unknown as ICache['setIfNotExists'],\n get store() { return store; },\n get sortedSets() { return sortedSets; },\n reset: () => {\n store.clear();\n sortedSets.clear();\n for (const s of spies) {s.mockClear();}\n },\n };\n\n return instance;\n}\n","/**\n * @module @kb-labs/shared-testing/mock-storage\n *\n * Virtual filesystem mock for IStorage interface.\n * All methods are vi.fn() spies.\n *\n * @example\n * ```typescript\n * const storage = mockStorage({\n * 'config.json': '{\"key\": \"value\"}',\n * 'data/file.txt': 'hello world',\n * });\n *\n * const content = await storage.read('config.json');\n * expect(content?.toString()).toBe('{\"key\": \"value\"}');\n * expect(storage.read).toHaveBeenCalledWith('config.json');\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type { IStorage } from '@kb-labs/core-platform';\n\n/**\n * Mock storage instance with in-memory virtual filesystem.\n */\nexport interface MockStorageInstance extends IStorage {\n /** Direct access to the virtual filesystem (for assertions) */\n readonly files: Map<string, Buffer>;\n /** Reset all files and spy call history */\n reset: () => void;\n}\n\n/**\n * Create a mock storage with in-memory virtual filesystem.\n *\n * @param initial - Optional initial files. Keys are paths, values are content (string or Buffer).\n *\n * @example\n * ```typescript\n * const storage = mockStorage({\n * 'data.json': JSON.stringify({ items: [] }),\n * 'binary.dat': Buffer.from([0x00, 0x01]),\n * });\n *\n * // Write and read\n * await storage.write('new.txt', Buffer.from('hello'));\n * expect(await storage.exists('new.txt')).toBe(true);\n *\n * // List files\n * const files = await storage.list('');\n * expect(files).toContain('new.txt');\n * ```\n */\nexport function mockStorage(initial?: Record<string, string | Buffer>): MockStorageInstance {\n const files = new Map<string, Buffer>();\n\n // Populate initial files\n if (initial) {\n for (const [path, content] of Object.entries(initial)) {\n files.set(path, typeof content === 'string' ? Buffer.from(content) : content);\n }\n }\n\n const readFn = vi.fn(async (path: string): Promise<Buffer | null> => {\n return files.get(path) ?? null;\n });\n\n const writeFn = vi.fn(async (path: string, data: Buffer): Promise<void> => {\n files.set(path, data);\n });\n\n const deleteFn = vi.fn(async (path: string): Promise<void> => {\n files.delete(path);\n });\n\n const listFn = vi.fn(async (prefix: string): Promise<string[]> => {\n const result: string[] = [];\n for (const path of files.keys()) {\n if (path.startsWith(prefix)) {\n result.push(path);\n }\n }\n return result.sort();\n });\n\n const existsFn = vi.fn(async (path: string): Promise<boolean> => {\n return files.has(path);\n });\n\n const instance: MockStorageInstance = {\n read: readFn,\n write: writeFn,\n delete: deleteFn,\n list: listFn,\n exists: existsFn,\n get files() { return files; },\n reset: () => {\n files.clear();\n readFn.mockClear();\n writeFn.mockClear();\n deleteFn.mockClear();\n listFn.mockClear();\n existsFn.mockClear();\n },\n };\n\n return instance;\n}\n","/**\n * @module @kb-labs/shared-testing/mock-logger\n *\n * Logger mock with message recording and vi.fn() spies.\n *\n * @example\n * ```typescript\n * const logger = mockLogger();\n * logger.info('hello', { extra: 'data' });\n * logger.error('oops', new Error('fail'));\n *\n * expect(logger.messages).toEqual([\n * { level: 'info', msg: 'hello', meta: { extra: 'data' } },\n * { level: 'error', msg: 'oops', error: expect.any(Error), meta: undefined },\n * ]);\n * expect(logger.info).toHaveBeenCalledWith('hello', { extra: 'data' });\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type { ILogger } from '@kb-labs/core-platform';\n\n/** Recorded log entry */\nexport interface LogEntry {\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n msg: string;\n error?: Error;\n meta?: Record<string, unknown>;\n}\n\n/**\n * Mock logger instance with message recording.\n */\nexport interface MockLoggerInstance extends ILogger {\n /** All recorded log messages */\n readonly messages: LogEntry[];\n /** Reset messages and spy call history */\n reset: () => void;\n}\n\n/**\n * Create a mock logger with message recording.\n *\n * All log methods are vi.fn() spies. Messages are collected\n * into a `.messages` array for easy assertion.\n *\n * Child loggers share the same messages array.\n *\n * @example\n * ```typescript\n * const logger = mockLogger();\n * const child = logger.child({ module: 'auth' });\n *\n * child.info('user logged in');\n * expect(logger.messages).toHaveLength(1);\n * expect(logger.messages[0].msg).toBe('user logged in');\n * ```\n */\nexport function mockLogger(sharedMessages?: LogEntry[]): MockLoggerInstance {\n const messages: LogEntry[] = sharedMessages ?? [];\n\n // trace, debug, info, warn: (message, meta?)\n function createSimpleLogMethod(level: LogEntry['level']) {\n return vi.fn((msg: string, meta?: Record<string, unknown>) => {\n messages.push({ level, msg, meta });\n });\n }\n\n // error, fatal: (message, error?, meta?)\n function createErrorLogMethod(level: 'error' | 'fatal') {\n return vi.fn((msg: string, error?: Error, meta?: Record<string, unknown>) => {\n messages.push({ level, msg, error, meta });\n });\n }\n\n const instance: MockLoggerInstance = {\n trace: createSimpleLogMethod('trace'),\n debug: createSimpleLogMethod('debug'),\n info: createSimpleLogMethod('info'),\n warn: createSimpleLogMethod('warn'),\n error: createErrorLogMethod('error'),\n fatal: createErrorLogMethod('fatal'),\n child: (_bindings?: Record<string, unknown>) => mockLogger(messages),\n get messages() { return messages; },\n reset: () => {\n messages.length = 0;\n (instance.trace as ReturnType<typeof vi.fn>).mockClear();\n (instance.debug as ReturnType<typeof vi.fn>).mockClear();\n (instance.info as ReturnType<typeof vi.fn>).mockClear();\n (instance.warn as ReturnType<typeof vi.fn>).mockClear();\n (instance.error as ReturnType<typeof vi.fn>).mockClear();\n (instance.fatal as ReturnType<typeof vi.fn>).mockClear();\n },\n };\n\n return instance;\n}\n","/**\n * @module @kb-labs/shared-testing/create-test-context\n *\n * Enhanced test context factory that bridges ctx.platform and the global singleton.\n *\n * Unlike the original createTestContext() from SDK, this version:\n * - Uses mockLLM/mockCache/mockLogger with vi.fn() spies (not noop functions)\n * - Syncs ctx.platform adapters with the global singleton via setupTestPlatform()\n * - Provides a cleanup function to reset the singleton in afterEach()\n *\n * @example\n * ```typescript\n * import { createTestContext, mockLLM } from '@kb-labs/shared-testing';\n *\n * const llm = mockLLM().onAnyComplete().respondWith('hello');\n * const { ctx, cleanup } = createTestContext({ platform: { llm } });\n *\n * // Both work — ctx.platform and useLLM() return the same mock\n * await handler.execute(ctx, args);\n * expect(llm.complete).toHaveBeenCalled();\n *\n * cleanup(); // Reset singleton in afterEach\n * ```\n */\n\nimport type {\n PluginContextV3,\n HostContext,\n PlatformServices,\n UIFacade,\n TraceContext,\n RuntimeAPI,\n PluginAPI,\n FSShim,\n FetchShim,\n EnvShim,\n InvokeAPI,\n EnvironmentAPI,\n WorkspaceAPI,\n SnapshotAPI,\n} from '@kb-labs/plugin-contracts';\n\nimport { setupTestPlatform } from './setup-platform.js';\nimport { mockLLM } from './mock-llm.js';\nimport { mockCache } from './mock-cache.js';\nimport { mockStorage } from './mock-storage.js';\nimport { mockLogger } from './mock-logger.js';\nimport { vi } from 'vitest';\n\n// ────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────\n\nexport interface CreateTestContextOptions {\n pluginId?: string;\n pluginVersion?: string;\n host?: 'cli' | 'rest' | 'workflow' | 'webhook';\n hostContext?: HostContext;\n config?: unknown;\n cwd?: string;\n outdir?: string;\n tenantId?: string;\n signal?: AbortSignal;\n /** Override platform services. Also synced to global singleton. */\n platform?: Partial<PlatformServices>;\n /** Override UI facade */\n ui?: Partial<UIFacade>;\n /**\n * If true, sync platform adapters to the global singleton\n * so that useLLM(), useCache(), etc. return the test mocks.\n * @default true\n */\n syncSingleton?: boolean;\n}\n\nexport interface TestContextResult<TConfig = unknown> {\n /** The plugin context with test mocks */\n ctx: PluginContextV3<TConfig>;\n /** Call in afterEach() to reset the global singleton */\n cleanup: () => void;\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Mock factories\n// ────────────────────────────────────────────────────────────────────\n\nexport function createMockTrace(): TraceContext {\n return {\n traceId: 'test-trace-id',\n spanId: 'test-span-id',\n parentSpanId: undefined,\n addEvent: () => {},\n setAttribute: () => {},\n recordError: () => {},\n };\n}\n\nexport function createMockUI(): UIFacade {\n const messages: string[] = [];\n\n const mockColor = (text: string) => text;\n const mockColors = {\n success: mockColor,\n error: mockColor,\n warning: mockColor,\n info: mockColor,\n primary: mockColor,\n accent: mockColor,\n highlight: mockColor,\n secondary: mockColor,\n emphasis: mockColor,\n muted: mockColor,\n foreground: mockColor,\n dim: mockColor,\n bold: mockColor,\n underline: mockColor,\n inverse: mockColor,\n };\n\n return {\n colors: mockColors,\n symbols: {\n success: '+',\n error: 'x',\n warning: '!',\n info: 'i',\n bullet: '-',\n clock: 'T',\n folder: 'D',\n package: 'P',\n pointer: '>',\n section: '#',\n separator: '-',\n border: '|',\n topLeft: '+',\n topRight: '+',\n bottomLeft: '+',\n bottomRight: '+',\n leftT: '+',\n rightT: '+',\n },\n write: vi.fn((text) => messages.push(`WRITE: ${text}`)),\n info: vi.fn((msg) => messages.push(`INFO: ${msg}`)),\n success: vi.fn((msg) => messages.push(`SUCCESS: ${msg}`)),\n warn: vi.fn((msg) => messages.push(`WARN: ${msg}`)),\n error: vi.fn((err) => messages.push(`ERROR: ${err instanceof Error ? err.message : err}`)),\n debug: vi.fn((msg) => messages.push(`DEBUG: ${msg}`)),\n spinner: vi.fn((msg) => {\n messages.push(`SPINNER: ${msg}`);\n return {\n update: vi.fn((m: string) => messages.push(`SPINNER UPDATE: ${m}`)),\n succeed: vi.fn((m?: string) => messages.push(`SPINNER SUCCEED: ${m ?? msg}`)),\n fail: vi.fn((m?: string) => messages.push(`SPINNER FAIL: ${m ?? msg}`)),\n stop: vi.fn(),\n };\n }),\n table: vi.fn((data) => messages.push(`TABLE: ${JSON.stringify(data)}`)),\n json: vi.fn((data) => messages.push(`JSON: ${JSON.stringify(data)}`)),\n newline: vi.fn(() => messages.push('')),\n divider: vi.fn(() => messages.push('-'.repeat(40))),\n box: vi.fn((content, title) => {\n if (title) {messages.push(`+- ${title} -+`);}\n messages.push(content);\n if (title) {messages.push(`+${'-'.repeat(title.length + 4)}+`);}\n }),\n sideBox: vi.fn((options) => {\n messages.push(`+- ${options.title} -+`);\n if (options.summary) {\n Object.entries(options.summary).forEach(([key, value]) => {\n messages.push(` ${key}: ${value}`);\n });\n }\n if (options.sections) {\n options.sections.forEach((section: { header?: string; items: string[] }) => {\n if (section.header) {messages.push(` ${section.header}:`);}\n section.items.forEach((item: string) => messages.push(` ${item}`));\n });\n }\n if (options.timing) {\n messages.push(` Timing: ${options.timing}ms`);\n }\n messages.push('+' + '-'.repeat(options.title.length + 4) + '+');\n }),\n confirm: vi.fn(async () => true),\n prompt: vi.fn(async () => ''),\n };\n}\n\nexport function createMockRuntime(): RuntimeAPI {\n const mockFS: FSShim = {\n readFile: vi.fn(async () => ''),\n readFileBuffer: vi.fn(async () => new Uint8Array()),\n writeFile: vi.fn(async () => {}),\n readdir: vi.fn(async () => []),\n readdirWithStats: vi.fn(async () => []),\n mkdir: vi.fn(async () => {}),\n rm: vi.fn(async () => {}),\n copy: vi.fn(async () => {}),\n move: vi.fn(async () => {}),\n glob: vi.fn(async () => []),\n stat: vi.fn(async () => ({\n isFile: () => false,\n isDirectory: () => false,\n size: 0,\n mtime: Date.now(),\n ctime: Date.now(),\n })),\n exists: vi.fn(async () => false),\n resolve: (path: string) => path,\n relative: (path: string) => path,\n join: (...segments: string[]) => segments.join('/'),\n dirname: (path: string) => path.split('/').slice(0, -1).join('/'),\n basename: (path: string) => path.split('/').pop() ?? '',\n extname: (path: string) => {\n const base = path.split('/').pop() ?? '';\n const idx = base.lastIndexOf('.');\n return idx > 0 ? base.slice(idx) : '';\n },\n };\n\n const mockFetch: FetchShim = vi.fn(async () => new Response('mock'));\n const mockEnv: EnvShim = vi.fn((_key: string) => undefined) as EnvShim;\n\n return {\n fs: mockFS,\n fetch: mockFetch,\n env: mockEnv,\n };\n}\n\nexport function createMockEnvironmentAPI(): EnvironmentAPI {\n return {\n create: vi.fn(async () => ({\n environmentId: 'env_mock_1',\n provider: 'mock',\n status: 'ready' as const,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n })),\n status: vi.fn(async (environmentId: string) => ({\n environmentId,\n status: 'ready' as const,\n updatedAt: new Date().toISOString(),\n })),\n destroy: vi.fn(async () => {}),\n renewLease: vi.fn(async () => ({\n leaseId: 'lease_mock_1',\n acquiredAt: new Date().toISOString(),\n expiresAt: new Date(Date.now() + 10 * 60 * 1000).toISOString(),\n })),\n };\n}\n\nexport function createMockWorkspaceAPI(): WorkspaceAPI {\n return {\n materialize: vi.fn(async () => ({\n workspaceId: 'ws_mock_1',\n provider: 'mock',\n status: 'ready' as const,\n rootPath: '/tmp/ws_mock_1',\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n })),\n attach: vi.fn(async (request) => ({\n workspaceId: request.workspaceId,\n environmentId: request.environmentId,\n mountPath: request.mountPath ?? '/workspace',\n attachedAt: new Date().toISOString(),\n })),\n release: vi.fn(async () => {}),\n status: vi.fn(async (workspaceId: string) => ({\n workspaceId,\n status: 'ready' as const,\n updatedAt: new Date().toISOString(),\n })),\n };\n}\n\nexport function createMockSnapshotAPI(): SnapshotAPI {\n return {\n capture: vi.fn(async (request) => ({\n snapshotId: request.snapshotId ?? 'snap_mock_1',\n provider: 'mock',\n status: 'ready' as const,\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n workspaceId: request.workspaceId,\n environmentId: request.environmentId,\n })),\n restore: vi.fn(async (request) => ({\n snapshotId: request.snapshotId,\n restoredAt: new Date().toISOString(),\n workspaceId: request.workspaceId,\n environmentId: request.environmentId,\n targetPath: request.targetPath,\n })),\n status: vi.fn(async (snapshotId: string) => ({\n snapshotId,\n status: 'ready' as const,\n updatedAt: new Date().toISOString(),\n })),\n delete: vi.fn(async () => {}),\n gc: vi.fn(async (request) => ({\n scanned: 0,\n deleted: 0,\n dryRun: request?.dryRun ?? false,\n })),\n };\n}\n\nexport function createInfraApiMocks(): Pick<PluginAPI, 'environment' | 'workspace' | 'snapshot'> {\n return {\n environment: createMockEnvironmentAPI(),\n workspace: createMockWorkspaceAPI(),\n snapshot: createMockSnapshotAPI(),\n };\n}\n\nexport function createMockPluginAPI(): PluginAPI {\n const infra = createInfraApiMocks();\n return {\n lifecycle: {\n onCleanup: vi.fn(),\n },\n state: {\n get: vi.fn(async () => undefined),\n set: vi.fn(async () => {}),\n delete: vi.fn(async () => {}),\n has: vi.fn(async () => false),\n getMany: vi.fn(async () => new Map()),\n setMany: vi.fn(async () => {}),\n },\n artifacts: {\n write: vi.fn(async () => '/mock/path'),\n list: vi.fn(async () => []),\n read: vi.fn(async () => ''),\n readBuffer: vi.fn(async () => new Uint8Array()),\n exists: vi.fn(async () => false),\n path: vi.fn(() => '/mock/path'),\n },\n shell: {\n exec: vi.fn(async () => ({ code: 0, stdout: '', stderr: '', ok: true })),\n },\n events: {\n emit: vi.fn(async () => {}),\n },\n invoke: {\n call: vi.fn(async (_pluginId: string, _input?: unknown, _options?: unknown) => undefined) as InvokeAPI['call'],\n },\n workflows: {\n run: vi.fn(async () => 'mock-run-id'),\n wait: vi.fn(async () => undefined),\n status: vi.fn(async () => null),\n cancel: vi.fn(async () => {}),\n list: vi.fn(async () => []),\n },\n jobs: {\n submit: vi.fn(async () => 'mock-job-id'),\n schedule: vi.fn(async () => 'mock-scheduled-job-id'),\n wait: vi.fn(async () => undefined),\n status: vi.fn(async () => null),\n cancel: vi.fn(async () => false),\n list: vi.fn(async () => []),\n },\n cron: {\n register: vi.fn(async () => {}),\n unregister: vi.fn(async () => {}),\n list: vi.fn(async () => []),\n pause: vi.fn(async () => {}),\n resume: vi.fn(async () => {}),\n trigger: vi.fn(async () => {}),\n },\n environment: infra.environment,\n workspace: infra.workspace,\n snapshot: infra.snapshot,\n };\n}\n\nexport const createMockPlatformApi = createMockPluginAPI;\n\nexport function createMockPluginContextV3<TConfig = unknown>(\n options: CreateTestContextOptions = {}\n): TestContextResult<TConfig> {\n return createTestContext<TConfig>(options);\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Public API\n// ────────────────────────────────────────────────────────────────────\n\n/**\n * Create a test context for plugin development.\n *\n * Unlike the original SDK version, this:\n * - Uses mock builders with vi.fn() spies (not noop functions)\n * - Syncs platform adapters to the global singleton by default\n * - Returns a cleanup function for afterEach()\n *\n * @example\n * ```typescript\n * import { createTestContext, mockLLM } from '@kb-labs/shared-testing';\n *\n * describe('my handler', () => {\n * let cleanup: () => void;\n *\n * beforeEach(() => {\n * const llm = mockLLM().onAnyComplete().respondWith('ok');\n * const result = createTestContext({ platform: { llm } });\n * cleanup = result.cleanup;\n * // Use result.ctx in your tests\n * });\n *\n * afterEach(() => cleanup());\n * });\n * ```\n */\nexport function createTestContext<TConfig = unknown>(\n options: CreateTestContextOptions = {}\n): TestContextResult<TConfig> {\n const {\n pluginId = 'test-plugin',\n pluginVersion = '0.0.0',\n host = 'cli',\n hostContext,\n config,\n cwd = process.cwd(),\n outdir,\n tenantId,\n signal,\n platform: platformOverrides,\n ui: uiOverrides,\n syncSingleton = true,\n } = options;\n\n const resolvedOutdir = outdir ?? `${cwd}/.kb/output`;\n\n // Build default host context\n const defaultHostContext: HostContext =\n hostContext ??\n (() => {\n switch (host) {\n case 'cli':\n return { host: 'cli', argv: ['test'], flags: {} };\n case 'rest':\n return {\n host: 'rest',\n method: 'GET',\n path: '/test',\n requestId: 'test-req-001',\n traceId: 'test-trace-001',\n };\n case 'workflow':\n return { host: 'workflow', workflowId: 'test-wf', runId: 'test-run', stepId: 'test-step' };\n case 'webhook':\n return { host: 'webhook', event: 'test:event' };\n }\n })();\n\n // Create default mocks (with spies, not noop)\n const defaultLogger = mockLogger();\n const defaultLLM = mockLLM();\n const defaultCache = mockCache();\n const defaultStorage = mockStorage();\n\n const defaultPlatform: PlatformServices = {\n logger: defaultLogger,\n llm: defaultLLM,\n embeddings: {\n embed: vi.fn(async () => []),\n embedBatch: vi.fn(async () => [[]]),\n dimensions: 1536,\n getDimensions: vi.fn(async () => 1536),\n },\n vectorStore: {\n search: vi.fn(async () => []),\n upsert: vi.fn(async () => {}),\n delete: vi.fn(async () => {}),\n count: vi.fn(async () => 0),\n },\n cache: defaultCache,\n storage: defaultStorage,\n analytics: {\n track: vi.fn(async () => {}),\n identify: vi.fn(async () => {}),\n flush: vi.fn(async () => {}),\n },\n eventBus: {\n publish: vi.fn(async () => {}),\n subscribe: vi.fn(() => () => {}),\n },\n config: {\n getConfig: vi.fn(async () => ({})),\n getRawConfig: vi.fn(async () => ({})),\n },\n invoke: {\n call: vi.fn(async () => ({ success: true, data: null })),\n isAvailable: vi.fn(async () => false),\n } as PlatformServices['invoke'],\n sqlDatabase: {\n query: vi.fn(async () => ({ rows: [], rowCount: 0 })),\n transaction: vi.fn(async () => ({ query: vi.fn(), commit: vi.fn(), rollback: vi.fn() })),\n close: vi.fn(async () => {}),\n },\n documentDatabase: {\n find: vi.fn(async () => []),\n findById: vi.fn(async () => null),\n insertOne: vi.fn(async () => ({ id: 'mock', createdAt: 0, updatedAt: 0 })),\n updateMany: vi.fn(async () => 0),\n updateById: vi.fn(async () => null),\n deleteMany: vi.fn(async () => 0),\n deleteById: vi.fn(async () => false),\n count: vi.fn(async () => 0),\n close: vi.fn(async () => {}),\n } as PlatformServices['documentDatabase'],\n logs: {\n query: vi.fn(async () => ({ logs: [], total: 0, hasMore: false, source: 'buffer' as const })),\n getById: vi.fn(async () => null),\n search: vi.fn(async () => ({ logs: [], total: 0, hasMore: false })),\n subscribe: vi.fn(() => () => {}),\n getStats: vi.fn(async () => ({})),\n getCapabilities: vi.fn(() => ({ hasBuffer: false, hasPersistence: false, hasSearch: false, hasStreaming: false })),\n },\n };\n\n // Merge overrides\n const finalPlatform: PlatformServices = {\n ...defaultPlatform,\n ...platformOverrides,\n };\n\n // Sync to global singleton\n let cleanupFn = () => {};\n if (syncSingleton) {\n const result = setupTestPlatform({\n llm: finalPlatform.llm,\n cache: finalPlatform.cache,\n storage: finalPlatform.storage,\n logger: finalPlatform.logger,\n analytics: finalPlatform.analytics,\n embeddings: finalPlatform.embeddings,\n vectorStore: finalPlatform.vectorStore,\n eventBus: finalPlatform.eventBus,\n });\n cleanupFn = result.cleanup;\n }\n\n // UI\n const defaultUI = createMockUI();\n const finalUI: UIFacade = {\n ...defaultUI,\n ...uiOverrides,\n };\n\n // Build context\n const ctx: PluginContextV3<TConfig> = {\n host,\n requestId: 'test-trace:test-span',\n pluginId,\n pluginVersion,\n tenantId,\n cwd,\n outdir: resolvedOutdir,\n config: config as TConfig,\n signal,\n trace: createMockTrace(),\n hostContext: defaultHostContext,\n ui: finalUI,\n platform: finalPlatform,\n runtime: createMockRuntime(),\n api: createMockPluginAPI(),\n };\n\n return { ctx, cleanup: cleanupFn };\n}\n","/**\n * @module @kb-labs/shared-testing/test-command\n *\n * Single-function test runner for plugin command handlers.\n *\n * Eliminates boilerplate: no manual context creation, no descriptor setup,\n * no mock wiring. Import your handler, call testCommand(), assert on result.\n *\n * @example\n * ```typescript\n * import { testCommand, mockLLM } from '@kb-labs/shared-testing';\n * import handler from '../src/commands/greet.js';\n *\n * it('greets the user', async () => {\n * const result = await testCommand(handler, {\n * flags: { name: 'Alice' },\n * });\n *\n * expect(result.exitCode).toBe(0);\n * expect(result.result).toEqual({ message: 'Hello, Alice!' });\n * expect(result.ui.success).toHaveBeenCalledWith('Hello, Alice!');\n * });\n * ```\n */\n\nimport type {\n PluginContextV3,\n CommandResult,\n UIFacade,\n PlatformServices,\n} from '@kb-labs/plugin-contracts';\n\nimport {\n createTestContext,\n type CreateTestContextOptions,\n} from './create-test-context.js';\n\n// ────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────\n\n/**\n * Any handler that has an execute(ctx, input) method.\n * Covers CommandHandlerV3, RouteHandler, Handler, and raw objects.\n */\nexport interface TestableHandler<TConfig = unknown, TInput = unknown, TResult = unknown> {\n execute(\n context: PluginContextV3<TConfig>,\n input: TInput\n ): Promise<TResult> | TResult;\n cleanup?(): Promise<void> | void;\n}\n\n/**\n * Options for testCommand().\n *\n * Provide only what you need — everything else gets sensible defaults.\n */\nexport interface TestCommandOptions<TConfig = unknown> {\n // ── Input ──────────────────────────────────────────────────────\n\n /** CLI flags (sugar for input: { flags, argv }) */\n flags?: Record<string, unknown>;\n\n /** CLI positional arguments (default: []) */\n argv?: string[];\n\n /** REST query params (sugar for input: { query }) */\n query?: Record<string, unknown>;\n\n /** REST request body (sugar for input: { body }) */\n body?: unknown;\n\n /** REST route params (sugar for input: { params }) */\n params?: Record<string, unknown>;\n\n /**\n * Raw input object. If provided, flags/argv/query/body/params are ignored.\n * Use this when your handler expects a custom input shape.\n */\n input?: unknown;\n\n // ── Context ────────────────────────────────────────────────────\n\n /** Host type (default: 'cli') */\n host?: 'cli' | 'rest' | 'workflow' | 'webhook';\n\n /** Plugin config passed as ctx.config */\n config?: TConfig;\n\n /** Working directory (default: process.cwd()) */\n cwd?: string;\n\n /** Tenant ID */\n tenantId?: string;\n\n /** Abort signal */\n signal?: AbortSignal;\n\n // ── Mocks ──────────────────────────────────────────────────────\n\n /** Override platform services (e.g., { llm: mockLLM().onAnyComplete().respondWith('ok') }) */\n platform?: Partial<PlatformServices>;\n\n /** Override UI facade (individual methods are merged with default spy UI) */\n ui?: Partial<UIFacade>;\n\n /**\n * Sync platform mocks to global singleton for composables (useLLM, useCache, etc.)\n * @default true\n */\n syncSingleton?: boolean;\n}\n\n/**\n * Result of testCommand() — everything you need for assertions.\n */\nexport interface TestCommandResult<TResult = unknown> {\n /** Exit code from CommandResult (0 = success). Defaults to 0 if handler returns raw data. */\n exitCode: number;\n\n /** The result data from the handler */\n result: TResult | undefined;\n\n /** Custom metadata from CommandResult.meta */\n meta: Record<string, unknown> | undefined;\n\n /** The raw return value from handler.execute() — useful for non-CommandResult handlers */\n raw: unknown;\n\n /** UI facade with vi.fn() spies — assert on ui.success, ui.error, etc. */\n ui: UIFacade;\n\n /** The full context that was passed to the handler */\n ctx: PluginContextV3;\n\n /** Call in afterEach() to reset the global platform singleton */\n cleanup: () => void;\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Implementation\n// ────────────────────────────────────────────────────────────────────\n\n/**\n * Test a plugin command handler with minimal boilerplate.\n *\n * Creates a test context, builds the input object, calls handler.execute(),\n * and returns a result with UI spies for assertions.\n *\n * @example CLI command\n * ```typescript\n * import handler from '../src/commands/greet.js';\n *\n * const result = await testCommand(handler, {\n * flags: { name: 'Alice' },\n * });\n * expect(result.exitCode).toBe(0);\n * expect(result.ui.success).toHaveBeenCalledWith('Hello, Alice!');\n * ```\n *\n * @example REST handler\n * ```typescript\n * import handler from '../src/routes/create-plan.js';\n *\n * const result = await testCommand(handler, {\n * host: 'rest',\n * body: { name: 'v2.0' },\n * query: { workspace: 'root' },\n * });\n * expect(result.result).toMatchObject({ name: 'v2.0' });\n * ```\n *\n * @example With LLM mock\n * ```typescript\n * import { testCommand, mockLLM } from '@kb-labs/shared-testing';\n * import handler from '../src/commands/analyze.js';\n *\n * const llm = mockLLM().onAnyComplete().respondWith('looks good');\n * const result = await testCommand(handler, {\n * flags: { file: 'index.ts' },\n * platform: { llm },\n * });\n * expect(llm.complete).toHaveBeenCalled();\n * expect(result.exitCode).toBe(0);\n * ```\n */\nexport async function testCommand<TResult = unknown, TConfig = unknown>(\n handler: TestableHandler<TConfig, any, any>,\n options: TestCommandOptions<TConfig> = {}\n): Promise<TestCommandResult<TResult>> {\n const {\n flags,\n argv = [],\n query,\n body,\n params,\n input: rawInput,\n host = 'cli',\n config,\n cwd,\n tenantId,\n signal,\n platform,\n ui,\n syncSingleton = true,\n } = options;\n\n // ── Build context ──────────────────────────────────────────────\n\n const ctxOptions: CreateTestContextOptions = {\n host,\n config,\n cwd,\n tenantId,\n signal,\n platform,\n ui,\n syncSingleton,\n };\n\n const { ctx, cleanup } = createTestContext<TConfig>(ctxOptions);\n\n // ── Build input ────────────────────────────────────────────────\n\n let input: unknown;\n\n if (rawInput !== undefined) {\n // Explicit raw input takes precedence\n input = rawInput;\n } else if (host === 'rest') {\n // REST handlers expect { query?, body?, params? }\n input = {\n ...(query !== undefined ? { query } : {}),\n ...(body !== undefined ? { body } : {}),\n ...(params !== undefined ? { params } : {}),\n };\n } else {\n // CLI/workflow handlers expect { flags, argv }\n input = { flags: flags ?? {}, argv };\n }\n\n // ── Execute ────────────────────────────────────────────────────\n\n let raw: unknown;\n try {\n raw = await handler.execute(ctx as PluginContextV3<TConfig>, input);\n } finally {\n // Call cleanup if handler has one\n await handler.cleanup?.();\n }\n\n // ── Normalize result ───────────────────────────────────────────\n\n // Handler may return CommandResult<T>, void, or raw data.\n // Detect by checking for exitCode property.\n let exitCode = 0;\n let result: TResult | undefined;\n let meta: Record<string, unknown> | undefined;\n\n if (raw != null && typeof raw === 'object' && 'exitCode' in raw) {\n // CommandResult shape\n const cmdResult = raw as CommandResult<TResult>;\n exitCode = cmdResult.exitCode;\n result = cmdResult.result;\n meta = cmdResult.meta;\n } else if (raw === undefined || raw === null) {\n // void return (RouteHandler can return void)\n exitCode = 0;\n result = undefined;\n meta = undefined;\n } else {\n // Raw data (Handler from defineHandler returns data directly)\n exitCode = 0;\n result = raw as TResult;\n meta = undefined;\n }\n\n return {\n exitCode,\n result,\n meta,\n raw,\n ui: ctx.ui,\n ctx: ctx as PluginContextV3,\n cleanup,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/mock-llm.ts","../src/mock-cache.ts","../src/mock-storage.ts","../src/mock-logger.ts","../src/mock-embeddings.ts"],"names":["vi"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/shared-testing/mock-llm\n *\n * LLM mock builder with fluent API and call tracking.\n *\n * @example\n * ```typescript\n * const llm = mockLLM()\n * .onComplete('Generate commit').respondWith('feat: add login')\n * .onComplete(/explain/i).respondWith('This function does X')\n * .onAnyComplete().respondWith('default answer');\n *\n * const res = await llm.complete('Generate commit message');\n * expect(res.content).toBe('feat: add login');\n * expect(llm.complete).toHaveBeenCalledOnce();\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type {\n ILLM,\n LLMOptions,\n LLMResponse,\n LLMMessage,\n LLMToolCallOptions,\n LLMToolCallResponse,\n LLMToolCall,\n} from '@kb-labs/core-platform';\n\n// ────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────\n\n/** Recorded call to complete() */\nexport interface LLMCall {\n prompt: string;\n options?: LLMOptions;\n response: LLMResponse;\n}\n\n/** Recorded call to chatWithTools() */\nexport interface LLMToolCallRecord {\n messages: LLMMessage[];\n options: LLMToolCallOptions;\n response: LLMToolCallResponse;\n}\n\ntype PromptMatcher = string | RegExp | ((prompt: string) => boolean);\n\ninterface CompletionRule {\n matcher: PromptMatcher;\n response: string | LLMResponse | ((prompt: string) => string | LLMResponse);\n}\n\n/**\n * Mock LLM instance with call tracking and fluent API.\n * All methods are vi.fn() spies.\n */\nexport interface MockLLMInstance extends ILLM {\n /** All recorded complete() calls */\n calls: LLMCall[];\n /** Last complete() call (or undefined) */\n lastCall: LLMCall | undefined;\n /** All recorded chatWithTools() calls */\n toolCalls: LLMToolCallRecord[];\n /** Reset all recorded calls and spies */\n resetCalls: () => void;\n}\n\n/** Public type returned by mockLLM() — builder fluent API + ILLM spy instance */\nexport interface MockLLM extends MockLLMInstance {\n onComplete(matcher: PromptMatcher): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLM };\n onAnyComplete(): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLM };\n streaming(chunks: string[]): MockLLM;\n failing(error: Error): MockLLM;\n withToolCalls(calls: LLMToolCall[], content?: string): MockLLM;\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Builder\n// ────────────────────────────────────────────────────────────────────\n\nclass MockLLMBuilder {\n private rules: CompletionRule[] = [];\n private defaultResponse: string | LLMResponse | ((prompt: string) => string | LLMResponse) = 'mock response';\n private streamChunks: string[] = ['mock'];\n private errorToThrow: Error | null = null;\n private toolCallsToReturn: LLMToolCall[] = [];\n private toolCallResponseContent = '';\n\n /**\n * Match a specific prompt (exact string or regex).\n * Returns a handler to set the response.\n */\n onComplete(matcher: PromptMatcher): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLMBuilder } {\n return {\n respondWith: (response) => {\n this.rules.push({ matcher, response });\n return this;\n },\n };\n }\n\n /**\n * Set the default response for any prompt that doesn't match a rule.\n */\n onAnyComplete(): { respondWith: (response: string | LLMResponse | ((prompt: string) => string | LLMResponse)) => MockLLMBuilder } {\n return {\n respondWith: (response) => {\n this.defaultResponse = response;\n return this;\n },\n };\n }\n\n /**\n * Configure streaming to yield specific chunks.\n */\n streaming(chunks: string[]): MockLLMBuilder {\n this.streamChunks = chunks;\n return this;\n }\n\n /**\n * Make the LLM throw an error on every call.\n */\n failing(error: Error): MockLLMBuilder {\n this.errorToThrow = error;\n return this;\n }\n\n /**\n * Configure chatWithTools() to return specific tool calls.\n */\n withToolCalls(calls: LLMToolCall[], content = ''): MockLLMBuilder {\n this.toolCallsToReturn = calls;\n this.toolCallResponseContent = content;\n return this;\n }\n\n /**\n * Build the mock ILLM instance. Called automatically by mockLLM().\n */\n build(): MockLLMInstance {\n const calls: LLMCall[] = [];\n const toolCallRecords: LLMToolCallRecord[] = [];\n const rules = this.rules;\n const defaultResponse = this.defaultResponse;\n const streamChunks = this.streamChunks;\n const errorToThrow = this.errorToThrow;\n const toolCallsToReturn = this.toolCallsToReturn;\n const toolCallResponseContent = this.toolCallResponseContent;\n\n function resolveResponse(prompt: string): LLMResponse {\n // Check rules in order\n for (const rule of rules) {\n if (matchesPrompt(rule.matcher, prompt)) {\n return toResponse(rule.response, prompt);\n }\n }\n // Fallback\n return toResponse(defaultResponse, prompt);\n }\n\n const completeFn = vi.fn(async (prompt: string, options?: LLMOptions): Promise<LLMResponse> => {\n if (errorToThrow) {throw errorToThrow;}\n const response = resolveResponse(prompt);\n calls.push({ prompt, options, response });\n return response;\n });\n\n const streamFn = vi.fn(async function* (_prompt: string, _options?: LLMOptions): AsyncIterable<string> {\n if (errorToThrow) {throw errorToThrow;}\n for (const chunk of streamChunks) {\n yield chunk;\n }\n });\n\n const chatWithToolsFn = vi.fn(async (messages: LLMMessage[], options: LLMToolCallOptions): Promise<LLMToolCallResponse> => {\n if (errorToThrow) {throw errorToThrow;}\n\n const lastUserMsg = messages.filter(m => m.role === 'user').pop();\n const prompt = lastUserMsg?.content ?? '';\n const baseResponse = resolveResponse(prompt);\n\n const response: LLMToolCallResponse = {\n ...baseResponse,\n content: toolCallsToReturn.length > 0 ? toolCallResponseContent : baseResponse.content,\n toolCalls: toolCallsToReturn.length > 0 ? toolCallsToReturn : undefined,\n };\n\n toolCallRecords.push({ messages, options, response });\n return response;\n });\n\n const instance: MockLLMInstance = {\n complete: completeFn,\n stream: streamFn,\n chatWithTools: chatWithToolsFn,\n calls,\n toolCalls: toolCallRecords,\n get lastCall() {\n return calls[calls.length - 1];\n },\n resetCalls: () => {\n calls.length = 0;\n toolCallRecords.length = 0;\n completeFn.mockClear();\n streamFn.mockClear();\n chatWithToolsFn.mockClear();\n },\n };\n\n return instance;\n }\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────────────\n\nfunction matchesPrompt(matcher: PromptMatcher, prompt: string): boolean {\n if (typeof matcher === 'string') {return prompt.includes(matcher);}\n if (matcher instanceof RegExp) {return matcher.test(prompt);}\n return matcher(prompt);\n}\n\nfunction toResponse(\n value: string | LLMResponse | ((prompt: string) => string | LLMResponse),\n prompt: string,\n): LLMResponse {\n const resolved = typeof value === 'function' ? value(prompt) : value;\n if (typeof resolved === 'string') {\n return {\n content: resolved,\n usage: { promptTokens: 0, completionTokens: 0 },\n model: 'mock',\n };\n }\n return resolved;\n}\n\n// ────────────────────────────────────────────────────────────────────\n// Public API\n// ────────────────────────────────────────────────────────────────────\n\n/**\n * Create a mock LLM with fluent builder API.\n *\n * @example\n * ```typescript\n * // Simple: always returns same response\n * const llm = mockLLM();\n *\n * // With specific responses\n * const llm = mockLLM()\n * .onComplete('generate commit').respondWith('feat: add feature')\n * .onComplete(/explain/i).respondWith('This code does...')\n * .onAnyComplete().respondWith('default');\n *\n * // Streaming\n * const llm = mockLLM().streaming(['chunk1', 'chunk2', 'chunk3']);\n *\n * // Error simulation\n * const llm = mockLLM().failing(new Error('rate limit exceeded'));\n *\n * // Tool calling\n * const llm = mockLLM().withToolCalls([\n * { id: 'call-1', name: 'search', input: { query: 'test' } },\n * ]);\n * ```\n */\nexport function mockLLM(): MockLLM {\n const builder = new MockLLMBuilder();\n\n // Create a Proxy that acts as both builder and built instance.\n // Builder methods are forwarded to the builder.\n // ILLM methods (complete, stream, etc.) trigger auto-build on first access.\n let built: MockLLMInstance | null = null;\n\n function ensureBuilt(): MockLLMInstance {\n if (!built) {built = builder.build();}\n return built;\n }\n\n // Use Record<string, unknown> index signatures so we can introspect builder / instance\n // properties without falling back to `any`. The Proxy target is the builder.\n type BuilderRecord = MockLLMBuilder & Record<string, unknown>;\n type InstanceRecord = MockLLMInstance & Record<string, unknown>;\n\n const proxy = new Proxy(builder as BuilderRecord, {\n get(target: BuilderRecord, prop: string | symbol, _receiver: unknown): unknown {\n // Builder methods — return from builder\n if (prop in target && typeof target[prop as string] === 'function') {\n const method = (target[prop as string] as (...args: unknown[]) => unknown).bind(target);\n // After calling a builder method, invalidate the built instance\n return (...args: unknown[]): unknown => {\n built = null;\n const result = method(...args);\n // If the result is the builder itself (chaining), return the proxy\n if (result === target || (result !== null && typeof result === 'object' && 'respondWith' in result)) {\n if (result === target) {return proxy;}\n // Wrap respondWith to return proxy\n const resultWithRespond = result as { respondWith: (...rArgs: unknown[]) => unknown };\n return {\n respondWith: (...rArgs: unknown[]): unknown => {\n built = null;\n resultWithRespond.respondWith(...rArgs);\n return proxy;\n },\n };\n }\n return result;\n };\n }\n\n // ILLM instance properties — auto-build\n const instance = ensureBuilt() as InstanceRecord;\n // Return functions directly (not bound) to preserve vi.fn() spy identity\n return instance[prop as string];\n },\n });\n\n return proxy as unknown as MockLLM;\n}\n","/**\n * @module @kb-labs/shared-testing/mock-cache\n *\n * In-memory cache mock that actually stores data (unlike noop mocks).\n * All methods are vi.fn() spies for assertion.\n *\n * @example\n * ```typescript\n * const cache = mockCache();\n * await cache.set('key', { data: 42 }, 5000);\n * expect(await cache.get('key')).toEqual({ data: 42 });\n * expect(cache.set).toHaveBeenCalledWith('key', { data: 42 }, 5000);\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type { ICache } from '@kb-labs/core-platform';\n\ninterface CacheEntry {\n value: unknown;\n expiresAt: number | null; // null = no TTL\n}\n\ninterface SortedSetMember {\n score: number;\n member: string;\n}\n\n/**\n * Mock cache instance with working in-memory storage.\n * All methods are vi.fn() spies.\n */\nexport interface MockCacheInstance extends ICache {\n /** Direct access to the internal store (for assertions) */\n readonly store: Map<string, CacheEntry>;\n /** Direct access to sorted sets (for assertions) */\n readonly sortedSets: Map<string, SortedSetMember[]>;\n /** Reset all data and spy call history */\n reset: () => void;\n}\n\n/**\n * Create a mock cache with working in-memory storage.\n *\n * Unlike noop mocks, this cache actually stores and retrieves data,\n * respects TTL, and supports sorted set operations.\n *\n * @param initial - Optional initial data to populate the cache\n *\n * @example\n * ```typescript\n * // Empty cache\n * const cache = mockCache();\n *\n * // Pre-populated cache\n * const cache = mockCache({ 'user:1': { name: 'Alice' } });\n *\n * // TTL works\n * await cache.set('temp', 'value', 100); // expires in 100ms\n * await new Promise(r => setTimeout(r, 150));\n * expect(await cache.get('temp')).toBeNull(); // expired\n * ```\n */\nexport function mockCache(initial?: Record<string, unknown>): MockCacheInstance {\n const store = new Map<string, CacheEntry>();\n const sortedSets = new Map<string, SortedSetMember[]>();\n\n // Populate initial data\n if (initial) {\n for (const [key, value] of Object.entries(initial)) {\n store.set(key, { value, expiresAt: null });\n }\n }\n\n function isExpired(entry: CacheEntry): boolean {\n return entry.expiresAt !== null && Date.now() > entry.expiresAt;\n }\n\n // Keep raw vi.fn() references for mockClear() in reset.\n // The ICache-typed versions are cast below for the public interface.\n const spies: ReturnType<typeof vi.fn>[] = [];\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function spy<T extends (...args: any[]) => unknown>(fn: T): T {\n const s = vi.fn(fn) as unknown as T;\n spies.push(s as unknown as ReturnType<typeof vi.fn>);\n return s;\n }\n\n const getFn = spy(async (key: string): Promise<unknown> => {\n const entry = store.get(key);\n if (!entry || isExpired(entry)) {\n if (entry) {store.delete(key);} // cleanup expired\n return null;\n }\n return entry.value;\n });\n\n const setFn = spy(async (key: string, value: unknown, ttl?: number): Promise<void> => {\n store.set(key, {\n value,\n expiresAt: ttl ? Date.now() + ttl : null,\n });\n });\n\n const deleteFn = spy(async (key: string): Promise<void> => {\n store.delete(key);\n });\n\n const clearFn = spy(async (pattern?: string): Promise<void> => {\n if (!pattern) {\n store.clear();\n return;\n }\n // Simple glob matching: only support trailing *\n const prefix = pattern.replace(/\\*$/, '');\n for (const key of store.keys()) {\n if (key.startsWith(prefix)) {\n store.delete(key);\n }\n }\n });\n\n const zaddFn = spy(async (key: string, score: number, member: string): Promise<void> => {\n if (!sortedSets.has(key)) {sortedSets.set(key, []);}\n const set = sortedSets.get(key)!;\n // Remove existing member\n const idx = set.findIndex(m => m.member === member);\n if (idx >= 0) {set.splice(idx, 1);}\n // Add with new score\n set.push({ score, member });\n // Keep sorted by score\n set.sort((a, b) => a.score - b.score);\n });\n\n const zrangebyscoreFn = spy(async (key: string, min: number, max: number): Promise<string[]> => {\n const set = sortedSets.get(key);\n if (!set) {return [];}\n return set\n .filter(m => m.score >= min && m.score <= max)\n .map(m => m.member);\n });\n\n const zremFn = spy(async (key: string, member: string): Promise<void> => {\n const set = sortedSets.get(key);\n if (!set) {return;}\n const idx = set.findIndex(m => m.member === member);\n if (idx >= 0) {set.splice(idx, 1);}\n });\n\n const setIfNotExistsFn = spy(async (key: string, value: unknown, ttl?: number): Promise<boolean> => {\n const existing = store.get(key);\n if (existing && !isExpired(existing)) {return false;}\n store.set(key, {\n value,\n expiresAt: ttl ? Date.now() + ttl : null,\n });\n return true;\n });\n\n const instance: MockCacheInstance = {\n get: getFn as unknown as ICache['get'],\n set: setFn as unknown as ICache['set'],\n delete: deleteFn,\n clear: clearFn,\n zadd: zaddFn,\n zrangebyscore: zrangebyscoreFn,\n zrem: zremFn,\n setIfNotExists: setIfNotExistsFn as unknown as ICache['setIfNotExists'],\n get store() { return store; },\n get sortedSets() { return sortedSets; },\n reset: () => {\n store.clear();\n sortedSets.clear();\n for (const s of spies) {s.mockClear();}\n },\n };\n\n return instance;\n}\n","/**\n * @module @kb-labs/shared-testing/mock-storage\n *\n * Virtual filesystem mock for IStorage interface.\n * All methods are vi.fn() spies.\n *\n * @example\n * ```typescript\n * const storage = mockStorage({\n * 'config.json': '{\"key\": \"value\"}',\n * 'data/file.txt': 'hello world',\n * });\n *\n * const content = await storage.read('config.json');\n * expect(content?.toString()).toBe('{\"key\": \"value\"}');\n * expect(storage.read).toHaveBeenCalledWith('config.json');\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type { IStorage } from '@kb-labs/core-platform';\n\n/**\n * Mock storage instance with in-memory virtual filesystem.\n */\nexport interface MockStorageInstance extends IStorage {\n /** Direct access to the virtual filesystem (for assertions) */\n readonly files: Map<string, Buffer>;\n /** Reset all files and spy call history */\n reset: () => void;\n}\n\n/**\n * Create a mock storage with in-memory virtual filesystem.\n *\n * @param initial - Optional initial files. Keys are paths, values are content (string or Buffer).\n *\n * @example\n * ```typescript\n * const storage = mockStorage({\n * 'data.json': JSON.stringify({ items: [] }),\n * 'binary.dat': Buffer.from([0x00, 0x01]),\n * });\n *\n * // Write and read\n * await storage.write('new.txt', Buffer.from('hello'));\n * expect(await storage.exists('new.txt')).toBe(true);\n *\n * // List files\n * const files = await storage.list('');\n * expect(files).toContain('new.txt');\n * ```\n */\nexport function mockStorage(initial?: Record<string, string | Buffer>): MockStorageInstance {\n const files = new Map<string, Buffer>();\n\n // Populate initial files\n if (initial) {\n for (const [path, content] of Object.entries(initial)) {\n files.set(path, typeof content === 'string' ? Buffer.from(content) : content);\n }\n }\n\n const readFn = vi.fn(async (path: string): Promise<Buffer | null> => {\n return files.get(path) ?? null;\n });\n\n const writeFn = vi.fn(async (path: string, data: Buffer): Promise<void> => {\n files.set(path, data);\n });\n\n const deleteFn = vi.fn(async (path: string): Promise<void> => {\n files.delete(path);\n });\n\n const listFn = vi.fn(async (prefix: string): Promise<string[]> => {\n const result: string[] = [];\n for (const path of files.keys()) {\n if (path.startsWith(prefix)) {\n result.push(path);\n }\n }\n return result.sort();\n });\n\n const existsFn = vi.fn(async (path: string): Promise<boolean> => {\n return files.has(path);\n });\n\n const instance: MockStorageInstance = {\n read: readFn,\n write: writeFn,\n delete: deleteFn,\n list: listFn,\n exists: existsFn,\n get files() { return files; },\n reset: () => {\n files.clear();\n readFn.mockClear();\n writeFn.mockClear();\n deleteFn.mockClear();\n listFn.mockClear();\n existsFn.mockClear();\n },\n };\n\n return instance;\n}\n","/**\n * @module @kb-labs/shared-testing/mock-logger\n *\n * Logger mock with message recording and vi.fn() spies.\n *\n * @example\n * ```typescript\n * const logger = mockLogger();\n * logger.info('hello', { extra: 'data' });\n * logger.error('oops', new Error('fail'));\n *\n * expect(logger.messages).toEqual([\n * { level: 'info', msg: 'hello', meta: { extra: 'data' } },\n * { level: 'error', msg: 'oops', error: expect.any(Error), meta: undefined },\n * ]);\n * expect(logger.info).toHaveBeenCalledWith('hello', { extra: 'data' });\n * ```\n */\n\nimport { vi } from 'vitest';\nimport type { ILogger } from '@kb-labs/core-platform';\n\n/** Recorded log entry */\nexport interface LogEntry {\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';\n msg: string;\n error?: Error;\n meta?: Record<string, unknown>;\n}\n\n/**\n * Mock logger instance with message recording.\n */\nexport interface MockLoggerInstance extends ILogger {\n /** All recorded log messages */\n readonly messages: LogEntry[];\n /** Reset messages and spy call history */\n reset: () => void;\n}\n\n/**\n * Create a mock logger with message recording.\n *\n * All log methods are vi.fn() spies. Messages are collected\n * into a `.messages` array for easy assertion.\n *\n * Child loggers share the same messages array.\n *\n * @example\n * ```typescript\n * const logger = mockLogger();\n * const child = logger.child({ module: 'auth' });\n *\n * child.info('user logged in');\n * expect(logger.messages).toHaveLength(1);\n * expect(logger.messages[0].msg).toBe('user logged in');\n * ```\n */\nexport function mockLogger(sharedMessages?: LogEntry[]): MockLoggerInstance {\n const messages: LogEntry[] = sharedMessages ?? [];\n\n // trace, debug, info, warn: (message, meta?)\n function createSimpleLogMethod(level: LogEntry['level']) {\n return vi.fn((msg: string, meta?: Record<string, unknown>) => {\n messages.push({ level, msg, meta });\n });\n }\n\n // error, fatal: (message, error?, meta?)\n function createErrorLogMethod(level: 'error' | 'fatal') {\n return vi.fn((msg: string, error?: Error, meta?: Record<string, unknown>) => {\n messages.push({ level, msg, error, meta });\n });\n }\n\n const instance: MockLoggerInstance = {\n trace: createSimpleLogMethod('trace'),\n debug: createSimpleLogMethod('debug'),\n info: createSimpleLogMethod('info'),\n warn: createSimpleLogMethod('warn'),\n error: createErrorLogMethod('error'),\n fatal: createErrorLogMethod('fatal'),\n child: (_bindings?: Record<string, unknown>) => mockLogger(messages),\n get messages() { return messages; },\n reset: () => {\n messages.length = 0;\n (instance.trace as ReturnType<typeof vi.fn>).mockClear();\n (instance.debug as ReturnType<typeof vi.fn>).mockClear();\n (instance.info as ReturnType<typeof vi.fn>).mockClear();\n (instance.warn as ReturnType<typeof vi.fn>).mockClear();\n (instance.error as ReturnType<typeof vi.fn>).mockClear();\n (instance.fatal as ReturnType<typeof vi.fn>).mockClear();\n },\n };\n\n return instance;\n}\n","/**\n * @module @kb-labs/shared-testing/mock-embeddings\n *\n * Deterministic in-memory `IEmbeddings` test double.\n *\n * Generates stable, normalised vectors from a text hash so tests that\n * need real math (cosine similarity, nearest-neighbour lookups) produce\n * reproducible results across runs — without any network call or model.\n *\n * @example\n * ```typescript\n * import { MockEmbeddings } from '@kb-labs/shared-testing';\n * const embed = new MockEmbeddings();\n * const vec = await embed.embed('hello'); // length === 1536, normalised\n * ```\n */\n\nimport type { IEmbeddings } from '@kb-labs/core-platform/adapters';\n\nexport class MockEmbeddings implements IEmbeddings {\n readonly dimensions = 1536;\n\n private hash(text: string): number {\n let h = 0;\n for (let i = 0; i < text.length; i++) {\n h = ((h << 5) - h) + text.charCodeAt(i);\n h = h & h;\n }\n return h;\n }\n\n private generateVector(text: string): number[] {\n const seed = this.hash(text);\n const vec: number[] = [];\n for (let i = 0; i < this.dimensions; i++) {\n vec.push(Math.sin(seed * (i + 1)) * 0.5);\n }\n const mag = Math.sqrt(vec.reduce((s, v) => s + v * v, 0));\n return vec.map((v) => v / mag);\n }\n\n async embed(text: string): Promise<number[]> {\n return this.generateVector(text);\n }\n\n async embedBatch(texts: string[]): Promise<number[][]> {\n return texts.map((t) => this.generateVector(t));\n }\n\n async getDimensions(): Promise<number> {\n return this.dimensions;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kb-labs/shared-testing",
3
3
  "description": "Test utilities for KB Labs plugin development — mock builders, platform setup, test context",
4
- "version": "2.94.0",
4
+ "version": "2.96.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -17,17 +17,15 @@
17
17
  "LICENSE"
18
18
  ],
19
19
  "dependencies": {
20
- "vitest": "^3.2.4",
21
- "@kb-labs/core-platform": "2.94.0",
22
- "@kb-labs/core-runtime": "2.94.0",
23
- "@kb-labs/plugin-contracts": "2.94.0"
20
+ "vitest": "^3.2.6",
21
+ "@kb-labs/core-platform": "2.96.0"
24
22
  },
25
23
  "devDependencies": {
26
24
  "@types/node": "^24.3.3",
27
25
  "rimraf": "^6.0.1",
28
26
  "tsup": "^8.5.0",
29
27
  "typescript": "^5.6.3",
30
- "@kb-labs/devkit": "2.94.0"
28
+ "@kb-labs/devkit": "2.96.0"
31
29
  },
32
30
  "engines": {
33
31
  "node": ">=20.0.0",