@testchimp/cli 0.1.27 → 0.1.29

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.
@@ -127,8 +127,14 @@ export function buildCliProgram() {
127
127
  .option("--branch-name <s>")
128
128
  .option("--platform <web|ios|android>")
129
129
  .option("--record-types <csv>", "coverage sources: smart_test,manual (aliases: automated,smarttest)")
130
- .option("--include-manual", "include manual sessions in addition to automated (default)")
130
+ .option("--include-manual", "include manual session coverage in addition to automated SmartTests")
131
131
  .option("--manual-only", "manual-only coverage (no automated)")
132
+ .option("--lifecycle-statuses <csv>", "scenario lifecycle allowlist (e.g. ready or draft,ready)")
133
+ .option("--limit <n>", "top N gaps after filter+rank into rankedScenarios (max 200)", (v) => parseInt(v, 10))
134
+ .option("--consider-scenario-priority", "rank by scenario priority high→medium→low→unset")
135
+ .option("--consider-semantic-coverage", "reserved ranking signal (accepted; no server effect yet)")
136
+ .option("--auto-verification-only", "exclude verification_strategy=manual (server default when unset)")
137
+ .option("--include-manual-verification", "include verification_strategy=manual (overrides --auto-verification-only)")
132
138
  .option("--file-paths <csv>", "comma-separated paths under platform tests root")
133
139
  .option("--folder-path <path>", "folder under tests root, slash-separated")
134
140
  .action(async (opts) => {
@@ -150,6 +156,22 @@ export function buildCliProgram() {
150
156
  recordTypes = ["manual"];
151
157
  if (recordTypes && recordTypes.length > 0)
152
158
  body.recordTypes = recordTypes;
159
+ if (opts.lifecycleStatuses) {
160
+ body.scenarioLifecycleStatuses = String(opts.lifecycleStatuses)
161
+ .split(",")
162
+ .map((s) => s.trim())
163
+ .filter(Boolean);
164
+ }
165
+ if (opts.limit != null && !Number.isNaN(opts.limit))
166
+ body.limit = opts.limit;
167
+ if (opts.considerScenarioPriority)
168
+ body.considerScenarioPriority = true;
169
+ if (opts.considerSemanticCoverage)
170
+ body.considerSemanticCoverage = true;
171
+ if (opts.includeManualVerification)
172
+ body.autoVerificationOnly = false;
173
+ else if (opts.autoVerificationOnly)
174
+ body.autoVerificationOnly = true;
153
175
  const scope = {};
154
176
  if (opts.filePaths)
155
177
  scope.filePaths = String(opts.filePaths).split(",").map((s) => s.trim()).filter(Boolean);
@@ -198,6 +220,43 @@ export function buildCliProgram() {
198
220
  const out = await runTool("get-execution-history", merged, { postMcp });
199
221
  console.log(out);
200
222
  });
223
+ program
224
+ .command("get-suite-execution-stats")
225
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "get-suite-execution-stats").description)
226
+ .addOption(jsonInputOption())
227
+ .option("--release <s>")
228
+ .option("--environment <s>")
229
+ .option("--branch-name <s>")
230
+ .option("--scenario-id <id>")
231
+ .option("--test-id <id>")
232
+ .option("--platform <web|ios|android>")
233
+ .option("--file-paths <csv>")
234
+ .option("--folder-path <path>")
235
+ .action(async (opts) => {
236
+ const body = {};
237
+ if (opts.release)
238
+ body.release = opts.release;
239
+ if (opts.environment)
240
+ body.environment = opts.environment;
241
+ if (opts.branchName)
242
+ body.branchName = opts.branchName;
243
+ if (opts.scenarioId)
244
+ body.scenarioId = opts.scenarioId;
245
+ if (opts.testId)
246
+ body.testId = opts.testId;
247
+ if (opts.platform)
248
+ body.platform = opts.platform;
249
+ const scope = {};
250
+ if (opts.filePaths)
251
+ scope.filePaths = String(opts.filePaths).split(",").map((s) => s.trim()).filter(Boolean);
252
+ if (opts.folderPath)
253
+ scope.folderPath = opts.folderPath;
254
+ if (Object.keys(scope).length)
255
+ body.scope = scope;
256
+ const merged = mergeBodies(body, opts.jsonInput);
257
+ const out = await runTool("get-suite-execution-stats", merged, { postMcp });
258
+ console.log(out);
259
+ });
201
260
  program
202
261
  .command("fetch-execution-report")
203
262
  .description(TOOL_DEFINITIONS.find((t) => t.kebab === "fetch-execution-report").description)
@@ -481,6 +540,40 @@ export function buildCliProgram() {
481
540
  }, { postMcp });
