genat-mcp 2.3.2 → 2.3.3

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
@@ -225,8 +225,19 @@ Login credentials: provide in the prompt (loginUsername, loginPassword) or in pr
225
225
 
226
226
  Framework detection: language (TypeScript vs JavaScript vs Python), BDD (Cucumber, pytest-bdd, Behave), and Page Object pattern (e.g. `pages/` directory or `*Page.ts`, `*Page.js`, `*_page.py` files). Returns JSON with `testCode`, `featureFile`, `stepDefCode`, `pageObjectCode` (and `_written` paths when writing).
227
227
 
228
+ **Response fields for calling applications:** When `stepDefCode` is present, the MCP may set **`_stepDefIncomplete`** (boolean) and **`_stepDefIncompleteReason`** (string) using a lightweight heuristic (unclosed strings/delimiters, line continuation at EOF). Treat `true` as a signal to warn the user, retry generation, or block merge—do not assume the step file is runnable. Other optional fields from the workflow include **`usedLogin`** and **`loginReason`** when using login-capable workflows.
229
+
228
230
  Generated tests include keyboard accessibility checks (Tab order, focus) by default. For complex pages, the workflow uses page complexity (regions, tables, widgets) to scale assertion count.
229
231
 
232
+ ### Feature ↔ step parity (consumer CI)
233
+
234
+ GenAT does not guarantee that every Gherkin line has an implemented step. For **pytest-bdd** projects, the calling application or CI can:
235
+
236
+ 1. Parse `*.feature` step lines (after `Given`/`When`/`Then`/`And`/`But`).
237
+ 2. Confirm `accessibility_steps.py` (or your step module) defines matching `@given`/`@when`/`@then` patterns (or run `pytest --collect-only` / a dry run).
238
+
239
+ Keep this as an optional pre-merge check alongside `_stepDefIncomplete`.
240
+
230
241
  ## Sample prompts
231
242
 
232
243
  See [prompts.md](prompts.md) for a full list of prompts including specific parent folders, login, JIRA, and combined options.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Lightweight heuristic: detect likely truncated or syntactically incomplete pytest-bdd step Python.
3
+ * For calling apps to surface _stepDefIncomplete and retry or warn.
4
+ */
5
+
6
+ /**
7
+ * @param {string} code
8
+ * @returns {{ incomplete: boolean, reason: string | null }}
9
+ */
10
+ export function detectStepDefIncomplete(code) {
11
+ if (code == null || typeof code !== 'string') {
12
+ return { incomplete: false, reason: null };
13
+ }
14
+ const t = code.replace(/\r\n/g, '\n').trimEnd();
15
+ if (t.length === 0) {
16
+ return { incomplete: true, reason: 'empty' };
17
+ }
18
+
19
+ const triples = (t.match(/"""/g) || []).length;
20
+ if (triples % 2 !== 0) {
21
+ return { incomplete: true, reason: 'unclosed_triple_double_quoted_string' };
22
+ }
23
+ const tripleSingle = (t.match(/'''/g) || []).length;
24
+ if (tripleSingle % 2 !== 0) {
25
+ return { incomplete: true, reason: 'unclosed_triple_single_quoted_string' };
26
+ }
27
+
28
+ const lines = t.split('\n');
29
+ const lastLine = lines[lines.length - 1];
30
+ if (/\\\s*$/.test(lastLine)) {
31
+ return { incomplete: true, reason: 'line_continuation_at_eof' };
32
+ }
33
+
34
+ const depth = bracketDepthAtEnd(t);
35
+ if (depth !== 0) {
36
+ return { incomplete: true, reason: 'unclosed_delimiters' };
37
+ }
38
+
39
+ return { incomplete: false, reason: null };
40
+ }
41
+
42
+ /**
43
+ * Rough balance of (), [], {} at end of file, skipping strings and comments.
44
+ * @param {string} s
45
+ * @returns {number} 0 if balanced, non-zero if likely incomplete
46
+ */
47
+ function bracketDepthAtEnd(s) {
48
+ let i = 0;
49
+ const stack = [];
50
+ let state = 'code';
51
+
52
+ while (i < s.length) {
53
+ const c = s[i];
54
+
55
+ if (state === 'code') {
56
+ if (c === '#') {
57
+ while (i < s.length && s[i] !== '\n') i++;
58
+ continue;
59
+ }
60
+ if (s.slice(i, i + 3) === '"""') {
61
+ state = 'triple_double';
62
+ i += 3;
63
+ continue;
64
+ }
65
+ if (s.slice(i, i + 3) === "'''") {
66
+ state = 'triple_single';
67
+ i += 3;
68
+ continue;
69
+ }
70
+ if (c === '"' || c === "'") {
71
+ state = c === '"' ? 'string_double' : 'string_single';
72
+ i++;
73
+ continue;
74
+ }
75
+ if (c === '(') stack.push('(');
76
+ else if (c === '[') stack.push('[');
77
+ else if (c === '{') stack.push('{');
78
+ else if (c === ')' || c === ']' || c === '}') {
79
+ const want = c === ')' ? '(' : c === ']' ? '[' : '{';
80
+ if (stack.length === 0 || stack[stack.length - 1] !== want) {
81
+ return -1;
82
+ }
83
+ stack.pop();
84
+ }
85
+ i++;
86
+ continue;
87
+ }
88
+
89
+ if (state === 'string_single') {
90
+ if (c === '\\') {
91
+ i += 2;
92
+ continue;
93
+ }
94
+ if (c === "'") {
95
+ state = 'code';
96
+ }
97
+ i++;
98
+ continue;
99
+ }
100
+
101
+ if (state === 'string_double') {
102
+ if (c === '\\') {
103
+ i += 2;
104
+ continue;
105
+ }
106
+ if (c === '"') {
107
+ state = 'code';
108
+ }
109
+ i++;
110
+ continue;
111
+ }
112
+
113
+ if (state === 'triple_double') {
114
+ if (s.slice(i, i + 3) === '"""') {
115
+ state = 'code';
116
+ i += 3;
117
+ continue;
118
+ }
119
+ i++;
120
+ continue;
121
+ }
122
+
123
+ if (state === 'triple_single') {
124
+ if (s.slice(i, i + 3) === "'''") {
125
+ state = 'code';
126
+ i += 3;
127
+ continue;
128
+ }
129
+ i++;
130
+ continue;
131
+ }
132
+
133
+ i++;
134
+ }
135
+
136
+ if (state !== 'code') {
137
+ return 1;
138
+ }
139
+ return stack.length;
140
+ }
package/index.js CHANGED
@@ -11,6 +11,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
11
11
  import { z } from 'zod';
