@plune-ai/cli 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
  }
@@ -23,6 +31,54 @@ interface RunError {
23
31
  code: string;
24
32
  message: string;
25
33
  }
34
+ interface ManualStep {
35
+ action: string;
36
+ expected?: string;
37
+ }
38
+ interface ExternalKey {
39
+ kind: 'playwright-id' | 'path-title' | 'allure-history' | 'cairn-stable' | 'eval-id' | 'qase' | 'testrail';
40
+ value: string;
41
+ }
42
+ type ManualCase = {
43
+ type: 'manual';
44
+ steps: {
45
+ action: string;
46
+ expected?: string;
47
+ }[];
48
+ expected?: string;
49
+ };
50
+ type AutomatedCase = {
51
+ type: 'automated';
52
+ specRef: string;
53
+ };
54
+ type EvalCase = {
55
+ type: 'eval';
56
+ prompt: string;
57
+ goldenSetId?: string;
58
+ assertions: AssertionResultRecord[];
59
+ };
60
+ interface TestCaseBase {
61
+ schemaVersion: 2;
62
+ id: string;
63
+ externalKeys?: ExternalKey[];
64
+ is_flaky?: boolean;
65
+ is_muted?: boolean;
66
+ }
67
+ type TestCase = TestCaseBase & (ManualCase | AutomatedCase | EvalCase);
68
+ /**
69
+ * The two words a row may use to say the product was never judged (ADR 0045).
70
+ *
71
+ * Both are already statuses this system has — they are `RESULT_STATUSES` entries — and neither was
72
+ * reachable through this contract: a row could only report `passed`, `failed` or `broken`. So a
73
+ * runner whose row was stopped by something outside the test had exactly one word left, `failed`,
74
+ * and the word was wrong. A red build because a dependency fell over reads identically to a red
75
+ * build because the code is broken, and those are two different pieces of work.
76
+ *
77
+ * `invalid` is deliberately absent: Qase's `invalid` is our `broken`, and a second word for one
78
+ * idea would fork the contract at both ends.
79
+ */
80
+ declare const ROW_INCONCLUSIVE: readonly ["blocked", "skipped"];
81
+ type RowInconclusive = (typeof ROW_INCONCLUSIVE)[number];
26
82
  interface RowResult {
27
83
  vars: Record<string, unknown>;
28
84
  output: string | null;
@@ -31,6 +87,14 @@ interface RowResult {
31
87
  latency_ms?: number;
32
88
  error?: RunError;
33
89
  assertions: AssertionResultRecord[];
90
+ /**
91
+ * ADR 0045. Say it when the test was never judged, rather than letting `failed` stand for both
92
+ * "the environment broke" and "the code is wrong".
93
+ *
94
+ * Optional and purely additive: a producer that does not send it validates exactly as before,
95
+ * which is why `schemaVersion` stays 1.
96
+ */
97
+ inconclusive?: RowInconclusive;
34
98
  }
35
99
  interface EvalResult {
36
100
  id: string;
@@ -374,6 +438,8 @@ declare const runResultSchema: z.ZodObject<{
374
438
  passed: z.ZodNumber;
375
439
  failed: z.ZodNumber;
376
440
  errored: z.ZodNumber;
441
+ blocked: z.ZodOptional<z.ZodNumber>;
442
+ skipped: z.ZodOptional<z.ZodNumber>;
377
443
  cost_usd: z.ZodNumber;
378
444
  duration_ms: z.ZodNumber;
379
445
  }, z.core.$strip>;
@@ -404,6 +470,10 @@ declare const runResultSchema: z.ZodObject<{
404
470
  passed: z.ZodBoolean;
405
471
  }, z.core.$strip>>>;
406
472
  }, z.core.$strip>>;
473
+ inconclusive: z.ZodOptional<z.ZodEnum<{
474
+ blocked: "blocked";
475
+ skipped: "skipped";
476
+ }>>;
407
477
  }, z.core.$strip>>;
408
478
  passed: z.ZodBoolean;
409
479
  }, z.core.$strip>>;
@@ -417,6 +487,133 @@ type BinaryVerdict = z.infer<typeof binaryVerdictSchema>;
417
487
  /** Parse + validate an unknown value as a RunResult. Throws ZodError on failure. */
418
488
  declare function parseRunResult(value: unknown): ParsedRunResult;
419
489
 