482
541
  console.log(out);
483
542
  });
543
+ program
544
+ .command("get-spec-lifecycle-details")
545
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "get-spec-lifecycle-details").description)
546
+ .addOption(jsonInputOption())
547
+ .option("--scenario-ids <csv>", "comma-separated scenario ordinals (bare 107 or TS-107 / #TS-107)")
548
+ .option("--story-ids <csv>", "comma-separated story ordinals (bare 12 or US-12 / #US-12)")
549
+ .action(async (opts) => {
550
+ const body = {};
551
+ if (opts.scenarioIds) {
552
+ const ids = String(opts.scenarioIds)
553
+ .split(",")
554
+ .map((s) => s.trim())
555
+ .filter((s) => s.length > 0);
556
+ if (ids.length > 0)
557
+ body.scenarioIds = ids;
558
+ }
559
+ if (opts.storyIds) {
560
+ const ids = String(opts.storyIds)
561
+ .split(",")
562
+ .map((s) => s.trim())
563
+ .filter((s) => s.length > 0);
564
+ if (ids.length > 0)
565
+ body.storyIds = ids;
566
+ }
567
+ const merged = mergeBodies(body, opts.jsonInput);
568
+ if (Array.isArray(merged.scenarioIds) && merged.scenarioIds.length === 0) {
569
+ delete merged.scenarioIds;
570
+ }
571
+ if (Array.isArray(merged.storyIds) && merged.storyIds.length === 0) {
572
+ delete merged.storyIds;
573
+ }
574
+ const out = await runTool("get-spec-lifecycle-details", merged, { postMcp });
575
+ console.log(out);
576
+ });
484
577
  addAgentTraceabilityOptions(program
485
578
  .command("update-test-scenario")
486
579
  .description(TOOL_DEFINITIONS.find((t) => t.kebab === "update-test-scenario").description)
@@ -1075,6 +1168,7 @@ export function buildCliProgram() {
1075
1168
  console.log(await runTool("get-last-run-workflow-detail", merged, { postMcp }));
1076
1169
  });
1077
1170
  for (const kebab of [
1171
+ "get-org-capabilities",
1078
1172
  "list-workflow-executions",
1079
1173
  "get-workflow-execution",
1080
1174
  "get-policy",
@@ -1134,6 +1228,68 @@ export function buildCliProgram() {
1134
1228
  }
1135
1229
  console.log(await runTool("upsert-plans-support-file", merged, { postMcp }));
1136
1230
  });
1231
+ program
1232
+ .command("list-api-operation-services")
1233
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-api-operation-services").description)
1234
+ .addOption(jsonInputOption())
1235
+ .action(async (opts) => {
1236
+ const merged = mergeBodies({}, opts.jsonInput);
1237
+ console.log(await runTool("list-api-operation-services", merged, { postMcp }));
1238
+ });
1239
+ program
1240
+ .command("list-api-operations")
1241
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "list-api-operations").description)
1242
+ .addOption(jsonInputOption())
1243
+ .option("--root-file-path <path>", "Repo-relative OpenAPI root path (preferred service resource id)")
1244
+ .option("--service-key <key>", "Internal service key alias")
1245
+ .option("--include-manual", "Include MANUAL test_mode coverage in previews")
1246
+ .option("--include-removed", "Include soft-deleted (REMOVED) operations")
1247
+ .action(async (opts) => {
1248
+ const body = {};
1249
+ if (opts.rootFilePath)
1250
+ body.rootFilePath = String(opts.rootFilePath);
1251
+ if (opts.serviceKey)
1252
+ body.serviceKey = String(opts.serviceKey);
1253
+ if (opts.includeManual)
1254
+ body.includeManual = true;
1255
+ if (opts.includeRemoved)
1256
+ body.includeRemoved = true;
1257
+ const merged = mergeBodies(body, opts.jsonInput);
1258
+ console.log(await runTool("list-api-operations", merged, { postMcp }));
1259
+ });
1260
+ program
1261
+ .command("get-api-operation-detail")
1262
+ .description(TOOL_DEFINITIONS.find((t) => t.kebab === "get-api-operation-detail").description)
1263
+ .addOption(jsonInputOption())
1264
+ .option("--id <ulid>", "TestChimp operation id (ULID PK) — preferred")
1265
+ .option("--root-file-path <path>", "Repo-relative OpenAPI root path")
1266
+ .option("--service-key <key>", "Internal service key")
1267
+ .option("--oas-operation-id <id>", "OpenAPI operationId")
1268
+ .option("--http-method <method>", "HTTP method (with --path-template)")
1269
+ .option("--path-template <path>", "OpenAPI path template (with --http-method)")
1270
+ .option("--include-manual", "Include MANUAL test_mode coverage")
1271
+ .option("--include-removed", "Include soft-deleted schema fields / response codes")
1272
+ .action(async (opts) => {
1273
+ const body = {};
1274
+ if (opts.id)
1275
+ body.id = String(opts.id);
1276
+ if (opts.rootFilePath)
1277
+ body.rootFilePath = String(opts.rootFilePath);
1278
+ if (opts.serviceKey)
1279
+ body.serviceKey = String(opts.serviceKey);
1280
+ if (opts.oasOperationId)
1281
+ body.oasOperationId = String(opts.oasOperationId);
1282
+ if (opts.httpMethod)
1283
+ body.httpMethod = String(opts.httpMethod);
1284
+ if (opts.pathTemplate)
1285
+ body.pathTemplate = String(opts.pathTemplate);
1286
+ if (opts.includeManual)
1287
+ body.includeManual = true;
1288
+ if (opts.includeRemoved)
1289
+ body.includeRemoved = true;
1290
+ const merged = mergeBodies(body, opts.jsonInput);
1291
+ console.log(await runTool("get-api-operation-detail", merged, { postMcp }));
1292
+ });
1137
1293
  program.on("--help", () => {
1138
1294
  /* default */
1139
1295
  });
