@pauly4010/evalai-sdk 1.8.0 → 1.9.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/cli/ci.d.ts +45 -0
  3. package/dist/cli/ci.js +192 -0
  4. package/dist/cli/diff.d.ts +173 -0
  5. package/dist/cli/diff.js +680 -0
  6. package/dist/cli/discover.d.ts +84 -0
  7. package/dist/cli/discover.js +408 -0
  8. package/dist/cli/doctor.js +19 -10
  9. package/dist/cli/env.d.ts +21 -0
  10. package/dist/cli/env.js +42 -0
  11. package/dist/cli/explain.js +143 -37
  12. package/dist/cli/impact-analysis.d.ts +63 -0
  13. package/dist/cli/impact-analysis.js +251 -0
  14. package/dist/cli/index.js +173 -0
  15. package/dist/cli/manifest.d.ts +105 -0
  16. package/dist/cli/manifest.js +275 -0
  17. package/dist/cli/migrate.d.ts +41 -0
  18. package/dist/cli/migrate.js +349 -0
  19. package/dist/cli/print-config.js +18 -14
  20. package/dist/cli/run.d.ts +101 -0
  21. package/dist/cli/run.js +389 -0
  22. package/dist/cli/workspace.d.ts +28 -0
  23. package/dist/cli/workspace.js +58 -0
  24. package/dist/index.d.ts +6 -0
  25. package/dist/index.js +30 -5
  26. package/dist/runtime/adapters/config-to-dsl.d.ts +33 -0
  27. package/dist/runtime/adapters/config-to-dsl.js +391 -0
  28. package/dist/runtime/adapters/testsuite-to-dsl.d.ts +63 -0
  29. package/dist/runtime/adapters/testsuite-to-dsl.js +271 -0
  30. package/dist/runtime/context.d.ts +26 -0
  31. package/dist/runtime/context.js +74 -0
  32. package/dist/runtime/eval.d.ts +46 -0
  33. package/dist/runtime/eval.js +237 -0
  34. package/dist/runtime/execution-mode.d.ts +80 -0
  35. package/dist/runtime/execution-mode.js +353 -0
  36. package/dist/runtime/executor.d.ts +16 -0
  37. package/dist/runtime/executor.js +152 -0
  38. package/dist/runtime/registry.d.ts +78 -0
  39. package/dist/runtime/registry.js +416 -0
  40. package/dist/runtime/run-report.d.ts +202 -0
  41. package/dist/runtime/run-report.js +220 -0
  42. package/dist/runtime/types.d.ts +356 -0
  43. package/dist/runtime/types.js +76 -0
  44. package/dist/testing.d.ts +65 -0
  45. package/dist/testing.js +42 -0
  46. package/dist/version.d.ts +1 -1
  47. package/dist/version.js +1 -1
  48. package/package.json +4 -3
package/dist/cli/index.js CHANGED
@@ -10,11 +10,17 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  const baseline_1 = require("./baseline");
12
12
  const check_1 = require("./check");
13
+ const ci_1 = require("./ci");
14
+ const diff_1 = require("./diff");
15
+ const discover_1 = require("./discover");
13
16
  const doctor_1 = require("./doctor");
14
17
  const explain_1 = require("./explain");
18
+ const impact_analysis_1 = require("./impact-analysis");
15
19
  const init_1 = require("./init");
20
+ const migrate_1 = require("./migrate");
16
21
  const print_config_1 = require("./print-config");
17
22
  const regression_gate_1 = require("./regression-gate");
23
+ const run_1 = require("./run");
18
24
  const share_1 = require("./share");
19
25
  const upgrade_1 = require("./upgrade");
20
26
  const argv = process.argv.slice(2);
@@ -32,6 +38,60 @@ else if (subcommand === "gate") {
32
38
  const code = (0, regression_gate_1.runGate)(argv.slice(1));
33
39
  process.exit(code);
34
40
  }