12
12
  import { detectFramework } from './detect-framework.js';
13
13
  import { detectLogin } from './detect-login.js';
14
+ import { detectStepDefIncomplete } from './detect-incomplete-step-def.js';
14
15
  import { finalizePytestBddStepDef } from './finalize-pytest-bdd-steps.js';
15
16
  import { resolvePytestBddPaths } from './pytest-bdd-paths.js';
16
17
  import { writeGeneratedFiles } from './write-files.js';
@@ -58,7 +59,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body, signal })
58
59
  const server = new McpServer(
59
60
  {
60
61
  name: 'GenAT',
61
- version: '2.3.2',
62
+ version: '2.3.3',
62
63
  },
63
64
  {
64
65
  capabilities: {
@@ -283,6 +284,12 @@ server.registerTool(
283
284
  if (data.testCode) data.testCode = finalizePytestBddStepDef(resolvedPath, data.testCode, pathOverrides);
284
285
  }
285
286
 
287
+ if (data.stepDefCode && typeof data.stepDefCode === 'string') {
288
+ const inc = detectStepDefIncomplete(data.stepDefCode);
289
+ data._stepDefIncomplete = inc.incomplete;
290
+ if (inc.reason) data._stepDefIncompleteReason = inc.reason;
291
+ }
292
+
286
293
  if (writeToProject && resolvedPath && data) {
287
294
  if (data.bddFramework == null) data.bddFramework = framework.bddFramework;
288
295
  try {
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "genat-mcp",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "mcpName": "io.github.asokans@oclc.org/genat",
5
5
  "description": "MCP server GenAT: generate accessibility tests via n8n workflow (url + project folder)",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "bin": { "genat-mcp": "index.js" },
9
9
  "engines": { "node": ">=20" },
10
- "files": ["index.js", "detect-framework.js", "detect-login.js", "write-files.js", "strip-gherkin-from-python-steps.js", "finalize-pytest-bdd-steps.js", "fix-pytest-bdd-scenario-path.js", "pytest-bdd-paths.js", "oclc-context.js", "oclc-accessibility-confluence-context.md", "run-genat.mjs", "rules/genat-mcp.mdc"],
10
+ "files": ["index.js", "detect-framework.js", "detect-login.js", "detect-incomplete-step-def.js", "write-files.js", "strip-gherkin-from-python-steps.js", "finalize-pytest-bdd-steps.js", "fix-pytest-bdd-scenario-path.js", "pytest-bdd-paths.js", "oclc-context.js", "oclc-accessibility-confluence-context.md", "run-genat.mjs", "rules/genat-mcp.mdc"],
11
11
  "keywords": ["mcp", "accessibility", "playwright", "n8n", "model-context-protocol", "a11y", "testing"],
12
12
  "repository": {
13
13
  "type": "git",