@evalgate/sdk 2.0.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/CHANGELOG.md +638 -0
- package/README.md +398 -0
- package/dist/assertions.d.ts +189 -0
- package/dist/assertions.js +662 -0
- package/dist/batch.d.ts +68 -0
- package/dist/batch.js +179 -0
- package/dist/cache.d.ts +65 -0
- package/dist/cache.js +131 -0
- package/dist/cli/api.d.ts +108 -0
- package/dist/cli/api.js +132 -0
- package/dist/cli/baseline.d.ts +10 -0
- package/dist/cli/baseline.js +172 -0
- package/dist/cli/check.d.ts +73 -0
- package/dist/cli/check.js +355 -0
- package/dist/cli/ci-context.d.ts +6 -0
- package/dist/cli/ci-context.js +112 -0
- package/dist/cli/ci.d.ts +45 -0
- package/dist/cli/ci.js +192 -0
- package/dist/cli/config.d.ts +30 -0
- package/dist/cli/config.js +230 -0
- package/dist/cli/constants.d.ts +15 -0
- package/dist/cli/constants.js +18 -0
- package/dist/cli/diff.d.ts +173 -0
- package/dist/cli/diff.js +685 -0
- package/dist/cli/discover.d.ts +84 -0
- package/dist/cli/discover.js +419 -0
- package/dist/cli/doctor.d.ts +88 -0
- package/dist/cli/doctor.js +675 -0
- package/dist/cli/env.d.ts +21 -0
- package/dist/cli/env.js +42 -0
- package/dist/cli/explain.d.ts +58 -0
- package/dist/cli/explain.js +561 -0
- package/dist/cli/formatters/github.d.ts +8 -0
- package/dist/cli/formatters/github.js +135 -0
- package/dist/cli/formatters/human.d.ts +6 -0
- package/dist/cli/formatters/human.js +110 -0
- package/dist/cli/formatters/json.d.ts +6 -0
- package/dist/cli/formatters/json.js +10 -0
- package/dist/cli/formatters/pr-comment.d.ts +12 -0
- package/dist/cli/formatters/pr-comment.js +103 -0
- package/dist/cli/formatters/types.d.ts +103 -0
- package/dist/cli/formatters/types.js +8 -0
- package/dist/cli/gate.d.ts +21 -0
- package/dist/cli/gate.js +179 -0
- package/dist/cli/impact-analysis.d.ts +63 -0
- package/dist/cli/impact-analysis.js +252 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +332 -0
- package/dist/cli/init.d.ts +16 -0
- package/dist/cli/init.js +292 -0
- package/dist/cli/manifest.d.ts +103 -0
- package/dist/cli/manifest.js +282 -0
- package/dist/cli/migrate.d.ts +41 -0
- package/dist/cli/migrate.js +349 -0
- package/dist/cli/policy-packs.d.ts +23 -0
- package/dist/cli/policy-packs.js +89 -0
- package/dist/cli/print-config.d.ts +29 -0
- package/dist/cli/print-config.js +270 -0
- package/dist/cli/profiles.d.ts +28 -0
- package/dist/cli/profiles.js +30 -0
- package/dist/cli/reason-codes.d.ts +17 -0
- package/dist/cli/reason-codes.js +19 -0
- package/dist/cli/regression-gate.d.ts +15 -0
- package/dist/cli/regression-gate.js +341 -0
- package/dist/cli/render/snippet.d.ts +5 -0
- package/dist/cli/render/snippet.js +15 -0
- package/dist/cli/render/sort.d.ts +10 -0
- package/dist/cli/render/sort.js +24 -0
- package/dist/cli/report/build-check-report.d.ts +19 -0
- package/dist/cli/report/build-check-report.js +132 -0
- package/dist/cli/run.d.ts +101 -0
- package/dist/cli/run.js +395 -0
- package/dist/cli/share.d.ts +17 -0
- package/dist/cli/share.js +91 -0
- package/dist/cli/upgrade.d.ts +15 -0
- package/dist/cli/upgrade.js +492 -0
- package/dist/cli/workspace.d.ts +31 -0
- package/dist/cli/workspace.js +68 -0
- package/dist/client.d.ts +368 -0
- package/dist/client.js +893 -0
- package/dist/client.request.test.d.ts +1 -0
- package/dist/client.request.test.js +232 -0
- package/dist/context.d.ts +134 -0
- package/dist/context.js +215 -0
- package/dist/errors.d.ts +82 -0
- package/dist/errors.js +298 -0
- package/dist/export.d.ts +195 -0
- package/dist/export.js +344 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +153 -0
- package/dist/integrations/anthropic.d.ts +91 -0
- package/dist/integrations/anthropic.js +163 -0
- package/dist/integrations/openai-eval.d.ts +57 -0
- package/dist/integrations/openai-eval.js +232 -0
- package/dist/integrations/openai.d.ts +92 -0
- package/dist/integrations/openai.js +160 -0
- package/dist/local.d.ts +39 -0
- package/dist/local.js +148 -0
- package/dist/logger.d.ts +128 -0
- package/dist/logger.js +227 -0
- package/dist/matchers/index.d.ts +1 -0
- package/dist/matchers/index.js +6 -0
- package/dist/matchers/to-pass-gate.d.ts +29 -0
- package/dist/matchers/to-pass-gate.js +35 -0
- package/dist/pagination.d.ts +74 -0
- package/dist/pagination.js +139 -0
- package/dist/regression.d.ts +100 -0
- package/dist/regression.js +44 -0
- package/dist/runtime/adapters/config-to-dsl.d.ts +33 -0
- package/dist/runtime/adapters/config-to-dsl.js +400 -0
- package/dist/runtime/adapters/testsuite-to-dsl.d.ts +63 -0
- package/dist/runtime/adapters/testsuite-to-dsl.js +276 -0
- package/dist/runtime/context.d.ts +26 -0
- package/dist/runtime/context.js +74 -0
- package/dist/runtime/eval.d.ts +46 -0
- package/dist/runtime/eval.js +244 -0
- package/dist/runtime/execution-mode.d.ts +80 -0
- package/dist/runtime/execution-mode.js +357 -0
- package/dist/runtime/executor.d.ts +16 -0
- package/dist/runtime/executor.js +152 -0
- package/dist/runtime/registry.d.ts +78 -0
- package/dist/runtime/registry.js +403 -0
- package/dist/runtime/run-report.d.ts +200 -0
- package/dist/runtime/run-report.js +222 -0
- package/dist/runtime/types.d.ts +356 -0
- package/dist/runtime/types.js +76 -0
- package/dist/snapshot.d.ts +176 -0
- package/dist/snapshot.js +322 -0
- package/dist/streaming.d.ts +173 -0
- package/dist/streaming.js +268 -0
- package/dist/testing.d.ts +273 -0
- package/dist/testing.js +317 -0
- package/dist/types.d.ts +754 -0
- package/dist/types.js +54 -0
- package/dist/utils/input-hash.d.ts +8 -0
- package/dist/utils/input-hash.js +41 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.js +10 -0
- package/dist/workflows.d.ts +389 -0
- package/dist/workflows.js +671 -0
- package/package.json +117 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* evalgate doctor — Comprehensive CI/CD readiness checklist.
|
|
4
|
+
*
|
|
5
|
+
* Runs itemized pass/fail checks with exact remediation commands.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* 0 — All checks passed (ready)
|
|
9
|
+
* 2 — One or more checks failed (not ready)
|
|
10
|
+
* 3 — Infrastructure error (couldn't complete checks)
|
|
11
|
+
*
|
|
12
|
+
* Flags:
|
|
13
|
+
* --report Output JSON diagnostic bundle (redacted)
|
|
14
|
+
* --format <fmt> Output format: human (default), json
|
|
15
|
+
* --apiKey <key> API key (or EVALGATE_API_KEY env)
|
|
16
|
+
* --baseUrl <url> API base URL
|
|
17
|
+
* --evaluationId <id> Evaluation to verify
|
|
18
|
+
* --baseline <mode> Baseline mode
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.DOCTOR_EXIT = void 0;
|
|
55
|
+
exports.checkProject = checkProject;
|
|
56
|
+
exports.checkConfig = checkConfig;
|
|
57
|
+
exports.checkBaseline = checkBaseline;
|
|
58
|
+
exports.checkAuth = checkAuth;
|
|
59
|
+
exports.checkConnectivity = checkConnectivity;
|
|
60
|
+
exports.checkEvalTarget = checkEvalTarget;
|
|
61
|
+
exports.checkEvalAccess = checkEvalAccess;
|
|
62
|
+
exports.checkCiWiring = checkCiWiring;
|
|
63
|
+
exports.checkProviderEnv = checkProviderEnv;
|
|
64
|
+
exports.runDoctor = runDoctor;
|
|
65
|
+
const node_crypto_1 = require("node:crypto");
|
|
66
|
+
const fs = __importStar(require("node:fs"));
|
|
67
|
+
const path = __importStar(require("node:path"));
|
|
68
|
+
const version_1 = require("../version");
|
|
69
|
+
const api_1 = require("./api");
|
|
70
|
+
const config_1 = require("./config");
|
|
71
|
+
// ── Exit codes ──
|
|
72
|
+
exports.DOCTOR_EXIT = {
|
|
73
|
+
READY: 0,
|
|
74
|
+
NOT_READY: 2,
|
|
75
|
+
INFRA_ERROR: 3,
|
|
76
|
+
};
|
|
77
|
+
// ── Arg parsing ──
|
|
78
|
+
function parseFlags(argv) {
|
|
79
|
+
const raw = {};
|
|
80
|
+
for (let i = 0; i < argv.length; i++) {
|
|
81
|
+
const arg = argv[i];
|
|
82
|
+
if (arg.startsWith("--")) {
|
|
83
|
+
const key = arg.slice(2);
|
|
84
|
+
const next = argv[i + 1];
|
|
85
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
86
|
+
raw[key] = next;
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
raw[key] = "true";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const report = raw.report === "true" || raw.report === "1";
|
|
95
|
+
const fmt = raw.format === "json" ? "json" : "human";
|
|
96
|
+
const baseUrl = raw.baseUrl ||
|
|
97
|
+
process.env.EVALGATE_BASE_URL ||
|
|
98
|
+
process.env.EVALAI_BASE_URL ||
|
|
99
|
+
"http://localhost:3000";
|
|
100
|
+
const apiKey = raw.apiKey ||
|
|
101
|
+
process.env.EVALGATE_API_KEY ||
|
|
102
|
+
process.env.EVALAI_API_KEY ||
|
|
103
|
+
"";
|
|
104
|
+
let evaluationId = raw.evaluationId || "";
|
|
105
|
+
const baseline = (raw.baseline === "previous"
|
|
106
|
+
? "previous"
|
|
107
|
+
: raw.baseline === "production"
|
|
108
|
+
? "production"
|
|
109
|
+
: "published");
|
|
110
|
+
// Try to fill evaluationId from config
|
|
111
|
+
if (!evaluationId) {
|
|
112
|
+
const config = (0, config_1.loadConfig)(process.cwd());
|
|
113
|
+
const merged = (0, config_1.mergeConfigWithArgs)(config, {
|
|
114
|
+
evaluationId: raw.evaluationId,
|
|
115
|
+
baseUrl: raw.baseUrl ||
|
|
116
|
+
process.env.EVALGATE_BASE_URL ||
|
|
117
|
+
process.env.EVALAI_BASE_URL,
|
|
118
|
+
baseline: raw.baseline,
|
|
119
|
+
});
|
|
120
|
+
if (merged.evaluationId)
|
|
121
|
+
evaluationId = String(merged.evaluationId);
|
|
122
|
+
}
|
|
123
|
+
const strict = raw.strict === "true" || raw.strict === "1";
|
|
124
|
+
return {
|
|
125
|
+
report,
|
|
126
|
+
format: report ? "json" : fmt,
|
|
127
|
+
strict,
|
|
128
|
+
baseUrl,
|
|
129
|
+
apiKey,
|
|
130
|
+
evaluationId,
|
|
131
|
+
baseline,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// ── Individual checks ──
|
|
135
|
+
function checkProject(cwd) {
|
|
136
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
137
|
+
if (!fs.existsSync(pkgPath)) {
|
|
138
|
+
return {
|
|
139
|
+
id: "project",
|
|
140
|
+
label: "Project detection",
|
|
141
|
+
status: "fail",
|
|
142
|
+
message: "No package.json found",
|
|
143
|
+
remediation: "Run this command from your project root, or run: npm init -y",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
let pm = "npm";
|
|
147
|
+
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml")))
|
|
148
|
+
pm = "pnpm";
|
|
149
|
+
else if (fs.existsSync(path.join(cwd, "yarn.lock")))
|
|
150
|
+
pm = "yarn";
|
|
151
|
+
const hasLockfile = fs.existsSync(path.join(cwd, "package-lock.json")) ||
|
|
152
|
+
fs.existsSync(path.join(cwd, "pnpm-lock.yaml")) ||
|
|
153
|
+
fs.existsSync(path.join(cwd, "yarn.lock"));
|
|
154
|
+
if (!hasLockfile) {
|
|
155
|
+
return {
|
|
156
|
+
id: "project",
|
|
157
|
+
label: "Project detection",
|
|
158
|
+
status: "warn",
|
|
159
|
+
message: `${pm} project detected but no lockfile found`,
|
|
160
|
+
remediation: `Run: ${pm} install`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
id: "project",
|
|
165
|
+
label: "Project detection",
|
|
166
|
+
status: "pass",
|
|
167
|
+
message: `${pm} project with lockfile`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function checkConfig(cwd) {
|
|
171
|
+
const configPath = (0, config_1.findConfigPath)(cwd);
|
|
172
|
+
if (!configPath) {
|
|
173
|
+
return {
|
|
174
|
+
id: "config",
|
|
175
|
+
label: "Config file",
|
|
176
|
+
status: "fail",
|
|
177
|
+
message: "No evalgate.config.json (or evalai.config.json) found",
|
|
178
|
+
remediation: "Run: npx evalgate init",
|
|
179
|
+
config: null,
|
|
180
|
+
configPath: null,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const config = (0, config_1.loadConfig)(cwd);
|
|
184
|
+
if (!config) {
|
|
185
|
+
return {
|
|
186
|
+
id: "config",
|
|
187
|
+
label: "Config file",
|
|
188
|
+
status: "fail",
|
|
189
|
+
message: `Config at ${path.relative(cwd, configPath)} is invalid JSON`,
|
|
190
|
+
remediation: "Fix JSON syntax in your config file, or delete it and run: npx evalgate init",
|
|
191
|
+
config: null,
|
|
192
|
+
configPath,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// Check required fields — gate config lives in the raw JSON but EvalAIConfig only exposes typed fields
|
|
196
|
+
if (!config.evaluationId && !config.baseline) {
|
|
197
|
+
return {
|
|
198
|
+
id: "config",
|
|
199
|
+
label: "Config file",
|
|
200
|
+
status: "warn",
|
|
201
|
+
message: `Config at ${path.relative(cwd, configPath)} has no evaluationId or gate section`,
|
|
202
|
+
remediation: "Add evaluationId to evalgate.config.json (from the dashboard) or ensure gate.baseline is set",
|
|
203
|
+
config,
|
|
204
|
+
configPath,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
id: "config",
|
|
209
|
+
label: "Config file",
|
|
210
|
+
status: "pass",
|
|
211
|
+
message: `Loaded ${path.relative(cwd, configPath)}`,
|
|
212
|
+
config,
|
|
213
|
+
configPath,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function checkBaseline(cwd) {
|
|
217
|
+
const baselinePath = path.join(cwd, "evals", "baseline.json");
|
|
218
|
+
if (!fs.existsSync(baselinePath)) {
|
|
219
|
+
return {
|
|
220
|
+
id: "baseline",
|
|
221
|
+
label: "Baseline file",
|
|
222
|
+
status: "fail",
|
|
223
|
+
message: "evals/baseline.json not found",
|
|
224
|
+
remediation: "Run: npx evalgate init (or: npx evalgate baseline init)",
|
|
225
|
+
baselineInfo: { path: "evals/baseline.json", exists: false },
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
let data;
|
|
229
|
+
try {
|
|
230
|
+
data = JSON.parse(fs.readFileSync(baselinePath, "utf-8"));
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return {
|
|
234
|
+
id: "baseline",
|
|
235
|
+
label: "Baseline file",
|
|
236
|
+
status: "fail",
|
|
237
|
+
message: "evals/baseline.json is not valid JSON",
|
|
238
|
+
remediation: "Delete evals/baseline.json and run: npx evalgate baseline init",
|
|
239
|
+
baselineInfo: { path: "evals/baseline.json", exists: true },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const schemaVersion = typeof data.schemaVersion === "number" ? data.schemaVersion : undefined;
|
|
243
|
+
const hash = (0, node_crypto_1.createHash)("sha256")
|
|
244
|
+
.update(JSON.stringify(data))
|
|
245
|
+
.digest("hex")
|
|
246
|
+
.slice(0, 12);
|
|
247
|
+
const updatedAt = typeof data.updatedAt === "string" ? data.updatedAt : undefined;
|
|
248
|
+
// Staleness: warn if baseline older than 30 days
|
|
249
|
+
let stale = false;
|
|
250
|
+
if (updatedAt) {
|
|
251
|
+
const age = Date.now() - new Date(updatedAt).getTime();
|
|
252
|
+
stale = age > 30 * 24 * 60 * 60 * 1000;
|
|
253
|
+
}
|
|
254
|
+
if (schemaVersion !== 1) {
|
|
255
|
+
return {
|
|
256
|
+
id: "baseline",
|
|
257
|
+
label: "Baseline file",
|
|
258
|
+
status: "fail",
|
|
259
|
+
message: `Unsupported baseline schemaVersion: ${schemaVersion ?? "missing"}`,
|
|
260
|
+
remediation: "Run: npx evalgate baseline init (creates schemaVersion 1)",
|
|
261
|
+
baselineInfo: {
|
|
262
|
+
path: "evals/baseline.json",
|
|
263
|
+
exists: true,
|
|
264
|
+
hash,
|
|
265
|
+
schemaVersion,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (stale) {
|
|
270
|
+
return {
|
|
271
|
+
id: "baseline",
|
|
272
|
+
label: "Baseline file",
|
|
273
|
+
status: "warn",
|
|
274
|
+
message: `Baseline is stale (last updated ${updatedAt})`,
|
|
275
|
+
remediation: "Run: npx evalgate baseline update",
|
|
276
|
+
baselineInfo: {
|
|
277
|
+
path: "evals/baseline.json",
|
|
278
|
+
exists: true,
|
|
279
|
+
hash,
|
|
280
|
+
schemaVersion,
|
|
281
|
+
stale,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
id: "baseline",
|
|
287
|
+
label: "Baseline file",
|
|
288
|
+
status: "pass",
|
|
289
|
+
message: `schemaVersion ${schemaVersion}, hash ${hash}`,
|
|
290
|
+
baselineInfo: {
|
|
291
|
+
path: "evals/baseline.json",
|
|
292
|
+
exists: true,
|
|
293
|
+
hash,
|
|
294
|
+
schemaVersion,
|
|
295
|
+
stale,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function checkAuth(apiKey) {
|
|
300
|
+
if (!apiKey) {
|
|
301
|
+
return {
|
|
302
|
+
id: "auth",
|
|
303
|
+
label: "Authentication",
|
|
304
|
+
status: "fail",
|
|
305
|
+
message: "No API key found",
|
|
306
|
+
remediation: "Set EVALGATE_API_KEY environment variable, or pass --apiKey <key>",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// Redact key for display
|
|
310
|
+
const redacted = apiKey.length > 8 ? `${apiKey.slice(0, 4)}...${apiKey.slice(-4)}` : "****";
|
|
311
|
+
return {
|
|
312
|
+
id: "auth",
|
|
313
|
+
label: "Authentication",
|
|
314
|
+
status: "pass",
|
|
315
|
+
message: `API key present (${redacted})`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
async function checkConnectivity(baseUrl, apiKey) {
|
|
319
|
+
const url = `${baseUrl.replace(/\/$/, "")}/api/mcp/tools`;
|
|
320
|
+
const t0 = Date.now();
|
|
321
|
+
try {
|
|
322
|
+
const res = await fetch(url, {
|
|
323
|
+
headers: {
|
|
324
|
+
"X-EvalGate-SDK-Version": version_1.SDK_VERSION,
|
|
325
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
326
|
+
},
|
|
327
|
+
signal: AbortSignal.timeout(10000),
|
|
328
|
+
});
|
|
329
|
+
const latencyMs = Date.now() - t0;
|
|
330
|
+
if (!res.ok) {
|
|
331
|
+
return {
|
|
332
|
+
id: "connectivity",
|
|
333
|
+
label: "API connectivity",
|
|
334
|
+
status: "fail",
|
|
335
|
+
message: `${baseUrl} returned ${res.status}`,
|
|
336
|
+
remediation: res.status === 401
|
|
337
|
+
? "Check your API key is valid"
|
|
338
|
+
: `Verify EVALGATE_BASE_URL is correct (currently: ${baseUrl})`,
|
|
339
|
+
latencyMs,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (latencyMs > 5000) {
|
|
343
|
+
return {
|
|
344
|
+
id: "connectivity",
|
|
345
|
+
label: "API connectivity",
|
|
346
|
+
status: "warn",
|
|
347
|
+
message: `${baseUrl} reachable but slow (${latencyMs}ms)`,
|
|
348
|
+
latencyMs,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
id: "connectivity",
|
|
353
|
+
label: "API connectivity",
|
|
354
|
+
status: "pass",
|
|
355
|
+
message: `${baseUrl} reachable (${latencyMs}ms)`,
|
|
356
|
+
latencyMs,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
return {
|
|
361
|
+
id: "connectivity",
|
|
362
|
+
label: "API connectivity",
|
|
363
|
+
status: "fail",
|
|
364
|
+
message: `Cannot reach ${baseUrl}: ${err instanceof Error ? err.message : String(err)}`,
|
|
365
|
+
remediation: `Verify EVALGATE_BASE_URL is correct and the server is running`,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function checkEvalTarget(evaluationId) {
|
|
370
|
+
if (!evaluationId) {
|
|
371
|
+
return {
|
|
372
|
+
id: "eval_target",
|
|
373
|
+
label: "Evaluation target",
|
|
374
|
+
status: "fail",
|
|
375
|
+
message: "No evaluationId configured",
|
|
376
|
+
remediation: "Add evaluationId to evalgate.config.json, or pass --evaluationId <id>",
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
id: "eval_target",
|
|
381
|
+
label: "Evaluation target",
|
|
382
|
+
status: "pass",
|
|
383
|
+
message: `evaluationId: ${evaluationId}`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
async function checkEvalAccess(baseUrl, apiKey, evaluationId, baseline) {
|
|
387
|
+
if (!apiKey || !evaluationId) {
|
|
388
|
+
return {
|
|
389
|
+
id: "eval_access",
|
|
390
|
+
label: "Evaluation access",
|
|
391
|
+
status: "skip",
|
|
392
|
+
message: "Skipped (missing apiKey or evaluationId)",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const result = await (0, api_1.fetchQualityLatest)(baseUrl, apiKey, evaluationId, baseline);
|
|
396
|
+
if (!result.ok) {
|
|
397
|
+
if (result.status === 0) {
|
|
398
|
+
return {
|
|
399
|
+
id: "eval_access",
|
|
400
|
+
label: "Evaluation access",
|
|
401
|
+
status: "fail",
|
|
402
|
+
message: `Network error reaching quality endpoint`,
|
|
403
|
+
remediation: `Check connectivity and API key`,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (result.status === 401 || result.status === 403) {
|
|
407
|
+
return {
|
|
408
|
+
id: "eval_access",
|
|
409
|
+
label: "Evaluation access",
|
|
410
|
+
status: "fail",
|
|
411
|
+
message: `Access denied (${result.status}) for evaluationId ${evaluationId}`,
|
|
412
|
+
remediation: "Verify your API key has eval:read and runs:read scopes",
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (result.status === 404) {
|
|
416
|
+
return {
|
|
417
|
+
id: "eval_access",
|
|
418
|
+
label: "Evaluation access",
|
|
419
|
+
status: "fail",
|
|
420
|
+
message: `Evaluation ${evaluationId} not found`,
|
|
421
|
+
remediation: "Check the evaluationId in your config matches an existing evaluation",
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
id: "eval_access",
|
|
426
|
+
label: "Evaluation access",
|
|
427
|
+
status: "fail",
|
|
428
|
+
message: `Quality API returned ${result.status}`,
|
|
429
|
+
remediation: `API response: ${result.body?.slice(0, 200)}`,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (result.data.baselineMissing === true) {
|
|
433
|
+
return {
|
|
434
|
+
id: "eval_access",
|
|
435
|
+
label: "Evaluation access",
|
|
436
|
+
status: "warn",
|
|
437
|
+
message: `Evaluation ${evaluationId} accessible, but no baseline run found`,
|
|
438
|
+
remediation: "Publish a run from the dashboard, or use --baseline previous once you have runs",
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
id: "eval_access",
|
|
443
|
+
label: "Evaluation access",
|
|
444
|
+
status: "pass",
|
|
445
|
+
message: `Evaluation ${evaluationId} accessible, score: ${result.data.score ?? "n/a"}`,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function checkCiWiring(cwd) {
|
|
449
|
+
const evalgatePath = path.join(".github", "workflows", "evalgate-gate.yml");
|
|
450
|
+
const legacyPath = path.join(".github", "workflows", "evalai-gate.yml");
|
|
451
|
+
const absEvalgate = path.join(cwd, evalgatePath);
|
|
452
|
+
const absLegacy = path.join(cwd, legacyPath);
|
|
453
|
+
const absPath = fs.existsSync(absEvalgate)
|
|
454
|
+
? absEvalgate
|
|
455
|
+
: fs.existsSync(absLegacy)
|
|
456
|
+
? absLegacy
|
|
457
|
+
: null;
|
|
458
|
+
const workflowPath = absPath ? path.relative(cwd, absPath) : evalgatePath;
|
|
459
|
+
if (!absPath) {
|
|
460
|
+
return {
|
|
461
|
+
id: "ci_wiring",
|
|
462
|
+
label: "CI wiring",
|
|
463
|
+
status: "fail",
|
|
464
|
+
message: `${workflowPath} not found`,
|
|
465
|
+
remediation: "Run: npx evalgate init (generates the workflow file)",
|
|
466
|
+
ciInfo: { workflowPath, exists: false },
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
// Basic sanity: check it references evalgate or evalgate SDK
|
|
470
|
+
let content;
|
|
471
|
+
try {
|
|
472
|
+
content = fs.readFileSync(absPath, "utf-8");
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
return {
|
|
476
|
+
id: "ci_wiring",
|
|
477
|
+
label: "CI wiring",
|
|
478
|
+
status: "fail",
|
|
479
|
+
message: `${workflowPath} exists but cannot be read`,
|
|
480
|
+
remediation: "Check file permissions",
|
|
481
|
+
ciInfo: { workflowPath, exists: true },
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
if (!content.includes("evalgate") &&
|
|
485
|
+
!content.includes("@evalgate/sdk") &&
|
|
486
|
+
!content.includes("evalai")) {
|
|
487
|
+
return {
|
|
488
|
+
id: "ci_wiring",
|
|
489
|
+
label: "CI wiring",
|
|
490
|
+
status: "warn",
|
|
491
|
+
message: `${workflowPath} exists but does not reference evalgate`,
|
|
492
|
+
remediation: "Verify the workflow runs: npx -y @evalgate/sdk@^2 gate --format github",
|
|
493
|
+
ciInfo: { workflowPath, exists: true },
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
id: "ci_wiring",
|
|
498
|
+
label: "CI wiring",
|
|
499
|
+
status: "pass",
|
|
500
|
+
message: `${workflowPath} present and references evalgate`,
|
|
501
|
+
ciInfo: { workflowPath, exists: true },
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function checkProviderEnv() {
|
|
505
|
+
const providers = [
|
|
506
|
+
{ name: "OpenAI", envVar: "OPENAI_API_KEY" },
|
|
507
|
+
{ name: "Anthropic", envVar: "ANTHROPIC_API_KEY" },
|
|
508
|
+
{ name: "Azure OpenAI", envVar: "AZURE_OPENAI_API_KEY" },
|
|
509
|
+
];
|
|
510
|
+
const found = providers.filter((p) => !!process.env[p.envVar]);
|
|
511
|
+
if (found.length === 0) {
|
|
512
|
+
return {
|
|
513
|
+
id: "provider_env",
|
|
514
|
+
label: "Provider env vars",
|
|
515
|
+
status: "skip",
|
|
516
|
+
message: "No model provider env vars detected (optional — needed only for LLM-as-judge evals)",
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
id: "provider_env",
|
|
521
|
+
label: "Provider env vars",
|
|
522
|
+
status: "pass",
|
|
523
|
+
message: `Found: ${found.map((p) => p.name).join(", ")}`,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
// ── Output formatting ──
|
|
527
|
+
function icon(status) {
|
|
528
|
+
switch (status) {
|
|
529
|
+
case "pass":
|
|
530
|
+
return "\u2705"; // ✅
|
|
531
|
+
case "fail":
|
|
532
|
+
return "\u274C"; // ❌
|
|
533
|
+
case "warn":
|
|
534
|
+
return "\u26A0\uFE0F"; // ⚠️
|
|
535
|
+
case "skip":
|
|
536
|
+
return "\u23ED\uFE0F"; // ⏭️
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function printHuman(checks, overall) {
|
|
540
|
+
console.log("\n evalgate doctor\n");
|
|
541
|
+
for (const c of checks) {
|
|
542
|
+
console.log(` ${icon(c.status)} ${c.label}: ${c.message}`);
|
|
543
|
+
if (c.remediation && (c.status === "fail" || c.status === "warn")) {
|
|
544
|
+
console.log(` \u2192 ${c.remediation}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const passed = checks.filter((c) => c.status === "pass").length;
|
|
548
|
+
const failed = checks.filter((c) => c.status === "fail").length;
|
|
549
|
+
const warned = checks.filter((c) => c.status === "warn").length;
|
|
550
|
+
const skipped = checks.filter((c) => c.status === "skip").length;
|
|
551
|
+
console.log("");
|
|
552
|
+
if (overall === "ready") {
|
|
553
|
+
console.log(` \u2705 Ready (${passed} passed${warned ? `, ${warned} warnings` : ""}${skipped ? `, ${skipped} skipped` : ""})`);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
console.log(` \u274C Not ready (${failed} failed, ${passed} passed${warned ? `, ${warned} warnings` : ""}${skipped ? `, ${skipped} skipped` : ""})`);
|
|
557
|
+
}
|
|
558
|
+
console.log("");
|
|
559
|
+
}
|
|
560
|
+
// ── Main ──
|
|
561
|
+
async function runDoctor(argv) {
|
|
562
|
+
const flags = parseFlags(argv);
|
|
563
|
+
const cwd = process.cwd();
|
|
564
|
+
const checks = [];
|
|
565
|
+
let infraError = false;
|
|
566
|
+
// 1. Project detection
|
|
567
|
+
checks.push(checkProject(cwd));
|
|
568
|
+
// 2. Config
|
|
569
|
+
const configResult = checkConfig(cwd);
|
|
570
|
+
checks.push(configResult);
|
|
571
|
+
// 3. Baseline
|
|
572
|
+
const baselineResult = checkBaseline(cwd);
|
|
573
|
+
checks.push(baselineResult);
|
|
574
|
+
// 4. Auth
|
|
575
|
+
checks.push(checkAuth(flags.apiKey));
|
|
576
|
+
// 5. Eval target
|
|
577
|
+
checks.push(checkEvalTarget(flags.evaluationId));
|
|
578
|
+
// 6. Connectivity (async)
|
|
579
|
+
let connectivityResult;
|
|
580
|
+
try {
|
|
581
|
+
connectivityResult = await checkConnectivity(flags.baseUrl, flags.apiKey);
|
|
582
|
+
checks.push(connectivityResult);
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
checks.push({
|
|
586
|
+
id: "connectivity",
|
|
587
|
+
label: "API connectivity",
|
|
588
|
+
status: "fail",
|
|
589
|
+
message: "Infrastructure error during connectivity check",
|
|
590
|
+
});
|
|
591
|
+
infraError = true;
|
|
592
|
+
connectivityResult = {
|
|
593
|
+
id: "connectivity",
|
|
594
|
+
label: "API connectivity",
|
|
595
|
+
status: "fail",
|
|
596
|
+
message: "",
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
// 7. Eval access (async, depends on auth + connectivity)
|
|
600
|
+
if (flags.apiKey &&
|
|
601
|
+
flags.evaluationId &&
|
|
602
|
+
connectivityResult.status !== "fail") {
|
|
603
|
+
try {
|
|
604
|
+
const accessResult = await checkEvalAccess(flags.baseUrl, flags.apiKey, flags.evaluationId, flags.baseline);
|
|
605
|
+
checks.push(accessResult);
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
checks.push({
|
|
609
|
+
id: "eval_access",
|
|
610
|
+
label: "Evaluation access",
|
|
611
|
+
status: "fail",
|
|
612
|
+
message: "Infrastructure error during access check",
|
|
613
|
+
});
|
|
614
|
+
infraError = true;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
checks.push({
|
|
619
|
+
id: "eval_access",
|
|
620
|
+
label: "Evaluation access",
|
|
621
|
+
status: "skip",
|
|
622
|
+
message: "Skipped (prerequisite check failed)",
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
// 8. CI wiring
|
|
626
|
+
const ciResult = checkCiWiring(cwd);
|
|
627
|
+
checks.push(ciResult);
|
|
628
|
+
// 9. Provider env vars
|
|
629
|
+
checks.push(checkProviderEnv());
|
|
630
|
+
// Determine overall status
|
|
631
|
+
const hasFail = checks.some((c) => c.status === "fail");
|
|
632
|
+
const hasWarn = checks.some((c) => c.status === "warn");
|
|
633
|
+
const effectiveFail = hasFail || (flags.strict && hasWarn);
|
|
634
|
+
const overall = infraError
|
|
635
|
+
? "infra_error"
|
|
636
|
+
: effectiveFail
|
|
637
|
+
? "not_ready"
|
|
638
|
+
: "ready";
|
|
639
|
+
// --report: JSON diagnostic bundle
|
|
640
|
+
if (flags.report || flags.format === "json") {
|
|
641
|
+
const redactedConfig = {
|
|
642
|
+
...(configResult.config ?? {}),
|
|
643
|
+
path: configResult.configPath
|
|
644
|
+
? path.relative(cwd, configResult.configPath)
|
|
645
|
+
: null,
|
|
646
|
+
};
|
|
647
|
+
const bundle = {
|
|
648
|
+
timestamp: new Date().toISOString(),
|
|
649
|
+
cliVersion: version_1.SDK_VERSION,
|
|
650
|
+
specVersion: version_1.SPEC_VERSION,
|
|
651
|
+
platform: `${process.platform}/${process.arch}`,
|
|
652
|
+
nodeVersion: process.version,
|
|
653
|
+
checks,
|
|
654
|
+
config: redactedConfig,
|
|
655
|
+
baseline: baselineResult.baselineInfo,
|
|
656
|
+
api: {
|
|
657
|
+
reachable: connectivityResult.status === "pass" ||
|
|
658
|
+
connectivityResult.status === "warn",
|
|
659
|
+
latencyMs: connectivityResult.latencyMs,
|
|
660
|
+
},
|
|
661
|
+
ci: ciResult.ciInfo,
|
|
662
|
+
overall,
|
|
663
|
+
};
|
|
664
|
+
console.log(JSON.stringify(bundle, null, 2));
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
printHuman(checks, overall);
|
|
668
|
+
}
|
|
669
|
+
// Exit code
|
|
670
|
+
if (infraError)
|
|
671
|
+
return exports.DOCTOR_EXIT.INFRA_ERROR;
|
|
672
|
+
if (effectiveFail)
|
|
673
|
+
return exports.DOCTOR_EXIT.NOT_READY;
|
|
674
|
+
return exports.DOCTOR_EXIT.READY;
|
|
675
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORE-401: Centralized environment detection
|
|
3
|
+
*
|
|
4
|
+
* Provides unified environment detection for all EvalGate CLI commands
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Check if running in CI environment
|
|
8
|
+
*/
|
|
9
|
+
export declare function isCI(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Check if running in GitHub Actions
|
|
12
|
+
*/
|
|
13
|
+
export declare function isGitHubActions(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Get GitHub Step Summary path if available
|
|
16
|
+
*/
|
|
17
|
+
export declare function getGitHubStepSummaryPath(): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Check if string looks like a git reference
|
|
20
|
+
*/
|
|
21
|
+
export declare function isGitRef(ref: string): boolean;
|