41
+ else if (subcommand === "migrate") {
42
+ // Handle migrate subcommand
43
+ const migrateSubcommand = argv[1];
44
+ if (migrateSubcommand === "config") {
45
+ // Parse migrate config arguments
46
+ let inputPath = "";
47
+ let outputPath = "";
48
+ let verbose = false;
49
+ let helpers = true;
50
+ let preserveIds = true;
51
+ let provenance = true;
52
+ for (let i = 2; i < argv.length; i++) {
53
+ const arg = argv[i];
54
+ if (arg === "--in" || arg === "-i") {
55
+ inputPath = argv[++i];
56
+ }
57
+ else if (arg === "--out" || arg === "-o") {
58
+ outputPath = argv[++i];
59
+ }
60
+ else if (arg === "--verbose" || arg === "-v") {
61
+ verbose = true;
62
+ }
63
+ else if (arg === "--no-helpers") {
64
+ helpers = false;
65
+ }
66
+ else if (arg === "--no-preserve-ids") {
67
+ preserveIds = false;
68
+ }
69
+ else if (arg === "--no-provenance") {
70
+ provenance = false;
71
+ }
72
+ }
73
+ if (!inputPath || !outputPath) {
74
+ console.error("Error: Both --in and --out options are required");
75
+ console.error("Usage: evalai migrate config --in <input> --out <output> [options]");
76
+ process.exit(1);
77
+ }
78
+ (0, migrate_1.migrateConfig)({
79
+ input: inputPath,
80
+ output: outputPath,
81
+ verbose,
82
+ helpers,
83
+ preserveIds,
84
+ provenance,
85
+ }).catch((err) => {
86
+ console.error(`Migration failed: ${err instanceof Error ? err.message : String(err)}`);
87
+ process.exit(1);
88
+ });
89
+ }
90
+ else {
91
+ console.error("Error: Unknown migrate subcommand. Use 'evalai migrate config'");
92
+ process.exit(1);
93
+ }
94
+ }
35
95
  else if (subcommand === "upgrade") {
36
96
  const code = (0, upgrade_1.runUpgrade)(argv.slice(1));
37
97
  process.exit(code);
@@ -82,11 +142,111 @@ else if (subcommand === "share") {
82
142
  process.exit(1);
83
143
  });
84
144
  }
