@plune-ai/cli 0.17.1 → 0.19.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/README.md +25 -7
- package/dist/cli.cjs +209 -130
- package/dist/index.cjs +110 -76
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +110 -76
- package/dist/reporter-core.cjs +9 -4
- package/dist/reporter-core.d.cts +17 -3
- package/dist/reporter-core.d.ts +17 -3
- package/dist/reporter-core.js +9 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1272,6 +1272,64 @@ async function mapLimit(items, limit, fn) {
|
|
|
1272
1272
|
return results;
|
|
1273
1273
|
}
|
|
1274
1274
|
|
|
1275
|
+
// src/types/test-case-schema.ts
|
|
1276
|
+
var import_zod2 = require("zod");
|
|
1277
|
+
var TEST_CASE_SCHEMA_VERSION = 2;
|
|
1278
|
+
var externalKeySchema = import_zod2.z.object({
|
|
1279
|
+
kind: import_zod2.z.enum(["playwright-id", "path-title", "allure-history", "cairn-stable", "eval-id", "qase", "testrail"]),
|
|
1280
|
+
value: import_zod2.z.string().min(1)
|
|
1281
|
+
});
|
|
1282
|
+
var manualStepSchema = import_zod2.z.object({
|
|
1283
|
+
action: import_zod2.z.string().min(1),
|
|
1284
|
+
expected: import_zod2.z.string().min(1).optional()
|
|
1285
|
+
});
|
|
1286
|
+
var assertionResultRecordSchema = import_zod2.z.object({
|
|
1287
|
+
type: import_zod2.z.string().min(1),
|
|
1288
|
+
passed: import_zod2.z.boolean(),
|
|
1289
|
+
score: import_zod2.z.number().optional(),
|
|
1290
|
+
reason: import_zod2.z.string().optional()
|
|
1291
|
+
});
|
|
1292
|
+
var manualCaseSchema = import_zod2.z.object({
|
|
1293
|
+
type: import_zod2.z.literal("manual"),
|
|
1294
|
+
steps: import_zod2.z.array(manualStepSchema).min(1),
|
|
1295
|
+
expected: import_zod2.z.string().min(1).optional()
|
|
1296
|
+
});
|
|
1297
|
+
var automatedCaseSchema = import_zod2.z.object({
|
|
1298
|
+
type: import_zod2.z.literal("automated"),
|
|
1299
|
+
specRef: import_zod2.z.string().min(1)
|
|
1300
|
+
});
|
|
1301
|
+
var evalCaseSchema = import_zod2.z.object({
|
|
1302
|
+
type: import_zod2.z.literal("eval"),
|
|
1303
|
+
prompt: import_zod2.z.string().min(1),
|
|
1304
|
+
goldenSetId: import_zod2.z.string().min(1).optional(),
|
|
1305
|
+
assertions: import_zod2.z.array(assertionResultRecordSchema).min(1)
|
|
1306
|
+
});
|
|
1307
|
+
var testCaseBaseSchema = import_zod2.z.object({
|
|
1308
|
+
schemaVersion: import_zod2.z.literal(TEST_CASE_SCHEMA_VERSION),
|
|
1309
|
+
id: import_zod2.z.string().min(1),
|
|
1310
|
+
externalKeys: import_zod2.z.array(externalKeySchema).optional(),
|
|
1311
|
+
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1312
|
+
is_flaky: import_zod2.z.boolean().optional(),
|
|
1313
|
+
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1314
|
+
is_muted: import_zod2.z.boolean().optional()
|
|
1315
|
+
});
|
|
1316
|
+
var testCaseSchema = import_zod2.z.discriminatedUnion("type", [
|
|
1317
|
+
testCaseBaseSchema.merge(manualCaseSchema),
|
|
1318
|
+
testCaseBaseSchema.merge(automatedCaseSchema),
|
|
1319
|
+
testCaseBaseSchema.merge(evalCaseSchema)
|
|
1320
|
+
]);
|
|
1321
|
+
function parseTestCase(value) {
|
|
1322
|
+
return testCaseSchema.parse(value);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// src/types/results.ts
|
|
1326
|
+
var ROW_INCONCLUSIVE = ["blocked", "skipped"];
|
|
1327
|
+
function rowStatus(row) {
|
|
1328
|
+
if (row.inconclusive !== void 0) return row.inconclusive;
|
|
1329
|
+
if (row.error !== void 0) return "errored";
|
|
1330
|
+
return row.assertions.every((a) => a.passed) ? "passed" : "failed";
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1275
1333
|
// src/orchestrator/run.ts
|
|
1276
1334
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
1277
1335
|
var PLUNE_VERSION = "0.1.0";
|
|
@@ -1414,10 +1472,25 @@ function resolveTemplate(ev, baseDir) {
|
|
|
1414
1472
|
}
|
|
1415
1473
|
throw new RunConfigError(`Eval "${ev.id}" has neither prompt nor prompt_file`);
|
|
1416
1474
|
}
|
|
1417
|
-
function
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1475
|
+
function summarize(evals, durationMs) {
|
|
1476
|
+
const summary = {
|
|
1477
|
+
total: 0,
|
|
1478
|
+
passed: 0,
|
|
1479
|
+
failed: 0,
|
|
1480
|
+
errored: 0,
|
|
1481
|
+
blocked: 0,
|
|
1482
|
+
skipped: 0,
|
|
1483
|
+
cost_usd: 0,
|
|
1484
|
+
duration_ms: durationMs
|
|
1485
|
+
};
|
|
1486
|
+
for (const ev of evals) {
|
|
1487
|
+
for (const row of ev.rows) {
|
|
1488
|
+
summary.total += 1;
|
|
1489
|
+
summary[rowStatus(row)] += 1;
|
|
1490
|
+
summary.cost_usd += row.usage?.cost_usd ?? 0;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return summary;
|
|
1421
1494
|
}
|
|
1422
1495
|
async function runOrchestration(config, options, deps) {
|
|
1423
1496
|
const started = deps.now();
|
|
@@ -1452,26 +1525,12 @@ async function runOrchestration(config, options, deps) {
|
|
|
1452
1525
|
noCache
|
|
1453
1526
|
})
|
|
1454
1527
|
);
|
|
1455
|
-
const passed = rowResults.every((r) =>
|
|
1528
|
+
const passed = rowResults.every((r) => rowStatus(r) === "passed");
|
|
1456
1529
|
evalResults.push({ id: ev.id, tags: ev.tags ?? [], rows: rowResults, passed });
|
|
1457
1530
|
if (options.bail === true && !passed) break;
|
|
1458
1531
|
}
|
|
1459
1532
|
const finished = deps.now();
|
|
1460
|
-
const summary =
|
|
1461
|
-
total: 0,
|
|
1462
|
-
passed: 0,
|
|
1463
|
-
failed: 0,
|
|
1464
|
-
errored: 0,
|
|
1465
|
-
cost_usd: 0,
|
|
1466
|
-
duration_ms: finished - started
|
|
1467
|
-
};
|
|
1468
|
-
for (const ev of evalResults) {
|
|
1469
|
-
for (const row of ev.rows) {
|
|
1470
|
-
summary.total += 1;
|
|
1471
|
-
summary[classify(row)] += 1;
|
|
1472
|
-
summary.cost_usd += row.usage?.cost_usd ?? 0;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1533
|
+
const summary = summarize(evalResults, finished - started);
|
|
1475
1534
|
return {
|
|
1476
1535
|
schemaVersion: 1,
|
|
1477
1536
|
plune_version: PLUNE_VERSION,
|
|
@@ -1556,61 +1615,6 @@ var path5 = __toESM(require("path"), 1);
|
|
|
1556
1615
|
|
|
1557
1616
|
// src/types/run-result-schema.ts
|
|
1558
1617
|
var import_zod3 = require("zod");
|
|
1559
|
-
|
|
1560
|
-
// src/types/test-case-schema.ts
|
|
1561
|
-
var import_zod2 = require("zod");
|
|
1562
|
-
var TEST_CASE_SCHEMA_VERSION = 2;
|
|
1563
|
-
var externalKeySchema = import_zod2.z.object({
|
|
1564
|
-
kind: import_zod2.z.enum(["playwright-id", "path-title", "allure-history", "cairn-stable", "eval-id", "qase", "testrail"]),
|
|
1565
|
-
value: import_zod2.z.string().min(1)
|
|
1566
|
-
});
|
|
1567
|
-
var manualStepSchema = import_zod2.z.object({
|
|
1568
|
-
action: import_zod2.z.string().min(1),
|
|
1569
|
-
expected: import_zod2.z.string().min(1).optional()
|
|
1570
|
-
});
|
|
1571
|
-
var assertionResultRecordSchema = import_zod2.z.object({
|
|
1572
|
-
type: import_zod2.z.string().min(1),
|
|
1573
|
-
passed: import_zod2.z.boolean(),
|
|
1574
|
-
score: import_zod2.z.number().optional(),
|
|
1575
|
-
reason: import_zod2.z.string().optional()
|
|
1576
|
-
});
|
|
1577
|
-
var manualCaseSchema = import_zod2.z.object({
|
|
1578
|
-
type: import_zod2.z.literal("manual"),
|
|
1579
|
-
steps: import_zod2.z.array(manualStepSchema).min(1),
|
|
1580
|
-
expected: import_zod2.z.string().min(1).optional()
|
|
1581
|
-
});
|
|
1582
|
-
var automatedCaseSchema = import_zod2.z.object({
|
|
1583
|
-
type: import_zod2.z.literal("automated"),
|
|
1584
|
-
specRef: import_zod2.z.string().min(1)
|
|
1585
|
-
});
|
|
1586
|
-
var evalCaseSchema = import_zod2.z.object({
|
|
1587
|
-
type: import_zod2.z.literal("eval"),
|
|
1588
|
-
prompt: import_zod2.z.string().min(1),
|
|
1589
|
-
goldenSetId: import_zod2.z.string().min(1).optional(),
|
|
1590
|
-
assertions: import_zod2.z.array(assertionResultRecordSchema).min(1)
|
|
1591
|
-
});
|
|
1592
|
-
var testCaseBaseSchema = import_zod2.z.object({
|
|
1593
|
-
schemaVersion: import_zod2.z.literal(TEST_CASE_SCHEMA_VERSION),
|
|
1594
|
-
id: import_zod2.z.string().min(1),
|
|
1595
|
-
externalKeys: import_zod2.z.array(externalKeySchema).optional(),
|
|
1596
|
-
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1597
|
-
is_flaky: import_zod2.z.boolean().optional(),
|
|
1598
|
-
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1599
|
-
is_muted: import_zod2.z.boolean().optional()
|
|
1600
|
-
});
|
|
1601
|
-
var testCaseSchema = import_zod2.z.discriminatedUnion("type", [
|
|
1602
|
-
testCaseBaseSchema.merge(manualCaseSchema),
|
|
1603
|
-
testCaseBaseSchema.merge(automatedCaseSchema),
|
|
1604
|
-
testCaseBaseSchema.merge(evalCaseSchema)
|
|
1605
|
-
]);
|
|
1606
|
-
function parseTestCase(value) {
|
|
1607
|
-
return testCaseSchema.parse(value);
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// src/types/results.ts
|
|
1611
|
-
var ROW_INCONCLUSIVE = ["blocked", "skipped"];
|
|
1612
|
-
|
|
1613
|
-
// src/types/run-result-schema.ts
|
|
1614
1618
|
var RUN_RESULT_SCHEMA_VERSION = 1;
|
|
1615
1619
|
var binaryVerdictSchema = import_zod3.z.object({
|
|
1616
1620
|
question: import_zod3.z.string().min(1),
|
|
@@ -1648,6 +1652,9 @@ var summarySchema = import_zod3.z.object({
|
|
|
1648
1652
|
passed: import_zod3.z.number(),
|
|
1649
1653
|
failed: import_zod3.z.number(),
|
|
1650
1654
|
errored: import_zod3.z.number(),
|
|
1655
|
+
/** plune#1245. Optional and additive — a RunResult written before them validates unchanged. */
|
|
1656
|
+
blocked: import_zod3.z.number().optional(),
|
|
1657
|
+
skipped: import_zod3.z.number().optional(),
|
|
1651
1658
|
cost_usd: import_zod3.z.number(),
|
|
1652
1659
|
duration_ms: import_zod3.z.number()
|
|
1653
1660
|
});
|
|
@@ -1696,6 +1703,22 @@ function loadToken() {
|
|
|
1696
1703
|
}
|
|
1697
1704
|
}
|
|
1698
1705
|
|
|
1706
|
+
// src/cli/gate.ts
|
|
1707
|
+
function gateExit(verdict, runnerCode) {
|
|
1708
|
+
if (verdict?.statusEvent === "terminated") {
|
|
1709
|
+
return { code: 1, line: "plune: gate failed \u2014 the run was terminated, so it has no verdict to pass on." };
|
|
1710
|
+
}
|
|
1711
|
+
const counts = verdict?.counts;
|
|
1712
|
+
if (counts == null) return { code: 1, line: "plune: gate failed \u2014 no verdict from the platform, and no verdict is no pass." };
|
|
1713
|
+
const n = (status) => counts[status] ?? 0;
|
|
1714
|
+
const notRun = verdict?.notRun?.length ?? 0;
|
|
1715
|
+
if (runnerCode !== void 0 && notRun === 0 && Object.values(counts).every((k) => !k)) {
|
|
1716
|
+
return { code: runnerCode, line: `plune: gate \u2014 no result reached the run, so the exit code is the command's own (${runnerCode}).` };
|
|
1717
|
+
}
|
|
1718
|
+
const said = `${n("failed")} failed \xB7 ${n("broken")} broken \xB7 ${notRun} not run \xB7 ${n("muted")} muted`;
|
|
1719
|
+
return n("failed") + n("broken") > 0 || notRun > 0 ? { code: 1, line: `plune: gate failed \u2014 ${said}` } : { code: 0, line: `plune: gate passed \u2014 ${said}` };
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1699
1722
|
// src/cli/commands/sync.ts
|
|
1700
1723
|
var NotLoggedInError = class extends Error {
|
|
1701
1724
|
constructor() {
|
|
@@ -1788,7 +1811,18 @@ async function handleSync(deps = {}) {
|
|
|
1788
1811
|
}
|
|
1789
1812
|
const body = await res.json().catch(() => ({}));
|
|
1790
1813
|
const id = typeof body.id === "string" ? body.id : "";
|
|
1791
|
-
|
|
1814
|
+
const result = { id, url: `${endpoint}/${id}` };
|
|
1815
|
+
if (deps.failOnFailures === true) result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, token));
|
|
1816
|
+
return result;
|
|
1817
|
+
}
|
|
1818
|
+
async function readVerdict(doFetch, url, token) {
|
|
1819
|
+
try {
|
|
1820
|
+
const res = await doFetch(url, { method: "GET", headers: { authorization: `Bearer ${token}` } });
|
|
1821
|
+
const body = await res.json().catch(() => void 0);
|
|
1822
|
+
return res.ok ? { statusEvent: body?.run?.statusEvent, counts: body?.counts, notRun: body?.notRun } : void 0;
|
|
1823
|
+
} catch {
|
|
1824
|
+
return void 0;
|
|
1825
|
+
}
|
|
1792
1826
|
}
|
|
1793
1827
|
function reportSyncFailure(err, write) {
|
|
1794
1828
|
if (err instanceof NotLoggedInError || err instanceof TokenRejectedError || err instanceof SyncFileError) {
|
package/dist/index.d.cts
CHANGED
|
@@ -5,6 +5,14 @@ interface Summary {
|
|
|
5
5
|
passed: number;
|
|
6
6
|
failed: number;
|
|
7
7
|
errored: number;
|
|
8
|
+
/**
|
|
9
|
+
* plune#1245. Rows the runner said were never judged (`RowResult.inconclusive`), counted apart from
|
|
10
|
+
* `failed`. The runner always writes both; they are optional so a RunResult written before them is
|
|
11
|
+
* still valid and `schemaVersion` stays 1. With them, `passed + failed + errored + blocked + skipped`
|
|
12
|
+
* is `total`.
|
|
13
|
+
*/
|
|
14
|
+
blocked?: number;
|
|
15
|
+
skipped?: number;
|
|
8
16
|
cost_usd: number;
|
|
9
17
|
duration_ms: number;
|
|
10
18
|
}
|
|
@@ -307,12 +315,19 @@ interface SyncDeps {
|
|
|
307
315
|
loadToken?: () => string | null;
|
|
308
316
|
/** HTTP transport (injected in tests — e.g. a real Hono app's `request`). Defaults to global `fetch`. */
|
|
309
317
|
fetchImpl?: typeof fetch;
|
|
318
|
+
/** `--fail-on-failures`: read the run's summary back once after the upload and judge it (plune#1247). */
|
|
319
|
+
failOnFailures?: boolean;
|
|
310
320
|
}
|
|
311
321
|
interface SyncResult {
|
|
312
322
|
/** Server-assigned run id. */
|
|
313
323
|
id: string;
|
|
314
324
|
/** The `GET /v1/runs/:id` URL where the uploaded run can be read back. */
|
|
315
325
|
url: string;
|
|
326
|
+
/** The exit code `--fail-on-failures` asks for and the line that says why; absent without the flag. */
|
|
327
|
+
gate?: {
|
|
328
|
+
code: number;
|
|
329
|
+
line: string;
|
|
330
|
+
};
|
|
316
331
|
}
|
|
317
332
|
/**
|
|
318
333
|
* Upload the latest (or `--file`) local run to `POST {apiUrl}/v1/runs` with the stored Bearer token.
|
|
@@ -430,6 +445,8 @@ declare const runResultSchema: z.ZodObject<{
|
|
|
430
445
|
passed: z.ZodNumber;
|
|
431
446
|
failed: z.ZodNumber;
|
|
432
447
|
errored: z.ZodNumber;
|
|
448
|
+
blocked: z.ZodOptional<z.ZodNumber>;
|
|
449
|
+
skipped: z.ZodOptional<z.ZodNumber>;
|
|
433
450
|
cost_usd: z.ZodNumber;
|
|
434
451
|
duration_ms: z.ZodNumber;
|
|
435
452
|
}, z.core.$strip>;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,14 @@ interface Summary {
|
|
|
5
5
|
passed: number;
|
|
6
6
|
failed: number;
|
|
7
7
|
errored: number;
|
|
8
|
+
/**
|
|
9
|
+
* plune#1245. Rows the runner said were never judged (`RowResult.inconclusive`), counted apart from
|
|
10
|
+
* `failed`. The runner always writes both; they are optional so a RunResult written before them is
|
|
11
|
+
* still valid and `schemaVersion` stays 1. With them, `passed + failed + errored + blocked + skipped`
|
|
12
|
+
* is `total`.
|
|
13
|
+
*/
|
|
14
|
+
blocked?: number;
|
|
15
|
+
skipped?: number;
|
|
8
16
|
cost_usd: number;
|
|
9
17
|
duration_ms: number;
|
|
10
18
|
}
|
|
@@ -307,12 +315,19 @@ interface SyncDeps {
|
|
|
307
315
|
loadToken?: () => string | null;
|
|
308
316
|
/** HTTP transport (injected in tests — e.g. a real Hono app's `request`). Defaults to global `fetch`. */
|
|
309
317
|
fetchImpl?: typeof fetch;
|
|
318
|
+
/** `--fail-on-failures`: read the run's summary back once after the upload and judge it (plune#1247). */
|
|
319
|
+
failOnFailures?: boolean;
|
|
310
320
|
}
|
|
311
321
|
interface SyncResult {
|
|
312
322
|
/** Server-assigned run id. */
|
|
313
323
|
id: string;
|
|
314
324
|
/** The `GET /v1/runs/:id` URL where the uploaded run can be read back. */
|
|
315
325
|
url: string;
|
|
326
|
+
/** The exit code `--fail-on-failures` asks for and the line that says why; absent without the flag. */
|
|
327
|
+
gate?: {
|
|
328
|
+
code: number;
|
|
329
|
+
line: string;
|
|
330
|
+
};
|
|
316
331
|
}
|
|
317
332
|
/**
|
|
318
333
|
* Upload the latest (or `--file`) local run to `POST {apiUrl}/v1/runs` with the stored Bearer token.
|
|
@@ -430,6 +445,8 @@ declare const runResultSchema: z.ZodObject<{
|
|
|
430
445
|
passed: z.ZodNumber;
|
|
431
446
|
failed: z.ZodNumber;
|
|
432
447
|
errored: z.ZodNumber;
|
|
448
|
+
blocked: z.ZodOptional<z.ZodNumber>;
|
|
449
|
+
skipped: z.ZodOptional<z.ZodNumber>;
|
|
433
450
|
cost_usd: z.ZodNumber;
|
|
434
451
|
duration_ms: z.ZodNumber;
|
|
435
452
|
}, z.core.$strip>;
|
package/dist/index.js
CHANGED
|
@@ -1216,6 +1216,64 @@ async function mapLimit(items, limit, fn) {
|
|
|
1216
1216
|
return results;
|
|
1217
1217
|
}
|
|
1218
1218
|
|
|
1219
|
+
// src/types/test-case-schema.ts
|
|
1220
|
+
import { z as z2 } from "zod";
|
|
1221
|
+
var TEST_CASE_SCHEMA_VERSION = 2;
|
|
1222
|
+
var externalKeySchema = z2.object({
|
|
1223
|
+
kind: z2.enum(["playwright-id", "path-title", "allure-history", "cairn-stable", "eval-id", "qase", "testrail"]),
|
|
1224
|
+
value: z2.string().min(1)
|
|
1225
|
+
});
|
|
1226
|
+
var manualStepSchema = z2.object({
|
|
1227
|
+
action: z2.string().min(1),
|
|
1228
|
+
expected: z2.string().min(1).optional()
|
|
1229
|
+
});
|
|
1230
|
+
var assertionResultRecordSchema = z2.object({
|
|
1231
|
+
type: z2.string().min(1),
|
|
1232
|
+
passed: z2.boolean(),
|
|
1233
|
+
score: z2.number().optional(),
|
|
1234
|
+
reason: z2.string().optional()
|
|
1235
|
+
});
|
|
1236
|
+
var manualCaseSchema = z2.object({
|
|
1237
|
+
type: z2.literal("manual"),
|
|
1238
|
+
steps: z2.array(manualStepSchema).min(1),
|
|
1239
|
+
expected: z2.string().min(1).optional()
|
|
1240
|
+
});
|
|
1241
|
+
var automatedCaseSchema = z2.object({
|
|
1242
|
+
type: z2.literal("automated"),
|
|
1243
|
+
specRef: z2.string().min(1)
|
|
1244
|
+
});
|
|
1245
|
+
var evalCaseSchema = z2.object({
|
|
1246
|
+
type: z2.literal("eval"),
|
|
1247
|
+
prompt: z2.string().min(1),
|
|
1248
|
+
goldenSetId: z2.string().min(1).optional(),
|
|
1249
|
+
assertions: z2.array(assertionResultRecordSchema).min(1)
|
|
1250
|
+
});
|
|
1251
|
+
var testCaseBaseSchema = z2.object({
|
|
1252
|
+
schemaVersion: z2.literal(TEST_CASE_SCHEMA_VERSION),
|
|
1253
|
+
id: z2.string().min(1),
|
|
1254
|
+
externalKeys: z2.array(externalKeySchema).optional(),
|
|
1255
|
+
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1256
|
+
is_flaky: z2.boolean().optional(),
|
|
1257
|
+
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1258
|
+
is_muted: z2.boolean().optional()
|
|
1259
|
+
});
|
|
1260
|
+
var testCaseSchema = z2.discriminatedUnion("type", [
|
|
1261
|
+
testCaseBaseSchema.merge(manualCaseSchema),
|
|
1262
|
+
testCaseBaseSchema.merge(automatedCaseSchema),
|
|
1263
|
+
testCaseBaseSchema.merge(evalCaseSchema)
|
|
1264
|
+
]);
|
|
1265
|
+
function parseTestCase(value) {
|
|
1266
|
+
return testCaseSchema.parse(value);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/types/results.ts
|
|
1270
|
+
var ROW_INCONCLUSIVE = ["blocked", "skipped"];
|
|
1271
|
+
function rowStatus(row) {
|
|
1272
|
+
if (row.inconclusive !== void 0) return row.inconclusive;
|
|
1273
|
+
if (row.error !== void 0) return "errored";
|
|
1274
|
+
return row.assertions.every((a) => a.passed) ? "passed" : "failed";
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1219
1277
|
// src/orchestrator/run.ts
|
|
1220
1278
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
1221
1279
|
var PLUNE_VERSION = "0.1.0";
|
|
@@ -1358,10 +1416,25 @@ function resolveTemplate(ev, baseDir) {
|
|
|
1358
1416
|
}
|
|
1359
1417
|
throw new RunConfigError(`Eval "${ev.id}" has neither prompt nor prompt_file`);
|
|
1360
1418
|
}
|
|
1361
|
-
function
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1419
|
+
function summarize(evals, durationMs) {
|
|
1420
|
+
const summary = {
|
|
1421
|
+
total: 0,
|
|
1422
|
+
passed: 0,
|
|
1423
|
+
failed: 0,
|
|
1424
|
+
errored: 0,
|
|
1425
|
+
blocked: 0,
|
|
1426
|
+
skipped: 0,
|
|
1427
|
+
cost_usd: 0,
|
|
1428
|
+
duration_ms: durationMs
|
|
1429
|
+
};
|
|
1430
|
+
for (const ev of evals) {
|
|
1431
|
+
for (const row of ev.rows) {
|
|
1432
|
+
summary.total += 1;
|
|
1433
|
+
summary[rowStatus(row)] += 1;
|
|
1434
|
+
summary.cost_usd += row.usage?.cost_usd ?? 0;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return summary;
|
|
1365
1438
|
}
|
|
1366
1439
|
async function runOrchestration(config, options, deps) {
|
|
1367
1440
|
const started = deps.now();
|
|
@@ -1396,26 +1469,12 @@ async function runOrchestration(config, options, deps) {
|
|
|
1396
1469
|
noCache
|
|
1397
1470
|
})
|
|
1398
1471
|
);
|
|
1399
|
-
const passed = rowResults.every((r) =>
|
|
1472
|
+
const passed = rowResults.every((r) => rowStatus(r) === "passed");
|
|
1400
1473
|
evalResults.push({ id: ev.id, tags: ev.tags ?? [], rows: rowResults, passed });
|
|
1401
1474
|
if (options.bail === true && !passed) break;
|
|
1402
1475
|
}
|
|
1403
1476
|
const finished = deps.now();
|
|
1404
|
-
const summary =
|
|
1405
|
-
total: 0,
|
|
1406
|
-
passed: 0,
|
|
1407
|
-
failed: 0,
|
|
1408
|
-
errored: 0,
|
|
1409
|
-
cost_usd: 0,
|
|
1410
|
-
duration_ms: finished - started
|
|
1411
|
-
};
|
|
1412
|
-
for (const ev of evalResults) {
|
|
1413
|
-
for (const row of ev.rows) {
|
|
1414
|
-
summary.total += 1;
|
|
1415
|
-
summary[classify(row)] += 1;
|
|
1416
|
-
summary.cost_usd += row.usage?.cost_usd ?? 0;
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1477
|
+
const summary = summarize(evalResults, finished - started);
|
|
1419
1478
|
return {
|
|
1420
1479
|
schemaVersion: 1,
|
|
1421
1480
|
plune_version: PLUNE_VERSION,
|
|
@@ -1500,61 +1559,6 @@ import * as path5 from "path";
|
|
|
1500
1559
|
|
|
1501
1560
|
// src/types/run-result-schema.ts
|
|
1502
1561
|
import { z as z3 } from "zod";
|
|
1503
|
-
|
|
1504
|
-
// src/types/test-case-schema.ts
|
|
1505
|
-
import { z as z2 } from "zod";
|
|
1506
|
-
var TEST_CASE_SCHEMA_VERSION = 2;
|
|
1507
|
-
var externalKeySchema = z2.object({
|
|
1508
|
-
kind: z2.enum(["playwright-id", "path-title", "allure-history", "cairn-stable", "eval-id", "qase", "testrail"]),
|
|
1509
|
-
value: z2.string().min(1)
|
|
1510
|
-
});
|
|
1511
|
-
var manualStepSchema = z2.object({
|
|
1512
|
-
action: z2.string().min(1),
|
|
1513
|
-
expected: z2.string().min(1).optional()
|
|
1514
|
-
});
|
|
1515
|
-
var assertionResultRecordSchema = z2.object({
|
|
1516
|
-
type: z2.string().min(1),
|
|
1517
|
-
passed: z2.boolean(),
|
|
1518
|
-
score: z2.number().optional(),
|
|
1519
|
-
reason: z2.string().optional()
|
|
1520
|
-
});
|
|
1521
|
-
var manualCaseSchema = z2.object({
|
|
1522
|
-
type: z2.literal("manual"),
|
|
1523
|
-
steps: z2.array(manualStepSchema).min(1),
|
|
1524
|
-
expected: z2.string().min(1).optional()
|
|
1525
|
-
});
|
|
1526
|
-
var automatedCaseSchema = z2.object({
|
|
1527
|
-
type: z2.literal("automated"),
|
|
1528
|
-
specRef: z2.string().min(1)
|
|
1529
|
-
});
|
|
1530
|
-
var evalCaseSchema = z2.object({
|
|
1531
|
-
type: z2.literal("eval"),
|
|
1532
|
-
prompt: z2.string().min(1),
|
|
1533
|
-
goldenSetId: z2.string().min(1).optional(),
|
|
1534
|
-
assertions: z2.array(assertionResultRecordSchema).min(1)
|
|
1535
|
-
});
|
|
1536
|
-
var testCaseBaseSchema = z2.object({
|
|
1537
|
-
schemaVersion: z2.literal(TEST_CASE_SCHEMA_VERSION),
|
|
1538
|
-
id: z2.string().min(1),
|
|
1539
|
-
externalKeys: z2.array(externalKeySchema).optional(),
|
|
1540
|
-
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1541
|
-
is_flaky: z2.boolean().optional(),
|
|
1542
|
-
/** ADR 0045 / plune#1246 — optional, purely additive; schemaVersion stays 2. */
|
|
1543
|
-
is_muted: z2.boolean().optional()
|
|
1544
|
-
});
|
|
1545
|
-
var testCaseSchema = z2.discriminatedUnion("type", [
|
|
1546
|
-
testCaseBaseSchema.merge(manualCaseSchema),
|
|
1547
|
-
testCaseBaseSchema.merge(automatedCaseSchema),
|
|
1548
|
-
testCaseBaseSchema.merge(evalCaseSchema)
|
|
1549
|
-
]);
|
|
1550
|
-
function parseTestCase(value) {
|
|
1551
|
-
return testCaseSchema.parse(value);
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// src/types/results.ts
|
|
1555
|
-
var ROW_INCONCLUSIVE = ["blocked", "skipped"];
|
|
1556
|
-
|
|
1557
|
-
// src/types/run-result-schema.ts
|
|
1558
1562
|
var RUN_RESULT_SCHEMA_VERSION = 1;
|
|
1559
1563
|
var binaryVerdictSchema = z3.object({
|
|
1560
1564
|
question: z3.string().min(1),
|
|
@@ -1592,6 +1596,9 @@ var summarySchema = z3.object({
|
|
|
1592
1596
|
passed: z3.number(),
|
|
1593
1597
|
failed: z3.number(),
|
|
1594
1598
|
errored: z3.number(),
|
|
1599
|
+
/** plune#1245. Optional and additive — a RunResult written before them validates unchanged. */
|
|
1600
|
+
blocked: z3.number().optional(),
|
|
1601
|
+
skipped: z3.number().optional(),
|
|
1595
1602
|
cost_usd: z3.number(),
|
|
1596
1603
|
duration_ms: z3.number()
|
|
1597
1604
|
});
|
|
@@ -1640,6 +1647,22 @@ function loadToken() {
|
|
|
1640
1647
|
}
|
|
1641
1648
|
}
|
|
1642
1649
|
|
|
1650
|
+
// src/cli/gate.ts
|
|
1651
|
+
function gateExit(verdict, runnerCode) {
|
|
1652
|
+
if (verdict?.statusEvent === "terminated") {
|
|
1653
|
+
return { code: 1, line: "plune: gate failed \u2014 the run was terminated, so it has no verdict to pass on." };
|
|
1654
|
+
}
|
|
1655
|
+
const counts = verdict?.counts;
|
|
1656
|
+
if (counts == null) return { code: 1, line: "plune: gate failed \u2014 no verdict from the platform, and no verdict is no pass." };
|
|
1657
|
+
const n = (status) => counts[status] ?? 0;
|
|
1658
|
+
const notRun = verdict?.notRun?.length ?? 0;
|
|
1659
|
+
if (runnerCode !== void 0 && notRun === 0 && Object.values(counts).every((k) => !k)) {
|
|
1660
|
+
return { code: runnerCode, line: `plune: gate \u2014 no result reached the run, so the exit code is the command's own (${runnerCode}).` };
|
|
1661
|
+
}
|
|
1662
|
+
const said = `${n("failed")} failed \xB7 ${n("broken")} broken \xB7 ${notRun} not run \xB7 ${n("muted")} muted`;
|
|
1663
|
+
return n("failed") + n("broken") > 0 || notRun > 0 ? { code: 1, line: `plune: gate failed \u2014 ${said}` } : { code: 0, line: `plune: gate passed \u2014 ${said}` };
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1643
1666
|
// src/cli/commands/sync.ts
|
|
1644
1667
|
var NotLoggedInError = class extends Error {
|
|
1645
1668
|
constructor() {
|
|
@@ -1732,7 +1755,18 @@ async function handleSync(deps = {}) {
|
|
|
1732
1755
|
}
|
|
1733
1756
|
const body = await res.json().catch(() => ({}));
|
|
1734
1757
|
const id = typeof body.id === "string" ? body.id : "";
|
|
1735
|
-
|
|
1758
|
+
const result = { id, url: `${endpoint}/${id}` };
|
|
1759
|
+
if (deps.failOnFailures === true) result.gate = gateExit(await readVerdict(doFetch, `${endpoint}/${id}/summary`, token));
|
|
1760
|
+
return result;
|
|
1761
|
+
}
|
|
1762
|
+
async function readVerdict(doFetch, url, token) {
|
|
1763
|
+
try {
|
|
1764
|
+
const res = await doFetch(url, { method: "GET", headers: { authorization: `Bearer ${token}` } });
|
|
1765
|
+
const body = await res.json().catch(() => void 0);
|
|
1766
|
+
return res.ok ? { statusEvent: body?.run?.statusEvent, counts: body?.counts, notRun: body?.notRun } : void 0;
|
|
1767
|
+
} catch {
|
|
1768
|
+
return void 0;
|
|
1769
|
+
}
|
|
1736
1770
|
}
|
|
1737
1771
|
function reportSyncFailure(err, write) {
|
|
1738
1772
|
if (err instanceof NotLoggedInError || err instanceof TokenRejectedError || err instanceof SyncFileError) {
|
package/dist/reporter-core.cjs
CHANGED
|
@@ -215,7 +215,7 @@ function createClient(opts) {
|
|
|
215
215
|
if (res.status === 429 && retryAfterMs(res) === null) {
|
|
216
216
|
last.kind = "invalid";
|
|
217
217
|
last.detail = "quota exceeded (no Retry-After)";
|
|
218
|
-
if (said.detail) last.detail += ` \u2014 ${said.detail}`;
|
|
218
|
+
if (said.detail) last.detail += ` \u2014 ${scrub(said.detail)}`;
|
|
219
219
|
break;
|
|
220
220
|
}
|
|
221
221
|
if (attempt < RETRY_DELAYS_MS.length) {
|
|
@@ -1100,21 +1100,26 @@ async function startRun(passed, declared) {
|
|
|
1100
1100
|
},
|
|
1101
1101
|
flush,
|
|
1102
1102
|
async finish(reason) {
|
|
1103
|
-
if (done) return;
|
|
1103
|
+
if (done) return void 0;
|
|
1104
1104
|
done = true;
|
|
1105
1105
|
await flush();
|
|
1106
1106
|
await offer();
|
|
1107
|
+
let frozen;
|
|
1107
1108
|
if (runId !== null && !offline) {
|
|
1108
1109
|
const out = await client.post(`/v1/runs/${runId}/events`, {
|
|
1109
1110
|
event: "finish",
|
|
1110
1111
|
...reason !== void 0 ? { reason } : {}
|
|
1111
1112
|
});
|
|
1112
1113
|
if (!out.ok) log(`plune: could not close run ${runId} (${out.detail || out.kind}).`);
|
|
1113
|
-
else
|
|
1114
|
-
|
|
1114
|
+
else {
|
|
1115
|
+
frozen = out.body?.summary ?? void 0;
|
|
1116
|
+
if (typeof out.body?.detached === "number" && out.body.detached > 0) {
|
|
1117
|
+
log(`plune: ${out.body.detached} test case(s) marked detached \u2014 this full run no longer reports them.`);
|
|
1118
|
+
}
|
|
1115
1119
|
}
|
|
1116
1120
|
}
|
|
1117
1121
|
summarise();
|
|
1122
|
+
return frozen;
|
|
1118
1123
|
},
|
|
1119
1124
|
async leaveOpen() {
|
|
1120
1125
|
if (done) return;
|
package/dist/reporter-core.d.cts
CHANGED
|
@@ -221,6 +221,17 @@ interface RunRecord {
|
|
|
221
221
|
projectId?: string;
|
|
222
222
|
schemaVersion?: number;
|
|
223
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* What the platform froze into a run at `finish` and answers the event with (plune#1247): the verdict a
|
|
226
|
+
* release gate is made on, never recomputed. `counts` is by the status each result is counted under — a
|
|
227
|
+
* muted case's `failed` or `broken` is under `muted`, outside both — and an absent key is zero.
|
|
228
|
+
*/
|
|
229
|
+
interface FrozenSummary {
|
|
230
|
+
counts: Record<string, number>;
|
|
231
|
+
/** The expected cases that never reported. */
|
|
232
|
+
notRun: string[];
|
|
233
|
+
total: number;
|
|
234
|
+
}
|
|
224
235
|
/** Per-element verdicts from a batch. `conflict` means two runners disagreed about one result —
|
|
225
236
|
* the platform refuses to resolve that by overwriting, and neither do we. */
|
|
226
237
|
interface SubmitCounts {
|
|
@@ -357,8 +368,11 @@ interface RunSession {
|
|
|
357
368
|
add(result: PendingResult): Promise<void>;
|
|
358
369
|
/** Send whatever is buffered. Safe to call when nothing is. */
|
|
359
370
|
flush(): Promise<void>;
|
|
360
|
-
/**
|
|
361
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Send what is left and close the run. Resolves to the summary the platform froze at `finish` (plune#1247), or
|
|
373
|
+
* `undefined` when it answered with none: no run, a refused finish, a deployment older than the summary.
|
|
374
|
+
*/
|
|
375
|
+
finish(reason?: string): Promise<FrozenSummary | undefined>;
|
|
362
376
|
/** Send what is left and deliberately leave the run open — the shard case (AC-03). */
|
|
363
377
|
leaveOpen(): Promise<void>;
|
|
364
378
|
}
|
|
@@ -592,4 +606,4 @@ interface PlatformClient {
|
|
|
592
606
|
}
|
|
593
607
|
declare function createClient(opts: ClientOptions): PlatformClient;
|
|
594
608
|
|
|
595
|
-
export { type AssertionRecord, type Attachment, type AttemptError, type ClientFailureKind, type ClientOptions, type ClientOutcome, DEFAULT_FALLBACK_PATH, type DeclaredStep, type DeferredBatch, type DeferredResult, type EnvSettings, type Execution, type ExpectedEntry, type ExternalKeyKind, type FailedAttempt, type FailureDetail, type KeyRef, type PendingResult, type PlatformClient, RESULT_KEY_MAX, type ReporterConfig, type ResultFile, type ResultStatus, type ResultSubmission, type RunKind, type RunMeta, type RunRecord, type RunSession, type RunStats, type RunnerLocation, type SubmitCounts, UNSUPPORTED_VARS, type UnofferedWhy, appendBatch, ciCommit, createClient, errorContextOf, failureOf, filesOf, readEnv, repoRootOf, resultKey, startRun, webLink };
|
|
609
|
+
export { type AssertionRecord, type Attachment, type AttemptError, type ClientFailureKind, type ClientOptions, type ClientOutcome, DEFAULT_FALLBACK_PATH, type DeclaredStep, type DeferredBatch, type DeferredResult, type EnvSettings, type Execution, type ExpectedEntry, type ExternalKeyKind, type FailedAttempt, type FailureDetail, type FrozenSummary, type KeyRef, type PendingResult, type PlatformClient, RESULT_KEY_MAX, type ReporterConfig, type ResultFile, type ResultStatus, type ResultSubmission, type RunKind, type RunMeta, type RunRecord, type RunSession, type RunStats, type RunnerLocation, type SubmitCounts, UNSUPPORTED_VARS, type UnofferedWhy, appendBatch, ciCommit, createClient, errorContextOf, failureOf, filesOf, readEnv, repoRootOf, resultKey, startRun, webLink };
|
package/dist/reporter-core.d.ts
CHANGED
|
@@ -221,6 +221,17 @@ interface RunRecord {
|
|
|
221
221
|
projectId?: string;
|
|
222
222
|
schemaVersion?: number;
|
|
223
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* What the platform froze into a run at `finish` and answers the event with (plune#1247): the verdict a
|
|
226
|
+
* release gate is made on, never recomputed. `counts` is by the status each result is counted under — a
|
|
227
|
+
* muted case's `failed` or `broken` is under `muted`, outside both — and an absent key is zero.
|
|
228
|
+
*/
|
|
229
|
+
interface FrozenSummary {
|
|
230
|
+
counts: Record<string, number>;
|
|
231
|
+
/** The expected cases that never reported. */
|
|
232
|
+
notRun: string[];
|
|
233
|
+
total: number;
|
|
234
|
+
}
|
|
224
235
|
/** Per-element verdicts from a batch. `conflict` means two runners disagreed about one result —
|
|
225
236
|
* the platform refuses to resolve that by overwriting, and neither do we. */
|
|
226
237
|
interface SubmitCounts {
|
|
@@ -357,8 +368,11 @@ interface RunSession {
|
|
|
357
368
|
add(result: PendingResult): Promise<void>;
|
|
358
369
|
/** Send whatever is buffered. Safe to call when nothing is. */
|
|
359
370
|
flush(): Promise<void>;
|
|
360
|
-
/**
|
|
361
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Send what is left and close the run. Resolves to the summary the platform froze at `finish` (plune#1247), or
|
|
373
|
+
* `undefined` when it answered with none: no run, a refused finish, a deployment older than the summary.
|
|
374
|
+
*/
|
|
375
|
+
finish(reason?: string): Promise<FrozenSummary | undefined>;
|
|
362
376
|
/** Send what is left and deliberately leave the run open — the shard case (AC-03). */
|
|
363
377
|
leaveOpen(): Promise<void>;
|
|
364
378
|
}
|
|
@@ -592,4 +606,4 @@ interface PlatformClient {
|
|
|
592
606
|
}
|
|
593
607
|
declare function createClient(opts: ClientOptions): PlatformClient;
|
|
594
608
|
|
|
595
|
-
export { type AssertionRecord, type Attachment, type AttemptError, type ClientFailureKind, type ClientOptions, type ClientOutcome, DEFAULT_FALLBACK_PATH, type DeclaredStep, type DeferredBatch, type DeferredResult, type EnvSettings, type Execution, type ExpectedEntry, type ExternalKeyKind, type FailedAttempt, type FailureDetail, type KeyRef, type PendingResult, type PlatformClient, RESULT_KEY_MAX, type ReporterConfig, type ResultFile, type ResultStatus, type ResultSubmission, type RunKind, type RunMeta, type RunRecord, type RunSession, type RunStats, type RunnerLocation, type SubmitCounts, UNSUPPORTED_VARS, type UnofferedWhy, appendBatch, ciCommit, createClient, errorContextOf, failureOf, filesOf, readEnv, repoRootOf, resultKey, startRun, webLink };
|
|
609
|
+
export { type AssertionRecord, type Attachment, type AttemptError, type ClientFailureKind, type ClientOptions, type ClientOutcome, DEFAULT_FALLBACK_PATH, type DeclaredStep, type DeferredBatch, type DeferredResult, type EnvSettings, type Execution, type ExpectedEntry, type ExternalKeyKind, type FailedAttempt, type FailureDetail, type FrozenSummary, type KeyRef, type PendingResult, type PlatformClient, RESULT_KEY_MAX, type ReporterConfig, type ResultFile, type ResultStatus, type ResultSubmission, type RunKind, type RunMeta, type RunRecord, type RunSession, type RunStats, type RunnerLocation, type SubmitCounts, UNSUPPORTED_VARS, type UnofferedWhy, appendBatch, ciCommit, createClient, errorContextOf, failureOf, filesOf, readEnv, repoRootOf, resultKey, startRun, webLink };
|