genat-mcp 2.2.4 → 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.
@@ -18,7 +18,7 @@ export function detectFramework(parentProjectFolder) {
18
18
  scriptType: 'typescript',
19
19
  bddFramework: 'none',
20
20
  pageObject: false,
21
- projectSummary: 'Folder not found or empty; using defaults.',
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
- if (hasPy && (hasRequirements || hasPyproject || hasPy)) {
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
- bddFramework = 'behave';
90
- summaryParts.push('BDD: Behave (features/ present)');
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/ or *Page.ts, *Page.js, *_page.py
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 = summaryParts.length ? summaryParts.join('; ') : 'No framework hints detected.';
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.4',
55
+ version: '2.2.5',
56
56
  },
57
57
  {
58
58
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genat-mcp",
3
- "version": "2.2.4",
3
+ "version": "2.2.5",
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",