@qa-gentic/agents 1.1.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.
- package/README.md +203 -0
- package/bin/postinstall.js +75 -0
- package/bin/qa-stlc.js +76 -0
- package/package.json +48 -0
- package/skills/qa-stlc/AGENT-BEHAVIOR.md +373 -0
- package/skills/qa-stlc/deduplication-protocol.md +303 -0
- package/skills/qa-stlc/generate-gherkin.md +550 -0
- package/skills/qa-stlc/generate-playwright-code.md +439 -0
- package/skills/qa-stlc/generate-test-cases.md +176 -0
- package/skills/qa-stlc/write-helix-files.md +349 -0
- package/src/cmd-init.js +84 -0
- package/src/cmd-mcp-config.js +177 -0
- package/src/cmd-skills.js +124 -0
- package/src/cmd-verify.js +129 -0
- package/src/qa_stlc_agents/__init__.py +0 -0
- package/src/qa_stlc_agents/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/__pycache__/server.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/server.py +502 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/tools/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/tools/__pycache__/ado_gherkin.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_gherkin_generator/tools/ado_gherkin.py +854 -0
- package/src/qa_stlc_agents/agent_helix_writer/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_helix_writer/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_helix_writer/__pycache__/server.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_helix_writer/server.py +529 -0
- package/src/qa_stlc_agents/agent_helix_writer/tools/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_helix_writer/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_helix_writer/tools/__pycache__/helix_write.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_helix_writer/tools/helix_write.py +622 -0
- package/src/qa_stlc_agents/agent_playwright_generator/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_playwright_generator/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_playwright_generator/__pycache__/server.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_playwright_generator/server.py +2771 -0
- package/src/qa_stlc_agents/agent_playwright_generator/tools/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_playwright_generator/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_playwright_generator/tools/__pycache__/ado_attach.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_playwright_generator/tools/ado_attach.py +62 -0
- package/src/qa_stlc_agents/agent_test_case_manager/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_test_case_manager/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_test_case_manager/__pycache__/server.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_test_case_manager/server.py +483 -0
- package/src/qa_stlc_agents/agent_test_case_manager/tools/__init__.py +0 -0
- package/src/qa_stlc_agents/agent_test_case_manager/tools/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_test_case_manager/tools/__pycache__/ado_workitem.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/agent_test_case_manager/tools/ado_workitem.py +302 -0
- package/src/qa_stlc_agents/shared/__init__.py +0 -0
- package/src/qa_stlc_agents/shared/__pycache__/__init__.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/shared/__pycache__/auth.cpython-310.pyc +0 -0
- package/src/qa_stlc_agents/shared/auth.py +119 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: write-helix-files
|
|
3
|
+
description: >
|
|
4
|
+
Use this skill whenever generated Playwright TypeScript files (locators,
|
|
5
|
+
page objects, step definitions, feature files, healing infrastructure) need
|
|
6
|
+
to be placed into a local Helix-QA project on disk. Triggers after
|
|
7
|
+
generate-playwright-code or scaffold_locator_repository has produced a
|
|
8
|
+
'files' dict. Always calls inspect_helix_project first to determine whether
|
|
9
|
+
the framework exists, then picks the correct write mode automatically.
|
|
10
|
+
compatibility:
|
|
11
|
+
tools:
|
|
12
|
+
- qa-helix-writer:inspect_helix_project
|
|
13
|
+
- qa-helix-writer:list_helix_tree
|
|
14
|
+
- qa-helix-writer:read_helix_file
|
|
15
|
+
- qa-helix-writer:write_helix_files
|
|
16
|
+
- qa-helix-writer:update_helix_file
|
|
17
|
+
- qa-playwright-generator:generate_playwright_code
|
|
18
|
+
- qa-playwright-generator:scaffold_locator_repository
|
|
19
|
+
- qa-playwright-generator:pre_validate_cucumber_steps
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Write Helix Files Skill
|
|
23
|
+
|
|
24
|
+
## 🚨 HARD STOP RULES — Read Before Anything Else
|
|
25
|
+
|
|
26
|
+
**HARD STOP 1 — NEVER use `create_file` as a fallback.**
|
|
27
|
+
`create_file` bypasses deduplication, interface-adaptation rewrites, and file routing.
|
|
28
|
+
If `write_helix_files` returns an error, diagnose and fix the payload — do NOT route
|
|
29
|
+
around the tool. If the error cannot be resolved, surface it to the user verbatim.
|
|
30
|
+
|
|
31
|
+
**HARD STOP 2 — NEVER create a new locator or step file when one already exists.**
|
|
32
|
+
Before writing, call `list_helix_tree` to check what files are already on disk.
|
|
33
|
+
If `src/locators/<foo>.locators.ts` already exists → it is the only correct target.
|
|
34
|
+
Do NOT create `src/locators/<foo>-bar.locators.ts` or any variant filename.
|
|
35
|
+
`write_helix_files` will merge new keys into the existing file automatically.
|
|
36
|
+
|
|
37
|
+
**HARD STOP 3 — Step definitions MUST use Cucumber expressions, not regex.**
|
|
38
|
+
`write_helix_files` runs a parenthesis-balance validator on every `*.steps.ts` file.
|
|
39
|
+
Regex step patterns (e.g. `/^I click "([^"]*)"$/`) contain un-paired parentheses and
|
|
40
|
+
will fail this check. Before submitting, verify every `When/Then/Given` block matches
|
|
41
|
+
the Cucumber expression format:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// ✅ Correct — Cucumber expression
|
|
45
|
+
When('the user enters {string} in the username field', async function (value: string) {
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// ❌ Rejected — regex pattern
|
|
50
|
+
When(/^the user enters "([^"]*)" in the username field$/, async function (value: string) {
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If `generate_playwright_code` produced regex patterns, use `pre_validate_cucumber_steps`
|
|
54
|
+
to identify them, then convert to Cucumber expressions before calling `write_helix_files`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
Place files produced by `qa-playwright-generator` into a local Helix-QA
|
|
59
|
+
project. The skill handles two situations automatically:
|
|
60
|
+
|
|
61
|
+
| Situation | What happens |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Framework does not exist yet | Scaffold infrastructure + write test files |
|
|
64
|
+
| Framework already exists | Merge only the new work item's test files; never touch infrastructure |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Helix-QA Directory Layout
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
<helix_root>/
|
|
72
|
+
├── src/
|
|
73
|
+
│ ├── locators/ ← <kebab>.locators.ts (merged per file)
|
|
74
|
+
│ ├── pages/ ← <Page>.page.ts (merged per file)
|
|
75
|
+
│ ├── test/
|
|
76
|
+
│ │ ├── features/ ← <feature>.feature (overwritten)
|
|
77
|
+
│ │ └── steps/ ← <feature>.steps.ts (merged per file)
|
|
78
|
+
│ ├── utils/
|
|
79
|
+
│ │ └── locators/ ← LocatorHealer.ts etc. (protected)
|
|
80
|
+
│ └── config/
|
|
81
|
+
│ └── cucumber.config.ts ← profile blocks appended (deduplicated)
|
|
82
|
+
└── package.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Decision Flow
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
inspect_helix_project(helix_root)
|
|
91
|
+
│
|
|
92
|
+
├─ framework_state: "absent" or "partial"
|
|
93
|
+
│ │
|
|
94
|
+
│ └─ scaffold_locator_repository(...)
|
|
95
|
+
│ │
|
|
96
|
+
│ └─ write_helix_files(..., mode="scaffold_and_tests")
|
|
97
|
+
│ Writes infra files that don't exist + test files
|
|
98
|
+
│
|
|
99
|
+
└─ framework_state: "present"
|
|
100
|
+
│
|
|
101
|
+
└─ write_helix_files(..., mode="tests_only")
|
|
102
|
+
Merges only locators / page / steps / feature
|
|
103
|
+
Infrastructure files are NEVER touched
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Step-by-Step Workflow
|
|
109
|
+
|
|
110
|
+
### Step 1 — Confirm helix_root
|
|
111
|
+
|
|
112
|
+
Ask the user for the absolute path to the Helix-QA project root if not provided.
|
|
113
|
+
This is the directory containing `package.json` and `src/`.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### Step 2 — Inspect framework state
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
qa-helix-writer:inspect_helix_project(helix_root)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Read the response:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"framework_state": "present",
|
|
128
|
+
"existing_infra": ["LocatorHealer.ts", "LocatorRepository.ts", ...],
|
|
129
|
+
"missing_infra": [],
|
|
130
|
+
"recommendation": "tests_only",
|
|
131
|
+
"message": "Helix-QA framework is present. ..."
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Act on `recommendation` directly — do not ask the user to choose the mode.**
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Step 3A — Framework absent or partial → scaffold first
|
|
140
|
+
|
|
141
|
+
Only when `recommendation` is `"scaffold_and_tests"`:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
# Generate infrastructure
|
|
145
|
+
qa-playwright-generator:scaffold_locator_repository(
|
|
146
|
+
output_dir = "src/utils/locators",
|
|
147
|
+
enable_ai_vision = true,
|
|
148
|
+
repository_path = "test-results/locator-repository.json",
|
|
149
|
+
dashboard_port = 7890,
|
|
150
|
+
enable_timing_healing = true,
|
|
151
|
+
enable_visual_regression = true,
|
|
152
|
+
enable_devtools_healer = true
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Then proceed to Step 4 with `mode="scaffold_and_tests"`.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### Step 3B — Framework present → skip scaffold
|
|
161
|
+
|
|
162
|
+
When `recommendation` is `"tests_only"`, skip Step 3A entirely.
|
|
163
|
+
Proceed to Step 4 with `mode="tests_only"`.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### Step 4 — Generate test files
|
|
168
|
+
|
|
169
|
+
Run `generate_playwright_code` for the work item's Gherkin feature:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
qa-playwright-generator:generate_playwright_code(
|
|
173
|
+
gherkin_content = <validated .feature file content>,
|
|
174
|
+
page_class_name = "<PageClass>",
|
|
175
|
+
app_name = "<app>",
|
|
176
|
+
healing_strategy = "role-label-text-ai",
|
|
177
|
+
enable_visual_regression = true,
|
|
178
|
+
enable_timing_healing = true
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Step 5 — Write files
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
qa-helix-writer:write_helix_files(
|
|
188
|
+
helix_root = "<helix_root>",
|
|
189
|
+
files = <files dict from generate_playwright_code>,
|
|
190
|
+
mode = "<recommendation from Step 2>"
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The tool handles everything automatically:
|
|
195
|
+
|
|
196
|
+
**File routing:**
|
|
197
|
+
|
|
198
|
+
| Generator key | Helix destination |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `src/pages/<k>/locators.ts` | `src/locators/<k>.locators.ts` |
|
|
201
|
+
| `src/pages/<k>/<k>.page.ts` | `src/pages/<k>.page.ts` |
|
|
202
|
+
| `src/test/steps/<k>.steps.ts` | `src/test/steps/<k>.steps.ts` |
|
|
203
|
+
| `*.feature` | `src/test/features/<k>.feature` |
|
|
204
|
+
| `config/cucumber.js (add this profile)` | `src/config/cucumber.config.ts` (appended) |
|
|
205
|
+
| `src/utils/locators/Locator*.ts` | `src/utils/locators/<file>` |
|
|
206
|
+
|
|
207
|
+
**Deduplication per file type:**
|
|
208
|
+
|
|
209
|
+
| File | Behaviour when file already exists |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `locators.ts` | New const-object keys appended; existing keys skipped |
|
|
212
|
+
| `*.steps.ts` | New step blocks appended; steps with matching regex skipped |
|
|
213
|
+
| `*.page.ts` | New async methods appended; existing method names skipped |
|
|
214
|
+
| `*.feature` | Overwritten (Gherkin is the source of truth) |
|
|
215
|
+
| `cucumber.config.ts` | Profile block appended; duplicate profile names skipped |
|
|
216
|
+
| Infrastructure `*.ts` | Protected in `tests_only`; written-if-absent in `scaffold_and_tests` |
|
|
217
|
+
|
|
218
|
+
**Interface adaptation (applied automatically):**
|
|
219
|
+
|
|
220
|
+
| Generated code | Rewritten to |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `repo.updateHealed(k, s, …)` | `repo.setHealed(k, s)` |
|
|
223
|
+
| `repo.getBBox(key)` | `null` |
|
|
224
|
+
| `repo.incrementSuccess/Failure(…)` | removed |
|
|
225
|
+
| `repo.queueSuggestion(…)` | removed |
|
|
226
|
+
| `repo.updateBoundingBox(…)` | removed |
|
|
227
|
+
| `fixture().logger` | `this.logger` |
|
|
228
|
+
| `fixture().locatorRepository` | `this.repo` |
|
|
229
|
+
| `fixture().page` | `this.page` |
|
|
230
|
+
| `import { Logger } from "winston"` | `import { HealerLogger }` |
|
|
231
|
+
| `EnvironmentManager` | Helix `environment` singleton |
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Step 6 — Verify the response
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"summary": { "requested": 4, "written": 4, "skipped": 0 },
|
|
240
|
+
"deduplication": {
|
|
241
|
+
"src/locators/checkout.locators.ts": {
|
|
242
|
+
"type": "locators",
|
|
243
|
+
"added_keys": ["checkoutBtn", "summaryTotal"],
|
|
244
|
+
"skipped_keys": ["submitBtn", "cancelBtn"]
|
|
245
|
+
},
|
|
246
|
+
"src/test/steps/checkout.steps.ts": {
|
|
247
|
+
"type": "steps",
|
|
248
|
+
"added_patterns": ["user completes checkout"],
|
|
249
|
+
"skipped_patterns": ["user logs in"]
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Report the `deduplication` summary to the user so they can confirm which
|
|
256
|
+
symbols were added vs skipped.
|
|
257
|
+
|
|
258
|
+
**Skipped reasons to handle:**
|
|
259
|
+
|
|
260
|
+
| Reason | Action |
|
|
261
|
+
|---|---|
|
|
262
|
+
| `"empty content"` | Normal — generator produced no content for that file |
|
|
263
|
+
| `"profile '…' already exists"` | Normal — no action needed |
|
|
264
|
+
| `"infrastructure file already exists"` | Normal in `tests_only` mode |
|
|
265
|
+
| `"infrastructure file — skipped in tests_only mode"` | Normal |
|
|
266
|
+
| Any `OSError` | Report path and error to user |
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Scaffold vs Regenerate Infrastructure
|
|
271
|
+
|
|
272
|
+
**First-time setup** (`framework_state` is `"absent"` or `"partial"`):
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
scaffold_locator_repository(...) # generate 6 infra files
|
|
276
|
+
write_helix_files(..., mode="scaffold_and_tests")
|
|
277
|
+
# Writes missing infra files + test files
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Intentional infrastructure regeneration** (user explicitly requests it):
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
write_helix_files(..., mode="scaffold_and_tests", force_scaffold=true)
|
|
284
|
+
# Overwrites ALL infra files — use deliberately
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Never** call with `force_scaffold=true` unless the user has explicitly asked
|
|
288
|
+
to regenerate the healing infrastructure.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Quality Gates
|
|
293
|
+
|
|
294
|
+
Before calling `write_helix_files`:
|
|
295
|
+
|
|
296
|
+
- [ ] `inspect_helix_project` has been called and `recommendation` noted.
|
|
297
|
+
- [ ] `list_helix_tree` has been called — existing file names recorded to prevent new-file creation.
|
|
298
|
+
- [ ] Every `*.steps.ts` in the `files` dict uses Cucumber expression syntax (not regex).
|
|
299
|
+
- [ ] `pre_validate_cucumber_steps` has been called on each `*.steps.ts` and returned `valid: true`.
|
|
300
|
+
- [ ] `mode` matches `recommendation` — never override without user instruction.
|
|
301
|
+
- [ ] `force_scaffold` is `false` unless the user explicitly asked to regenerate.
|
|
302
|
+
- [ ] The `files` dict comes directly from `generate_playwright_code` or
|
|
303
|
+
`scaffold_locator_repository` — never manually constructed.
|
|
304
|
+
- [ ] After writing, the `deduplication` field has been reviewed and reported.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Failure Recovery Procedure
|
|
309
|
+
|
|
310
|
+
When `write_helix_files` returns an error:
|
|
311
|
+
|
|
312
|
+
### 1 — Parenthesis / bracket balance error in a steps file
|
|
313
|
+
|
|
314
|
+
**Symptom:** Tool returns a message about parenthesis balance, bracket imbalance,
|
|
315
|
+
or validator failure on a `*.steps.ts` file.
|
|
316
|
+
|
|
317
|
+
**Root cause:** The step definitions contain regex patterns. Regex uses un-paired
|
|
318
|
+
parentheses (`(`, `)`) that the file validator counts as unbalanced.
|
|
319
|
+
|
|
320
|
+
**Fix:**
|
|
321
|
+
1. Call `pre_validate_cucumber_steps` on the failing steps file content — it identifies
|
|
322
|
+
every offending pattern and provides a ready-to-paste Cucumber expression replacement.
|
|
323
|
+
2. Convert any regex patterns to Cucumber expressions:
|
|
324
|
+
- `"([^"]*)"` → `{string}`
|
|
325
|
+
- `(\d+)` → `{int}`
|
|
326
|
+
- `/^…$/` wrapper → plain quoted string
|
|
327
|
+
3. Retry `write_helix_files` with the corrected `files` dict.
|
|
328
|
+
4. Do NOT call `create_file` at any point.
|
|
329
|
+
|
|
330
|
+
### 2 — `OSError` / path error
|
|
331
|
+
|
|
332
|
+
**Symptom:** Tool returns an `OSError`, `FileNotFoundError`, or similar path message.
|
|
333
|
+
|
|
334
|
+
**Fix:** Report the exact path and error to the user. Do not attempt to create the
|
|
335
|
+
directory or file manually unless the user explicitly instructs it.
|
|
336
|
+
|
|
337
|
+
### 3 — Any other tool error
|
|
338
|
+
|
|
339
|
+
**Fix:** Report the raw error text to the user verbatim. Ask: "How would you like
|
|
340
|
+
to proceed?" Do not attempt any workaround autonomously.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Known Tool Limitations (Document These for Future Agent Runs)
|
|
345
|
+
|
|
346
|
+
| Limitation | Description | Workaround |
|
|
347
|
+
|---|---|---|
|
|
348
|
+
| Parenthesis validator | Rejects step files with regex step patterns; error message names regex preprocessor as cause and prescribes Cucumber expressions | Use `pre_validate_cucumber_steps` before submitting to catch and fix offending patterns |
|
|
349
|
+
| `update_helix_file` for single-file edits | Use `qa-helix-writer:update_helix_file` for targeted single-file changes instead of re-submitting the full `files` dict | Pass `relative_path` and the new content directly; set `force_overwrite=true` only when replacing existing content |
|
package/src/cmd-init.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cmd-init.js — `qa-stlc init`
|
|
3
|
+
*
|
|
4
|
+
* Full bootstrap: install Python agents + skills + MCP config.
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const { execSync, spawnSync } = require("child_process");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
|
|
12
|
+
const cmdSkills = require("./cmd-skills");
|
|
13
|
+
const cmdMcpConfig = require("./cmd-mcp-config");
|
|
14
|
+
|
|
15
|
+
const C = {
|
|
16
|
+
reset: "\x1b[0m", bold: "\x1b[1m",
|
|
17
|
+
green: "\x1b[32m", cyan: "\x1b[36m",
|
|
18
|
+
yellow: "\x1b[33m", red: "\x1b[31m", dim: "\x1b[2m",
|
|
19
|
+
};
|
|
20
|
+
const ok = (m) => console.log(`${C.green}✓${C.reset} ${m}`);
|
|
21
|
+
const info = (m) => console.log(`${C.cyan}→${C.reset} ${m}`);
|
|
22
|
+
const warn = (m) => console.log(`${C.yellow}⚠${C.reset} ${m}`);
|
|
23
|
+
const die = (m) => { console.error(`${C.red}✗${C.reset} ${m}`); process.exit(1); };
|
|
24
|
+
|
|
25
|
+
module.exports = async function init(opts) {
|
|
26
|
+
console.log(`\n${C.bold}QA STLC Agents — init${C.reset}\n`);
|
|
27
|
+
|
|
28
|
+
// ── 1. Check Python ──────────────────────────────────────────────────────
|
|
29
|
+
const python = opts.python || "python3";
|
|
30
|
+
info(`Checking Python (${python})…`);
|
|
31
|
+
const pyCheck = spawnSync(python, ["--version"], { encoding: "utf8" });
|
|
32
|
+
if (pyCheck.status !== 0) {
|
|
33
|
+
die(`Python not found at '${python}'. Install Python 3.10+ or pass --python <path>.`);
|
|
34
|
+
}
|
|
35
|
+
const pyVersion = (pyCheck.stdout || pyCheck.stderr || "").trim();
|
|
36
|
+
const match = pyVersion.match(/Python (\d+)\.(\d+)/);
|
|
37
|
+
if (!match || parseInt(match[1]) < 3 || parseInt(match[2]) < 10) {
|
|
38
|
+
die(`Python 3.10+ required, found: ${pyVersion}`);
|
|
39
|
+
}
|
|
40
|
+
ok(`${pyVersion} found.`);
|
|
41
|
+
|
|
42
|
+
// ── 2. pip install qa-gentic-agents ────────────────────────────────────────
|
|
43
|
+
info("Installing qa-gentic-agents (pip)…");
|
|
44
|
+
const pip = spawnSync(python, ["-m", "pip", "install", "qa-gentic-agents>=1.0.1", "--quiet"], {
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
});
|
|
48
|
+
if (pip.status !== 0) {
|
|
49
|
+
die("pip install failed. Run manually: pip install qa-gentic-agents");
|
|
50
|
+
}
|
|
51
|
+
ok("qa-gentic-agents installed.");
|
|
52
|
+
|
|
53
|
+
// ── 3. Install skills ────────────────────────────────────────────────────
|
|
54
|
+
info("Installing skills…");
|
|
55
|
+
const skillTarget = opts.vscode ? "both" : "claude";
|
|
56
|
+
await cmdSkills({ target: skillTarget });
|
|
57
|
+
|
|
58
|
+
// ── 4. Write MCP config ──────────────────────────────────────────────────
|
|
59
|
+
info("Writing MCP config…");
|
|
60
|
+
await cmdMcpConfig({
|
|
61
|
+
vscode: opts.vscode || false,
|
|
62
|
+
print: false,
|
|
63
|
+
python: python,
|
|
64
|
+
playwrightPort: "8931",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ── 5. Done ──────────────────────────────────────────────────────────────
|
|
68
|
+
console.log(`
|
|
69
|
+
${C.bold}Done!${C.reset} Next steps:
|
|
70
|
+
|
|
71
|
+
Start Playwright MCP (required for live locator verification):
|
|
72
|
+
${C.cyan}npx @playwright/mcp@latest --port 8931${C.reset}
|
|
73
|
+
|
|
74
|
+
${C.bold}Claude Code${C.reset}
|
|
75
|
+
Open this project in Claude Code, then run:
|
|
76
|
+
${C.cyan}/mcp${C.reset} — verify all 5 servers are loaded
|
|
77
|
+
${C.cyan}/skills${C.reset} — browse installed skills
|
|
78
|
+
|
|
79
|
+
${C.bold}GitHub Copilot${C.reset}
|
|
80
|
+
Skills are at .github/copilot-instructions/
|
|
81
|
+
MCP config is at .vscode/mcp.json
|
|
82
|
+
Reload VS Code window to pick up the servers.
|
|
83
|
+
`);
|
|
84
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cmd-mcp-config.js — `qa-stlc mcp-config`
|
|
3
|
+
*
|
|
4
|
+
* Generates .mcp.json (Claude Code) or .vscode/mcp.json (GitHub Copilot / VS Code).
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const { spawnSync } = require("child_process");
|
|
11
|
+
|
|
12
|
+
const C = {
|
|
13
|
+
reset: "\x1b[0m", bold: "\x1b[1m",
|
|
14
|
+
green: "\x1b[32m", cyan: "\x1b[36m",
|
|
15
|
+
yellow: "\x1b[33m", red: "\x1b[31m", dim: "\x1b[2m",
|
|
16
|
+
};
|
|
17
|
+
const ok = (m) => console.log(`${C.green}✓${C.reset} ${m}`);
|
|
18
|
+
const info = (m) => console.log(`${C.cyan}→${C.reset} ${m}`);
|
|
19
|
+
const warn = (m) => console.log(`${C.yellow}⚠${C.reset} ${m}`);
|
|
20
|
+
|
|
21
|
+
const CWD = process.cwd();
|
|
22
|
+
const IS_WIN = process.platform === "win32";
|
|
23
|
+
const EXT = IS_WIN ? ".exe" : "";
|
|
24
|
+
|
|
25
|
+
const AGENT_NAMES = [
|
|
26
|
+
"qa-test-case-manager",
|
|
27
|
+
"qa-gherkin-generator",
|
|
28
|
+
"qa-playwright-generator",
|
|
29
|
+
"qa-helix-writer",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Locate the binary for an agent.
|
|
34
|
+
* Priority: .venv → --python dir → system PATH → python sys.executable dir → macOS framework dirs
|
|
35
|
+
*/
|
|
36
|
+
function findBinary(name, pythonBin) {
|
|
37
|
+
// 1. .venv in cwd
|
|
38
|
+
const venvBin = IS_WIN
|
|
39
|
+
? path.join(CWD, ".venv", "Scripts", name + EXT)
|
|
40
|
+
: path.join(CWD, ".venv", "bin", name);
|
|
41
|
+
if (fs.existsSync(venvBin)) return venvBin;
|
|
42
|
+
|
|
43
|
+
// 2. dir of --python flag binary
|
|
44
|
+
if (pythonBin && pythonBin !== "python3" && pythonBin !== "python") {
|
|
45
|
+
const candidate = path.join(path.dirname(pythonBin), name + EXT);
|
|
46
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 3. system PATH via which/where
|
|
50
|
+
const which = spawnSync(IS_WIN ? "where" : "which", [name], { encoding: "utf8" });
|
|
51
|
+
if (which.status === 0 && which.stdout.trim()) return which.stdout.trim().split("\n")[0].trim();
|
|
52
|
+
|
|
53
|
+
// 4. ask Python where its own bin dir is (catches framework installs not on PATH)
|
|
54
|
+
for (const py of ["python3", "python"]) {
|
|
55
|
+
const r = spawnSync(py, ["-c", "import sys, os; print(os.path.dirname(sys.executable))"], { encoding: "utf8" });
|
|
56
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
57
|
+
const candidate = path.join(r.stdout.trim(), name + EXT);
|
|
58
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 5. macOS Python.org framework fallback
|
|
63
|
+
if (!IS_WIN) {
|
|
64
|
+
const frameworkDirs = [
|
|
65
|
+
"/Library/Frameworks/Python.framework/Versions/3.13/bin",
|
|
66
|
+
"/Library/Frameworks/Python.framework/Versions/3.12/bin",
|
|
67
|
+
"/Library/Frameworks/Python.framework/Versions/3.11/bin",
|
|
68
|
+
"/Library/Frameworks/Python.framework/Versions/3.10/bin",
|
|
69
|
+
];
|
|
70
|
+
for (const dir of frameworkDirs) {
|
|
71
|
+
const candidate = path.join(dir, name);
|
|
72
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildClaudeConfig(pythonBin, playwrightPort) {
|
|
80
|
+
const servers = {};
|
|
81
|
+
const missing = [];
|
|
82
|
+
|
|
83
|
+
for (const name of AGENT_NAMES) {
|
|
84
|
+
const bin = findBinary(name, pythonBin);
|
|
85
|
+
if (bin) {
|
|
86
|
+
servers[name] = { command: bin };
|
|
87
|
+
} else {
|
|
88
|
+
missing.push(name);
|
|
89
|
+
servers[name] = { command: `/path/to/.venv/bin/${name}`, "_comment": "NOT FOUND — run: pip install qa-gentic-agents" };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
servers["playwright"] = {
|
|
94
|
+
type: "url",
|
|
95
|
+
url: `ws://localhost:${playwrightPort}`,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return { config: { mcpServers: servers }, missing };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildVscodeConfig(pythonBin, playwrightPort) {
|
|
102
|
+
const servers = {};
|
|
103
|
+
const missing = [];
|
|
104
|
+
|
|
105
|
+
for (const name of AGENT_NAMES) {
|
|
106
|
+
const bin = findBinary(name, pythonBin);
|
|
107
|
+
if (bin) {
|
|
108
|
+
servers[name] = { command: bin };
|
|
109
|
+
} else {
|
|
110
|
+
missing.push(name);
|
|
111
|
+
servers[name] = { command: `/path/to/.venv/bin/${name}`, "_comment": "NOT FOUND — run: pip install qa-gentic-agents" };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
servers["playwright"] = {
|
|
116
|
+
type: "http",
|
|
117
|
+
url: `http://localhost:${playwrightPort}/mcp`,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return { config: { servers }, missing };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function printNextSteps(mode, playwrightPort) {
|
|
124
|
+
const isVscode = mode === "vscode";
|
|
125
|
+
console.log(`
|
|
126
|
+
${C.dim}Start Playwright MCP before running generation workflows:${C.reset}
|
|
127
|
+
npx @playwright/mcp@latest --port ${playwrightPort}
|
|
128
|
+
${C.dim}headless (CI): npx @playwright/mcp@latest --headless --port ${playwrightPort}${C.reset}
|
|
129
|
+
|
|
130
|
+
${isVscode
|
|
131
|
+
? `Reload VS Code window — all 5 MCP servers will appear in the MCP panel.`
|
|
132
|
+
: `In Claude Code, run /mcp to verify all 5 servers are loaded.`
|
|
133
|
+
}
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = async function mcpConfig(opts) {
|
|
138
|
+
const useVscode = opts.vscode || false;
|
|
139
|
+
const printOnly = opts.print || false;
|
|
140
|
+
const pythonBin = opts.python || "python3";
|
|
141
|
+
const playwrightPort = opts.playwrightPort || "8931";
|
|
142
|
+
|
|
143
|
+
if (printOnly) {
|
|
144
|
+
const { config: claudeCfg } = buildClaudeConfig(pythonBin, playwrightPort);
|
|
145
|
+
const { config: vscodeCfg } = buildVscodeConfig(pythonBin, playwrightPort);
|
|
146
|
+
console.log("\n=== Claude Code (.mcp.json) ===");
|
|
147
|
+
console.log(JSON.stringify(claudeCfg, null, 2));
|
|
148
|
+
console.log("\n=== VS Code (.vscode/mcp.json) ===");
|
|
149
|
+
console.log(JSON.stringify(vscodeCfg, null, 2));
|
|
150
|
+
printNextSteps("both", playwrightPort);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (useVscode) {
|
|
155
|
+
const { config, missing } = buildVscodeConfig(pythonBin, playwrightPort);
|
|
156
|
+
const dir = path.join(CWD, ".vscode");
|
|
157
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
158
|
+
const out = path.join(dir, "mcp.json");
|
|
159
|
+
fs.writeFileSync(out, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
160
|
+
ok(`Written → .vscode/mcp.json`);
|
|
161
|
+
if (missing.length) {
|
|
162
|
+
warn(`${missing.length} agent(s) not found — run: pip install qa-gentic-agents`);
|
|
163
|
+
missing.forEach((m) => warn(` missing: ${m}`));
|
|
164
|
+
}
|
|
165
|
+
printNextSteps("vscode", playwrightPort);
|
|
166
|
+
} else {
|
|
167
|
+
const { config, missing } = buildClaudeConfig(pythonBin, playwrightPort);
|
|
168
|
+
const out = path.join(CWD, ".mcp.json");
|
|
169
|
+
fs.writeFileSync(out, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
170
|
+
ok(`Written → .mcp.json`);
|
|
171
|
+
if (missing.length) {
|
|
172
|
+
warn(`${missing.length} agent(s) not found — run: pip install qa-gentic-agents`);
|
|
173
|
+
missing.forEach((m) => warn(` missing: ${m}`));
|
|
174
|
+
}
|
|
175
|
+
printNextSteps("claude", playwrightPort);
|
|
176
|
+
}
|
|
177
|
+
};
|