elasticdash-test 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ElasticDash
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # ElasticDash Test
2
+
3
+ An AI-native test runner for ElasticDash workflow testing. Built for async AI pipelines — not a general-purpose test runner.
4
+
5
+ - Trace-first: every test receives a `trace` context to record and assert on LLM calls and tool invocations
6
+ - AI-specific matchers: `toHaveLLMStep`, `toCallTool`, `toMatchSemanticOutput`
7
+ - Sequential execution, no parallelism overhead
8
+ - No Jest dependency
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install elasticdash-test
16
+ ```
17
+
18
+ Requires Node 20+.
19
+
20
+ ---
21
+
22
+ ## Quick Start
23
+
24
+ **1. Write a test file** (`my-flow.ai.test.ts`):
25
+
26
+ ```ts
27
+ import '../node_modules/elasticdash-test/dist/test-setup.js'
28
+ import { expect } from 'expect'
29
+
30
+ aiTest('checkout flow', async (ctx) => {
31
+ await runCheckout(ctx)
32
+
33
+ expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4', contains: 'order confirmed' })
34
+ expect(ctx.trace).toCallTool('chargeCard')
35
+ })
36
+ ```
37
+
38
+ **2. Run it:**
39
+
40
+ ```bash
41
+ elasticdash test # discover all *.ai.test.ts files
42
+ elasticdash test ./ai-tests # discover in a specific directory
43
+ elasticdash run my-flow.ai.test.ts # run a single file
44
+ ```
45
+
46
+ **3. Read the output:**
47
+
48
+ ```
49
+ ✓ checkout flow (1.2s)
50
+ ✗ refund flow (0.8s)
51
+ → Expected tool "chargeCard" to be called, but no tool calls were recorded
52
+
53
+ 2 passed
54
+ 1 failed
55
+ Total: 3
56
+ Duration: 3.4s
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Writing Tests
62
+
63
+ ### Globals
64
+
65
+ After importing `test-setup`, these are available globally — no imports needed:
66
+
67
+ | Global | Description |
68
+ |---|---|
69
+ | `aiTest(name, fn)` | Register a test |
70
+ | `beforeAll(fn)` | Run once before all tests in the file |
71
+ | `afterAll(fn)` | Run once after all tests in the file |
72
+
73
+ ### Test context
74
+
75
+ Each test function receives a `ctx: AITestContext` argument:
76
+
77
+ ```ts
78
+ aiTest('my test', async (ctx) => {
79
+ // ctx.trace — record and inspect LLM steps and tool calls
80
+ })
81
+ ```
82
+
83
+ ### Recording trace data
84
+
85
+ Your AI workflow code should call these on `ctx.trace` to make assertions possible:
86
+
87
+ ```ts
88
+ ctx.trace.recordLLMStep({
89
+ model: 'gpt-4',
90
+ prompt: 'What is the order status?',
91
+ completion: 'The order has been confirmed.',
92
+ })
93
+
94
+ ctx.trace.recordToolCall({
95
+ name: 'chargeCard',
96
+ args: { amount: 99.99 },
97
+ })
98
+ ```
99
+
100
+ ### Matchers
101
+
102
+ #### `toHaveLLMStep(config?)`
103
+
104
+ Assert the trace contains at least one LLM step matching the given config. All fields are optional.
105
+
106
+ ```ts
107
+ expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4' })
108
+ expect(ctx.trace).toHaveLLMStep({ contains: 'order confirmed' })
109
+ expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4', contains: 'order confirmed' })
110
+ ```
111
+
112
+ #### `toCallTool(toolName)`
113
+
114
+ Assert the trace contains a tool call with the given name.
115
+
116
+ ```ts
117
+ expect(ctx.trace).toCallTool('chargeCard')
118
+ ```
119
+
120
+ #### `toMatchSemanticOutput(expected)`
121
+
122
+ Assert the combined LLM output contains the expected string (case-insensitive substring match — designed for a future embedding-based similarity upgrade).
123
+
124
+ ```ts
125
+ expect(ctx.trace).toMatchSemanticOutput('order confirmed')
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Configuration
131
+
132
+ Create an optional `elasticdash.config.ts` at the project root:
133
+
134
+ ```ts
135
+ export default {
136
+ testMatch: ['**/*.ai.test.ts'],
137
+ traceMode: 'local' as const,
138
+ }
139
+ ```
140
+
141
+ | Option | Default | Description |
142
+ |---|---|---|
143
+ | `testMatch` | `['**/*.ai.test.ts']` | Glob patterns for test discovery |
144
+ | `traceMode` | `'local'` | `'local'` (stub) or `'remote'` (future ElasticDash backend) |
145
+
146
+ ---
147
+
148
+ ## TypeScript Setup
149
+
150
+ Add an `examples/tsconfig.json` (or your test directory's `tsconfig.json`) that extends the root config and includes the `src` types:
151
+
152
+ ```json
153
+ {
154
+ "extends": "../tsconfig.json",
155
+ "compilerOptions": {
156
+ "rootDir": "..",
157
+ "noEmit": true
158
+ },
159
+ "include": [
160
+ "../src/**/*",
161
+ "./**/*"
162
+ ]
163
+ }
164
+ ```
165
+
166
+ This gives you typed `aiTest`, `beforeAll`, `afterAll` globals and typed custom matchers in your test files.
167
+
168
+ ---
169
+
170
+ ## Project Structure
171
+
172
+ ```
173
+ src/
174
+ cli.ts CLI entry point (commander + fast-glob)
175
+ runner.ts Sequential test runner engine
176
+ reporter.ts Color-coded terminal output
177
+ test-setup.ts Import in test files for globals + matcher types
178
+ index.ts Programmatic API
179
+ core/
180
+ registry.ts aiTest / beforeAll / afterAll registry
181
+ trace-adapter/
182
+ context.ts TraceHandle, AITestContext, RunnerHooks scaffold
183
+ matchers/
184
+ index.ts Custom expect matchers
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Programmatic API
190
+
191
+ ```ts
192
+ import { runFiles, reportResults, registerMatchers } from 'elasticdash-test'
193
+
194
+ registerMatchers()
195
+ const results = await runFiles(['./tests/flow.ai.test.ts'])
196
+ reportResults(results)
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Non-Goals
202
+
203
+ This runner intentionally does not support:
204
+
205
+ - Parallel execution
206
+ - Watch mode
207
+ - Snapshot testing
208
+ - Coverage reporting
209
+ - Jest compatibility
210
+
211
+ ---
212
+
213
+ ## License
214
+
215
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import fg from 'fast-glob';
4
+ import path from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { existsSync } from 'node:fs';
7
+ import { registerMatchers } from './matchers/index.js';
8
+ import { runFiles } from './runner.js';
9
+ import { reportResults } from './reporter.js';
10
+ async function loadConfig(cwd) {
11
+ const configPath = path.join(cwd, 'elasticdash.config.ts');
12
+ const configPathJs = path.join(cwd, 'elasticdash.config.js');
13
+ for (const p of [configPath, configPathJs]) {
14
+ if (existsSync(p)) {
15
+ const mod = await import(pathToFileURL(p).href);
16
+ return (mod.default ?? {});
17
+ }
18
+ }
19
+ return {};
20
+ }
21
+ // --- File discovery ---
22
+ async function discoverTestFiles(patterns, cwd) {
23
+ const files = await fg(patterns, { cwd, absolute: true });
24
+ return files.sort();
25
+ }
26
+ // --- Bootstrap ---
27
+ async function bootstrap() {
28
+ registerMatchers();
29
+ const cwd = process.cwd();
30
+ const config = await loadConfig(cwd);
31
+ const defaultPattern = config.testMatch ?? ['**/*.ai.test.ts', '**/*.ai.test.js'];
32
+ const program = new Command();
33
+ program
34
+ .name('elasticdash')
35
+ .description('AI-native test runner for ElasticDash workflow testing')
36
+ .version('0.1.0');
37
+ // elasticdash test [dir]
38
+ program
39
+ .command('test [dir]')
40
+ .description('Discover and run all AI test files')
41
+ .action(async (dir) => {
42
+ const searchBase = dir ? path.resolve(cwd, dir) : cwd;
43
+ const files = await discoverTestFiles(defaultPattern, searchBase);
44
+ if (files.length === 0) {
45
+ console.error(`No test files found matching: ${defaultPattern.join(', ')}`);
46
+ process.exit(1);
47
+ }
48
+ const results = await runFiles(files);
49
+ reportResults(results);
50
+ const anyFailed = results.some((fr) => fr.results.some((r) => !r.passed));
51
+ process.exit(anyFailed ? 1 : 0);
52
+ });
53
+ // elasticdash run <file>
54
+ program
55
+ .command('run <file>')
56
+ .description('Run a single AI test file')
57
+ .action(async (file) => {
58
+ const absFile = pathToFileURL(path.resolve(cwd, file)).href;
59
+ const results = await runFiles([absFile]);
60
+ reportResults(results);
61
+ const anyFailed = results.some((fr) => fr.results.some((r) => !r.passed));
62
+ process.exit(anyFailed ? 1 : 0);
63
+ });
64
+ await program.parseAsync(process.argv);
65
+ }
66
+ bootstrap().catch((err) => {
67
+ console.error(err);
68
+ process.exit(1);
69
+ });
70
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { AITestContext } from '../trace-adapter/context.js';
2
+ export type TestFunction = (ctx: AITestContext) => Promise<void> | void;
3
+ export interface TestEntry {
4
+ name: string;
5
+ fn: TestFunction;
6
+ }
7
+ export interface Registry {
8
+ tests: TestEntry[];
9
+ beforeAllHooks: Array<() => Promise<void> | void>;
10
+ afterAllHooks: Array<() => Promise<void> | void>;
11
+ }
12
+ export declare function clearRegistry(): void;
13
+ export declare function getRegistry(): Registry;
14
+ export declare function aiTest(name: string, fn: TestFunction): void;
15
+ export declare function beforeAll(fn: () => Promise<void> | void): void;
16
+ export declare function afterAll(fn: () => Promise<void> | void): void;
17
+ declare global {
18
+ var aiTest: (name: string, fn: TestFunction) => void;
19
+ var beforeAll: (fn: () => Promise<void> | void) => void;
20
+ var afterAll: (fn: () => Promise<void> | void) => void;
21
+ }
22
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAEhE,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAEvE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,YAAY,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,cAAc,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;IACjD,aAAa,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;CACjD;AAYD,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,GAAG,IAAI,CAE3D;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE9D;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAE7D;AAGD,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAEpD,IAAI,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;IAEvD,IAAI,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;CACvD"}
@@ -0,0 +1,27 @@
1
+ let _registry = createEmptyRegistry();
2
+ function createEmptyRegistry() {
3
+ return {
4
+ tests: [],
5
+ beforeAllHooks: [],
6
+ afterAllHooks: [],
7
+ };
8
+ }
9
+ export function clearRegistry() {
10
+ _registry = createEmptyRegistry();
11
+ }
12
+ export function getRegistry() {
13
+ return _registry;
14
+ }
15
+ export function aiTest(name, fn) {
16
+ _registry.tests.push({ name, fn });
17
+ }
18
+ export function beforeAll(fn) {
19
+ _registry.beforeAllHooks.push(fn);
20
+ }
21
+ export function afterAll(fn) {
22
+ _registry.afterAllHooks.push(fn);
23
+ }
24
+ globalThis.aiTest = aiTest;
25
+ globalThis.beforeAll = beforeAll;
26
+ globalThis.afterAll = afterAll;
27
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AAeA,IAAI,SAAS,GAAa,mBAAmB,EAAE,CAAA;AAE/C,SAAS,mBAAmB;IAC1B,OAAO;QACL,KAAK,EAAE,EAAE;QACT,cAAc,EAAE,EAAE;QAClB,aAAa,EAAE,EAAE;KAClB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,SAAS,GAAG,mBAAmB,EAAE,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,EAAgB;IACnD,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAA8B;IACtD,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,EAA8B;IACrD,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAClC,CAAC;AAYD,UAAU,CAAC,MAAM,GAAG,MAAM,CAAA;AAC1B,UAAU,CAAC,SAAS,GAAG,SAAS,CAAA;AAChC,UAAU,CAAC,QAAQ,GAAG,QAAQ,CAAA"}
@@ -0,0 +1,8 @@
1
+ export { aiTest, beforeAll, afterAll, getRegistry, clearRegistry } from './core/registry.js';
2
+ export { createTraceHandle, startTraceSession } from './trace-adapter/context.js';
3
+ export { registerMatchers } from './matchers/index.js';
4
+ export { runFiles } from './runner.js';
5
+ export { reportResults } from './reporter.js';
6
+ export type { TestResult, FileResult, RunnerOptions } from './runner.js';
7
+ export type { AITestContext, TraceHandle, LLMStep, ToolCall, TraceStep, RunnerHooks } from './trace-adapter/context.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACxE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Public API surface for programmatic use
2
+ export { aiTest, beforeAll, afterAll, getRegistry, clearRegistry } from './core/registry.js';
3
+ export { createTraceHandle, startTraceSession } from './trace-adapter/context.js';
4
+ export { registerMatchers } from './matchers/index.js';
5
+ export { runFiles } from './runner.js';
6
+ export { reportResults } from './reporter.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA"}
@@ -0,0 +1,18 @@
1
+ interface LLMStepConfig {
2
+ model?: string;
3
+ contains?: string;
4
+ }
5
+ declare module 'expect' {
6
+ interface Matchers<R> {
7
+ toHaveLLMStep(config?: LLMStepConfig): R;
8
+ toCallTool(toolName: string): R;
9
+ toMatchSemanticOutput(expected: string): R;
10
+ }
11
+ }
12
+ /**
13
+ * Register all AI-specific custom matchers onto the `expect` instance.
14
+ * Call this once on runner startup.
15
+ */
16
+ export declare function registerMatchers(): void;
17
+ export {};
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/matchers/index.ts"],"names":[],"mappings":"AAGA,UAAU,aAAa;IACrB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAGD,OAAO,QAAQ,QAAQ,CAAC;IACtB,UAAU,QAAQ,CAAC,CAAC;QAClB,aAAa,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,CAAC,CAAA;QACxC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAA;QAC/B,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAA;KAC3C;CACF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAqFvC"}
@@ -0,0 +1,85 @@
1
+ import { expect } from 'expect';
2
+ /**
3
+ * Register all AI-specific custom matchers onto the `expect` instance.
4
+ * Call this once on runner startup.
5
+ */
6
+ export function registerMatchers() {
7
+ expect.extend({
8
+ /**
9
+ * Assert the trace contains at least one LLM step matching the config.
10
+ * Usage: expect(trace).toHaveLLMStep({ model: "gpt-4", contains: "order confirmed" })
11
+ */
12
+ toHaveLLMStep(trace, config = {}) {
13
+ const steps = trace.getLLMSteps();
14
+ const matching = steps.filter((step) => {
15
+ if (config.model && step.model !== config.model)
16
+ return false;
17
+ if (config.contains) {
18
+ const haystack = [step.completion, step.prompt, step.contains]
19
+ .filter(Boolean)
20
+ .join(' ')
21
+ .toLowerCase();
22
+ if (!haystack.includes(config.contains.toLowerCase()))
23
+ return false;
24
+ }
25
+ return true;
26
+ });
27
+ const pass = matching.length > 0;
28
+ return {
29
+ pass,
30
+ message: () => {
31
+ if (pass) {
32
+ return `Expected trace NOT to have LLM step matching ${JSON.stringify(config)}`;
33
+ }
34
+ const stepSummary = steps.length === 0
35
+ ? 'no LLM steps were recorded'
36
+ : `recorded steps: ${JSON.stringify(steps)}`;
37
+ return `Expected trace to have LLM step matching ${JSON.stringify(config)}, but ${stepSummary}`;
38
+ },
39
+ };
40
+ },
41
+ /**
42
+ * Assert the trace contains a tool call with the given name.
43
+ * Usage: expect(trace).toCallTool("chargeCard")
44
+ */
45
+ toCallTool(trace, toolName) {
46
+ const calls = trace.getToolCalls();
47
+ const pass = calls.some((c) => c.name === toolName);
48
+ return {
49
+ pass,
50
+ message: () => {
51
+ if (pass) {
52
+ return `Expected trace NOT to call tool "${toolName}"`;
53
+ }
54
+ const names = calls.map((c) => c.name);
55
+ const recorded = names.length === 0 ? 'no tool calls were recorded' : `recorded: [${names.join(', ')}]`;
56
+ return `Expected tool "${toolName}" to be called, but ${recorded}`;
57
+ },
58
+ };
59
+ },
60
+ /**
61
+ * Assert the trace output semantically matches an expected string.
62
+ * For MP: simple substring/case-insensitive match.
63
+ * Later: can swap in embedding-based similarity.
64
+ * Usage: expect(trace).toMatchSemanticOutput("order confirmed")
65
+ */
66
+ toMatchSemanticOutput(trace, expected) {
67
+ const steps = trace.getLLMSteps();
68
+ const fullOutput = steps
69
+ .map((s) => [s.completion, s.contains].filter(Boolean).join(' '))
70
+ .join(' ')
71
+ .toLowerCase();
72
+ const pass = fullOutput.includes(expected.toLowerCase());
73
+ return {
74
+ pass,
75
+ message: () => {
76
+ if (pass) {
77
+ return `Expected trace output NOT to semantically match "${expected}"`;
78
+ }
79
+ return `Expected trace output to semantically match "${expected}", but got: "${fullOutput || '(empty)'}"`;
80
+ },
81
+ };
82
+ },
83
+ });
84
+ }
85
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/matchers/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FileResult } from './runner.js';
2
+ export declare function reportResults(fileResults: FileResult[]): void;
3
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,aAAa,CAAA;AAEzD,wBAAgB,aAAa,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI,CAsB7D"}
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+ export function reportResults(fileResults) {
3
+ let totalPassed = 0;
4
+ let totalFailed = 0;
5
+ let totalDurationMs = 0;
6
+ for (const fileResult of fileResults) {
7
+ if (fileResults.length > 1) {
8
+ console.log(chalk.dim(`\n${fileResult.file}`));
9
+ }
10
+ for (const result of fileResult.results) {
11
+ printTestResult(result);
12
+ totalDurationMs += result.durationMs;
13
+ if (result.passed) {
14
+ totalPassed++;
15
+ }
16
+ else {
17
+ totalFailed++;
18
+ }
19
+ }
20
+ }
21
+ printSummary(totalPassed, totalFailed, totalDurationMs);
22
+ }
23
+ function printTestResult(result) {
24
+ const duration = chalk.dim(`(${formatDuration(result.durationMs)})`);
25
+ if (result.passed) {
26
+ console.log(` ${chalk.green('✓')} ${result.name} ${duration}`);
27
+ }
28
+ else {
29
+ console.log(` ${chalk.red('✗')} ${result.name} ${duration}`);
30
+ if (result.error) {
31
+ const errorLines = formatError(result.error);
32
+ for (const line of errorLines) {
33
+ console.log(` ${chalk.red('→')} ${line}`);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ function printSummary(passed, failed, totalMs) {
39
+ const total = passed + failed;
40
+ console.log('');
41
+ if (passed > 0) {
42
+ console.log(chalk.green(`${passed} passed`));
43
+ }
44
+ if (failed > 0) {
45
+ console.log(chalk.red(`${failed} failed`));
46
+ }
47
+ console.log(chalk.dim(`Total: ${total}`));
48
+ console.log(chalk.dim(`Duration: ${formatDuration(totalMs)}`));
49
+ }
50
+ function formatDuration(ms) {
51
+ if (ms >= 1000) {
52
+ return `${(ms / 1000).toFixed(1)}s`;
53
+ }
54
+ return `${ms}ms`;
55
+ }
56
+ function formatError(error) {
57
+ const lines = [];
58
+ if (error.message) {
59
+ lines.push(error.message);
60
+ }
61
+ if (error.stack) {
62
+ const stackLines = error.stack
63
+ .split('\n')
64
+ .slice(1)
65
+ .map((l) => l.trim())
66
+ .filter((l) => l.startsWith('at '))
67
+ .slice(0, 3);
68
+ lines.push(...stackLines);
69
+ }
70
+ return lines;
71
+ }
72
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { RunnerHooks } from './trace-adapter/context.js';
2
+ export interface TestResult {
3
+ name: string;
4
+ passed: boolean;
5
+ durationMs: number;
6
+ error?: Error;
7
+ }
8
+ export interface FileResult {
9
+ file: string;
10
+ results: TestResult[];
11
+ }
12
+ export interface RunnerOptions {
13
+ hooks?: RunnerHooks;
14
+ }
15
+ export declare function runFiles(files: string[], options?: RunnerOptions): Promise<FileResult[]>;
16
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAE7D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,UAAU,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAED,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CASlG"}
package/dist/runner.js ADDED
@@ -0,0 +1,55 @@
1
+ import { clearRegistry, getRegistry } from './core/registry.js';
2
+ import { startTraceSession } from './trace-adapter/context.js';
3
+ export async function runFiles(files, options = {}) {
4
+ const fileResults = [];
5
+ for (const file of files) {
6
+ const result = await runFile(file, options);
7
+ fileResults.push(result);
8
+ }
9
+ return fileResults;
10
+ }
11
+ async function runFile(file, options) {
12
+ const { hooks = {} } = options;
13
+ // 1. Clear the global registry before loading the file
14
+ clearRegistry();
15
+ // 2. Dynamically import the test file (triggers aiTest() registrations)
16
+ await import(file);
17
+ const registry = getRegistry();
18
+ const results = [];
19
+ // 3. Run beforeAll hooks
20
+ for (const hook of registry.beforeAllHooks) {
21
+ await hook();
22
+ }
23
+ // 4. Execute each test sequentially
24
+ for (const entry of registry.tests) {
25
+ const { context, finalise } = startTraceSession();
26
+ if (hooks.onTestStart) {
27
+ await hooks.onTestStart(entry.name);
28
+ }
29
+ const startTime = Date.now();
30
+ let passed = false;
31
+ let error;
32
+ try {
33
+ await entry.fn(context);
34
+ passed = true;
35
+ }
36
+ catch (err) {
37
+ error = err instanceof Error ? err : new Error(String(err));
38
+ }
39
+ const durationMs = Date.now() - startTime;
40
+ if (hooks.onTestFinish) {
41
+ await hooks.onTestFinish(entry.name, passed, durationMs);
42
+ }
43
+ if (hooks.onTraceComplete) {
44
+ await hooks.onTraceComplete(entry.name, context.trace);
45
+ }
46
+ finalise();
47
+ results.push({ name: entry.name, passed, durationMs, error });
48
+ }
49
+ // 5. Run afterAll hooks
50
+ for (const hook of registry.afterAllHooks) {
51
+ await hook();
52
+ }
53
+ return { file, results };
54
+ }
55
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAmB9D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAe,EAAE,UAAyB,EAAE;IACzE,MAAM,WAAW,GAAiB,EAAE,CAAA;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC3C,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC1B,CAAC;IAED,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,OAAsB;IACzD,MAAM,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,OAAO,CAAA;IAE9B,uDAAuD;IACvD,aAAa,EAAE,CAAA;IAEf,wEAAwE;IACxE,MAAM,MAAM,CAAC,IAAI,CAAC,CAAA;IAElB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,OAAO,GAAiB,EAAE,CAAA;IAEhC,yBAAyB;IACzB,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;QAC3C,MAAM,IAAI,EAAE,CAAA;IACd,CAAC;IAED,oCAAoC;IACpC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,iBAAiB,EAAE,CAAA;QAEjD,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACrC,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC5B,IAAI,MAAM,GAAG,KAAK,CAAA;QAClB,IAAI,KAAwB,CAAA;QAE5B,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,CAAA;YACvB,MAAM,GAAG,IAAI,CAAA;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC7D,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAA;QAEzC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;QAC1D,CAAC;QAED,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;YAC1B,MAAM,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;QACxD,CAAC;QAED,QAAQ,EAAE,CAAA;QAEV,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;IAC/D,CAAC;IAED,wBAAwB;IACxB,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC1C,MAAM,IAAI,EAAE,CAAA;IACd,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Import this file in your AI test files to get:
3
+ * - aiTest / beforeAll / afterAll available as globals (TypeScript types + runtime)
4
+ * - Custom matcher types (toHaveLLMStep, toCallTool, toMatchSemanticOutput)
5
+ *
6
+ * The CLI registers matchers at startup, so this import is for TypeScript
7
+ * type awareness only — no double-registration occurs at runtime.
8
+ */
9
+ import './core/registry.js';
10
+ import './matchers/index.js';
11
+ export {};
12
+ //# sourceMappingURL=test-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-setup.d.ts","sourceRoot":"","sources":["../src/test-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,oBAAoB,CAAA;AAG3B,OAAO,qBAAqB,CAAA;AAE5B,OAAO,EAAE,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Import this file in your AI test files to get:
3
+ * - aiTest / beforeAll / afterAll available as globals (TypeScript types + runtime)
4
+ * - Custom matcher types (toHaveLLMStep, toCallTool, toMatchSemanticOutput)
5
+ *
6
+ * The CLI registers matchers at startup, so this import is for TypeScript
7
+ * type awareness only — no double-registration occurs at runtime.
8
+ */
9
+ // Side-effect: populates globalThis.aiTest + brings declare global into scope
10
+ import './core/registry.js';
11
+ // Side-effect: brings declare module 'expect' augmentation into scope
12
+ import './matchers/index.js';
13
+ //# sourceMappingURL=test-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-setup.js","sourceRoot":"","sources":["../src/test-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,8EAA8E;AAC9E,OAAO,oBAAoB,CAAA;AAE3B,sEAAsE;AACtE,OAAO,qBAAqB,CAAA"}
@@ -0,0 +1,51 @@
1
+ export interface LLMStep {
2
+ model: string;
3
+ prompt?: string;
4
+ completion?: string;
5
+ contains?: string;
6
+ }
7
+ export interface ToolCall {
8
+ name: string;
9
+ args?: Record<string, unknown>;
10
+ result?: unknown;
11
+ }
12
+ export interface TraceStep {
13
+ type: 'llm' | 'tool' | 'generic';
14
+ timestamp: number;
15
+ durationMs: number;
16
+ data: Record<string, unknown>;
17
+ }
18
+ export interface TraceHandle {
19
+ /** All recorded steps in this trace session */
20
+ getSteps(): TraceStep[];
21
+ /** Only LLM inference steps */
22
+ getLLMSteps(): LLMStep[];
23
+ /** Only tool-call steps */
24
+ getToolCalls(): ToolCall[];
25
+ /** Record an LLM step (used by stubs / real adapter) */
26
+ recordLLMStep(step: LLMStep): void;
27
+ /** Record a tool call (used by stubs / real adapter) */
28
+ recordToolCall(call: ToolCall): void;
29
+ }
30
+ export interface AITestContext {
31
+ trace: TraceHandle;
32
+ }
33
+ /** Extension points for runner hooks (scaffold for future backend integration) */
34
+ export interface RunnerHooks {
35
+ onTestStart?(name: string): void | Promise<void>;
36
+ onTestFinish?(name: string, passed: boolean, durationMs: number): void | Promise<void>;
37
+ onTraceComplete?(name: string, trace: TraceHandle): void | Promise<void>;
38
+ }
39
+ /**
40
+ * Create a stubbed trace handle for a single test execution.
41
+ * Later this can be replaced with a real ElasticDash backend call.
42
+ */
43
+ export declare function createTraceHandle(): TraceHandle;
44
+ /**
45
+ * Start a trace session before a test and return the context + a finalise fn.
46
+ */
47
+ export declare function startTraceSession(): {
48
+ context: AITestContext;
49
+ finalise: () => void;
50
+ };
51
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/trace-adapter/context.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,QAAQ,IAAI,SAAS,EAAE,CAAA;IACvB,+BAA+B;IAC/B,WAAW,IAAI,OAAO,EAAE,CAAA;IACxB,2BAA2B;IAC3B,YAAY,IAAI,QAAQ,EAAE,CAAA;IAC1B,wDAAwD;IACxD,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;IAClC,wDAAwD;IACxD,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAA;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,WAAW,CAAA;CACnB;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtF,eAAe,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAsC/C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAAE,CASpF"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Create a stubbed trace handle for a single test execution.
3
+ * Later this can be replaced with a real ElasticDash backend call.
4
+ */
5
+ export function createTraceHandle() {
6
+ const steps = [];
7
+ const llmSteps = [];
8
+ const toolCalls = [];
9
+ return {
10
+ getSteps() {
11
+ return steps;
12
+ },
13
+ getLLMSteps() {
14
+ return llmSteps;
15
+ },
16
+ getToolCalls() {
17
+ return toolCalls;
18
+ },
19
+ recordLLMStep(step) {
20
+ llmSteps.push(step);
21
+ steps.push({
22
+ type: 'llm',
23
+ timestamp: Date.now(),
24
+ durationMs: 0,
25
+ data: step,
26
+ });
27
+ },
28
+ recordToolCall(call) {
29
+ toolCalls.push(call);
30
+ steps.push({
31
+ type: 'tool',
32
+ timestamp: Date.now(),
33
+ durationMs: 0,
34
+ data: call,
35
+ });
36
+ },
37
+ };
38
+ }
39
+ /**
40
+ * Start a trace session before a test and return the context + a finalise fn.
41
+ */
42
+ export function startTraceSession() {
43
+ const trace = createTraceHandle();
44
+ const context = { trace };
45
+ return {
46
+ context,
47
+ finalise() {
48
+ // Placeholder: flush / send to ElasticDash backend here in the future
49
+ },
50
+ };
51
+ }
52
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/trace-adapter/context.ts"],"names":[],"mappings":"AA4CA;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAgB,EAAE,CAAA;IAC7B,MAAM,QAAQ,GAAc,EAAE,CAAA;IAC9B,MAAM,SAAS,GAAe,EAAE,CAAA;IAEhC,OAAO;QACL,QAAQ;YACN,OAAO,KAAK,CAAA;QACd,CAAC;QAED,WAAW;YACT,OAAO,QAAQ,CAAA;QACjB,CAAC;QAED,YAAY;YACV,OAAO,SAAS,CAAA;QAClB,CAAC;QAED,aAAa,CAAC,IAAa;YACzB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACnB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,KAAK;gBACX,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,UAAU,EAAE,CAAC;gBACb,IAAI,EAAE,IAA0C;aACjD,CAAC,CAAA;QACJ,CAAC;QAED,cAAc,CAAC,IAAc;YAC3B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACpB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,MAAM;gBACZ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,UAAU,EAAE,CAAC;gBACb,IAAI,EAAE,IAA0C;aACjD,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAA;IACjC,MAAM,OAAO,GAAkB,EAAE,KAAK,EAAE,CAAA;IACxC,OAAO;QACL,OAAO;QACP,QAAQ;YACN,sEAAsE;QACxE,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "elasticdash-test",
3
+ "version": "0.1.0",
4
+ "description": "AI-native test runner for ElasticDash workflow testing",
5
+ "type": "module",
6
+ "bin": {
7
+ "elasticdash": "./dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsx src/cli.ts",
17
+ "start": "node dist/cli.js",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "chalk": "^5.3.0",
22
+ "commander": "^12.1.0",
23
+ "expect": "^29.7.0",
24
+ "fast-glob": "^3.3.2"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.14.0",
28
+ "tsx": "^4.15.6",
29
+ "typescript": "^5.5.2"
30
+ },
31
+ "engines": {
32
+ "node": ">=20.0.0"
33
+ },
34
+ "author": "ElasticDash <contact@elasticdash.com>",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/ElasticDash/elasticdash-test-js.git"
39
+ }
40
+ }