genat-mcp 2.2.3 → 2.2.5
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/detect-framework.js +91 -7
- package/index.js +2 -2
- package/package.json +1 -1
- package/rules/genat-mcp.mdc +4 -0
package/detect-framework.js
CHANGED
|
@@ -18,7 +18,7 @@ export function detectFramework(parentProjectFolder) {
|
|
|
18
18
|
scriptType: 'typescript',
|
|
19
19
|
bddFramework: 'none',
|
|
20
20
|
pageObject: false,
|
|
21
|
-
projectSummary:
|
|
21
|
+
projectSummary: `Folder not found or empty; using defaults. (resolved: ${root})`,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -44,8 +44,18 @@ export function detectFramework(parentProjectFolder) {
|
|
|
44
44
|
const hasTs = hasExtension(root, '.ts') || hasExtension(root, '.spec.ts');
|
|
45
45
|
const hasJs = hasExtension(root, '.js') || hasExtension(root, '.spec.js') || hasExtension(root, '.jsx') || hasExtension(root, '.spec.jsx');
|
|
46
46
|
const hasPy = hasExtension(root, '.py') || hasExtension(root, '_test.py');
|
|
47
|
+
const hasConftest = walk(root, (f) => f === 'conftest.py').length > 0;
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
// When both package.json and pyproject.toml exist, prefer Python if pyproject indicates Python project
|
|
50
|
+
let pyprojectIndicatesPython = false;
|
|
51
|
+
if (hasPyproject) {
|
|
52
|
+
try {
|
|
53
|
+
const pyproject = readFileSync(join(root, 'pyproject.toml'), 'utf8');
|
|
54
|
+
pyprojectIndicatesPython = /\[project\]/.test(pyproject) && (/pytest/i.test(pyproject) || /pytest-bdd/i.test(pyproject));
|
|
55
|
+
} catch (_) {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ((hasPy || hasConftest || pyprojectIndicatesPython) && (hasRequirements || hasPyproject || hasPy || hasConftest)) {
|
|
49
59
|
scriptType = 'python';
|
|
50
60
|
summaryParts.push('Language: Python');
|
|
51
61
|
} else if (hasPackageJson || hasTsConfig || hasTs || hasJs) {
|
|
@@ -85,9 +95,38 @@ export function detectFramework(parentProjectFolder) {
|
|
|
85
95
|
summaryParts.push('BDD: Behave');
|
|
86
96
|
}
|
|
87
97
|
}
|
|
98
|
+
if (bddFramework === 'none' && hasPyproject) {
|
|
99
|
+
try {
|
|
100
|
+
const pyproject = readFileSync(join(root, 'pyproject.toml'), 'utf8');
|
|
101
|
+
if (/pytest-bdd/i.test(pyproject)) {
|
|
102
|
+
bddFramework = 'pytest-bdd';
|
|
103
|
+
summaryParts.push('BDD: pytest-bdd (pyproject.toml)');
|
|
104
|
+
} else if (/behave/i.test(pyproject)) {
|
|
105
|
+
bddFramework = 'behave';
|
|
106
|
+
summaryParts.push('BDD: Behave (pyproject.toml)');
|
|
107
|
+
}
|
|
108
|
+
} catch (_) {}
|
|
109
|
+
}
|
|
88
110
|
if (bddFramework === 'none' && existsSync(join(root, 'features'))) {
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
// features/ exists: check .py files for pytest_bdd before defaulting to behave
|
|
112
|
+
const pyFilePaths = walkWithPaths(root, (f) => f.endsWith('.py'));
|
|
113
|
+
let foundPytestBdd = false;
|
|
114
|
+
for (const full of pyFilePaths) {
|
|
115
|
+
try {
|
|
116
|
+
const content = readFileSync(full, 'utf8');
|
|
117
|
+
if (/pytest_bdd|pytest-bdd|from pytest_bdd|import pytest_bdd/i.test(content)) {
|
|
118
|
+
foundPytestBdd = true;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
} catch (_) {}
|
|
122
|
+
}
|
|
123
|
+
if (foundPytestBdd) {
|
|
124
|
+
bddFramework = 'pytest-bdd';
|
|
125
|
+
summaryParts.push('BDD: pytest-bdd (found in .py files)');
|
|
126
|
+
} else {
|
|
127
|
+
bddFramework = 'behave';
|
|
128
|
+
summaryParts.push('BDD: Behave (features/ present)');
|
|
129
|
+
}
|
|
91
130
|
}
|
|
92
131
|
if (bddFramework === 'none' && hasFeatureFiles) summaryParts.push('Found .feature files');
|
|
93
132
|
} catch (_) {}
|
|
@@ -96,15 +135,33 @@ export function detectFramework(parentProjectFolder) {
|
|
|
96
135
|
summaryParts.push('Found .feature files (consider Cucumber)');
|
|
97
136
|
}
|
|
98
137
|
|
|
99
|
-
// Page Object: pages
|
|
138
|
+
// Page Object: pages/, page_objects/, or *Page.ts, *Page.js, *_page.py, *page.py
|
|
100
139
|
try {
|
|
140
|
+
const hasPagesInSubdir = (function findPagesDir(dir) {
|
|
141
|
+
try {
|
|
142
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
143
|
+
for (const e of entries) {
|
|
144
|
+
if (e.isDirectory() && (e.name === 'pages' || e.name === 'page_objects')) return true;
|
|
145
|
+
if (e.isDirectory() && !['node_modules', '.git', '__pycache__', '.venv', 'venv'].includes(e.name)) {
|
|
146
|
+
if (findPagesDir(join(dir, e.name))) return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch (_) {}
|
|
150
|
+
return false;
|
|
151
|
+
})(root);
|
|
101
152
|
if (existsSync(join(root, 'pages'))) {
|
|
102
153
|
pageObject = true;
|
|
103
154
|
summaryParts.push('Page Object: pages/ directory');
|
|
155
|
+
} else if (existsSync(join(root, 'page_objects'))) {
|
|
156
|
+
pageObject = true;
|
|
157
|
+
summaryParts.push('Page Object: page_objects/ directory');
|
|
158
|
+
} else if (hasPagesInSubdir) {
|
|
159
|
+
pageObject = true;
|
|
160
|
+
summaryParts.push('Page Object: pages/ or page_objects/ in subdir');
|
|
104
161
|
} else {
|
|
105
162
|
const pageTs = walk(root, (f) => f.endsWith('Page.ts') || f.endsWith('Page.tsx'));
|
|
106
163
|
const pageJs = walk(root, (f) => f.endsWith('Page.js') || f.endsWith('Page.jsx'));
|
|
107
|
-
const pagePy = walk(root, (f) => f.endsWith('_page.py') || f.endsWith('Page.py'));
|
|
164
|
+
const pagePy = walk(root, (f) => f.endsWith('_page.py') || f.endsWith('Page.py') || f.endsWith('page.py'));
|
|
108
165
|
if (pageTs.length || pageJs.length || pagePy.length) {
|
|
109
166
|
pageObject = true;
|
|
110
167
|
summaryParts.push('Page Object: *Page.ts / *Page.js / *_page.py files');
|
|
@@ -112,7 +169,10 @@ export function detectFramework(parentProjectFolder) {
|
|
|
112
169
|
}
|
|
113
170
|
} catch (_) {}
|
|
114
171
|
|
|
115
|
-
const projectSummary =
|
|
172
|
+
const projectSummary = [
|
|
173
|
+
...(summaryParts.length ? summaryParts : ['No framework hints detected']),
|
|
174
|
+
`(scanned: ${root})`,
|
|
175
|
+
].join('; ');
|
|
116
176
|
|
|
117
177
|
return {
|
|
118
178
|
scriptType,
|
|
@@ -122,6 +182,30 @@ export function detectFramework(parentProjectFolder) {
|
|
|
122
182
|
};
|
|
123
183
|
}
|
|
124
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Walk directory and return full paths of files matching predicate.
|
|
187
|
+
* @param {string} dir
|
|
188
|
+
* @param {(f: string) => boolean} predicate
|
|
189
|
+
* @param {string[]} [acc]
|
|
190
|
+
* @returns {string[]}
|
|
191
|
+
*/
|
|
192
|
+
function walkWithPaths(dir, predicate, acc = []) {
|
|
193
|
+
try {
|
|
194
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
195
|
+
for (const e of entries) {
|
|
196
|
+
const full = join(dir, e.name);
|
|
197
|
+
if (e.isDirectory()) {
|
|
198
|
+
if (e.name !== 'node_modules' && e.name !== '.git' && e.name !== '__pycache__' && e.name !== '.venv' && e.name !== 'venv') {
|
|
199
|
+
walkWithPaths(full, predicate, acc);
|
|
200
|
+
}
|
|
201
|
+
} else if (predicate(e.name)) {
|
|
202
|
+
acc.push(full);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (_) {}
|
|
206
|
+
return acc;
|
|
207
|
+
}
|
|
208
|
+
|
|
125
209
|
/**
|
|
126
210
|
* @param {string} dir
|
|
127
211
|
* @param {(f: string) => boolean} predicate
|
package/index.js
CHANGED
|
@@ -52,7 +52,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body }) {
|
|
|
52
52
|
const server = new McpServer(
|
|
53
53
|
{
|
|
54
54
|
name: 'GenAT',
|
|
55
|
-
version: '2.2.
|
|
55
|
+
version: '2.2.5',
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
capabilities: {
|
|
@@ -65,7 +65,7 @@ server.registerTool(
|
|
|
65
65
|
'GenAT',
|
|
66
66
|
{
|
|
67
67
|
description:
|
|
68
|
-
'Generate accessibility tests for a URL based on the project folder. Analyzes the page (DOM + a11y), detects project framework (TypeScript, JavaScript, or Python, BDD, Page Object), and returns generated test files (feature, spec/step defs, page objects). Optionally write files to the project folder. On failure: run node node_modules/genat-mcp/run-genat.mjs (NOT run-genat.mjs in project root). Use webhook-genat-login in N8N_WEBHOOK_URL (never webhook-genat).',
|
|
68
|
+
'Generate accessibility tests for a URL based on the project folder. Analyzes the page (DOM + a11y), detects project framework (TypeScript, JavaScript, or Python, BDD, Page Object), and returns generated test files (feature, spec/step defs, page objects). Optionally write files to the project folder. IMPORTANT: When the user mentions a JIRA ticket (e.g. "JIRA RB-40039"), pass jiraNumber with the key only (e.g. RB-40039) so the workflow can fetch acceptance criteria. On failure: run node node_modules/genat-mcp/run-genat.mjs (NOT run-genat.mjs in project root). Use webhook-genat-login in N8N_WEBHOOK_URL (never webhook-genat).',
|
|
69
69
|
inputSchema: {
|
|
70
70
|
url: z.string().describe('Page URL to analyze for accessibility (e.g. https://example.com)'),
|
|
71
71
|
parentProjectFolder: z
|
package/package.json
CHANGED
package/rules/genat-mcp.mdc
CHANGED
|
@@ -27,3 +27,7 @@ Example:
|
|
|
27
27
|
```bash
|
|
28
28
|
N8N_WEBHOOK_URL='https://your-n8n/webhook-test/webhook-genat-login' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs https://example.com .
|
|
29
29
|
```
|
|
30
|
+
|
|
31
|
+
## When invoking GenAT with JIRA
|
|
32
|
+
|
|
33
|
+
When the user mentions a JIRA ticket in their prompt (e.g. "JIRA RB-40039", "JIRA ticket PROJ-123", "JIRA RB-40039"), **always pass `jiraNumber`** to the GenAT tool with the extracted key only (e.g. `RB-40039`, not "JIRA RB-40039"). The workflow uses this to fetch acceptance criteria. If you omit it, jiraNumber will be null and JIRA integration will not run.
|