@ozperium/agentspec 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/dist/diff.js ADDED
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ensureStoreDir = ensureStoreDir;
37
+ exports.saveRun = saveRun;
38
+ exports.loadPreviousRun = loadPreviousRun;
39
+ exports.computeDiffs = computeDiffs;
40
+ exports.formatDiffs = formatDiffs;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const STORE_DIR = '.agentspec';
44
+ function ensureStoreDir() {
45
+ const dir = path.join(process.cwd(), STORE_DIR);
46
+ if (!fs.existsSync(dir)) {
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ }
49
+ return dir;
50
+ }
51
+ function saveRun(results) {
52
+ const dir = ensureStoreDir();
53
+ const timestamp = new Date().toISOString();
54
+ for (const result of results) {
55
+ const file = path.join(dir, `${sanitize(result.suite)}__${sanitize(result.test)}.json`);
56
+ const stored = {
57
+ test: result.test,
58
+ suite: result.suite,
59
+ passed: result.passed,
60
+ output: result.output || '',
61
+ timestamp,
62
+ };
63
+ fs.writeFileSync(file, JSON.stringify(stored, null, 2));
64
+ }
65
+ }
66
+ function loadPreviousRun(suite, test) {
67
+ const dir = ensureStoreDir();
68
+ const file = path.join(dir, `${sanitize(suite)}__${sanitize(test)}.json`);
69
+ if (!fs.existsSync(file))
70
+ return null;
71
+ try {
72
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ function computeDiffs(currentResults) {
79
+ const diffs = [];
80
+ for (const result of currentResults) {
81
+ const previous = loadPreviousRun(result.suite, result.test);
82
+ if (!previous) {
83
+ // New test, no diff
84
+ continue;
85
+ }
86
+ if (previous.output === result.output && previous.passed === result.passed) {
87
+ // No change
88
+ continue;
89
+ }
90
+ let status;
91
+ if (!previous.passed && result.passed) {
92
+ status = 'improvement';
93
+ }
94
+ else if (previous.passed && !result.passed) {
95
+ status = 'regression';
96
+ }
97
+ else {
98
+ status = 'changed';
99
+ }
100
+ const summary = generateDiffSummary(previous.output, result.output || '', status);
101
+ diffs.push({
102
+ test: result.test,
103
+ suite: result.suite,
104
+ status,
105
+ old_output: previous.output,
106
+ new_output: result.output || '',
107
+ old_passed: previous.passed,
108
+ new_passed: result.passed,
109
+ summary,
110
+ });
111
+ }
112
+ return diffs;
113
+ }
114
+ function formatDiffs(diffs) {
115
+ if (diffs.length === 0) {
116
+ return ' No behavior changes detected.';
117
+ }
118
+ const lines = [''];
119
+ lines.push(' AgentSpec — Behavior Diff Report');
120
+ lines.push(' ' + '═'.repeat(50));
121
+ for (const diff of diffs) {
122
+ const icon = diff.status === 'regression' ? '⚠️' : diff.status === 'improvement' ? '✨' : '📝';
123
+ const statusLabel = diff.status.toUpperCase();
124
+ lines.push(` ${icon} ${statusLabel} ${diff.suite} > ${diff.test}`);
125
+ lines.push(` ${'─'.repeat(50)}`);
126
+ lines.push(` ${diff.summary}`);
127
+ lines.push('');
128
+ }
129
+ const regressions = diffs.filter(d => d.status === 'regression').length;
130
+ const improvements = diffs.filter(d => d.status === 'improvement').length;
131
+ const changed = diffs.filter(d => d.status === 'changed').length;
132
+ lines.push(' ' + '═'.repeat(50));
133
+ lines.push(` ${regressions} regression(s), ${improvements} improvement(s), ${changed} changed`);
134
+ lines.push('');
135
+ return lines.join('\n');
136
+ }
137
+ function generateDiffSummary(oldOutput, newOutput, status) {
138
+ const oldWords = oldOutput.toLowerCase().split(/\s+/).filter(w => w.length > 2);
139
+ const newWords = newOutput.toLowerCase().split(/\s+/).filter(w => w.length > 2);
140
+ const oldSet = new Set(oldWords);
141
+ const newSet = new Set(newWords);
142
+ const removed = oldWords.filter(w => !newSet.has(w));
143
+ const added = newWords.filter(w => !oldSet.has(w));
144
+ const lines = [];
145
+ if (status === 'regression') {
146
+ lines.push(' ⚠ Behavior REGRESSED — test was passing, now failing');
147
+ }
148
+ else if (status === 'improvement') {
149
+ lines.push(' ✨ Behavior IMPROVED — test was failing, now passing');
150
+ }
151
+ else {
152
+ lines.push(' 📝 Behavior CHANGED — output differs but pass/fail status unchanged');
153
+ }
154
+ if (added.length > 0) {
155
+ lines.push(` + added: ${added.slice(0, 10).join(', ')}${added.length > 10 ? '...' : ''}`);
156
+ }
157
+ if (removed.length > 0) {
158
+ lines.push(` - removed: ${removed.slice(0, 10).join(', ')}${removed.length > 10 ? '...' : ''}`);
159
+ }
160
+ if (added.length === 0 && removed.length === 0) {
161
+ lines.push(' (pass/fail status changed but output text is identical — likely a metadata assertion)');
162
+ }
163
+ return lines.join('\n');
164
+ }
165
+ function sanitize(s) {
166
+ return s.replace(/[^a-zA-Z0-9_-]/g, '_');
167
+ }
@@ -0,0 +1,14 @@
1
+ import { AgentRunner, AgentOutput, TestSuite } from './runner';
2
+ export interface HTTPAgentConfig {
3
+ endpoint: string;
4
+ headers?: Record<string, string>;
5
+ timeout_ms?: number;
6
+ input_field?: string;
7
+ output_field?: string;
8
+ }
9
+ export declare class HTTPAgent implements AgentRunner {
10
+ config: HTTPAgentConfig;
11
+ constructor(config: HTTPAgentConfig);
12
+ run(input: string, suite: TestSuite): Promise<AgentOutput>;
13
+ private httpRequest;
14
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.HTTPAgent = void 0;
37
+ const http = __importStar(require("http"));
38
+ const https = __importStar(require("https"));
39
+ const url_1 = require("url");
40
+ class HTTPAgent {
41
+ config;
42
+ constructor(config) {
43
+ this.config = config;
44
+ }
45
+ async run(input, suite) {
46
+ const endpoint = this.config.endpoint;
47
+ const timeout = this.config.timeout_ms || 60000;
48
+ const inputField = this.config.input_field || 'input';
49
+ const body = JSON.stringify({ [inputField]: input, ...(suite.agent || {}) });
50
+ const headers = {
51
+ 'Content-Type': 'application/json',
52
+ ...this.config.headers,
53
+ };
54
+ try {
55
+ const response = await this.httpRequest(endpoint, body, headers, timeout);
56
+ const outputField = this.config.output_field || 'output';
57
+ const text = response[outputField] || response.text || response.response || JSON.stringify(response);
58
+ return {
59
+ text: typeof text === 'string' ? text : JSON.stringify(text),
60
+ tokens: response.tokens || response.usage?.total_tokens,
61
+ latencyMs: response.latency_ms,
62
+ toolsCalled: response.tools_called || response.toolsCalled || [],
63
+ };
64
+ }
65
+ catch (e) {
66
+ throw new Error(`HTTP agent error: ${e instanceof Error ? e.message : String(e)}`);
67
+ }
68
+ }
69
+ httpRequest(url, body, headers, timeoutMs) {
70
+ return new Promise((resolve, reject) => {
71
+ const parsed = new url_1.URL(url);
72
+ const isHttps = parsed.protocol === 'https:';
73
+ const lib = isHttps ? https : http;
74
+ const req = lib.request({
75
+ hostname: parsed.hostname,
76
+ port: parsed.port || (isHttps ? 443 : 80),
77
+ path: parsed.pathname + parsed.search,
78
+ method: 'POST',
79
+ headers: { ...headers, 'Content-Length': Buffer.byteLength(body) },
80
+ }, (res) => {
81
+ let data = '';
82
+ res.on('data', (chunk) => (data += chunk));
83
+ res.on('end', () => {
84
+ try {
85
+ resolve(JSON.parse(data));
86
+ }
87
+ catch {
88
+ // Non-JSON response — return as text
89
+ resolve({ output: data });
90
+ }
91
+ });
92
+ });
93
+ req.on('error', reject);
94
+ req.setTimeout(timeoutMs, () => {
95
+ req.destroy(new Error('Agent request timeout'));
96
+ });
97
+ req.write(body);
98
+ req.end();
99
+ });
100
+ }
101
+ }
102
+ exports.HTTPAgent = HTTPAgent;
@@ -0,0 +1,10 @@
1
+ export { runAll, runSuite, MockAgent } from './runner';
2
+ export { AgentRunner, AgentOutput } from './runner';
3
+ export { HTTPAgent, HTTPAgentConfig } from './http-agent';
4
+ export { evaluateAssertions, evaluateAssertionsAsync, setLLMJudgeConfig, getLLMJudgeConfig } from './assertions';
5
+ export { llmJudge, LLMJudgeConfig } from './llm-judge';
6
+ export { loadTestSuite, findTestSuites } from './loader';
7
+ export { formatResults, formatResultsJSON, formatResultsJUnit } from './reporter';
8
+ export { saveRun, computeDiffs, formatDiffs } from './diff';
9
+ export * from './types';
10
+ export * from './types/diff';
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.formatDiffs = exports.computeDiffs = exports.saveRun = exports.formatResultsJUnit = exports.formatResultsJSON = exports.formatResults = exports.findTestSuites = exports.loadTestSuite = exports.llmJudge = exports.getLLMJudgeConfig = exports.setLLMJudgeConfig = exports.evaluateAssertionsAsync = exports.evaluateAssertions = exports.HTTPAgent = exports.MockAgent = exports.runSuite = exports.runAll = void 0;
18
+ var runner_1 = require("./runner");
19
+ Object.defineProperty(exports, "runAll", { enumerable: true, get: function () { return runner_1.runAll; } });
20
+ Object.defineProperty(exports, "runSuite", { enumerable: true, get: function () { return runner_1.runSuite; } });
21
+ Object.defineProperty(exports, "MockAgent", { enumerable: true, get: function () { return runner_1.MockAgent; } });
22
+ var http_agent_1 = require("./http-agent");
23
+ Object.defineProperty(exports, "HTTPAgent", { enumerable: true, get: function () { return http_agent_1.HTTPAgent; } });
24
+ var assertions_1 = require("./assertions");
25
+ Object.defineProperty(exports, "evaluateAssertions", { enumerable: true, get: function () { return assertions_1.evaluateAssertions; } });
26
+ Object.defineProperty(exports, "evaluateAssertionsAsync", { enumerable: true, get: function () { return assertions_1.evaluateAssertionsAsync; } });
27
+ Object.defineProperty(exports, "setLLMJudgeConfig", { enumerable: true, get: function () { return assertions_1.setLLMJudgeConfig; } });
28
+ Object.defineProperty(exports, "getLLMJudgeConfig", { enumerable: true, get: function () { return assertions_1.getLLMJudgeConfig; } });
29
+ var llm_judge_1 = require("./llm-judge");
30
+ Object.defineProperty(exports, "llmJudge", { enumerable: true, get: function () { return llm_judge_1.llmJudge; } });
31
+ var loader_1 = require("./loader");
32
+ Object.defineProperty(exports, "loadTestSuite", { enumerable: true, get: function () { return loader_1.loadTestSuite; } });
33
+ Object.defineProperty(exports, "findTestSuites", { enumerable: true, get: function () { return loader_1.findTestSuites; } });
34
+ var reporter_1 = require("./reporter");
35
+ Object.defineProperty(exports, "formatResults", { enumerable: true, get: function () { return reporter_1.formatResults; } });
36
+ Object.defineProperty(exports, "formatResultsJSON", { enumerable: true, get: function () { return reporter_1.formatResultsJSON; } });
37
+ Object.defineProperty(exports, "formatResultsJUnit", { enumerable: true, get: function () { return reporter_1.formatResultsJUnit; } });
38
+ var diff_1 = require("./diff");
39
+ Object.defineProperty(exports, "saveRun", { enumerable: true, get: function () { return diff_1.saveRun; } });
40
+ Object.defineProperty(exports, "computeDiffs", { enumerable: true, get: function () { return diff_1.computeDiffs; } });
41
+ Object.defineProperty(exports, "formatDiffs", { enumerable: true, get: function () { return diff_1.formatDiffs; } });
42
+ __exportStar(require("./types"), exports);
43
+ __exportStar(require("./types/diff"), exports);
@@ -0,0 +1,11 @@
1
+ export interface LLMJudgeConfig {
2
+ endpoint?: string;
3
+ model?: string;
4
+ api_key?: string;
5
+ timeout_ms?: number;
6
+ }
7
+ export declare function llmJudge(input: string, output: string, criteria: string, config?: LLMJudgeConfig): Promise<{
8
+ passed: boolean;
9
+ score: number;
10
+ reasoning: string;
11
+ }>;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.llmJudge = llmJudge;
37
+ const http = __importStar(require("http"));
38
+ const https = __importStar(require("https"));
39
+ const url_1 = require("url");
40
+ const DEFAULT_ENDPOINT = 'http://127.0.0.1:11434/v1/chat/completions';
41
+ const DEFAULT_MODEL = 'qwen2.5:7b';
42
+ const DEFAULT_TIMEOUT = 30000;
43
+ async function llmJudge(input, output, criteria, config) {
44
+ const endpoint = config?.endpoint || DEFAULT_ENDPOINT;
45
+ const model = config?.model || DEFAULT_MODEL;
46
+ const timeout = config?.timeout_ms || DEFAULT_TIMEOUT;
47
+ const apiKey = config?.api_key;
48
+ const systemPrompt = `You are a test judge. Evaluate whether the AI agent's response meets the criteria.
49
+
50
+ Input given to agent: "${input}"
51
+ Agent's response: "${output}"
52
+ Criteria: "${criteria}"
53
+
54
+ Respond with ONLY a JSON object:
55
+ {"passed": true/false, "score": 0.0-1.0, "reasoning": "brief explanation"}`;
56
+ const body = JSON.stringify({
57
+ model,
58
+ messages: [
59
+ { role: 'system', content: systemPrompt },
60
+ { role: 'user', content: `Evaluate: does the response meet the criteria?` }
61
+ ],
62
+ temperature: 0.1,
63
+ max_tokens: 200,
64
+ });
65
+ try {
66
+ const response = await httpRequest(endpoint, body, apiKey, timeout);
67
+ const text = response.choices?.[0]?.message?.content || '';
68
+ // Parse JSON from response (LLMs sometimes wrap in markdown)
69
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
70
+ if (!jsonMatch) {
71
+ return { passed: false, score: 0, reasoning: `Judge returned non-JSON: ${text.substring(0, 100)}` };
72
+ }
73
+ const result = JSON.parse(jsonMatch[0]);
74
+ return {
75
+ passed: !!result.passed,
76
+ score: typeof result.score === 'number' ? result.score : (result.passed ? 1.0 : 0.0),
77
+ reasoning: result.reasoning || 'No reasoning provided',
78
+ };
79
+ }
80
+ catch (e) {
81
+ return {
82
+ passed: false,
83
+ score: 0,
84
+ reasoning: `Judge error: ${e instanceof Error ? e.message : String(e)}`,
85
+ };
86
+ }
87
+ }
88
+ function httpRequest(url, body, apiKey, timeoutMs) {
89
+ return new Promise((resolve, reject) => {
90
+ const parsed = new url_1.URL(url);
91
+ const isHttps = parsed.protocol === 'https:';
92
+ const lib = isHttps ? https : http;
93
+ const headers = {
94
+ 'Content-Type': 'application/json',
95
+ };
96
+ if (apiKey) {
97
+ headers['Authorization'] = `Bearer ${apiKey}`;
98
+ }
99
+ const req = lib.request({
100
+ hostname: parsed.hostname,
101
+ port: parsed.port || (isHttps ? 443 : 80),
102
+ path: parsed.pathname + parsed.search,
103
+ method: 'POST',
104
+ headers,
105
+ }, (res) => {
106
+ let data = '';
107
+ res.on('data', (chunk) => (data += chunk));
108
+ res.on('end', () => {
109
+ try {
110
+ resolve(JSON.parse(data));
111
+ }
112
+ catch (e) {
113
+ reject(new Error(`Invalid JSON response: ${data.substring(0, 200)}`));
114
+ }
115
+ });
116
+ });
117
+ req.on('error', reject);
118
+ req.setTimeout(timeoutMs, () => {
119
+ req.destroy(new Error('Request timeout'));
120
+ });
121
+ req.write(body);
122
+ req.end();
123
+ });
124
+ }
@@ -0,0 +1,3 @@
1
+ import { TestSuite } from './types';
2
+ export declare function loadTestSuite(filePath: string): TestSuite;
3
+ export declare function findTestSuites(dir: string): string[];
package/dist/loader.js ADDED
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadTestSuite = loadTestSuite;
37
+ exports.findTestSuites = findTestSuites;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const yaml_1 = require("yaml");
41
+ function loadTestSuite(filePath) {
42
+ const content = fs.readFileSync(filePath, 'utf-8');
43
+ const parsed = (0, yaml_1.parse)(content);
44
+ if (!parsed.name || !parsed.tests) {
45
+ throw new Error(`Invalid test suite: missing name or tests in ${filePath}`);
46
+ }
47
+ return parsed;
48
+ }
49
+ function findTestSuites(dir) {
50
+ if (!fs.existsSync(dir)) {
51
+ return [];
52
+ }
53
+ const files = [];
54
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
55
+ const fullPath = path.join(dir, entry.name);
56
+ if (entry.isDirectory()) {
57
+ files.push(...findTestSuites(fullPath));
58
+ }
59
+ else if (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml')) {
60
+ files.push(fullPath);
61
+ }
62
+ }
63
+ return files;
64
+ }
@@ -0,0 +1,4 @@
1
+ import { RunResult } from './types';
2
+ export declare function formatResults(result: RunResult): string;
3
+ export declare function formatResultsJSON(result: RunResult): string;
4
+ export declare function formatResultsJUnit(result: RunResult): string;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatResults = formatResults;
4
+ exports.formatResultsJSON = formatResultsJSON;
5
+ exports.formatResultsJUnit = formatResultsJUnit;
6
+ function formatResults(result) {
7
+ const lines = [];
8
+ lines.push('');
9
+ lines.push(' AgentSpec — Test Results');
10
+ lines.push(' ' + '─'.repeat(50));
11
+ for (const test of result.results) {
12
+ const status = test.passed ? '✓' : '✗';
13
+ const statusColor = test.passed ? '\x1b[32m' : '\x1b[31m';
14
+ const resetColor = '\x1b[0m';
15
+ lines.push(` ${statusColor}${status}${resetColor} ${test.suite} > ${test.test} (${test.duration_ms}ms)`);
16
+ if (!test.passed) {
17
+ if (test.error) {
18
+ lines.push(` Error: ${test.error}`);
19
+ }
20
+ for (const a of test.assertions) {
21
+ if (!a.passed) {
22
+ lines.push(` ${a.assertion}: ${a.message}`);
23
+ if (a.expected && a.actual) {
24
+ lines.push(` expected: ${a.expected}`);
25
+ lines.push(` actual: ${a.actual}`);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ lines.push(' ' + '─'.repeat(50));
32
+ const totalColor = result.failed > 0 ? '\x1b[31m' : '\x1b[32m';
33
+ const resetColor2 = '\x1b[0m';
34
+ lines.push(` ${totalColor}${result.passed} passed${resetColor2}, ${result.failed} failed (${result.total} total)`);
35
+ lines.push(` Duration: ${result.duration_ms}ms`);
36
+ lines.push('');
37
+ return lines.join('\n');
38
+ }
39
+ function formatResultsJSON(result) {
40
+ return JSON.stringify(result, null, 2);
41
+ }
42
+ function formatResultsJUnit(result) {
43
+ const lines = ['<?xml version="1.0" encoding="UTF-8"?>'];
44
+ lines.push(`<testsuites tests="${result.total}" failures="${result.failed}" time="${result.duration_ms / 1000}">`);
45
+ const bySuite = new Map();
46
+ for (const test of result.results) {
47
+ if (!bySuite.has(test.suite))
48
+ bySuite.set(test.suite, []);
49
+ bySuite.get(test.suite).push(test);
50
+ }
51
+ const suiteEntries = Array.from(bySuite.entries());
52
+ for (const [suiteName, tests] of suiteEntries) {
53
+ const suiteFailures = tests.filter(t => !t.passed).length;
54
+ const suiteTime = tests.reduce((sum, t) => sum + t.duration_ms, 0) / 1000;
55
+ lines.push(` <testsuite name="${escapeXml(suiteName)}" tests="${tests.length}" failures="${suiteFailures}" time="${suiteTime}">`);
56
+ for (const test of tests) {
57
+ lines.push(` <testcase name="${escapeXml(test.test)}" classname="${escapeXml(suiteName)}" time="${test.duration_ms / 1000}"${test.passed ? ' />' : '>'}`);
58
+ if (!test.passed) {
59
+ const failureMsg = test.error || test.assertions.filter(a => !a.passed).map(a => a.message).join('; ');
60
+ lines.push(` <failure message="${escapeXml(failureMsg)}">${escapeXml(failureMsg)}</failure>`);
61
+ lines.push(` </testcase>`);
62
+ }
63
+ }
64
+ lines.push(` </testsuite>`);
65
+ }
66
+ lines.push('</testsuites>');
67
+ return lines.join('\n');
68
+ }
69
+ function escapeXml(s) {
70
+ return s.replace(/[<>&'"]/g, (c) => {
71
+ switch (c) {
72
+ case '<': return '&lt;';
73
+ case '>': return '&gt;';
74
+ case '&': return '&amp;';
75
+ case '\'': return '&apos;';
76
+ case '"': return '&quot;';
77
+ default: return c;
78
+ }
79
+ });
80
+ }
@@ -0,0 +1,19 @@
1
+ import { TestResult, RunResult, TestSuite } from './types';
2
+ export { TestSuite } from './types';
3
+ export interface AgentRunner {
4
+ run(input: string, suite: TestSuite): Promise<AgentOutput>;
5
+ }
6
+ export interface AgentOutput {
7
+ text: string;
8
+ tokens?: number;
9
+ latencyMs?: number;
10
+ toolsCalled?: string[];
11
+ }
12
+ export declare function runSuite(suitePath: string, agent: AgentRunner): Promise<TestResult[]>;
13
+ export declare function runAll(dir: string, agent: AgentRunner, filter?: string | null): Promise<RunResult>;
14
+ export declare class MockAgent implements AgentRunner {
15
+ responses: Record<string, string>;
16
+ echo: boolean;
17
+ constructor(responses?: Record<string, string>, echo?: boolean);
18
+ run(input: string, suite: TestSuite): Promise<AgentOutput>;
19
+ }