145
+ else if (subcommand === "discover") {
146
+ // Parse arguments for discover command
147
+ const args = argv.slice(1);
148
+ const manifestFlag = args.includes("--manifest");
149
+ (0, discover_1.discoverSpecs)({ manifest: manifestFlag })
150
+ .then(() => process.exit(0))
151
+ .catch((err) => {
152
+ console.error(`EvalAI ERROR: ${err instanceof Error ? err.message : String(err)}`);
153
+ process.exit(1);
154
+ });
155
+ }
156
+ else if (subcommand === "impact-analysis") {
157
+ // Parse arguments for impact-analysis command
158
+ const args = argv.slice(1);
159
+ const baseIndex = args.indexOf("--base");
160
+ const changedFilesIndex = args.indexOf("--changed-files");
161
+ const formatIndex = args.indexOf("--format");
162
+ const baseBranch = baseIndex !== -1 ? args[baseIndex + 1] : "main";
163
+ const changedFiles = changedFilesIndex !== -1 ? args[changedFilesIndex + 1]?.split(",") : undefined;
164
+ const format = formatIndex !== -1 ? args[formatIndex + 1] : "human";
165
+ (0, impact_analysis_1.runImpactAnalysisCLI)({ baseBranch, changedFiles, format }).catch((err) => {
166
+ console.error(`EvalAI ERROR: ${err instanceof Error ? err.message : String(err)}`);
167
+ process.exit(2);
168
+ });
169
+ }
170
+ else if (subcommand === "run") {
171
+ // Parse arguments for run command
172
+ const args = argv.slice(1);
173
+ const specIdsIndex = args.indexOf("--spec-ids");
174
+ const impactedOnlyIndex = args.indexOf("--impacted-only");
175
+ const baseIndex = args.indexOf("--base");
176
+ const formatIndex = args.indexOf("--format");
177
+ const writeResultsIndex = args.indexOf("--write-results");
178
+ const specIds = specIdsIndex !== -1 ? args[specIdsIndex + 1]?.split(",") : undefined;
179
+ const impactedOnly = impactedOnlyIndex !== -1;
180
+ const baseBranch = baseIndex !== -1 ? args[baseIndex + 1] : undefined;
181
+ const format = formatIndex !== -1 ? args[formatIndex + 1] : "human";
182
+ const writeResults = writeResultsIndex !== -1;
183
+ (0, run_1.runEvaluationsCLI)({
184
+ specIds,
185
+ impactedOnly: impactedOnly ? !!baseBranch : false,
186
+ baseBranch,
187
+ format,
188
+ writeResults,
189
+ }).catch((err) => {
190
+ console.error(`EvalAI ERROR: ${err instanceof Error ? err.message : String(err)}`);
191
+ process.exit(2);
192
+ });
193
+ }
194
+ else if (subcommand === "diff") {
195
+ // Parse arguments for diff command
196
+ const args = argv.slice(1);
197
+ const baseIndex = args.indexOf("--base");
198
+ const headIndex = args.indexOf("--head");
199
+ const formatIndex = args.indexOf("--format");
200
+ const base = baseIndex !== -1 ? args[baseIndex + 1] : undefined;
201
+ const head = headIndex !== -1 ? args[headIndex + 1] : undefined;
202
+ const format = formatIndex !== -1 ? args[formatIndex + 1] : "human";
203
+ (0, diff_1.runDiffCLI)({ base, head, format }).catch((err) => {
204
+ console.error(`EvalAI ERROR: ${err instanceof Error ? err.message : String(err)}`);
205
+ process.exit(2);
206
+ });
207
+ }
208
+ else if (subcommand === "ci") {
209
+ // Parse arguments for ci command
210
+ const args = argv.slice(1);
211
+ const baseIndex = args.indexOf("--base");
212
+ const impactedOnlyIndex = args.indexOf("--impacted-only");
213
+ const formatIndex = args.indexOf("--format");
214
+ const writeResultsIndex = args.indexOf("--write-results");
215
+ const base = baseIndex !== -1 ? args[baseIndex + 1] : undefined;
216
+ const impactedOnly = impactedOnlyIndex !== -1;
217
+ const format = formatIndex !== -1 ? args[formatIndex + 1] : "human";
218
+ const writeResults = writeResultsIndex !== -1;
219
+ (0, ci_1.runCICLI)({ base, impactedOnly, format, writeResults }).catch((err) => {
220
+ console.error(`EvalAI ERROR: ${err instanceof Error ? err.message : String(err)}`);
221
+ process.exit(2);
222
+ });
223
+ }
85
224
  else {
86
225
  console.log(`EvalAI CLI
87
226
 
88
227
  Usage:
89
228
  evalai init Create evalai.config.json + baseline + CI workflow
229
+ evalai discover Discover behavioral specs in project and show statistics
230
+ evalai discover --manifest Generate evaluation manifest for incremental analysis
231
+ evalai impact-analysis Analyze impact of changes and suggest targeted tests
232
+ --base <branch> Base branch to compare against (default: main)
233
+ --changed-files <files> Comma-separated list of changed files (for CI)
234
+ --format <fmt> Output format: human (default), json
235
+ evalai ci One-command CI loop (manifest → impact → run → diff)
236
+ --base <ref> Base reference for diff (baseline|last|<runId>|<path>|<gitref>)
237
+ --impacted-only Run only specs impacted by changes
238
+ --format <fmt> Output format: human (default), json, github
239
+ --write-results Write run results to .evalai/last-run.json
240
+ evalai run Run evaluation specifications
241
+ --spec-ids <ids> Comma-separated list of spec IDs to run
242
+ --impacted-only Run only specs impacted by changes (requires --base)
243
+ --base <branch> Base branch for impact analysis (with --impacted-only)
244
+ --format <fmt> Output format: human (default), json
245
+ --write-results Write results to .evalai/last-run.json
246
+ evalai diff Compare two run reports and show behavioral changes
247
+ --base <branch> Base branch or report path (default: main)
248
+ --head <path> Head report path (default: .evalai/last-run.json)
249
+ --format <fmt> Output format: human (default), json
90
250
  evalai gate [options] Run regression gate (local test-based, no API needed)
91
251
  evalai check [options] CI/CD evaluation gate (API-based)
92
252
  evalai explain [options] Explain last gate/check failure with root causes + fixes
@@ -133,6 +293,19 @@ Options for doctor:
133
293
 
134
294
  Examples:
135
295
  evalai init
296
+ evalai discover
297
+ evalai discover --manifest
298
+ evalai impact-analysis --base main
299
+ evalai impact-analysis --base main --format json
300
+ evalai impact-analysis --changed-files src/utils.ts,datasets/test.json
301
+ evalai run
302
+ evalai run --spec-ids spec1,spec2
303
+ evalai run --impacted-only --base main
304
+ evalai run --format json --write-results
305
+ evalai diff
306
+ evalai diff --base main
307
+ evalai diff --base main --format json
308
+ evalai diff --a .evalai/runs/base.json --b .evalai/last-run.json
136
309
  evalai gate
137
310
  evalai gate --format json
138
311
  evalai explain
@@ -0,0 +1,105 @@
1
+ /**
2
+ * TICKET 2 — Evaluation Manifest Generation
3
+ *
4
+ * Goal: turn discovery output into a stable, versioned, machine-consumable artifact
5
+ * that becomes the input to run / impact / diff.
6
+ *
7
+ * This is the compiler output that everything else consumes.
8
+ */
9
+ import type { SpecAnalysis } from "./discover";
10
+ import type { ExecutionModeConfig } from "../runtime/execution-mode";
11
+ /**
12
+ * Manifest schema version
13
+ */
14
+ export declare const MANIFEST_SCHEMA_VERSION = 1;
15
+ /**
16
+ * SDK version from package.json
17
+ */
18
+ export declare const SDK_VERSION = "1.8.0";
19
+ /**
20
+ * Evaluation Manifest Schema
21
+ */
22
+ export interface EvaluationManifest {
23
+ /** Schema version for compatibility */
24
+ schemaVersion: number;
25
+ /** When this manifest was generated */
26
+ generatedAt: number;
27
+ /** Project metadata */
28
+ project: {
29
+ name: string;
30
+ root: string;
31
+ namespace: string;
32
+ };
33
+ /** Runtime information */
34
+ runtime: {
35
+ mode: "spec" | "legacy";
36
+ sdkVersion: string;
37
+ };
38
+ /** Spec files with hashes */
39
+ specFiles: SpecFile[];
40
+ /** Individual specifications */
41
+ specs: Spec[];
42
+ }
43
+ /**
44
+ * Spec file information
45
+ */
46
+ export interface SpecFile {
47
+ /** POSIX-relative file path */
48
+ filePath: string;
49
+ /** SHA-256 hash of file content */
50
+ fileHash: string;
51
+ /** Number of specs in this file */
52
+ specCount: number;
53
+ }
54
+ /**
55
+ * Individual specification
56
+ */
57
+ export interface Spec {
58
+ /** Stable canonical ID */
59
+ id: string;
60
+ /** Spec name */
61
+ name: string;
62
+ /** Suite path from tags or file structure */
63
+ suitePath: string[];
64
+ /** POSIX-relative file path */
65
+ filePath: string;
66
+ /** Position in file */
67
+ position: {
68
+ line: number;
69
+ column: number;
70
+ };
71
+ /** Tags/categories */
72
+ tags: string[];
73
+ /** Dependencies */
74
+ dependsOn: {
75
+ prompts: string[];
76
+ datasets: string[];
77
+ tools: string[];
78
+ code: string[];
79
+ };
80
+ }
81
+ /**
82
+ * Lock file for caching
83
+ */
84
+ export interface ManifestLock {
85
+ /** When lock was generated */
86
+ generatedAt: number;
87
+ /** File hashes for incremental updates */
88
+ fileHashes: Record<string, string>;
89
+ }
90
+ /**
91
+ * Generate evaluation manifest from discovery results
92
+ */
93
+ export declare function generateManifest(specs: SpecAnalysis[], projectRoot: string, projectName: string, executionMode: ExecutionModeConfig): Promise<EvaluationManifest>;
94
+ /**
95
+ * Write manifest to disk
96
+ */
97
+ export declare function writeManifest(manifest: EvaluationManifest, projectRoot: string): Promise<void>;
98
+ /**
99
+ * Read existing manifest
100
+ */
101
+ export declare function readManifest(projectRoot: string): Promise<EvaluationManifest | null>;
102
+ /**
103
+ * Read existing lock file
104
+ */
105
+ export declare function readLock(projectRoot: string): Promise<ManifestLock | null>;
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ /**
3
+ * TICKET 2 — Evaluation Manifest Generation
4
+ *
5
+ * Goal: turn discovery output into a stable, versioned, machine-consumable artifact
6
+ * that becomes the input to run / impact / diff.
7
+ *
8
+ * This is the compiler output that everything else consumes.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.SDK_VERSION = exports.MANIFEST_SCHEMA_VERSION = void 0;
45
+ exports.generateManifest = generateManifest;
46
+ exports.writeManifest = writeManifest;
47
+ exports.readManifest = readManifest;
48
+ exports.readLock = readLock;
49
+ const fs = __importStar(require("node:fs/promises"));
50
+ const path = __importStar(require("node:path"));
51
+ const crypto = __importStar(require("node:crypto"));
52
+ /**
53
+ * Manifest schema version
54
+ */
55
+ exports.MANIFEST_SCHEMA_VERSION = 1;
56
+ /**
57
+ * SDK version from package.json
58
+ */
59
+ exports.SDK_VERSION = "1.8.0";
60
+ /**
61
+ * Generate evaluation manifest from discovery results
62
+ */
63
+ async function generateManifest(specs, projectRoot, projectName, executionMode) {
64
+ const generatedAt = Math.floor(Date.now() / 1000);
65
+ const namespace = generateNamespace(projectRoot);
66
+ // Process spec files and specs
67
+ const specFiles = [];
68
+ const processedSpecs = [];
69
+ // Group specs by file
70
+ const specsByFile = new Map();
71
+ for (const spec of specs) {
72
+ const normalizedPath = normalizePath(spec.file, projectRoot);
73
+ if (!specsByFile.has(normalizedPath)) {
74
+ specsByFile.set(normalizedPath, []);
75
+ }
76
+ specsByFile.get(normalizedPath).push(spec);
77
+ }
78
+ // Process each file
79
+ for (const [filePath, fileSpecs] of specsByFile) {
80
+ const absolutePath = path.join(projectRoot, filePath);
81
+ const fileHash = await hashFile(absolutePath);
82
+ specFiles.push({
83
+ filePath,
84
+ fileHash,
85
+ specCount: fileSpecs.length,
86
+ });
87
+ // Process individual specs
88
+ for (const spec of fileSpecs) {
89
+ const processedSpec = await processSpec(spec, filePath, projectRoot);
90
+ processedSpecs.push(processedSpec);
91
+ }
92
+ }
93
+ return {
94
+ schemaVersion: exports.MANIFEST_SCHEMA_VERSION,
95
+ generatedAt,
96
+ project: {
97
+ name: projectName,
98
+ root: ".",
99
+ namespace,
100
+ },
101
+ runtime: {
102
+ mode: executionMode.mode,
103
+ sdkVersion: exports.SDK_VERSION,
104
+ },
105
+ specFiles,
106
+ specs: processedSpecs,
107
+ };
108
+ }
109
+ /**
110
+ * Process individual specification
111
+ */
112
+ async function processSpec(spec, filePath, projectRoot) {
113
+ const absolutePath = path.join(projectRoot, filePath);
114
+ const content = await fs.readFile(absolutePath, "utf-8");
115
+ // Extract position from AST analysis (simplified for now)
116
+ const position = extractPosition(content, spec.name);
117
+ // Extract dependencies from content
118
+ const dependsOn = extractDependencies(content);
119
+ // Generate suite path from tags or file structure
120
+ const suitePath = generateSuitePath(spec.tags, filePath);
121
+ return {
122
+ id: spec.id,
123
+ name: spec.name,
124
+ suitePath,
125
+ filePath: normalizePath(spec.file, projectRoot),
126
+ position,
127
+ tags: spec.tags,
128
+ dependsOn,
129
+ };
130
+ }
131
+ /**
132
+ * Extract position from content (simplified implementation)
133
+ */
134
+ function extractPosition(content, specName) {
135
+ const lines = content.split("\n");
136
+ const specPattern = new RegExp(`defineEval\\s*\\(\\s*["'\`]${specName}["'\`]`, "g");
137
+ let match = null;
138
+ let line = 1;
139
+ let column = 1;
140
+ for (let i = 0; i < lines.length; i++) {
141
+ const lineContent = lines[i];
142
+ specPattern.lastIndex = 0;
143
+ match = specPattern.exec(lineContent);
144
+ if (match) {
145
+ line = i + 1;
146
+ column = match.index + 1;
147
+ break;
148
+ }
149
+ }
150
+ return { line, column };
151
+ }
152
+ /**
153
+ * Extract dependencies from content
154
+ */
155
+ function extractDependencies(content) {
156
+ const dependsOn = {
157
+ prompts: [],
158
+ datasets: [],
159
+ tools: [],
160
+ code: [],
161
+ };
162
+ // Extract from dependsOn option if present
163
+ const dependsOnMatch = content.match(/dependsOn\s*:\s*({[^}]+})/s);
164
+ if (dependsOnMatch) {
165
+ try {
166
+ const deps = eval(`(${dependsOnMatch[1]})`);
167
+ return {
168
+ prompts: deps.prompts || [],
169
+ datasets: deps.datasets || [],
170
+ tools: deps.tools || [],
171
+ code: deps.code || [],
172
+ };
173
+ }
174
+ catch (error) {
175
+ // Fall back to simple extraction
176
+ }
177
+ }
178
+ // Simple extraction as fallback
179
+ const patterns = {
180
+ prompts: /["']([^"']*\.md)["']/g,
181
+ datasets: /["']([^"']*\.json)["']/g,
182
+ tools: /["']([^"']*\.ts)["']/g,
183
+ code: /import.*from\s*["']([^"']+)["']/g,
184
+ };
185
+ for (const [type, pattern] of Object.entries(patterns)) {
186
+ let match;
187
+ while ((match = pattern.exec(content)) !== null) {
188
+ dependsOn[type].push(match[1]);
189
+ }
190
+ }
191
+ return dependsOn;
192
+ }
193
+ /**
194
+ * Generate suite path from tags or file structure
195
+ */
196
+ function generateSuitePath(tags, filePath) {
197
+ // Use tags as primary suite path
198
+ if (tags.length > 0) {
199
+ return [tags[0]];
200
+ }
201
+ // Fall back to file structure
202
+ const parts = filePath.split("/");
203
+ if (parts.length > 1) {
204
+ return [parts[0]];
205
+ }
206
+ return ["general"];
207
+ }
208
+ /**
209
+ * Generate namespace from project root
210
+ */
211
+ function generateNamespace(projectRoot) {
212
+ const hash = crypto.createHash("sha256");
213
+ hash.update(projectRoot);
214
+ return hash.digest("hex").slice(0, 8);
215
+ }
216
+ /**
217
+ * Normalize path to POSIX format
218
+ */
219
+ function normalizePath(filePath, projectRoot) {
220
+ const relativePath = path.relative(projectRoot, filePath);
221
+ return relativePath.replace(/\\/g, "/");
222
+ }
223
+ /**
224
+ * Hash file content
225
+ */
226
+ async function hashFile(filePath) {
227
+ const content = await fs.readFile(filePath, "utf-8");
228
+ const hash = crypto.createHash("sha256");
229
+ hash.update(content);
230
+ return `sha256:${hash.digest("hex")}`;
231
+ }
232
+ /**
233
+ * Write manifest to disk
234
+ */
235
+ async function writeManifest(manifest, projectRoot) {
236
+ const evalaiDir = path.join(projectRoot, ".evalai");
237
+ // Ensure .evalai directory exists
238
+ await fs.mkdir(evalaiDir, { recursive: true });
239
+ // Write manifest.json
240
+ const manifestPath = path.join(evalaiDir, "manifest.json");
241
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
242
+ // Write lock file
243
+ const lock = {
244
+ generatedAt: manifest.generatedAt,
245
+ fileHashes: Object.fromEntries(manifest.specFiles.map((f) => [f.filePath, f.fileHash])),
246
+ };
247
+ const lockPath = path.join(evalaiDir, "manifest.lock.json");
248
+ await fs.writeFile(lockPath, JSON.stringify(lock, null, 2), "utf-8");
249
+ }
250
+ /**
251
+ * Read existing manifest
252
+ */
253
+ async function readManifest(projectRoot) {
254
+ const manifestPath = path.join(projectRoot, ".evalai", "manifest.json");
255
+ try {
256
+ const content = await fs.readFile(manifestPath, "utf-8");
257
+ return JSON.parse(content);
258
+ }
259
+ catch (error) {
260
+ return null;
261
+ }
262
+ }
263
+ /**
264
+ * Read existing lock file
265
+ */
266
+ async function readLock(projectRoot) {
267
+ const lockPath = path.join(projectRoot, ".evalai", "manifest.lock.json");
268
+ try {
269
+ const content = await fs.readFile(lockPath, "utf-8");
270
+ return JSON.parse(content);
271
+ }
272
+ catch (error) {
273
+ return null;
274
+ }
275
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * COMPAT-203: Config → DSL migration generator (file-based)
3
+ *
4
+ * CLI command: evalai migrate config --in evalai.config.json --out eval/legacy.spec.ts
5
+ * Generates defineEval() calls with comments and TODOs for manual completion
6
+ */
7
+ import { Command } from "commander";
8
+ /**
9
+ * Migration options
10
+ */
11
+ interface MigrateOptions {
12
+ /** Input config file path */
13
+ input: string;
14
+ /** Output DSL file path */
15
+ output: string;
16
+ /** Include detailed comments */
17
+ verbose?: boolean;
18
+ /** Generate helper functions */
19
+ helpers?: boolean;
20
+ /** Preserve original test IDs */
21
+ preserveIds?: boolean;
22
+ /** Include provenance metadata */
23
+ provenance?: boolean;
24
+ }
25
+ /**
26
+ * Main migration function
27
+ */
28
+ export declare function migrateConfig(options: MigrateOptions): Promise<void>;
29
+ /**
30
+ * CLI command definition
31
+ */
32
+ export declare function createMigrateCommand(): Command;
33
+ /**
34
+ * Validate config file structure
35
+ */
36
+ export declare function validateConfigFile(filePath: string): Promise<boolean>;
37
+ /**
38
+ * Show migration preview without writing files
39
+ */
40
+ export declare function previewMigration(filePath: string): Promise<void>;
41
+ export {};