490
+ /**
491
+ * Runtime validation for the TestCase contract — the JSON that `plune run import` emits
492
+ * or that the platform accepts on `POST /v1/test-cases`.
493
+ *
494
+ * The canonical TypeScript shape is owned by `./results.ts` (`TEST_CASE_SCHEMA_VERSION: 2`).
495
+ * This module is its zod mirror: the validator for anything that has to *ingest* that
496
+ * output rather than produce it. `plune sync` validates the local case file with it
497
+ * before uploading, and the Plune platform validates the same body on
498
+ * `POST /v1/test-cases` — one schema, both ends.
499
+ */
500
+ declare const TEST_CASE_SCHEMA_VERSION: TestCase['schemaVersion'];
501
+ declare const testCaseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
502
+ schemaVersion: z.ZodLiteral<2>;
503
+ id: z.ZodString;
504
+ externalKeys: z.ZodOptional<z.ZodArray<z.ZodObject<{
505
+ kind: z.ZodEnum<{
506
+ "playwright-id": "playwright-id";
507
+ "path-title": "path-title";
508
+ "allure-history": "allure-history";
509
+ "cairn-stable": "cairn-stable";
510
+ "eval-id": "eval-id";
511
+ qase: "qase";
512
+ testrail: "testrail";
513
+ }>;
514
+ value: z.ZodString;
515
+ }, z.core.$strip>>>;
516
+ is_flaky: z.ZodOptional<z.ZodBoolean>;
517
+ is_muted: z.ZodOptional<z.ZodBoolean>;
518
+ type: z.ZodLiteral<"manual">;
519
+ steps: z.ZodArray<z.ZodObject<{
520
+ action: z.ZodString;
521
+ expected: z.ZodOptional<z.ZodString>;
522
+ }, z.core.$strip>>;
523
+ expected: z.ZodOptional<z.ZodString>;
524
+ }, z.core.$strip>, z.ZodObject<{
525
+ schemaVersion: z.ZodLiteral<2>;
526
+ id: z.ZodString;
527
+ externalKeys: z.ZodOptional<z.ZodArray<z.ZodObject<{
528
+ kind: z.ZodEnum<{
529
+ "playwright-id": "playwright-id";
530
+ "path-title": "path-title";
531
+ "allure-history": "allure-history";
532
+ "cairn-stable": "cairn-stable";
533
+ "eval-id": "eval-id";
534
+ qase: "qase";
535
+ testrail: "testrail";
536
+ }>;
537
+ value: z.ZodString;
538
+ }, z.core.$strip>>>;
539
+ is_flaky: z.ZodOptional<z.ZodBoolean>;
540
+ is_muted: z.ZodOptional<z.ZodBoolean>;
541
+ type: z.ZodLiteral<"automated">;
542
+ specRef: z.ZodString;
543
+ }, z.core.$strip>, z.ZodObject<{
544
+ schemaVersion: z.ZodLiteral<2>;
545
+ id: z.ZodString;
546
+ externalKeys: z.ZodOptional<z.ZodArray<z.ZodObject<{
547
+ kind: z.ZodEnum<{
548
+ "playwright-id": "playwright-id";
549
+ "path-title": "path-title";
550
+ "allure-history": "allure-history";
551
+ "cairn-stable": "cairn-stable";
552
+ "eval-id": "eval-id";
553
+ qase: "qase";
554
+ testrail: "testrail";
555
+ }>;
556
+ value: z.ZodString;
557
+ }, z.core.$strip>>>;
558
+ is_flaky: z.ZodOptional<z.ZodBoolean>;
559
+ is_muted: z.ZodOptional<z.ZodBoolean>;
560
+ type: z.ZodLiteral<"eval">;
561
+ prompt: z.ZodString;
562
+ goldenSetId: z.ZodOptional<z.ZodString>;
563
+ assertions: z.ZodArray<z.ZodObject<{
564
+ type: z.ZodString;
565
+ passed: z.ZodBoolean;
566
+ score: z.ZodOptional<z.ZodNumber>;
567
+ reason: z.ZodOptional<z.ZodString>;
568
+ }, z.core.$strip>>;
569
+ }, z.core.$strip>], "type">;
570
+ /** Parse + validate an unknown value as a TestCase. Throws ZodError on failure. */
571
+ declare function parseTestCase(value: unknown): {
572
+ schemaVersion: 2;
573
+ id: string;
574
+ type: "manual";
575
+ steps: {
576
+ action: string;
577
+ expected?: string | undefined;
578
+ }[];
579
+ externalKeys?: {
580
+ kind: "playwright-id" | "path-title" | "allure-history" | "cairn-stable" | "eval-id" | "qase" | "testrail";
581
+ value: string;
582
+ }[] | undefined;
583
+ is_flaky?: boolean | undefined;
584
+ is_muted?: boolean | undefined;
585
+ expected?: string | undefined;
586
+ } | {
587
+ schemaVersion: 2;
588
+ id: string;
589
+ type: "automated";
590
+ specRef: string;
591
+ externalKeys?: {
592
+ kind: "playwright-id" | "path-title" | "allure-history" | "cairn-stable" | "eval-id" | "qase" | "testrail";
593
+ value: string;
594
+ }[] | undefined;
595
+ is_flaky?: boolean | undefined;
596
+ is_muted?: boolean | undefined;
597
+ } | {
598
+ schemaVersion: 2;
599
+ id: string;
600
+ type: "eval";
601
+ prompt: string;
602
+ assertions: {
603
+ type: string;
604
+ passed: boolean;
605
+ score?: number | undefined;
606
+ reason?: string | undefined;
607
+ }[];
608
+ externalKeys?: {
609
+ kind: "playwright-id" | "path-title" | "allure-history" | "cairn-stable" | "eval-id" | "qase" | "testrail";
610
+ value: string;
611
+ }[] | undefined;
612
+ is_flaky?: boolean | undefined;
613
+ is_muted?: boolean | undefined;
614
+ goldenSetId?: string | undefined;
615
+ };
616
+
420
617
  interface IngestDeps {
421
618
  /** The Cairn run directory to read. Omit it and the newest run under `runsDir` is used. */
422
619
  dir?: string;
@@ -485,4 +682,4 @@ interface ReadCairnRunDeps {
485
682
  */
486
683
  declare function readCairnRun(dir: string, deps?: ReadCairnRunDeps): Promise<CairnIngestPayload>;
487
684
 
488
- export { type AssertionResultRecord, type BinaryVerdict, type CairnIngestCase, type CairnIngestPayload, type Config, type EvalResult, type IngestDeps, type IngestResult, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, formatIngestResult, handleIngest as ingest, parseRunResult, readCairnRun, reportIngestFailure, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync };
685
+ export { type AssertionResultRecord, type BinaryVerdict, type CairnIngestCase, type CairnIngestPayload, type Config, type EvalResult, type ExternalKey, type IngestDeps, type IngestResult, type ManualStep, NotLoggedInError, type ParsedRunResult, RUN_RESULT_SCHEMA_VERSION, type RowResult, type RunError, type RunOptions, type RunResult, type Summary, type SyncDeps, SyncFileError, SyncHttpError, SyncNetworkError, type SyncResult, TEST_CASE_SCHEMA_VERSION, type TestCase, TokenRejectedError, type Usage, assertionConfigSchema, assertionResultRecordSchema, binaryVerdictSchema, formatIngestResult, handleIngest as ingest, parseRunResult, parseTestCase, readCairnRun, reportIngestFailure, reportSyncFailure, handleRun as run, runResultSchema, handleSync as sync, testCaseSchema };
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 classify(row) {
1362
- if (row.error !== void 0) return "errored";
1363
- if (row.assertions.some((a) => !a.passed)) return "failed";
1364
- return "passed";
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) => classify(r) === "passed");
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,
@@ -1499,59 +1558,64 @@ import * as fs6 from "fs";
1499
1558
  import * as path5 from "path";
1500
1559
 
1501
1560
  // src/types/run-result-schema.ts
1502
- import { z as z2 } from "zod";
1561
+ import { z as z3 } from "zod";
1503
1562
  var RUN_RESULT_SCHEMA_VERSION = 1;
1504
- var binaryVerdictSchema = z2.object({
1505
- question: z2.string().min(1),
1506
- passed: z2.boolean()
1563
+ var binaryVerdictSchema = z3.object({
1564
+ question: z3.string().min(1),
1565
+ passed: z3.boolean()
1507
1566
  });
1508
- var assertionResultRecordSchema = z2.object({
1509
- type: z2.string(),
1510
- passed: z2.boolean(),
1511
- score: z2.number().optional(),
1512
- reason: z2.string().optional(),
1513
- binaryVerdicts: z2.array(binaryVerdictSchema).optional()
1567
+ var assertionResultRecordSchema2 = z3.object({
1568
+ type: z3.string(),
1569
+ passed: z3.boolean(),
1570
+ score: z3.number().optional(),
1571
+ reason: z3.string().optional(),
1572
+ binaryVerdicts: z3.array(binaryVerdictSchema).optional()
1514
1573
  });
1515
- var usageSchema = z2.object({
1516
- input_tokens: z2.number(),
1517
- output_tokens: z2.number(),
1518
- cost_usd: z2.number()
1574
+ var usageSchema = z3.object({
1575
+ input_tokens: z3.number(),
1576
+ output_tokens: z3.number(),
1577
+ cost_usd: z3.number()
1519
1578
  });
1520
- var runErrorSchema = z2.object({
1521
- code: z2.string(),
1522
- message: z2.string()
1579
+ var runErrorSchema = z3.object({
1580
+ code: z3.string(),
1581
+ message: z3.string()
1523
1582
  });
1524
- var rowResultSchema = z2.object({
1525
- vars: z2.record(z2.string(), z2.unknown()),
1526
- output: z2.string().nullable(),
1527
- cached: z2.boolean(),
1583
+ var rowResultSchema = z3.object({
1584
+ vars: z3.record(z3.string(), z3.unknown()),
1585
+ output: z3.string().nullable(),
1586
+ cached: z3.boolean(),
1528
1587
  usage: usageSchema.optional(),
1529
- latency_ms: z2.number().optional(),
1588
+ latency_ms: z3.number().optional(),
1530
1589
  error: runErrorSchema.optional(),
1531
- assertions: z2.array(assertionResultRecordSchema)
1590
+ assertions: z3.array(assertionResultRecordSchema2),
1591
+ /** ADR 0045. Optional and additive — the same enum the platform validates on `POST /v1/runs`. */
1592
+ inconclusive: z3.enum(ROW_INCONCLUSIVE).optional()
1532
1593
  });
1533
- var summarySchema = z2.object({
1534
- total: z2.number(),
1535
- passed: z2.number(),
1536
- failed: z2.number(),
1537
- errored: z2.number(),
1538
- cost_usd: z2.number(),
1539
- duration_ms: z2.number()
1594
+ var summarySchema = z3.object({
1595
+ total: z3.number(),
1596
+ passed: z3.number(),
1597
+ failed: z3.number(),
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(),
1602
+ cost_usd: z3.number(),
1603
+ duration_ms: z3.number()
1540
1604
  });
1541
- var evalResultSchema = z2.object({
1542
- id: z2.string(),
1543
- tags: z2.array(z2.string()),
1544
- rows: z2.array(rowResultSchema),
1545
- passed: z2.boolean()
1605
+ var evalResultSchema = z3.object({
1606
+ id: z3.string(),
1607
+ tags: z3.array(z3.string()),
1608
+ rows: z3.array(rowResultSchema),
1609
+ passed: z3.boolean()
1546
1610
  });
1547
- var runResultSchema = z2.object({
1548
- schemaVersion: z2.literal(RUN_RESULT_SCHEMA_VERSION),
1549
- plune_version: z2.string(),
1550
- started_at: z2.string(),
1551
- finished_at: z2.string(),
1552
- config_hash: z2.string(),
1611
+ var runResultSchema = z3.object({
1612
+ schemaVersion: z3.literal(RUN_RESULT_SCHEMA_VERSION),
1613
+ plune_version: z3.string(),
1614
+ started_at: z3.string(),
1615
+ finished_at: z3.string(),
1616
+ config_hash: z3.string(),
1553
1617
  summary: summarySchema,
1554
- evals: z2.array(evalResultSchema)
1618
+ evals: z3.array(evalResultSchema)
1555
1619
  });
1556
1620
  function parseRunResult(value) {
1557
1621
  return runResultSchema.parse(value);
@@ -1934,17 +1998,20 @@ export {
1934
1998
  SyncFileError,
1935
1999
  SyncHttpError,
1936
2000
  SyncNetworkError,
2001
+ TEST_CASE_SCHEMA_VERSION,
1937
2002
  TokenRejectedError,
1938
2003
  assertionConfigSchema,
1939
- assertionResultRecordSchema,
2004
+ assertionResultRecordSchema2 as assertionResultRecordSchema,
1940
2005
  binaryVerdictSchema,
1941
2006
  formatIngestResult,
1942
2007
  handleIngest as ingest,
1943
2008
  parseRunResult,
2009
+ parseTestCase,
1944
2010
  readCairnRun,
1945
2011
  reportIngestFailure,
1946
2012
  reportSyncFailure,
1947
2013
  handleRun as run,
1948
2014
  runResultSchema,
1949
- handleSync as sync
2015
+ handleSync as sync,
2016
+ testCaseSchema
1950
2017
  };