import * as Effect from 'effect/Effect'; import * as FileSystem from 'effect/FileSystem'; import * as Layer from 'effect/Layer'; import * as Path from 'effect/Path'; import * as Stdio from 'effect/Stdio'; import { HookContext } from './Hook/Context.js'; import { HookEnvelope } from './Hook/Envelope.js'; import type * as Events from './Hook/Events/index.js'; import * as Plugin from './Plugin.js'; import { type HookDefinition } from './Hook/Runner.js'; /** * Build a `HookContext.Interface` with sensible defaults, overridable * via the `overrides` argument. * * @category Mocks * @since 0.1.0 */ export declare const makeMockHookContext: (overrides?: Partial) => HookContext.Interface; /** * Build a `HookEnvelope` with sensible defaults, overridable via `overrides`. * * @category Mocks * @since 0.1.0 */ export declare const makeMockEnvelope: (overrides?: Partial) => HookEnvelope; /** * Build a `Layer` whose stdin emits the given JSON string once * and whose stdout/stderr push into the given arrays. * * @category Mocks * @since 0.1.0 */ export declare const makeMockStdioLayer: (options: { readonly stdinJson: string; readonly stdoutBuffer: Array; readonly stderrBuffer?: Array; }) => Layer.Layer; /** * Result of running a hook against a mock stdin. * * @category Runner * @since 0.1.0 */ export interface RunHookResult { /** Parsed JSON written to stdout, or `undefined` if nothing was written. */ readonly output: unknown; /** Raw stdout string. */ readonly stdout: string; /** Captured stderr string. */ readonly stderr: string; /** * Exit code the runner would produce under the real `runMain` teardown. * `0` success, handler-authored `HookProcessOutput` exits use their * requested code, `2` input decode failure, `1` other runner failure, * `130` interrupt. */ readonly exitCode: number; /** The `_tag` of the runner failure, if any. */ readonly errorTag: string | undefined; } /** * Run a hook definition end-to-end against a mock stdin payload and capture * the stdout the runner would have written. * * This exercises the full runner pipeline (stdin read → JSON parse → * schema decode → handler → schema encode → stdout write) using * `Stdio.layerTest` instead of the real `process.stdin`/`process.stdout`. * No fiber is forked; no process.exit is called. * * @category Runner * @since 0.1.0 * @example * ```ts * import { describe, expect, it } from '@effect/vitest' * import * as Effect from 'effect/Effect' * import { Hook, Testing } from 'effect-claudecode' * * describe('Hook', () => { * it.effect('round-trips a trivial hook', () => * Effect.gen(function* () { * const result = yield* Testing.runHookWithMockStdin(myHook, jsonString) * expect(result.exitCode).toBe(0) * }) * ) * }) * ``` */ export declare const runHookWithMockStdin: (hook: HookDefinition, stdinJson: string) => Effect.Effect; /** * Fixture builders for every Claude Code hook event. Each entry * returns a JSON string suitable for passing to * `runHookWithMockStdin`. * * Defaults carry only the minimum fields required by the event * schema; callers override only what matters for the test. * * @category Fixtures * @since 0.1.0 * @example * ```ts * import { Testing } from 'effect-claudecode' * * const json = Testing.fixtures.PreToolUse({ * tool_name: 'Bash', * tool_input: { command: 'rm -rf /' } * }) * ``` */ export declare const fixtures: { PreToolUse: (overrides?: Partial | undefined) => string; PostToolUse: (overrides?: Partial | undefined) => string; UserPromptSubmit: (overrides?: Partial | undefined) => string; Notification: (overrides?: Partial | undefined) => string; Stop: (overrides?: Partial | undefined) => string; SubagentStop: (overrides?: Partial | undefined) => string; SessionStart: (overrides?: Partial | undefined) => string; Setup: (overrides?: Partial | undefined) => string; SessionEnd: (overrides?: Partial | undefined) => string; PreCompact: (overrides?: Partial | undefined) => string; PostCompact: (overrides?: Partial | undefined) => string; PermissionRequest: (overrides?: Partial | undefined) => string; PermissionDenied: (overrides?: Partial | undefined) => string; PostToolUseFailure: (overrides?: Partial | undefined) => string; InstructionsLoaded: (overrides?: Partial | undefined) => string; StopFailure: (overrides?: Partial | undefined) => string; CwdChanged: (overrides?: Partial | undefined) => string; FileChanged: (overrides?: Partial | undefined) => string; ConfigChange: (overrides?: Partial | undefined) => string; SubagentStart: (overrides?: Partial | undefined) => string; TaskCreated: (overrides?: Partial | undefined) => string; TaskCompleted: (overrides?: Partial | undefined) => string; TeammateIdle: (overrides?: Partial | undefined) => string; WorktreeCreate: (overrides?: Partial | undefined) => string; WorktreeRemove: (overrides?: Partial | undefined) => string; Elicitation: (overrides?: Partial | undefined) => string; ElicitationResult: (overrides?: Partial | undefined) => string; UserPromptExpansion: (overrides?: Partial | undefined) => string; PostToolBatch: (overrides?: Partial | undefined) => string; MessageDisplay: (overrides?: Partial | undefined) => string; }; /** * Assert that `output` is a PreToolUse `allow` decision. If `reason` * is provided, it must match `permissionDecisionReason`. * * @category Assertions * @since 0.1.0 */ export declare const expectAllowDecision: (output: unknown, reason?: string) => void; /** * Assert that `output` is a PreToolUse `deny` decision. If `reason` * is provided, it must match `permissionDecisionReason`. * * @category Assertions * @since 0.1.0 */ export declare const expectDenyDecision: (output: unknown, reason?: string) => void; /** * Assert that `output` is a PreToolUse `ask` decision. If `reason` * is provided, it must match `permissionDecisionReason`. * * @category Assertions * @since 0.1.0 */ export declare const expectAskDecision: (output: unknown, reason?: string) => void; /** * Assert that `output` is a top-level `block` decision. If `reason` * is provided, it must match `reason`. * * Applies to events that encode a top-level JSON `decision: "block"`, such * as UserPromptSubmit, PostToolUse, PostToolUseFailure, PostToolBatch, Stop, * SubagentStop, ConfigChange, UserPromptExpansion, and PreCompact. Events * that block with a controlled exit (TaskCreated, TaskCompleted, * TeammateIdle, WorktreeCreate) should assert on `exitCode` and `stderr`. * * @category Assertions * @since 0.1.0 */ export declare const expectBlockDecision: (output: unknown, reason?: string) => void; /** * Assert that `output` carries an `additionalContext` entry in its * `hookSpecificOutput`. When `context` is provided, the string must * match exactly. * * @category Assertions * @since 0.1.0 */ export declare const expectAddContext: (output: unknown, context?: string) => void; /** * Operations that the mock file system can intercept. * * @category Mocks * @since 0.1.0 */ export type MockFileSystemOperation = 'exists' | 'readFile' | 'readFileString' | 'writeFile' | 'writeFileString' | 'makeDirectory' | 'readDirectory' | 'remove' | 'copy'; /** * Options for the in-memory file system harness. * * @category Mocks * @since 0.1.0 */ export interface MockFileSystemOptions { readonly failOn?: (operation: MockFileSystemOperation, path: string) => boolean; } /** * Deterministic snapshot of the mock file system state. * * @category Mocks * @since 0.1.0 */ export interface MockFileSystemSnapshot { readonly files: ReadonlyMap; readonly directories: ReadonlyArray; } /** * Stateful in-memory file system harness used by tests. * * @category Mocks * @since 0.1.0 */ export interface MockFileSystem { readonly layer: Layer.Layer; readonly snapshot: () => MockFileSystemSnapshot; readonly readFile: (path: string) => string | undefined; readonly exists: (path: string) => boolean; } type MockFileEntries = ReadonlyMap | Record; /** * Build a stateful in-memory file system harness with a ready-to-provide * `FileSystem` + `Path` layer and snapshot helpers for assertions. * * Unlike the earlier read-only helper, this harness supports directory * listings and writes, so it can exercise `Plugin.write`, `Plugin.scan`, * `Plugin.load`, `Settings.load`, frontmatter parsing, transcript reads, and * install/sync flows against one consistent in-memory project tree. * * @category Mocks * @since 0.1.0 */ export declare const makeMockFileSystem: (files?: MockFileEntries, options?: MockFileSystemOptions) => MockFileSystem; /** * Assert that a written plugin tree matches the expected file set exactly. * * String expectations must match exactly. `RegExp` expectations must match the * full file content via `expect(...).toMatch(...)`. * * @category Assertions * @since 0.1.0 */ export declare const expectPluginTree: (input: MockFileSystem | MockFileSystemSnapshot, expected: Readonly>) => void; /** * Write a plugin definition into an in-memory file system harness and return * the harness for further assertions or round-trip loading. * * @category Runner * @since 0.1.0 */ export declare const writePluginToMemory: (definition: Plugin.PluginDefinition, destDir?: string, options?: MockFileSystemOptions) => Effect.Effect; /** * Result of writing a plugin to an in-memory file system and loading it back. * * @category Runner * @since 0.1.0 */ export interface PluginRoundTripResult { readonly fileSystem: MockFileSystem; readonly loaded: Plugin.LoadedPlugin; } /** * Round-trip a plugin definition through `Plugin.write` and `Plugin.load` * without touching disk. * * @category Runner * @since 0.1.0 */ export declare const roundTripPlugin: (definition: Plugin.PluginDefinition, destDir?: string, options?: MockFileSystemOptions) => Effect.Effect; export {}; //# sourceMappingURL=Testing.d.ts.map