wopee-mcp 1.30.0 → 1.30.2

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.
@@ -192,6 +192,9 @@ export const FetchExecutedTestCases = `
192
192
  agentReportStatus
193
193
  codeReport
194
194
  codeReportStatus
195
+ verdictIntegrity
196
+ integrityFlagged
197
+ finishedAt
195
198
  createdAt
196
199
  updatedAt
197
200
  }
@@ -284,6 +287,8 @@ export const FetchRecentExecutedTestCases = `
284
287
  agentReportStatus
285
288
  codeReport
286
289
  codeReportStatus
290
+ verdictIntegrity
291
+ integrityFlagged
287
292
  createdAt
288
293
  updatedAt
289
294
  }
@@ -69,16 +69,17 @@ export function _parseGenerateArtifactType(type) {
69
69
  };
70
70
  }
71
71
  }
72
+ /** One-line description of a failed request, e.g. "[GRAPHQL_ERROR] Not Authorised!". */
73
+ export function _describeError(error) {
74
+ if (error instanceof RequestError)
75
+ return `[${error.code}] ${error.message}${error.retryable ? " (retryable)" : ""}`;
76
+ return error instanceof Error ? error.message : String(error);
77
+ }
72
78
  export function _parseError(error) {
73
79
  console.error(error instanceof z.ZodError ? error.issues : error);
74
80
  if (error instanceof RequestError) {
75
81
  return {
76
- content: [
77
- {
78
- type: "text",
79
- text: `[${error.code}] ${error.message}${error.retryable ? " (retryable)" : ""}`,
80
- },
81
- ],
82
+ content: [{ type: "text", text: _describeError(error) }],
82
83
  };
83
84
  }
84
85
  return {
@@ -7,7 +7,7 @@ export const wopeeFetchRecentExecutions = {
7
7
  name: ToolName.WOPEE_FETCH_RECENT_EXECUTIONS,
8
8
  config: {
9
9
  title: "Fetch recent test executions",
10
- description: "Fetch the most recent test case executions for the current project (up to 20, newest first). Use this to check the status of recently dispatched tests without needing to remember specific suite UUIDs. Returns execution status (IN_PROGRESS, IN_QUEUE, FINISHED, FAILED), agent reports, and pass/fail results. Takes no input; uses WOPEE_PROJECT_UUID from environment. Prefer this tool when the user asks 'what's the status?' or 'how did the tests go?' and you don't have the specific suite UUID handy.",
10
+ description: "Fetch the most recent test case executions for the current project (up to 20, newest first). Use this to check the status of recently dispatched tests without needing to remember specific suite UUIDs. Returns each run's verdict (PASSED, FAILED, or INCOMPLETE — the run never established a result, e.g. an infrastructure error, and says nothing about the application) or, for a run with no verdict yet, its execution status (IN_QUEUE, IN_PROGRESS, FINISHED, FAILED, STOPPED), plus agent reports. Takes no input; uses WOPEE_PROJECT_UUID from environment. Prefer this tool when the user asks 'what's the status?' or 'how did the tests go?' and you don't have the specific suite UUID handy.",
11
11
  },
12
12
  handler: async () => {
13
13
  try {
@@ -48,15 +48,13 @@ export const wopeeFetchRecentExecutions = {
48
48
  "",
49
49
  ];
50
50
  for (const tc of executions) {
51
- let status;
52
- if (tc.executionStatus === "FINISHED") {
53
- status = tc.agentReportStatus ?? "FINISHED";
54
- }
55
- else {
56
- status = tc.executionStatus;
57
- }
51
+ // The verdict first (backlog#4453). A run whose agent threw is FAILED underneath but
52
+ // INCOMPLETE on top, and reporting it as FAILED tells the user their app is broken.
53
+ const status = tc.agentReportStatus ?? tc.executionStatus;
58
54
  lines.push(`- ${tc.userStoryId}:${tc.testCaseId} [${tc.analysisIdentifier}] → ${status} (${tc.updatedAt})`);
59
- if (tc.executionStatus === "FINISHED" && tc.agentReport) {
55
+ // Any report, not only a FINISHED run's: an incomplete run's report is what names the
56
+ // infrastructure error behind it.
57
+ if (tc.agentReport) {
60
58
  const shortReport = tc.agentReport.slice(0, 200);
61
59
  lines.push(` Report: ${shortReport}${tc.agentReport.length > 200 ? "..." : ""}`);
62
60
  }
@@ -1,6 +1,6 @@
1
1
  import { getConfig } from "../../utils/getConfig.js";
2
2
  import { requestClient } from "../../utils/requestClient.js";
3
- import { _parseError } from "../shared/helpers.js";
3
+ import { _describeError, _parseError } from "../shared/helpers.js";
4
4
  import { ArtifactType, ToolName, } from "../shared/types.js";
5
5
  import { FetchAnalysisSuites, FetchArtifact, FetchExecutedTestCases, } from "../shared/gql-queries.js";
6
6
  import { WopeeFetchTestInventoryInputSchema, } from "./schema.js";
@@ -9,7 +9,7 @@ export const wopeeFetchTestInventory = {
9
9
  name: ToolName.WOPEE_FETCH_TEST_INVENTORY,
10
10
  config: {
11
11
  title: "Fetch test inventory (counts + statuses)",
12
- description: "The authoritative tool for how many tests exist and their latest status. Returns, per analysis, the FULL list of authored test cases joined with their latest execution status — including never-run ones as NOT_RUN. Use this for questions like 'how many tests do I have', 'list the scenarios/test cases in A001', or 'show executed and not-run tests in one table'. Terminology: a 'scenario' is a test case; test cases are grouped under user stories (US001) and identified as US001:TC001. Reusable blocks (user story R001) are counted separately (reusableBlockCount) and are building blocks, not runnable, so they never carry an execution status. Regular tests are all non-R001 test cases. Read-only. Takes an optional analysisIdentifier (e.g. A001) to scope to one analysis; omit to cover every analysis in the project. Prefer this over wopee_fetch_recent_executions / wopee_fetch_executed_test_cases when the user asks about totals or the complete list — those return only test cases that have already run.",
12
+ description: "The authoritative tool for how many tests exist and their latest status. Returns, per analysis, the FULL list of authored test cases joined with their latest execution status — including never-run ones as NOT_RUN. Use this for questions like 'how many tests do I have', 'list the scenarios/test cases in A001', or 'show executed and not-run tests in one table'. Terminology: a 'scenario' is a test case; test cases are grouped under user stories (US001) and identified as US001:TC001. Reusable blocks (user story R001) are counted separately (reusableBlockCount) and are building blocks, not runnable, so they never carry an execution status. Regular tests are all non-R001 test cases. Read-only. Takes an optional analysisIdentifier (e.g. A001) to scope to one analysis; omit to cover every analysis in the project. Prefer this over wopee_fetch_recent_executions / wopee_fetch_executed_test_cases when the user asks about totals or the complete list — those return only test cases that have already run. Each test case carries executedTestCaseUuid (the latest run's uuid, the same one wopee_dispatch_agent returns) and verdictGateDisagreed (true when a verdict gate disagreed with a PASSED verdict — report it alongside the pass). Status UNKNOWN means the execution history could not be read (the reason is in the analysis's executionHistoryError); do not report such tests as not run.",
13
13
  inputSchema: WopeeFetchTestInventoryInputSchema.shape,
14
14
  },
15
15
  handler: async (input) => {
@@ -38,9 +38,11 @@ export const wopeeFetchTestInventory = {
38
38
  ],
39
39
  };
40
40
  // Fetch the authored test cases (USER_STORIES artifact) and execution
41
- // history for every analysis in parallel. A failure on one suite (e.g. an
42
- // analysis with no generated tests yet) degrades to an empty result for
43
- // that suite rather than failing the whole inventory.
41
+ // history for every analysis in parallel. A missing artifact (an analysis
42
+ // with no generated tests yet) degrades to an empty list rather than
43
+ // failing the whole inventory. A failed execution fetch does NOT degrade to
44
+ // "no runs": an analysis with no runs returns [], so a rejection is a real
45
+ // error (e.g. Not Authorised!) and is reported as UNKNOWN.
44
46
  const inputs = await Promise.all(suites.map(async (suite) => {
45
47
  const [artifactRes, execRes] = await Promise.allSettled([
46
48
  requestClient(FetchArtifact, {
@@ -59,6 +61,8 @@ export const wopeeFetchTestInventory = {
59
61
  },
60
62
  }),
61
63
  ]);
64
+ if (execRes.status === "rejected")
65
+ console.error(execRes.reason);
62
66
  return {
63
67
  suite,
64
68
  artifactContent: artifactRes.status === "fulfilled"
@@ -67,6 +71,9 @@ export const wopeeFetchTestInventory = {
67
71
  execGroups: execRes.status === "fulfilled"
68
72
  ? (execRes.value?.fetchExecutedTestCases ?? [])
69
73
  : [],
74
+ execError: execRes.status === "rejected"
75
+ ? _describeError(execRes.reason)
76
+ : null,
70
77
  };
71
78
  }));
72
79
  const inventory = buildTestInventory(inputs);
@@ -3,6 +3,10 @@
3
3
  // test cases at generation time (api generateReusableTestCases.ts).
4
4
  export const REUSABLE_USER_STORY_ID = "R001";
5
5
  export const NOT_RUN = "NOT_RUN";
6
+ // The execution history could not be read (e.g. the credential is not allowed to
7
+ // read executions), so whether a test ran is unknown. Never collapsed into
8
+ // NOT_RUN: "never ran" and "could not ask" must not look the same.
9
+ export const UNKNOWN = "UNKNOWN";
6
10
  /** Parse the USER_STORIES artifact JSON; tolerant of null/empty/malformed. */
7
11
  export function parseUserStories(content) {
8
12
  if (!content)
@@ -50,7 +54,11 @@ export function resolveStatus(exec) {
50
54
  return report;
51
55
  return exec.executionStatus ?? NOT_RUN;
52
56
  }
53
- function buildAnalysisInventory({ suite, artifactContent, execGroups, }) {
57
+ /** Mirrors CMD rowVerdict: only a pass is split, a flagged fail is already red. */
58
+ export function verdictGateDisagreed(exec, status) {
59
+ return !!exec?.integrityFlagged && status.toUpperCase() === "PASSED";
60
+ }
61
+ function buildAnalysisInventory({ suite, artifactContent, execGroups, execError = null, }) {
54
62
  const userStories = parseUserStories(artifactContent);
55
63
  const execIndex = indexExecutions(execGroups);
56
64
  const testCases = [];
@@ -69,7 +77,7 @@ function buildAnalysisInventory({ suite, artifactContent, execGroups, }) {
69
77
  const exec = isReusable
70
78
  ? undefined
71
79
  : execIndex.get(`${story.userStoryId}::${tc.testCaseId}`)?.[0];
72
- const status = resolveStatus(exec);
80
+ const status = execError && !isReusable ? UNKNOWN : resolveStatus(exec);
73
81
  if (!isReusable)
74
82
  statusSummary[status] = (statusSummary[status] ?? 0) + 1;
75
83
  testCases.push({
@@ -82,6 +90,8 @@ function buildAnalysisInventory({ suite, artifactContent, execGroups, }) {
82
90
  kind: isReusable ? "REUSABLE" : "REGULAR",
83
91
  status,
84
92
  executedAt: exec?.updatedAt ?? exec?.createdAt ?? null,
93
+ executedTestCaseUuid: exec?.uuid ?? null,
94
+ verdictGateDisagreed: verdictGateDisagreed(exec, status),
85
95
  });
86
96
  }
87
97
  }
@@ -93,6 +103,7 @@ function buildAnalysisInventory({ suite, artifactContent, execGroups, }) {
93
103
  regularTestCount,
94
104
  reusableBlockCount,
95
105
  statusSummary,
106
+ executionHistoryError: execError,
96
107
  testCases,
97
108
  };
98
109
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "bin": {
5
5
  "wopee-mcp": "./build/index.js"
6
6
  },
7
- "version": "1.30.0",
7
+ "version": "1.30.2",
8
8
  "mcpName": "io.github.Wopee-io/wopee-mcp",
9
9
  "description": "Wopee.io MCP server for autonomous testing platform",
10
10
  "main": "./build/index.js",