@@ -16,7 +16,10 @@ export type AgentTraceabilityFields = {
16
16
  * Build camelCase AgentActionTraceability for MCP JSON bodies.
17
17
  * Returns undefined unless the caller supplied explicit traceability intent
18
18
  * **and** a non-empty workflowId (server requires workflow_id for inline Activity).
19
- * Auto-fills gitSha / agentModel / userId only after that bar is met.
19
+ * For Activity/timeline attachment the server also requires workflowExecutionId
20
+ * (stable Plan ULID for the whole run) — omit it and the mutation still succeeds
21
+ * but no workflow_executions / Activity row is recorded (server does not auto-mint).
22
+ * Auto-fills gitSha / agentModel / userId only after the workflowId bar is met.
20
23
  * Non-empty nested `agentTraceability` wins over flat for overlapping keys.
21
24
  */
22
25
  export declare function buildAgentTraceabilityPayload(a: AgentTraceabilityFields): Record<string, unknown> | undefined;
@@ -43,7 +43,10 @@ function normalizeActorType(raw) {
43
43
  * Build camelCase AgentActionTraceability for MCP JSON bodies.
44
44
  * Returns undefined unless the caller supplied explicit traceability intent
45
45
  * **and** a non-empty workflowId (server requires workflow_id for inline Activity).
46
- * Auto-fills gitSha / agentModel / userId only after that bar is met.
46
+ * For Activity/timeline attachment the server also requires workflowExecutionId
47
+ * (stable Plan ULID for the whole run) — omit it and the mutation still succeeds
48
+ * but no workflow_executions / Activity row is recorded (server does not auto-mint).
49
+ * Auto-fills gitSha / agentModel / userId only after the workflowId bar is met.
47
50
  * Non-empty nested `agentTraceability` wins over flat for overlapping keys.
48
51
  */
49
52
  export function buildAgentTraceabilityPayload(a) {
@@ -28,6 +28,11 @@ export declare const listCoverageInput: z.ZodObject<{
28
28
  SMART_TEST: "SMART_TEST";
29
29
  MANUAL: "MANUAL";
30
30
  }>>>;
31
+ scenarioLifecycleStatuses: z.ZodOptional<z.ZodArray<z.ZodString>>;
32
+ limit: z.ZodOptional<z.ZodNumber>;
33
+ considerScenarioPriority: z.ZodOptional<z.ZodBoolean>;
34
+ considerSemanticCoverage: z.ZodOptional<z.ZodBoolean>;
35
+ autoVerificationOnly: z.ZodOptional<z.ZodBoolean>;
31
36
  }, z.core.$strip>;
32
37
  export declare const listExecutionInput: z.ZodObject<{
33
38
  release: z.ZodOptional<z.ZodString>;
@@ -51,6 +56,29 @@ export declare const listExecutionInput: z.ZodObject<{
51
56
  limit: z.ZodOptional<z.ZodNumber>;
52
57
  offset: z.ZodOptional<z.ZodNumber>;
53
58
  }, z.core.$strip>;
59
+ /** Same filters as get-execution-history; rolls up list_execution_history testStats. */
60
+ export declare const suiteExecutionStatsInput: z.ZodObject<{
61
+ release: z.ZodOptional<z.ZodString>;
62
+ environment: z.ZodOptional<z.ZodString>;
63
+ scope: z.ZodOptional<z.ZodObject<{
64
+ filePaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
65
+ folderPath: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodString]>>;
66
+ }, z.core.$strip>>;
67
+ branchName: z.ZodOptional<z.ZodString>;
68
+ scenarioId: z.ZodOptional<z.ZodString>;
69
+ testId: z.ZodOptional<z.ZodString>;
70
+ platform: z.ZodOptional<z.ZodEnum<{
71
+ web: "web";
72
+ ios: "ios";
73
+ android: "android";
74
+ }>>;
75
+ dimensionFilters: z.ZodOptional<z.ZodArray<z.ZodObject<{
76
+ dimension: z.ZodString;
77
+ values: z.ZodArray<z.ZodString>;
78
+ }, z.core.$strip>>>;
79
+ limit: z.ZodOptional<z.ZodNumber>;
80
+ offset: z.ZodOptional<z.ZodNumber>;
81
+ }, z.core.$strip>;
54
82
  export declare const fetchExecutionReportInput: z.ZodObject<{
55
83
  batchInvocationId: z.ZodOptional<z.ZodString>;
56
84
  jobId: z.ZodOptional<z.ZodString>;
@@ -222,6 +250,10 @@ export declare const updatePlanItemsLifecycleStatusInput: z.ZodObject<{
222
250
  ordinalId: z.ZodCoercedNumber<unknown>;
223
251
  status: z.ZodString;
224
252
  }, z.core.$strip>;
253
+ export declare const getSpecLifecycleDetailsInput: z.ZodObject<{
254
+ scenarioIds: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>>>;
255
+ storyIds: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>, z.ZodString>>>;
256
+ }, z.core.$strip>;
225
257
  export declare const getUserStoriesInput: z.ZodObject<{
226
258
  userStoryOrdinalIds: z.ZodArray<z.ZodCoercedNumber<unknown>>;
227
259
  }, z.core.$strip>;
@@ -1415,3 +1447,21 @@ export declare const upsertPlansSupportFileInput: z.ZodObject<{
1415
1447
  content: z.ZodString;
1416
1448
  }, z.core.$strip>;
1417
1449
  export declare const listWorkflowCatalogInput: z.ZodObject<{}, z.core.$strip>;
1450
+ /** API operation coverage (OpenAPI ops + denorm coverage) — CLI ≥ 0.1.28 */
1451
+ export declare const listApiOperationServicesInput: z.ZodObject<{}, z.core.$strip>;
1452
+ export declare const listApiOperationsInput: z.ZodObject<{
1453
+ rootFilePath: z.ZodOptional<z.ZodString>;
1454
+ serviceKey: z.ZodOptional<z.ZodString>;
1455
+ includeManual: z.ZodOptional<z.ZodBoolean>;
1456
+ includeRemoved: z.ZodOptional<z.ZodBoolean>;
1457
+ }, z.core.$strip>;
1458
+ export declare const getApiOperationDetailInput: z.ZodObject<{
1459
+ id: z.ZodOptional<z.ZodString>;
1460
+ rootFilePath: z.ZodOptional<z.ZodString>;
1461
+ serviceKey: z.ZodOptional<z.ZodString>;
1462
+ oasOperationId: z.ZodOptional<z.ZodString>;
1463
+ httpMethod: z.ZodOptional<z.ZodString>;
1464
+ pathTemplate: z.ZodOptional<z.ZodString>;
1465
+ includeManual: z.ZodOptional<z.ZodBoolean>;
1466
+ includeRemoved: z.ZodOptional<z.ZodBoolean>;
1467
+ }, z.core.$strip>;
@@ -26,6 +26,19 @@ export const listCoverageInput = z.object({
26
26
  * When provided, send proto enum names ("SMART_TEST", "MANUAL") or CLI-friendly aliases ("smart_test", "manual").
27
27
  */
28
28
  recordTypes: z.array(requirementCoverageRecordTypeSchema).optional(),
29
+ /** Allowlist of scenario lifecycle statuses (e.g. ["ready"] or ["draft","ready"]). Empty/omit = no status filter. */
30
+ scenarioLifecycleStatuses: z.array(z.string().min(1)).optional(),
31
+ /** When set (>0), truncate rankedScenarios to top N after filter+rank (server clamps to 200). */
32
+ limit: z.number().int().positive().max(200).optional(),
33
+ /** Rank by scenario priority high > medium > low > unset. */
34
+ considerScenarioPriority: z.boolean().optional(),
35
+ /** Reserved for future semantic-gap ranking; accepted by server, ignored in v1. */
36
+ considerSemanticCoverage: z.boolean().optional(),
37
+ /**
38
+ * Exclude scenarios with verification_strategy=manual. Server defaults to true when unset.
39
+ * Prefer --include-manual-verification (sets false) over setting this explicitly.
40
+ */
41
+ autoVerificationOnly: z.boolean().optional(),
29
42
  });
30
43
  export const listExecutionInput = z.object({
31
44
  release: z.string().optional(),
@@ -39,6 +52,8 @@ export const listExecutionInput = z.object({
39
52
  limit: z.number().int().positive().max(500).optional(),
40
53
  offset: z.number().int().nonnegative().optional(),
41
54
  });
55
+ /** Same filters as get-execution-history; rolls up list_execution_history testStats. */
56
+ export const suiteExecutionStatsInput = listExecutionInput;
42
57
  export const fetchExecutionReportInput = z
43
58
  .object({
44
59
  batchInvocationId: z.string().optional(),
@@ -105,6 +120,19 @@ export const updatePlanItemsLifecycleStatusInput = z.object({
105
120
  /** draft | ready | in progress | blocked | done | archived */
106
121
  status: z.string().min(1),
107
122
  });
123
+ export const getSpecLifecycleDetailsInput = z
124
+ .object({
125
+ /** Bare ordinals or TS-/ #TS- forms; numbers coerced to strings. */
126
+ scenarioIds: z
127
+ .array(z.union([z.string(), z.number()]).transform((v) => String(v).trim()).pipe(z.string().min(1)))
128
+ .optional(),
129
+ /** Bare ordinals or US-/ #US- forms; numbers coerced to strings. */
130
+ storyIds: z
131
+ .array(z.union([z.string(), z.number()]).transform((v) => String(v).trim()).pipe(z.string().min(1)))
132
+ .optional(),
133
+ })
134
+ .refine((v) => (v.scenarioIds != null && v.scenarioIds.length > 0) ||
135
+ (v.storyIds != null && v.storyIds.length > 0), { message: "Provide scenarioIds and/or storyIds (non-empty)" });
108
136
  export const getUserStoriesInput = z
109
137
  .object({
110
138
  userStoryOrdinalIds: z.array(z.coerce.number().int().positive()).min(1),
@@ -770,3 +798,47 @@ export const upsertPlansSupportFileInput = z.object({
770
798
  content: z.string().min(1),
771
799
  });
772
800
  export const listWorkflowCatalogInput = z.object({});
801
+ /** API operation coverage (OpenAPI ops + denorm coverage) — CLI ≥ 0.1.28 */
802
+ export const listApiOperationServicesInput = z.object({});
803
+ export const listApiOperationsInput = z.object({
804
+ /** Preferred: repo-relative OpenAPI root path. */
805
+ rootFilePath: z.string().optional(),
806
+ /** Alias for rootFilePath resolution; internal service key. */
807
+ serviceKey: z.string().optional(),
808
+ includeManual: z.boolean().optional(),
809
+ includeRemoved: z.boolean().optional(),
810
+ });
811
+ export const getApiOperationDetailInput = z
812
+ .object({
813
+ /** TestChimp operation id (ULID PK). Preferred. */
814
+ id: z.string().optional(),
815
+ rootFilePath: z.string().optional(),
816
+ serviceKey: z.string().optional(),
817
+ oasOperationId: z.string().optional(),
818
+ httpMethod: z.string().optional(),
819
+ pathTemplate: z.string().optional(),
820
+ includeManual: z.boolean().optional(),
821
+ includeRemoved: z.boolean().optional(),
822
+ })
823
+ .superRefine((v, ctx) => {
824
+ const id = v.id?.trim();
825
+ const root = v.rootFilePath?.trim();
826
+ const service = v.serviceKey?.trim();
827
+ const oas = v.oasOperationId?.trim();
828
+ const method = v.httpMethod?.trim();
829
+ const path = v.pathTemplate?.trim();
830
+ const hasService = !!(root || service);
831
+ if (id)
832
+ return;
833
+ if (oas && hasService)
834
+ return;
835
+ if (method && path && hasService)
836
+ return;
837
+ if (oas && !hasService)
838
+ return; // server allows project-wide oas fallback
839
+ ctx.addIssue({
840
+ code: "custom",
841
+ message: "Provide --id (TestChimp operation ULID), or --root-file-path/--service-key with --oas-operation-id, " +
842
+ "or --root-file-path/--service-key with --http-method and --path-template",
843
+ });
844
+ });
@@ -55,6 +55,17 @@ function listCoverageBody(args) {
55
55
  });
56
56
  body.recordTypes = normalized;
57
57
  }
58
+ if (args.scenarioLifecycleStatuses != null && args.scenarioLifecycleStatuses.length > 0) {
59
+ body.scenarioLifecycleStatuses = args.scenarioLifecycleStatuses.map((s) => String(s).trim()).filter(Boolean);
60
+ }
61
+ if (args.limit != null)
62
+ body.limit = args.limit;
63
+ if (args.considerScenarioPriority != null)
64
+ body.considerScenarioPriority = args.considerScenarioPriority;
65
+ if (args.considerSemanticCoverage != null)
66
+ body.considerSemanticCoverage = args.considerSemanticCoverage;
67
+ if (args.autoVerificationOnly != null)
68
+ body.autoVerificationOnly = args.autoVerificationOnly;
58
69
  return body;
59
70
  }
60
71
  function listExecutionBody(args) {
@@ -89,6 +100,45 @@ function listExecutionBody(args) {
89
100
  body.offset = args.offset;
90
101
  return body;
91
102
  }
103
+ /** Mean success duration above this counts as a slow test (mirrors UI ExecutionTimingSummary). */
104
+ const SLOW_TEST_MEAN_SECS = 30;
105
+ function parseMeanSecs(raw) {
106
+ if (raw == null)
107
+ return null;
108
+ const n = Number(raw);
109
+ return Number.isFinite(n) && !Number.isNaN(n) && n >= 0 ? n : null;
110
+ }
111
+ /** Suite rollup from list_execution_history testStats (sum of success means = UI Total time). */
112
+ function aggregateSuiteExecutionStats(testStats) {
113
+ const stats = Array.isArray(testStats) ? testStats : [];
114
+ let timedTestCount = 0;
115
+ let sumSuccessMeanSecs = 0;
116
+ let sumFailMeanSecs = 0;
117
+ let maxSuccessMeanSecs = 0;
118
+ let slowTestCount = 0;
119
+ for (const s of stats) {
120
+ const successMean = parseMeanSecs(s.successTiming?.meanSecs);
121
+ if (successMean != null) {
122
+ timedTestCount += 1;
123
+ sumSuccessMeanSecs += successMean;
124
+ if (successMean > maxSuccessMeanSecs)
125
+ maxSuccessMeanSecs = successMean;
126
+ if (successMean > SLOW_TEST_MEAN_SECS)
127
+ slowTestCount += 1;
128
+ }
129
+ const failMean = parseMeanSecs(s.failTiming?.meanSecs);
130
+ if (failMean != null)
131
+ sumFailMeanSecs += failMean;
132
+ }
133
+ return {
134
+ testCount: stats.length,
135
+ timedTestCount,
136
+ sumSuccessMeanSecs,
137
+ sumFailMeanSecs,
138
+ maxSuccessMeanSecs: timedTestCount > 0 ? maxSuccessMeanSecs : 0,
139
+ slowTestCount,
140
+ };
141
+ }
92
142
  function requirementQualitySubjectBody(subjectType, opts) {
93
143
  const body = { subjectType };
94
144
  const entityId = (opts.subjectEntityId ?? "").trim();
@@ -127,12 +177,22 @@ async function loadRequirementQualityReportJson(args) {
127
177
  return {};
128
178
  }
129
179
  export const TOOL_DEFINITIONS = [
180
+ {
181
+ kebab: "get-org-capabilities",
182
+ description: "Fetch the organization's enabled capabilities (e.g. TRUE_COVERAGE, API_CONTRACT_COVERAGE) and " +
183
+ "freeTrialActive flag. Call before relying on TrueCoverage / API contract coverage features so " +
184
+ "playbooks can soft-skip gated insights instead of failing. Authenticated via project API key.",
185
+ inputSchema: S.emptyInput,
186
+ execute: async (_args, { postMcp }) => postMcp("/api/mcp/get_org_capabilities", {}),
187
+ },
130
188
  {
131
189
  kebab: "get-requirement-coverage",
132
190
  description: "Fetch requirement (scenario) coverage under an optional platform-rooted folder scope (tests/... or plans/...). " +
133
191
  "Use scope.filePaths or scope.folderPath (platform tests/plans roots). Omit branchName for cross-branch coverage " +
134
192
  "(aggregates branch copies; execution jobs deduped by stable hash of tests-root-relative path + test name). " +
135
- "Pass branchName only when results must be limited to one Git branch. Optional platform (web|ios|android) filters rollup.",
193
+ "Pass branchName only when results must be limited to one Git branch. Optional platform (web|ios|android) filters rollup. " +
194
+ "For top-N gap recommendations: set scenarioLifecycleStatuses, considerScenarioPriority / considerSemanticCoverage, and limit; " +
195
+ "prefer response rankedScenarios (gaps only). Server excludes verification_strategy=manual by default (autoVerificationOnly).",
136
196
  inputSchema: S.listCoverageInput,
137
197
  execute: async (args, { postMcp }) => {
138
198
  const json = await postMcp("/api/mcp/list_requirement_coverage", listCoverageBody(args));
@@ -150,6 +210,20 @@ export const TOOL_DEFINITIONS = [
150
210
  return json;
151
211
  },
152
212
  },
213
+ {
214
+ kebab: "get-suite-execution-stats",
215
+ description: "Aggregate suite timing from list_execution_history testStats (same filters as get-execution-history). " +
216
+ "Returns testCount, timedTestCount, sumSuccessMeanSecs (UI ExecutionTimingSummary total), sumFailMeanSecs, " +
217
+ "maxSuccessMeanSecs, slowTestCount (success mean > 30s). " +
218
+ "Sum of means is aggregate test CPU-time, not CI wall-clock under parallelism. " +
219
+ "Compare sumSuccessMeanSecs against any suite budget in the agent — this tool only returns stats.",
220
+ inputSchema: S.suiteExecutionStatsInput,
221
+ execute: async (args, { postMcp }) => {
222
+ const json = await postMcp("/api/mcp/list_execution_history", listExecutionBody(args));
223
+ const parsed = JSON.parse(json);
224
+ return JSON.stringify(aggregateSuiteExecutionStats(parsed.testStats));
225
+ },
226
+ },
153
227
  {
154
228
  kebab: "fetch-execution-report",
155
229
  description: "Fetch a detailed execution report for failing SmartTests, given a batchInvocationId (batch run) or jobId (single run). " +
@@ -306,7 +380,8 @@ export const TOOL_DEFINITIONS = [
306
380
  "status must be one of: ACTIVE, IGNORED, FIXED, DUPLICATE, IN_PROGRESS_BUG, ARCHIVED_BUG, BLOCKED. " +
307
381
  "For /testchimp fix issue: set IN_PROGRESS_BUG after applying a code fix; set FIXED only after user confirmation / commits pushed. " +
308
382
  "Optional ignoreReason when status is IGNORED: INTENDED_BEHAVIOUR | INACCURATE_ASSESSMENT | NOT_IMPORTANT. " +
309
- "Optional agentTraceability records UPDATED Activity inline.",
383
+ "Optional agentTraceability records UPDATED Activity inline " +
384
+ "(requires both workflowId and workflowExecutionId for Activity attachment).",
310
385
  inputSchema: S.updateIssueStatusInput,
311
386
  execute: async (args, { postMcp }) => {
312
387
  const a = args;
@@ -330,8 +405,10 @@ export const TOOL_DEFINITIONS = [
330
405
  "linkTargets, labels, source, environment, attachments, artifactReference). " +
331
406
  "For /testchimp implement TASK_ISSUE creates: set labels=[\"TestChimp Implement\"] (not source), " +
332
407
  "severity from task priority, category (e.g. FUNCTIONAL), and linkTargets for STORY and/or SCENARIO ordinals. " +
333
- "Optional agentTraceability (or flat workflowId/policyFile/…) records CREATED Activity inline — " +
408
+ "Optional agentTraceability (or flat workflowId/workflowExecutionId/policyFile/…) records CREATED Activity inline — " +
334
409
  "prefer this over a separate report-agent-action for issue creates. " +
410
+ "For Activity/timeline attachment both workflowId and workflowExecutionId (stable Plan ULID) are required; " +
411
+ "do not omit workflowExecutionId or mint a new ULID per issue. " +
335
412
  "Authenticated via project API key; project is resolved from the key.",
336
413
  inputSchema: S.createIssueInput,
337
414
  execute: async (args, { postMcp }) => {
@@ -401,6 +478,22 @@ export const TOOL_DEFINITIONS = [
401
478
  });
402
479
  },
403
480
  },
481
+ {
482
+ kebab: "get-spec-lifecycle-details",
483
+ description: "Fetch lifecycle_fields for user stories and/or test scenarios by ordinal id (DB only; no markdown). " +
484
+ "Pass scenarioIds / storyIds as lists of bare ordinals (canonical) or prefixed forms (TS-107, #US-12). " +
485
+ "Use after identifying scenarios in scope for create-tests to read verification_strategy (auto|manual) and skip manual ones.",
486
+ inputSchema: S.getSpecLifecycleDetailsInput,
487
+ execute: async (args, { postMcp }) => {
488
+ const a = args;
489
+ const body = {};
490
+ if (a.scenarioIds?.length)
491
+ body.scenarioIds = a.scenarioIds;
492
+ if (a.storyIds?.length)
493
+ body.storyIds = a.storyIds;
494
+ return postMcp("/api/mcp/get_spec_lifecycle_details", body);
495
+ },
496
+ },
404
497
  {
405
498
  kebab: "get-eaas-config",
406
499
  description: "Return the project's BunnyShell (Environment-as-a-Service) settings. Secrets are never returned.",
@@ -1075,6 +1168,60 @@ export const TOOL_DEFINITIONS = [
1075
1168
  inputSchema: S.listWorkflowCatalogInput,
1076
1169
  execute: async (_args, { postMcp }) => postMcp("/api/mcp/list_workflow_catalog", {}),
1077
1170
  },
1171
+ {
1172
+ kebab: "list-api-operation-services",
1173
+ description: "List API operation service resources for the project (configured OpenAPI root file paths + operation counts). " +
1174
+ "Use rootFilePath as the service resource id for list-api-operations / get-api-operation-detail.",
1175
+ inputSchema: S.listApiOperationServicesInput,
1176
+ execute: async (_args, { postMcp }) => postMcp("/api/mcp/list_api_operation_services", {}),
1177
+ },
1178
+ {
1179
+ kebab: "list-api-operations",
1180
+ description: "List API operations for a service resource with covering-test previews and coverageSummary scores " +
1181
+ "(same payload as the Operations list UI). Prefer --root-file-path (repo-relative OpenAPI root).",
1182
+ inputSchema: S.listApiOperationsInput,
1183
+ execute: async (args, { postMcp }) => {
1184
+ const a = args;
1185
+ const body = {};
1186
+ if (a.rootFilePath != null && a.rootFilePath.trim() !== "")
1187
+ body.rootFilePath = a.rootFilePath.trim();
1188
+ if (a.serviceKey != null && a.serviceKey.trim() !== "")
1189
+ body.serviceKey = a.serviceKey.trim();
1190
+ if (a.includeManual != null)
1191
+ body.includeManual = a.includeManual;
1192
+ if (a.includeRemoved != null)
1193
+ body.includeRemoved = a.includeRemoved;
1194
+ return postMcp("/api/mcp/list_api_operations", body);
1195
+ },
1196
+ },
1197
+ {
1198
+ kebab: "get-api-operation-detail",
1199
+ description: "Fetch detailed API operation coverage (request/query/response fields, response codes, covering tests) — " +
1200
+ "same payload as the Operation detail UI. Prefer TestChimp operation id (--id ULID); " +
1201
+ "or rootFilePath + oasOperationId; or rootFilePath + httpMethod + pathTemplate.",
1202
+ inputSchema: S.getApiOperationDetailInput,
1203
+ execute: async (args, { postMcp }) => {
1204
+ const a = args;
1205
+ const body = {};
1206
+ if (a.id != null && a.id.trim() !== "")
1207
+ body.id = a.id.trim();
1208
+ if (a.rootFilePath != null && a.rootFilePath.trim() !== "")
1209
+ body.rootFilePath = a.rootFilePath.trim();
1210
+ if (a.serviceKey != null && a.serviceKey.trim() !== "")
1211
+ body.serviceKey = a.serviceKey.trim();
1212
+ if (a.oasOperationId != null && a.oasOperationId.trim() !== "")
1213
+ body.oasOperationId = a.oasOperationId.trim();
1214
+ if (a.httpMethod != null && a.httpMethod.trim() !== "")
1215
+ body.httpMethod = a.httpMethod.trim();
1216
+ if (a.pathTemplate != null && a.pathTemplate.trim() !== "")
1217
+ body.pathTemplate = a.pathTemplate.trim();
1218
+ if (a.includeManual != null)
1219
+ body.includeManual = a.includeManual;
1220
+ if (a.includeRemoved != null)
1221
+ body.includeRemoved = a.includeRemoved;
1222
+ return postMcp("/api/mcp/get_api_operation_detail", body);
1223
+ },
1224
+ },
1078
1225
  ];
1079
1226
  const TOOL_BY_KEBAB = new Map(TOOL_DEFINITIONS.map((t) => [t.kebab, t]));
1080
1227
  export function getToolDefinition(kebab) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@testchimp/cli",
3
- "version": "0.1.27",
4
- "description": "TestChimp CLI and MCP server \u2014 coverage, plans, EaaS, TrueCoverage (calls /api/mcp/*)",
3
+ "version": "0.1.29",
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",
7
7
  "types": "dist/bin/testchimp.d.ts",