@testchimp/cli 0.1.30 → 0.1.31

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 CHANGED
@@ -42,10 +42,17 @@ testchimp get-requirement-coverage --branch-name main --help
42
42
  testchimp create-user-story --platform-file-path plans/stories/foo.md --title "Checkout"
43
43
  testchimp list-screen-states --json-input '{}'
44
44
  testchimp upsert-screen-states --json-input '{"screenStates":[{"screen":"Checkout","states":["empty","filled"]}]}'
45
+ testchimp list-perf-runs --testchimp-id TC-123 --kind JOURNEY --limit 20
46
+ testchimp get-perf-run --run-id 01ABC --include-raw
47
+ testchimp promote-perf-baseline --run-id 01ABC --env-class CI
48
+ testchimp compare-perf-to-baseline --run-id 01ABC --max-p95-regression-percent 10
49
+ testchimp list-related-perf-tests --scenario-titles "Checkout,Refund"
50
+ testchimp list-api-operation-interactions --operation-id 01XYZ --interaction-type REAL --limit 100
45
51
  ```
46
52
 
47
53
  - **stdout:** API response JSON.
48
54
  - **stderr:** progress for `provision-ephemeral-environment-and-wait` (“still waiting…” polls).
55
+ - **performance gate:** `compare-perf-to-baseline` still prints its JSON response but exits nonzero when `regressed` is `true` (top-level or under `comparison`).
49
56
  - **Flags:** default for each subcommand; **`--json-input '<json>'`** or **`--json-input @file.json`** merges over flags (JSON wins on conflicts). Use JSON for nested bodies (e.g. TrueCoverage scopes).
50
57
 
51
58
  ## Migration from `testchimp-mcp-client`
@@ -8,6 +8,13 @@ import { TOOL_DEFINITIONS } from "../core/tools.js";
8
8
  import { resolveGitHeadSha } from "../core/gitSha.js";
9
9
  import { PACKAGE_VERSION } from "../core/version.js";
10
10
  export { PACKAGE_VERSION };
11
+ /** True when ComparePerfToBaselineResponse (or a flat PerfComparison) reports a regression. */
12
+ function isPerfComparisonRegressed(parsed) {
13
+ if (!parsed || typeof parsed !== "object")
14
+ return false;
15
+ const body = parsed;
16
+ return body.regressed === true || body.comparison?.regressed === true;
17
+ }
11
18
  function parseRecordTypesCsv(raw) {
12
19
  return String(raw)
13
20
  .split(",")
@@ -23,9 +30,11 @@ function parseRecordTypesCsv(raw) {
23
30
  return "smart_test";
24
31
  if (s === "manual")
25
32
  return "manual";
33
+ if (s === "perf" || s === "perftest" || s === "perf_test")
34
+ return "perf_test";
26
35
  return s;
27
36
  })
28
- .filter((v) => v === "smart_test" || v === "manual");
37
+ .filter((v) => v === "smart_test" || v === "manual" || v === "perf_test");
29
38
  }
30
39
  function parseJsonInput(raw) {
31
40
  if (raw == null || raw.trim() === "")
@@ -132,8 +141,9 @@ export function buildCliProgram() {
132
141
  .option("--environment <s>")
133
142
  .option("--branch-name <s>")
134
143
  .option("--platform <web|ios|android>")
135
- .option("--record-types <csv>", "coverage sources: smart_test,manual (aliases: automated,smarttest)")
144
+ .option("--record-types <csv>", "coverage sources: smart_test,manual,perf_test (aliases: automated,smarttest,perf)")
136
145
  .option("--include-manual", "include manual session coverage in addition to automated SmartTests")
146
+ .option("--include-perf", "include PERF_TEST journey coverage in addition to automated SmartTests")
137
147
  .option("--manual-only", "manual-only coverage (no automated)")
138
148
  .option("--lifecycle-statuses <csv>", "scenario lifecycle allowlist (e.g. ready or draft,ready)")
139
149
  .option("--limit <n>", "top N gaps after filter+rank into rankedScenarios (max 200)", (v) => parseInt(v, 10))
@@ -160,6 +170,9 @@ export function buildCliProgram() {
160
170
  recordTypes = Array.from(new Set([...(recordTypes ?? ["smart_test"]), "manual"]));
161
171
  if (opts.manualOnly)
162
172
  recordTypes = ["manual"];
173
+ if (opts.includePerf) {
174
+ recordTypes = Array.from(new Set([...(recordTypes ?? ["smart_test"]), "perf_test"]));
175
+ }
163
176
  if (recordTypes && recordTypes.length > 0)
164
177
  body.recordTypes = recordTypes;
165
178
  if (opts.lifecycleStatuses) {
@@ -1302,6 +1315,174 @@ export function buildCliProgram() {
1302
1315
  const merged = mergeBodies(body, opts.jsonInput);
1303
1316
  console.log(await runTool("get-api-operation-detail", merged, { postMcp }));
1304
1317
  });
1318
+ program
1319
+ .command("list-perf-runs")
1320
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-perf-runs").description)
1321
+ .addOption(jsonInputOption())
1322
+ .option("--testchimp-id <id>")
1323
+ .option("--kind <kind>", "JOURNEY | COMPOSITE")
1324
+ .option("--branch-name <name>")
1325
+ .option("--profile <name>")
1326
+ .option("--dataset <name>")
1327
+ .option("--llm-mode <mode>")
1328
+ .option("--environment <name>")
1329
+ .option("--limit <n>", "Maximum results", (v) => Number(v))
1330
+ .option("--offset <n>", "Pagination offset", (v) => Number(v))
1331
+ .action(async (opts) => {
1332
+ const body = {};
1333
+ if (opts.testchimpId)
1334
+ body.testchimpId = String(opts.testchimpId);
1335
+ if (opts.kind)
1336
+ body.kind = String(opts.kind);
1337
+ if (opts.branchName)
1338
+ body.branchName = String(opts.branchName);
1339
+ if (opts.profile)
1340
+ body.profile = String(opts.profile);
1341
+ if (opts.dataset)
1342
+ body.dataset = String(opts.dataset);
1343
+ if (opts.llmMode)
1344
+ body.llmMode = String(opts.llmMode);
1345
+ if (opts.environment)
1346
+ body.environment = String(opts.environment);
1347
+ if (opts.limit != null)
1348
+ body.limit = opts.limit;
1349
+ if (opts.offset != null)
1350
+ body.offset = opts.offset;
1351
+ console.log(await runTool("list-perf-runs", mergeBodies(body, opts.jsonInput), { postMcp }));
1352
+ });
1353
+ program
1354
+ .command("get-perf-run")
1355
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "get-perf-run").description)
1356
+ .addOption(jsonInputOption())
1357
+ .option("--run-id <id>")
1358
+ .option("--include-raw", "Include the raw performance payload")
1359
+ .action(async (opts) => {
1360
+ const body = {};
1361
+ if (opts.runId)
1362
+ body.runId = String(opts.runId);
1363
+ if (opts.includeRaw)
1364
+ body.includeRaw = true;
1365
+ console.log(await runTool("get-perf-run", mergeBodies(body, opts.jsonInput), { postMcp }));
1366
+ });
1367
+ program
1368
+ .command("list-perf-baselines")
1369
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-perf-baselines").description)
1370
+ .addOption(jsonInputOption())
1371
+ .option("--testchimp-id <id>")
1372
+ .option("--limit <n>", "Maximum results", (v) => Number(v))
1373
+ .option("--offset <n>", "Pagination offset", (v) => Number(v))
1374
+ .action(async (opts) => {
1375
+ const body = {};
1376
+ if (opts.testchimpId)
1377
+ body.testchimpId = String(opts.testchimpId);
1378
+ if (opts.limit != null)
1379
+ body.limit = opts.limit;
1380
+ if (opts.offset != null)
1381
+ body.offset = opts.offset;
1382
+ console.log(await runTool("list-perf-baselines", mergeBodies(body, opts.jsonInput), { postMcp }));
1383
+ });
1384
+ addAgentTraceabilityOptions(program
1385
+ .command("promote-perf-baseline")
1386
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "promote-perf-baseline").description)
1387
+ .addOption(jsonInputOption())
1388
+ .option("--run-id <id>")
1389
+ .option("--env-class <name>")).action(async (opts) => {
1390
+ const body = {
1391
+ ...collectAgentTraceabilityFlags(opts),
1392
+ };
1393
+ if (opts.runId)
1394
+ body.runId = String(opts.runId);
1395
+ if (opts.envClass)
1396
+ body.envClass = String(opts.envClass);
1397
+ console.log(await runTool("promote-perf-baseline", mergeBodies(body, opts.jsonInput), { postMcp }));
1398
+ });
1399
+ program
1400
+ .command("compare-perf-to-baseline")
1401
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "compare-perf-to-baseline").description)
1402
+ .addOption(jsonInputOption())
1403
+ .option("--run-id <id>")
1404
+ .option("--testchimp-id <id>")
1405
+ .option("--profile <name>")
1406
+ .option("--dataset <name>")
1407
+ .option("--llm-mode <mode>")
1408
+ .option("--environment <name>")
1409
+ .option("--env-class <name>", "Baseline environment class (required)")
1410
+ .option("--max-p95-regression-percent <n>", "Allowed p95 regression percent", (v) => Number(v))
1411
+ .option("--max-fail-rate-increase <n>", "Allowed fail-rate increase", (v) => Number(v))
1412
+ .action(async (opts) => {
1413
+ const body = {};
1414
+ if (opts.runId)
1415
+ body.runId = String(opts.runId);
1416
+ if (opts.testchimpId)
1417
+ body.testchimpId = String(opts.testchimpId);
1418
+ if (opts.profile)
1419
+ body.profile = String(opts.profile);
1420
+ if (opts.dataset)
1421
+ body.dataset = String(opts.dataset);
1422
+ if (opts.llmMode)
1423
+ body.llmMode = String(opts.llmMode);
1424
+ if (opts.environment)
1425
+ body.environment = String(opts.environment);
1426
+ if (opts.envClass)
1427
+ body.envClass = String(opts.envClass);
1428
+ if (opts.maxP95RegressionPercent != null) {
1429
+ body.maxP95RegressionPercent = opts.maxP95RegressionPercent;
1430
+ }
1431
+ if (opts.maxFailRateIncrease != null)
1432
+ body.maxFailRateIncrease = opts.maxFailRateIncrease;
1433
+ const out = await runTool("compare-perf-to-baseline", mergeBodies(body, opts.jsonInput), { postMcp });
1434
+ // Always print response JSON; gate CI on regressed after stdout flush.
1435
+ console.log(out);
1436
+ try {
1437
+ if (isPerfComparisonRegressed(JSON.parse(out)))
1438
+ process.exitCode = 1;
1439
+ }
1440
+ catch {
1441
+ /* non-JSON responses still printed; leave exit 0 unless runTool threw */
1442
+ }
1443
+ });
1444
+ program
1445
+ .command("list-related-perf-tests")
1446
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-related-perf-tests").description)
1447
+ .addOption(jsonInputOption())
1448
+ .option("--scenario-titles <csv>", "Comma-separated scenario titles")
1449
+ .option("--testchimp-ids <csv>", "Comma-separated TestChimp ids")
1450
+ .option("--no-include-composites", "Exclude COMPOSITE tests")
1451
+ .option("--limit <n>", "Maximum results (max 100)", (v) => Number(v))
1452
+ .action(async (opts) => {
1453
+ const body = {
1454
+ includeComposites: opts.includeComposites,
1455
+ };
1456
+ if (opts.scenarioTitles) {
1457
+ body.scenarioTitles = String(opts.scenarioTitles).split(",").map((s) => s.trim()).filter(Boolean);
1458
+ }
1459
+ if (opts.testchimpIds) {
1460
+ body.testchimpIds = String(opts.testchimpIds).split(",").map((s) => s.trim()).filter(Boolean);
1461
+ }
1462
+ if (opts.limit != null)
1463
+ body.limit = opts.limit;
1464
+ console.log(await runTool("list-related-perf-tests", mergeBodies(body, opts.jsonInput), { postMcp }));
1465
+ });
1466
+ program
1467
+ .command("list-api-operation-interactions")
1468
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-api-operation-interactions").description)
1469
+ .addOption(jsonInputOption())
1470
+ .option("--test-id <id>")
1471
+ .option("--operation-id <id>")
1472
+ .option("--interaction-type <type>", "REAL | MOCKED", "REAL")
1473
+ .option("--limit <n>", "Maximum results (max 100)", (v) => Number(v))
1474
+ .action(async (opts) => {
1475
+ const body = {
1476
+ interactionType: opts.interactionType,
1477
+ };
1478
+ if (opts.testId)
1479
+ body.testId = String(opts.testId);
1480
+ if (opts.operationId)
1481
+ body.operationId = String(opts.operationId);
1482
+ if (opts.limit != null)
1483
+ body.limit = opts.limit;
1484
+ console.log(await runTool("list-api-operation-interactions", mergeBodies(body, opts.jsonInput), { postMcp }));
1485
+ });
1305
1486
  program.on("--help", () => {
1306
1487
  /* default */
1307
1488
  });
@@ -25,8 +25,10 @@ export declare const listCoverageInput: z.ZodObject<{
25
25
  recordTypes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
26
26
  smart_test: "smart_test";
27
27
  manual: "manual";
28
+ perf_test: "perf_test";
28
29
  SMART_TEST: "SMART_TEST";
29
30
  MANUAL: "MANUAL";
31
+ PERF_TEST: "PERF_TEST";
30
32
  }>>>;
31
33
  scenarioLifecycleStatuses: z.ZodOptional<z.ZodArray<z.ZodString>>;
32
34
  limit: z.ZodOptional<z.ZodNumber>;
@@ -1495,3 +1497,95 @@ export declare const getApiOperationDetailInput: z.ZodObject<{
1495
1497
  includeManual: z.ZodOptional<z.ZodBoolean>;
1496
1498
  includeRemoved: z.ZodOptional<z.ZodBoolean>;
1497
1499
  }, z.core.$strip>;
1500
+ /** Performance run kind persisted by the Phase 2 performance API. */
1501
+ export declare const perfRunKindSchema: z.ZodEnum<{
1502
+ JOURNEY: "JOURNEY";
1503
+ COMPOSITE: "COMPOSITE";
1504
+ }>;
1505
+ export declare const listPerfRunsInput: z.ZodObject<{
1506
+ testchimpId: z.ZodOptional<z.ZodString>;
1507
+ kind: z.ZodOptional<z.ZodEnum<{
1508
+ JOURNEY: "JOURNEY";
1509
+ COMPOSITE: "COMPOSITE";
1510
+ }>>;
1511
+ branchName: z.ZodOptional<z.ZodString>;
1512
+ profile: z.ZodOptional<z.ZodString>;
1513
+ dataset: z.ZodOptional<z.ZodString>;
1514
+ llmMode: z.ZodOptional<z.ZodString>;
1515
+ environment: z.ZodOptional<z.ZodString>;
1516
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1517
+ offset: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1518
+ }, z.core.$strip>;
1519
+ export declare const getPerfRunInput: z.ZodObject<{
1520
+ runId: z.ZodString;
1521
+ includeRaw: z.ZodOptional<z.ZodBoolean>;
1522
+ }, z.core.$strip>;
1523
+ export declare const listPerfBaselinesInput: z.ZodObject<{
1524
+ testchimpId: z.ZodOptional<z.ZodString>;
1525
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1526
+ offset: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1527
+ }, z.core.$strip>;
1528
+ export declare const promotePerfBaselineInput: z.ZodObject<{
1529
+ runId: z.ZodString;
1530
+ envClass: z.ZodString;
1531
+ workflowId: z.ZodOptional<z.ZodString>;
1532
+ workflowExecutionId: z.ZodOptional<z.ZodString>;
1533
+ policyFile: z.ZodOptional<z.ZodString>;
1534
+ policyVersion: z.ZodOptional<z.ZodString>;
1535
+ gitSha: z.ZodOptional<z.ZodString>;
1536
+ actorType: z.ZodOptional<z.ZodEnum<{
1537
+ LOCAL_AGENT: "LOCAL_AGENT";
1538
+ CLOUD_AGENT: "CLOUD_AGENT";
1539
+ "local-agent": "local-agent";
1540
+ "cloud-agent": "cloud-agent";
1541
+ }>>;
1542
+ userId: z.ZodOptional<z.ZodString>;
1543
+ branchName: z.ZodOptional<z.ZodString>;
1544
+ agentModel: z.ZodOptional<z.ZodString>;
1545
+ skillVersion: z.ZodOptional<z.ZodString>;
1546
+ cliVersion: z.ZodOptional<z.ZodString>;
1547
+ agentTraceability: z.ZodOptional<z.ZodObject<{
1548
+ workflowId: z.ZodOptional<z.ZodString>;
1549
+ workflowExecutionId: z.ZodOptional<z.ZodString>;
1550
+ policyFile: z.ZodOptional<z.ZodString>;
1551
+ policyVersion: z.ZodOptional<z.ZodString>;
1552
+ gitSha: z.ZodOptional<z.ZodString>;
1553
+ actorType: z.ZodOptional<z.ZodEnum<{
1554
+ LOCAL_AGENT: "LOCAL_AGENT";
1555
+ CLOUD_AGENT: "CLOUD_AGENT";
1556
+ "local-agent": "local-agent";
1557
+ "cloud-agent": "cloud-agent";
1558
+ }>>;
1559
+ userId: z.ZodOptional<z.ZodString>;
1560
+ branchName: z.ZodOptional<z.ZodString>;
1561
+ agentModel: z.ZodOptional<z.ZodString>;
1562
+ skillVersion: z.ZodOptional<z.ZodString>;
1563
+ cliVersion: z.ZodOptional<z.ZodString>;
1564
+ }, z.core.$strict>>;
1565
+ }, z.core.$strip>;
1566
+ export declare const comparePerfToBaselineInput: z.ZodObject<{
1567
+ runId: z.ZodOptional<z.ZodString>;
1568
+ testchimpId: z.ZodOptional<z.ZodString>;
1569
+ profile: z.ZodOptional<z.ZodString>;
1570
+ dataset: z.ZodOptional<z.ZodString>;
1571
+ llmMode: z.ZodOptional<z.ZodString>;
1572
+ environment: z.ZodOptional<z.ZodString>;
1573
+ envClass: z.ZodString;
1574
+ maxP95RegressionPercent: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1575
+ maxFailRateIncrease: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1576
+ }, z.core.$strip>;
1577
+ export declare const listRelatedPerfTestsInput: z.ZodObject<{
1578
+ scenarioTitles: z.ZodOptional<z.ZodArray<z.ZodString>>;
1579
+ testchimpIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
1580
+ includeComposites: z.ZodDefault<z.ZodBoolean>;
1581
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1582
+ }, z.core.$strip>;
1583
+ export declare const listApiOperationInteractionsInput: z.ZodObject<{
1584
+ testId: z.ZodOptional<z.ZodString>;
1585
+ operationId: z.ZodOptional<z.ZodString>;
1586
+ interactionType: z.ZodDefault<z.ZodEnum<{
1587
+ REAL: "REAL";
1588
+ MOCKED: "MOCKED";
1589
+ }>>;
1590
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1591
+ }, z.core.$strip>;
@@ -6,7 +6,14 @@ export const scopeSchema = z
6
6
  })
7
7
  .optional();
8
8
  const executionPlatformSchema = z.enum(["web", "ios", "android"]);
9
- const requirementCoverageRecordTypeSchema = z.enum(["smart_test", "manual", "SMART_TEST", "MANUAL"]);
9
+ const requirementCoverageRecordTypeSchema = z.enum([
10
+ "smart_test",
11
+ "manual",
12
+ "perf_test",
13
+ "SMART_TEST",
14
+ "MANUAL",
15
+ "PERF_TEST",
16
+ ]);
10
17
  export const executionJobDimensionFilterSchema = z.object({
11
18
  dimension: z.string().min(1),
12
19
  values: z.array(z.string()).min(1),
@@ -23,7 +30,8 @@ export const listCoverageInput = z.object({
23
30
  * Which coverage sources to include.
24
31
  *
25
32
  * Omit for legacy default: SMART_TEST only.
26
- * When provided, send proto enum names ("SMART_TEST", "MANUAL") or CLI-friendly aliases ("smart_test", "manual").
33
+ * When provided, send proto enum names ("SMART_TEST", "MANUAL", "PERF_TEST")
34
+ * or CLI-friendly aliases ("smart_test", "manual", "perf_test").
27
35
  */
28
36
  recordTypes: z.array(requirementCoverageRecordTypeSchema).optional(),
29
37
  /** Allowlist of scenario lifecycle statuses (e.g. ["ready"] or ["draft","ready"]). Empty/omit = no status filter. */
@@ -848,3 +856,81 @@ export const getApiOperationDetailInput = z
848
856
  "or --root-file-path/--service-key with --http-method and --path-template",
849
857
  });
850
858
  });
859
+ /** Performance run kind persisted by the Phase 2 performance API. */
860
+ export const perfRunKindSchema = z.enum(["JOURNEY", "COMPOSITE"]);
861
+ export const listPerfRunsInput = z.object({
862
+ testchimpId: z.string().min(1).optional(),
863
+ kind: perfRunKindSchema.optional(),
864
+ branchName: z.string().min(1).optional(),
865
+ profile: z.string().min(1).optional(),
866
+ dataset: z.string().min(1).optional(),
867
+ llmMode: z.string().min(1).optional(),
868
+ environment: z.string().min(1).optional(),
869
+ limit: z.coerce.number().int().positive().max(100).optional(),
870
+ offset: z.coerce.number().int().nonnegative().optional(),
871
+ });
872
+ export const getPerfRunInput = z.object({
873
+ runId: z.string().min(1),
874
+ includeRaw: z.boolean().optional(),
875
+ });
876
+ export const listPerfBaselinesInput = z.object({
877
+ testchimpId: z.string().min(1).optional(),
878
+ limit: z.coerce.number().int().positive().max(100).optional(),
879
+ offset: z.coerce.number().int().nonnegative().optional(),
880
+ });
881
+ export const promotePerfBaselineInput = z
882
+ .object({
883
+ runId: z.string().min(1),
884
+ envClass: z.string().min(1),
885
+ })
886
+ .merge(agentTraceabilityFieldsSchema);
887
+ export const comparePerfToBaselineInput = z
888
+ .object({
889
+ runId: z.string().min(1).optional(),
890
+ testchimpId: z.string().min(1).optional(),
891
+ profile: z.string().min(1).optional(),
892
+ dataset: z.string().min(1).optional(),
893
+ llmMode: z.string().min(1).optional(),
894
+ environment: z.string().min(1).optional(),
895
+ envClass: z.string().min(1),
896
+ maxP95RegressionPercent: z.coerce.number().nonnegative().optional(),
897
+ maxFailRateIncrease: z.coerce.number().nonnegative().optional(),
898
+ })
899
+ .superRefine((v, ctx) => {
900
+ if (!(v.runId ?? "").trim() && !(v.testchimpId ?? "").trim()) {
901
+ ctx.addIssue({
902
+ code: z.ZodIssueCode.custom,
903
+ message: "Provide runId or testchimpId",
904
+ });
905
+ }
906
+ });
907
+ export const listRelatedPerfTestsInput = z
908
+ .object({
909
+ scenarioTitles: z.array(z.string().min(1)).min(1).optional(),
910
+ testchimpIds: z.array(z.string().min(1)).min(1).optional(),
911
+ includeComposites: z.boolean().default(true),
912
+ limit: z.coerce.number().int().positive().max(100).optional(),
913
+ })
914
+ .superRefine((v, ctx) => {
915
+ if (!v.scenarioTitles?.length && !v.testchimpIds?.length) {
916
+ ctx.addIssue({
917
+ code: z.ZodIssueCode.custom,
918
+ message: "Provide scenarioTitles and/or testchimpIds",
919
+ });
920
+ }
921
+ });
922
+ export const listApiOperationInteractionsInput = z
923
+ .object({
924
+ testId: z.string().min(1).optional(),
925
+ operationId: z.string().min(1).optional(),
926
+ interactionType: z.enum(["REAL", "MOCKED"]).default("REAL"),
927
+ limit: z.coerce.number().int().positive().max(100).optional(),
928
+ })
929
+ .superRefine((v, ctx) => {
930
+ if (!(v.testId ?? "").trim() && !(v.operationId ?? "").trim()) {
931
+ ctx.addIssue({
932
+ code: z.ZodIssueCode.custom,
933
+ message: "Provide testId and/or operationId",
934
+ });
935
+ }
936
+ });
@@ -51,6 +51,8 @@ function listCoverageBody(args) {
51
51
  return "MANUAL";
52
52
  if (raw === "smart_test")
53
53
  return "SMART_TEST";
54
+ if (raw === "perf_test")
55
+ return "PERF_TEST";
54
56
  return raw;
55
57
  });
56
58
  body.recordTypes = normalized;
@@ -1241,6 +1243,143 @@ export const TOOL_DEFINITIONS = [
1241
1243
  return postMcp("/api/mcp/get_api_operation_detail", body);
1242
1244
  },
1243
1245
  },
1246
+ {
1247
+ kebab: "list-perf-runs",
1248
+ description: "List performance runs, optionally filtered by TestChimp id, JOURNEY/COMPOSITE kind, branch, profile, dataset, LLM mode, or environment.",
1249
+ inputSchema: S.listPerfRunsInput,
1250
+ execute: async (args, { postMcp }) => {
1251
+ const a = args;
1252
+ const body = {};
1253
+ if (a.testchimpId)
1254
+ body.testchimpId = a.testchimpId;
1255
+ if (a.kind)
1256
+ body.kind = a.kind;
1257
+ if (a.branchName)
1258
+ body.branchName = a.branchName;
1259
+ if (a.profile)
1260
+ body.profile = a.profile;
1261
+ if (a.dataset)
1262
+ body.dataset = a.dataset;
1263
+ if (a.llmMode)
1264
+ body.llmMode = a.llmMode;
1265
+ if (a.environment)
1266
+ body.environment = a.environment;
1267
+ if (a.limit != null)
1268
+ body.limit = a.limit;
1269
+ if (a.offset != null)
1270
+ body.offset = a.offset;
1271
+ return postMcp("/api/mcp/list_perf_runs", body);
1272
+ },
1273
+ },
1274
+ {
1275
+ kebab: "get-perf-run",
1276
+ description: "Fetch one performance run by runId; set includeRaw to include its raw payload.",
1277
+ inputSchema: S.getPerfRunInput,
1278
+ execute: async (args, { postMcp }) => {
1279
+ const a = args;
1280
+ return postMcp("/api/mcp/get_perf_run", {
1281
+ runId: a.runId,
1282
+ ...(a.includeRaw != null ? { includeRaw: a.includeRaw } : {}),
1283
+ });
1284
+ },
1285
+ },
1286
+ {
1287
+ kebab: "list-perf-baselines",
1288
+ description: "List promoted performance baselines, optionally filtered by TestChimp id.",
1289
+ inputSchema: S.listPerfBaselinesInput,
1290
+ execute: async (args, { postMcp }) => {
1291
+ const a = args;
1292
+ const body = {};
1293
+ if (a.testchimpId)
1294
+ body.testchimpId = a.testchimpId;
1295
+ if (a.limit != null)
1296
+ body.limit = a.limit;
1297
+ if (a.offset != null)
1298
+ body.offset = a.offset;
1299
+ return postMcp("/api/mcp/list_perf_baselines", body);
1300
+ },
1301
+ },
1302
+ {
1303
+ kebab: "promote-perf-baseline",
1304
+ description: "Promote a performance run as the baseline for an environment class. Optional agent traceability records the mutation.",
1305
+ inputSchema: S.promotePerfBaselineInput,
1306
+ execute: async (args, { postMcp }) => {
1307
+ const a = args;
1308
+ const body = {
1309
+ runId: a.runId,
1310
+ envClass: a.envClass,
1311
+ };
1312
+ const trace = buildAgentTraceabilityPayload(a);
1313
+ if (trace)
1314
+ body.agentTraceability = trace;
1315
+ return postMcp("/api/mcp/promote_perf_baseline", body);
1316
+ },
1317
+ },
1318
+ {
1319
+ kebab: "compare-perf-to-baseline",
1320
+ description: "Compare a run (runId) or filtered target (testchimpId plus optional dimensions) to its promoted baseline. " +
1321
+ "envClass is required. Optional thresholds override max p95 regression percent and maximum fail-rate increase.",
1322
+ inputSchema: S.comparePerfToBaselineInput,
1323
+ execute: async (args, { postMcp }) => {
1324
+ const a = args;
1325
+ const body = {};
1326
+ if (a.runId)
1327
+ body.runId = a.runId;
1328
+ if (a.testchimpId)
1329
+ body.testchimpId = a.testchimpId;
1330
+ if (a.profile)
1331
+ body.profile = a.profile;
1332
+ if (a.dataset)
1333
+ body.dataset = a.dataset;
1334
+ if (a.llmMode)
1335
+ body.llmMode = a.llmMode;
1336
+ if (a.environment)
1337
+ body.environment = a.environment;
1338
+ body.envClass = a.envClass;
1339
+ if (a.maxP95RegressionPercent != null)
1340
+ body.maxP95RegressionPercent = a.maxP95RegressionPercent;
1341
+ if (a.maxFailRateIncrease != null)
1342
+ body.maxFailRateIncrease = a.maxFailRateIncrease;
1343
+ return postMcp("/api/mcp/compare_perf_to_baseline", body);
1344
+ },
1345
+ },
1346
+ {
1347
+ kebab: "list-related-perf-tests",
1348
+ description: "Find JOURNEY and, by default, COMPOSITE performance tests related to scenario titles and/or TestChimp ids.",
1349
+ inputSchema: S.listRelatedPerfTestsInput,
1350
+ execute: async (args, { postMcp }) => {
1351
+ const a = args;
1352
+ const body = {
1353
+ includeComposites: a.includeComposites,
1354
+ };
1355
+ if (a.scenarioTitles?.length)
1356
+ body.scenarioTitles = a.scenarioTitles;
1357
+ if (a.testchimpIds?.length)
1358
+ body.testchimpIds = a.testchimpIds;
1359
+ if (a.limit != null)
1360
+ body.limit = a.limit;
1361
+ return postMcp("/api/mcp/list_related_perf_tests", body);
1362
+ },
1363
+ },
1364
+ {
1365
+ kebab: "list-api-operation-interactions",
1366
+ description: "List recorded API operation interactions. Requires testId and/or operationId. " +
1367
+ "Defaults to REAL interactions; limit is capped at 100.",
1368
+ inputSchema: S.listApiOperationInteractionsInput,
1369
+ execute: async (args, { postMcp }) => {
1370
+ const a = args;
1371
+ const body = {
1372
+ interactionType: a.interactionType,
1373
+ };
1374
+ if (a.testId)
1375
+ body.testId = a.testId;
1376
+ if (a.operationId)
1377
+ body.operationId = a.operationId;
1378
+ if (a.limit != null)
1379
+ body.limit = a.limit;
1380
+ return postMcp("/api/mcp/list_api_operation_interactions", body);
1381
+ },
1382
+ },
1244
1383
  ];
1245
1384
  const TOOL_BY_KEBAB = new Map(TOOL_DEFINITIONS.map((t) => [t.kebab, t]));
1246
1385
  export function getToolDefinition(kebab) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testchimp/cli",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "TestChimp CLI and MCP server — coverage, plans, EaaS, TrueCoverage, API operations (calls /api/mcp/*)",
5
5
  "type": "module",
6
6
  "main": "dist/bin/testchimp.js",