@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.
- package/dist/cli/program.js +157 -1
- package/dist/core/agentTraceability.d.ts +4 -1
- package/dist/core/agentTraceability.js +4 -1
- package/dist/core/schemas.d.ts +50 -0
- package/dist/core/schemas.js +72 -0
- package/dist/core/tools.js +150 -3
- package/package.json +2 -2
package/dist/cli/program.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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) {
|
package/dist/core/schemas.d.ts
CHANGED
|
@@ -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>;
|
package/dist/core/schemas.js
CHANGED
|
@@ -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
|
+
});
|
package/dist/core/tools.js
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "TestChimp CLI and MCP server
|
|
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",
|