@prompd/test 0.5.0-beta.9

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.
Files changed (62) hide show
  1. package/dist/EvaluatorEngine.d.ts +32 -0
  2. package/dist/EvaluatorEngine.d.ts.map +1 -0
  3. package/dist/EvaluatorEngine.js +97 -0
  4. package/dist/TestDiscovery.d.ts +28 -0
  5. package/dist/TestDiscovery.d.ts.map +1 -0
  6. package/dist/TestDiscovery.js +137 -0
  7. package/dist/TestParser.d.ts +25 -0
  8. package/dist/TestParser.d.ts.map +1 -0
  9. package/dist/TestParser.js +187 -0
  10. package/dist/TestRunner.d.ts +57 -0
  11. package/dist/TestRunner.d.ts.map +1 -0
  12. package/dist/TestRunner.js +463 -0
  13. package/dist/cli-types.d.ts +62 -0
  14. package/dist/cli-types.d.ts.map +1 -0
  15. package/dist/cli-types.js +6 -0
  16. package/dist/evaluators/NlpEvaluator.d.ts +26 -0
  17. package/dist/evaluators/NlpEvaluator.d.ts.map +1 -0
  18. package/dist/evaluators/NlpEvaluator.js +145 -0
  19. package/dist/evaluators/PrmdEvaluator.d.ts +42 -0
  20. package/dist/evaluators/PrmdEvaluator.d.ts.map +1 -0
  21. package/dist/evaluators/PrmdEvaluator.js +265 -0
  22. package/dist/evaluators/ScriptEvaluator.d.ts +19 -0
  23. package/dist/evaluators/ScriptEvaluator.d.ts.map +1 -0
  24. package/dist/evaluators/ScriptEvaluator.js +161 -0
  25. package/dist/evaluators/types.d.ts +19 -0
  26. package/dist/evaluators/types.d.ts.map +1 -0
  27. package/dist/evaluators/types.js +5 -0
  28. package/dist/index.d.ts +25 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +33 -0
  31. package/dist/reporters/ConsoleReporter.d.ts +17 -0
  32. package/dist/reporters/ConsoleReporter.d.ts.map +1 -0
  33. package/dist/reporters/ConsoleReporter.js +85 -0
  34. package/dist/reporters/JsonReporter.d.ts +11 -0
  35. package/dist/reporters/JsonReporter.d.ts.map +1 -0
  36. package/dist/reporters/JsonReporter.js +18 -0
  37. package/dist/reporters/JunitReporter.d.ts +15 -0
  38. package/dist/reporters/JunitReporter.d.ts.map +1 -0
  39. package/dist/reporters/JunitReporter.js +89 -0
  40. package/dist/reporters/types.d.ts +8 -0
  41. package/dist/reporters/types.d.ts.map +1 -0
  42. package/dist/reporters/types.js +5 -0
  43. package/dist/types.d.ts +115 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +5 -0
  46. package/package.json +34 -0
  47. package/src/EvaluatorEngine.ts +130 -0
  48. package/src/TestDiscovery.ts +133 -0
  49. package/src/TestParser.ts +235 -0
  50. package/src/TestRunner.ts +516 -0
  51. package/src/cli-types.ts +92 -0
  52. package/src/evaluators/NlpEvaluator.ts +184 -0
  53. package/src/evaluators/PrmdEvaluator.ts +284 -0
  54. package/src/evaluators/ScriptEvaluator.ts +149 -0
  55. package/src/evaluators/types.ts +24 -0
  56. package/src/index.ts +76 -0
  57. package/src/reporters/ConsoleReporter.ts +100 -0
  58. package/src/reporters/JsonReporter.ts +21 -0
  59. package/src/reporters/JunitReporter.ts +113 -0
  60. package/src/reporters/types.ts +9 -0
  61. package/src/types.ts +133 -0
  62. package/tsconfig.json +20 -0
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Routes assertions to the correct evaluator and manages execution order.
3
+ *
4
+ * Execution order: nlp -> script -> prmd (cheap to expensive).
5
+ * Fail-fast by default — stops on first failure unless runAll is set.
6
+ */
7
+ import type { AssertionDef, AssertionResult, EvaluatorType } from './types';
8
+ import type { EvaluatorContext } from './evaluators/types';
9
+ import type { CompilerModule } from './cli-types';
10
+ export interface EvaluatorEngineOptions {
11
+ testFileDir: string;
12
+ evaluatorPrompt?: string;
13
+ workspaceRoot?: string;
14
+ registryUrl?: string;
15
+ allowedEvaluators?: EvaluatorType[];
16
+ failFast?: boolean;
17
+ cliModule?: CompilerModule;
18
+ provider?: string;
19
+ model?: string;
20
+ }
21
+ export declare class EvaluatorEngine {
22
+ private evaluators;
23
+ private allowedEvaluators;
24
+ private failFast;
25
+ constructor(options: EvaluatorEngineOptions);
26
+ /**
27
+ * Evaluate all assertions in cost-priority order.
28
+ * Returns results for each assertion.
29
+ */
30
+ evaluate(assertions: AssertionDef[], context: EvaluatorContext, onResult?: (result: AssertionResult) => void): Promise<AssertionResult[]>;
31
+ }
32
+ //# sourceMappingURL=EvaluatorEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EvaluatorEngine.d.ts","sourceRoot":"","sources":["../src/EvaluatorEngine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,aAAa,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAU;gBAEd,OAAO,EAAE,sBAAsB;IAqB3C;;;OAGG;IACG,QAAQ,CACZ,UAAU,EAAE,YAAY,EAAE,EAC1B,OAAO,EAAE,gBAAgB,EACzB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,GAC3C,OAAO,CAAC,eAAe,EAAE,CAAC;CA8D9B"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Routes assertions to the correct evaluator and manages execution order.
4
+ *
5
+ * Execution order: nlp -> script -> prmd (cheap to expensive).
6
+ * Fail-fast by default — stops on first failure unless runAll is set.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.EvaluatorEngine = void 0;
10
+ const NlpEvaluator_1 = require("./evaluators/NlpEvaluator");
11
+ const ScriptEvaluator_1 = require("./evaluators/ScriptEvaluator");
12
+ const PrmdEvaluator_1 = require("./evaluators/PrmdEvaluator");
13
+ /** Execution priority — lower number runs first */
14
+ const EVALUATOR_PRIORITY = {
15
+ nlp: 0,
16
+ script: 1,
17
+ prmd: 2,
18
+ };
19
+ class EvaluatorEngine {
20
+ constructor(options) {
21
+ this.failFast = options.failFast !== false;
22
+ this.allowedEvaluators = new Set(options.allowedEvaluators || ['nlp', 'script', 'prmd']);
23
+ const prmdOptions = {
24
+ testFileDir: options.testFileDir,
25
+ evaluatorPrompt: options.evaluatorPrompt,
26
+ workspaceRoot: options.workspaceRoot,
27
+ registryUrl: options.registryUrl,
28
+ cliModule: options.cliModule,
29
+ provider: options.provider,
30
+ model: options.model,
31
+ };
32
+ this.evaluators = new Map([
33
+ ['nlp', new NlpEvaluator_1.NlpEvaluator()],
34
+ ['script', new ScriptEvaluator_1.ScriptEvaluator(options.testFileDir)],
35
+ ['prmd', new PrmdEvaluator_1.PrmdEvaluator(prmdOptions)],
36
+ ]);
37
+ }
38
+ /**
39
+ * Evaluate all assertions in cost-priority order.
40
+ * Returns results for each assertion.
41
+ */
42
+ async evaluate(assertions, context, onResult) {
43
+ const results = [];
44
+ // Sort by evaluator priority (nlp first, prmd last)
45
+ const sorted = [...assertions].sort((a, b) => EVALUATOR_PRIORITY[a.evaluator] - EVALUATOR_PRIORITY[b.evaluator]);
46
+ for (const assertion of sorted) {
47
+ // Skip evaluators that aren't allowed
48
+ if (!this.allowedEvaluators.has(assertion.evaluator)) {
49
+ const skipped = {
50
+ evaluator: assertion.evaluator,
51
+ check: assertion.check,
52
+ status: 'skip',
53
+ reason: `Evaluator type "${assertion.evaluator}" skipped by filter`,
54
+ duration: 0,
55
+ };
56
+ results.push(skipped);
57
+ onResult?.(skipped);
58
+ continue;
59
+ }
60
+ const evaluator = this.evaluators.get(assertion.evaluator);
61
+ if (!evaluator) {
62
+ const errorResult = {
63
+ evaluator: assertion.evaluator,
64
+ check: assertion.check,
65
+ status: 'error',
66
+ reason: `No evaluator registered for type "${assertion.evaluator}"`,
67
+ duration: 0,
68
+ };
69
+ results.push(errorResult);
70
+ onResult?.(errorResult);
71
+ continue;
72
+ }
73
+ const result = await evaluator.evaluate(assertion, context);
74
+ results.push(result);
75
+ onResult?.(result);
76
+ // Fail-fast: stop on first failure
77
+ if (this.failFast && result.status !== 'pass') {
78
+ // Mark remaining assertions as skipped
79
+ const remaining = sorted.slice(sorted.indexOf(assertion) + 1);
80
+ for (const rem of remaining) {
81
+ const skipped = {
82
+ evaluator: rem.evaluator,
83
+ check: rem.check,
84
+ status: 'skip',
85
+ reason: 'Skipped due to prior failure (fail-fast)',
86
+ duration: 0,
87
+ };
88
+ results.push(skipped);
89
+ onResult?.(skipped);
90
+ }
91
+ break;
92
+ }
93
+ }
94
+ return results;
95
+ }
96
+ }
97
+ exports.EvaluatorEngine = EvaluatorEngine;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Discovers .test.prmd files and pairs them with their source .prmd files.
3
+ */
4
+ import type { TestSuite } from './types';
5
+ export interface DiscoveryResult {
6
+ suites: TestSuite[];
7
+ errors: DiscoveryError[];
8
+ }
9
+ export interface DiscoveryError {
10
+ filePath: string;
11
+ message: string;
12
+ }
13
+ export declare class TestDiscovery {
14
+ private parser;
15
+ constructor();
16
+ /**
17
+ * Discover test suites from a target path.
18
+ *
19
+ * - If targetPath is a .test.prmd file, parse it directly.
20
+ * - If targetPath is a .prmd file, look for a colocated .test.prmd sidecar.
21
+ * - If targetPath is a directory, glob for all .test.prmd files recursively.
22
+ */
23
+ discover(targetPath: string): Promise<DiscoveryResult>;
24
+ private discoverDirectory;
25
+ private discoverTestFile;
26
+ private discoverFromSource;
27
+ }
28
+ //# sourceMappingURL=TestDiscovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestDiscovery.d.ts","sourceRoot":"","sources":["../src/TestDiscovery.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAa;;IAM3B;;;;;;OAMG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YA+B9C,iBAAiB;YAsBjB,gBAAgB;YA4BhB,kBAAkB;CAiBjC"}
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * Discovers .test.prmd files and pairs them with their source .prmd files.
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.TestDiscovery = void 0;
40
+ const path = __importStar(require("path"));
41
+ const fs = __importStar(require("fs"));
42
+ const glob_1 = require("glob");
43
+ const TestParser_1 = require("./TestParser");
44
+ class TestDiscovery {
45
+ constructor() {
46
+ this.parser = new TestParser_1.TestParser();
47
+ }
48
+ /**
49
+ * Discover test suites from a target path.
50
+ *
51
+ * - If targetPath is a .test.prmd file, parse it directly.
52
+ * - If targetPath is a .prmd file, look for a colocated .test.prmd sidecar.
53
+ * - If targetPath is a directory, glob for all .test.prmd files recursively.
54
+ */
55
+ async discover(targetPath) {
56
+ const resolved = path.resolve(targetPath);
57
+ const suites = [];
58
+ const errors = [];
59
+ if (!fs.existsSync(resolved)) {
60
+ errors.push({ filePath: resolved, message: 'Path does not exist' });
61
+ return { suites, errors };
62
+ }
63
+ const stat = fs.statSync(resolved);
64
+ if (stat.isDirectory()) {
65
+ return this.discoverDirectory(resolved);
66
+ }
67
+ if (resolved.endsWith('.test.prmd')) {
68
+ return this.discoverTestFile(resolved);
69
+ }
70
+ if (resolved.endsWith('.prmd')) {
71
+ return this.discoverFromSource(resolved);
72
+ }
73
+ errors.push({
74
+ filePath: resolved,
75
+ message: 'Target must be a .prmd file, .test.prmd file, or directory',
76
+ });
77
+ return { suites, errors };
78
+ }
79
+ async discoverDirectory(dirPath) {
80
+ const suites = [];
81
+ const errors = [];
82
+ const pattern = '**/*.test.prmd';
83
+ const testFiles = await (0, glob_1.glob)(pattern, {
84
+ cwd: dirPath,
85
+ absolute: true,
86
+ nodir: true,
87
+ windowsPathsNoEscape: true,
88
+ });
89
+ for (const testFile of testFiles) {
90
+ const normalized = testFile.replace(/\\/g, '/');
91
+ const result = await this.discoverTestFile(normalized);
92
+ suites.push(...result.suites);
93
+ errors.push(...result.errors);
94
+ }
95
+ return { suites, errors };
96
+ }
97
+ async discoverTestFile(testFilePath) {
98
+ const suites = [];
99
+ const errors = [];
100
+ try {
101
+ const content = fs.readFileSync(testFilePath, 'utf-8');
102
+ const suite = this.parser.parse(content, testFilePath);
103
+ // Validate that the target .prmd file exists
104
+ if (!fs.existsSync(suite.target)) {
105
+ errors.push({
106
+ filePath: testFilePath,
107
+ message: `Target prompt file not found: ${suite.target}`,
108
+ });
109
+ return { suites, errors };
110
+ }
111
+ suites.push(suite);
112
+ }
113
+ catch (err) {
114
+ errors.push({
115
+ filePath: testFilePath,
116
+ message: err instanceof Error ? err.message : String(err),
117
+ });
118
+ }
119
+ return { suites, errors };
120
+ }
121
+ async discoverFromSource(sourcePath) {
122
+ const dir = path.dirname(sourcePath);
123
+ const base = path.basename(sourcePath, '.prmd');
124
+ const testFilePath = path.join(dir, `${base}.test.prmd`);
125
+ if (!fs.existsSync(testFilePath)) {
126
+ return {
127
+ suites: [],
128
+ errors: [{
129
+ filePath: sourcePath,
130
+ message: `No colocated test file found: ${testFilePath}`,
131
+ }],
132
+ };
133
+ }
134
+ return this.discoverTestFile(testFilePath);
135
+ }
136
+ }
137
+ exports.TestDiscovery = TestDiscovery;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Parses .test.prmd files into TestSuite structures.
3
+ *
4
+ * A .test.prmd file has YAML frontmatter (test definitions) and
5
+ * an optional content block (evaluator prompt for prmd evaluators).
6
+ */
7
+ import type { TestSuite } from './types';
8
+ export declare class TestParser {
9
+ /**
10
+ * Parse a .test.prmd file's raw content into a TestSuite.
11
+ */
12
+ parse(content: string, testFilePath: string): TestSuite;
13
+ private splitFrontmatter;
14
+ private resolveTarget;
15
+ private parseTests;
16
+ private parseAssertions;
17
+ private validateNlpAssertion;
18
+ private validateScriptAssertion;
19
+ private validatePrmdAssertion;
20
+ }
21
+ export declare class TestParseError extends Error {
22
+ readonly filePath: string;
23
+ constructor(message: string, filePath: string);
24
+ }
25
+ //# sourceMappingURL=TestParser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestParser.d.ts","sourceRoot":"","sources":["../src/TestParser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAmD,MAAM,SAAS,CAAC;AAgC1F,qBAAa,UAAU;IACrB;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS;IAmCvD,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,UAAU;IA2BlB,OAAO,CAAC,eAAe;IAgCvB,OAAO,CAAC,oBAAoB;IA4B5B,OAAO,CAAC,uBAAuB;IAmB/B,OAAO,CAAC,qBAAqB;CAc9B;AAED,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAK9C"}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /**
3
+ * Parses .test.prmd files into TestSuite structures.
4
+ *
5
+ * A .test.prmd file has YAML frontmatter (test definitions) and
6
+ * an optional content block (evaluator prompt for prmd evaluators).
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.TestParseError = exports.TestParser = void 0;
43
+ const path = __importStar(require("path"));
44
+ const YAML = __importStar(require("yaml"));
45
+ const VALID_EVALUATOR_TYPES = ['nlp', 'script', 'prmd'];
46
+ const VALID_NLP_CHECKS = [
47
+ 'contains', 'not_contains', 'matches',
48
+ 'max_tokens', 'min_tokens', 'starts_with', 'ends_with'
49
+ ];
50
+ class TestParser {
51
+ /**
52
+ * Parse a .test.prmd file's raw content into a TestSuite.
53
+ */
54
+ parse(content, testFilePath) {
55
+ const normalized = content.replace(/\r\n/g, '\n');
56
+ const { frontmatter, body } = this.splitFrontmatter(normalized);
57
+ if (!frontmatter) {
58
+ throw new TestParseError('Missing YAML frontmatter in .test.prmd file', testFilePath);
59
+ }
60
+ let parsed;
61
+ try {
62
+ parsed = YAML.parse(frontmatter);
63
+ }
64
+ catch (err) {
65
+ const message = err instanceof Error ? err.message : String(err);
66
+ throw new TestParseError(`Invalid YAML frontmatter: ${message}`, testFilePath);
67
+ }
68
+ if (!parsed || typeof parsed !== 'object') {
69
+ throw new TestParseError('Frontmatter must be a YAML object', testFilePath);
70
+ }
71
+ const name = parsed.name || path.basename(testFilePath, '.test.prmd');
72
+ const target = this.resolveTarget(parsed.target, testFilePath);
73
+ const tests = this.parseTests(parsed.tests, testFilePath);
74
+ const evaluatorPrompt = body.trim() || undefined;
75
+ return {
76
+ name,
77
+ description: parsed.description,
78
+ target,
79
+ testFilePath,
80
+ tests,
81
+ evaluatorPrompt,
82
+ };
83
+ }
84
+ splitFrontmatter(content) {
85
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
86
+ if (!match) {
87
+ return { frontmatter: null, body: content };
88
+ }
89
+ return {
90
+ frontmatter: match[1],
91
+ body: match[2],
92
+ };
93
+ }
94
+ resolveTarget(target, testFilePath) {
95
+ if (target) {
96
+ const dir = path.dirname(testFilePath);
97
+ return path.resolve(dir, target);
98
+ }
99
+ // Auto-discover: summarize.test.prmd -> summarize.prmd
100
+ const dir = path.dirname(testFilePath);
101
+ const base = path.basename(testFilePath);
102
+ const sourceBase = base.replace(/\.test\.prmd$/, '.prmd');
103
+ return path.resolve(dir, sourceBase);
104
+ }
105
+ parseTests(rawTests, filePath) {
106
+ if (!rawTests || !Array.isArray(rawTests)) {
107
+ throw new TestParseError('Frontmatter must contain a "tests" array', filePath);
108
+ }
109
+ if (rawTests.length === 0) {
110
+ throw new TestParseError('"tests" array must not be empty', filePath);
111
+ }
112
+ return rawTests.map((raw, index) => {
113
+ const name = raw.name || `test_${index + 1}`;
114
+ const params = raw.params && typeof raw.params === 'object' ? raw.params : {};
115
+ if (raw.expect_error) {
116
+ return {
117
+ name,
118
+ params,
119
+ assert: [],
120
+ expect_error: true,
121
+ };
122
+ }
123
+ const assertions = this.parseAssertions(raw.assert, name, filePath);
124
+ return { name, params, assert: assertions };
125
+ });
126
+ }
127
+ parseAssertions(rawAssertions, testName, filePath) {
128
+ if (!rawAssertions || !Array.isArray(rawAssertions)) {
129
+ return [];
130
+ }
131
+ return rawAssertions.map((raw, index) => {
132
+ if (!raw.evaluator || !VALID_EVALUATOR_TYPES.includes(raw.evaluator)) {
133
+ throw new TestParseError(`Test "${testName}", assertion ${index + 1}: invalid evaluator "${raw.evaluator}". ` +
134
+ `Must be one of: ${VALID_EVALUATOR_TYPES.join(', ')}`, filePath);
135
+ }
136
+ const evaluator = raw.evaluator;
137
+ if (evaluator === 'nlp') {
138
+ return this.validateNlpAssertion(raw, testName, index, filePath);
139
+ }
140
+ if (evaluator === 'script') {
141
+ return this.validateScriptAssertion(raw, testName, index, filePath);
142
+ }
143
+ return this.validatePrmdAssertion(raw, testName, index, filePath);
144
+ });
145
+ }
146
+ validateNlpAssertion(raw, testName, index, filePath) {
147
+ if (!raw.check || !VALID_NLP_CHECKS.includes(raw.check)) {
148
+ throw new TestParseError(`Test "${testName}", assertion ${index + 1}: NLP evaluator requires a valid "check". ` +
149
+ `Must be one of: ${VALID_NLP_CHECKS.join(', ')}`, filePath);
150
+ }
151
+ if (raw.value === undefined || raw.value === null) {
152
+ throw new TestParseError(`Test "${testName}", assertion ${index + 1}: NLP evaluator requires a "value"`, filePath);
153
+ }
154
+ return {
155
+ evaluator: 'nlp',
156
+ check: raw.check,
157
+ value: raw.value,
158
+ };
159
+ }
160
+ validateScriptAssertion(raw, testName, index, filePath) {
161
+ if (!raw.run || typeof raw.run !== 'string') {
162
+ throw new TestParseError(`Test "${testName}", assertion ${index + 1}: script evaluator requires a "run" path`, filePath);
163
+ }
164
+ return {
165
+ evaluator: 'script',
166
+ run: raw.run,
167
+ };
168
+ }
169
+ validatePrmdAssertion(raw, _testName, _index, _filePath) {
170
+ // prompt: is optional — if omitted, uses the content block of the .test.prmd
171
+ return {
172
+ evaluator: 'prmd',
173
+ prompt: raw.prompt || undefined,
174
+ provider: raw.provider || undefined,
175
+ model: raw.model || undefined,
176
+ };
177
+ }
178
+ }
179
+ exports.TestParser = TestParser;
180
+ class TestParseError extends Error {
181
+ constructor(message, filePath) {
182
+ super(`${message} (${filePath})`);
183
+ this.name = 'TestParseError';
184
+ this.filePath = filePath;
185
+ }
186
+ }
187
+ exports.TestParseError = TestParseError;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * TestRunner - orchestrates the full test lifecycle:
3
+ * discovery -> compile -> execute -> evaluate -> report
4
+ *
5
+ * Consumes @prompd/cli for compilation and execution.
6
+ * This is the primary public API for @prompd/test.
7
+ */
8
+ import type { CompilerModule } from './cli-types';
9
+ import type { TestHarness } from '@prompd/cli';
10
+ import type { TestRunResult, TestRunOptions, TestProgressCallback } from './types';
11
+ export declare class TestRunner implements TestHarness {
12
+ private discovery;
13
+ private cliModule;
14
+ private configLoaded;
15
+ /**
16
+ * @param cli - Optional pre-loaded @prompd/cli module. If provided, skips dynamic import.
17
+ * This is the recommended approach when running inside Electron where the CLI
18
+ * is already loaded by the main process.
19
+ */
20
+ constructor(cli?: CompilerModule);
21
+ /**
22
+ * Ensure CLI config is loaded (API keys, provider settings).
23
+ * Called once before any execution.
24
+ */
25
+ private ensureConfig;
26
+ /**
27
+ * Run tests for a target path (file or directory).
28
+ * Returns structured results and an exit code (0 = all pass, 1 = failures).
29
+ */
30
+ run(targetPath: string, options?: TestRunOptions, onProgress?: TestProgressCallback): Promise<TestRunResult>;
31
+ /**
32
+ * Run tests and return formatted output string.
33
+ */
34
+ runAndReport(targetPath: string, options?: TestRunOptions, onProgress?: TestProgressCallback): Promise<{
35
+ output: string;
36
+ exitCode: number;
37
+ }>;
38
+ private runSuite;
39
+ private runTestCase;
40
+ /**
41
+ * Compile a .prmd file and return both the compiled text and metadata
42
+ * (provider, model, temperature, max_tokens from frontmatter).
43
+ */
44
+ private compileTarget;
45
+ /**
46
+ * Execute compiled prompt text against an LLM using the executor's callLLM directly.
47
+ * This avoids re-compilation through executeRawText which loses metadata.
48
+ */
49
+ private executePrompt;
50
+ private resolveAllowedEvaluators;
51
+ private getReporter;
52
+ private buildSummary;
53
+ private buildErrorResult;
54
+ private getDefaultModel;
55
+ private getCli;
56
+ }
57
+ //# sourceMappingURL=TestRunner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestRunner.d.ts","sourceRoot":"","sources":["../src/TestRunner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAIV,aAAa,EAGb,cAAc,EACd,oBAAoB,EAErB,MAAM,SAAS,CAAC;AAEjB,qBAAa,UAAW,YAAW,WAAW;IAC5C,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,YAAY,CAAS;IAE7B;;;;OAIG;gBACS,GAAG,CAAC,EAAE,cAAc;IAOhC;;;OAGG;YACW,YAAY;IAyB1B;;;OAGG;IACG,GAAG,CACP,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,cAAmB,EAC5B,UAAU,CAAC,EAAE,oBAAoB,GAChC,OAAO,CAAC,aAAa,CAAC;IAgCzB;;OAEG;IACG,YAAY,CAChB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,cAAmB,EAC5B,UAAU,CAAC,EAAE,oBAAoB,GAChC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;YAQlC,QAAQ;YAoBR,WAAW;IAuJzB;;;OAGG;YACW,aAAa;IA+C3B;;;OAGG;YACW,aAAa;IAuD3B,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,eAAe;YAYT,MAAM;CAQrB"}