---
name: write-helix-files
description: >-
  Use when placing generated Playwright TypeScript files into a Helix-QA project on disk.
  Triggers after generate-playwright-code or scaffold_locator_repository has produced a
  files dict. Triggers on: "write to helix", "helix-qa", "write files to disk", "write
  locators", "write page objects", "write step definitions", "inspect helix", "helix root".
  Always calls inspect_helix_project first.
license: MIT
compatibility: "Requires qa-helix-writer, qa-playwright-generator MCP servers."
metadata:
  author: qa-gentic
  version: "1.1"
---

# Write Helix Files Skill

## 🚨 HARD STOP RULES — Read Before Anything Else

**HARD STOP 1 — NEVER use `create_file` as a fallback.**
`create_file` bypasses deduplication, interface-adaptation rewrites, and file routing.
If `write_helix_files` returns an error, diagnose and fix the payload — do NOT route
around the tool. If the error cannot be resolved, surface it to the user verbatim.

**HARD STOP 2 — NEVER create a new locator or step file when one already exists.**
Before writing, call `list_helix_tree` to check what files are already on disk.
If `src/locators/<foo>.locators.ts` already exists → it is the only correct target.
Do NOT create `src/locators/<foo>-bar.locators.ts` or any variant filename.
`write_helix_files` will merge new keys into the existing file automatically.

**HARD STOP 3 — Step definitions MUST use Cucumber expressions, not regex.**
`write_helix_files` runs a parenthesis-balance validator on every `*.steps.ts` file.
Regex step patterns (e.g. `/^I click "([^"]*)"$/`) contain un-paired parentheses and
will fail this check. Before submitting, verify every `When/Then/Given` block matches
the Cucumber expression format:

```typescript
// ✅ Correct — Cucumber expression
When('the user enters {string} in the username field', async function (value: string) {
```

```typescript
// ❌ Rejected — regex pattern
When(/^the user enters "([^"]*)" in the username field$/, async function (value: string) {
```

If `generate_playwright_code` produced regex patterns, use `pre_validate_cucumber_steps`
to identify them, then convert to Cucumber expressions before calling `write_helix_files`.

**HARD STOP 4 — Cucumber config: append only, never create.**
If any Cucumber config file exists (`src/config/cucumber.config.ts` or project-root
`cucumber.js`), only append new profiles—never create a new file.
`write_helix_files` will automatically skip profile creation if the file does not
exist, to prevent orphaned config files from being left behind.

---

Place files produced by `qa-playwright-generator` into a local Helix-QA
project. The skill handles two situations automatically:

| Situation | What happens |
|---|---|
| Framework does not exist yet | Scaffold infrastructure + write test files |
| Framework already exists | Merge only the new work item's test files; never touch infrastructure |

---

## Helix-QA Directory Layout

```
<helix_root>/
├── src/
│   ├── locators/                   ← <kebab>.locators.ts          (merged per file)
│   ├── pages/                      ← <Page>.page.ts               (merged per file)
│   ├── test/
│   │   ├── features/               ← <feature>.feature            (overwritten)
│   │   └── steps/                  ← <feature>.steps.ts           (merged per file)
│   ├── utils/
│   │   └── locators/               ← LocatorHealer.ts etc.        (protected)
│   └── config/
│       └── cucumber.config.ts      ← profile blocks appended      (deduplicated)
└── package.json
```

---

## Decision Flow

```
inspect_helix_project(helix_root)
         │
         ├─ framework_state: "absent" or "partial"
         │        │
         │        └─ scaffold_locator_repository(...)
         │                   │
         │                   └─ write_helix_files(..., mode="scaffold_and_tests")
         │                      Writes infra files that don't exist + test files
         │
         └─ framework_state: "present"
                  │
                  └─ write_helix_files(..., mode="tests_only")
                     Merges only locators / page / steps / feature
                     Infrastructure files are NEVER touched
```

---

## Step-by-Step Workflow

### Step 1 — Confirm helix_root

Ask the user for the absolute path to the Helix-QA project root if not provided.
This is the directory containing `package.json` and `src/`.

---

### Step 2 — Inspect framework state

```
qa-helix-writer:inspect_helix_project(helix_root)
```

Read the response:

```json
{
  "framework_state": "present",
  "existing_infra":  ["LocatorHealer.ts", "LocatorRepository.ts", ...],
  "missing_infra":   [],
  "recommendation":  "tests_only",
  "message":         "Helix-QA framework is present. ..."
}
```

**Act on `recommendation` directly — do not ask the user to choose the mode.**

---

### Step 3A — Framework absent or partial → scaffold first

Only when `recommendation` is `"scaffold_and_tests"`:

```
# Generate infrastructure
qa-playwright-generator:scaffold_locator_repository(
  output_dir               = "src/utils/locators",
  enable_ai_vision         = true,
  repository_path          = "test-results/locator-repository.json",
  dashboard_port           = 7890,
  enable_timing_healing    = true,
  enable_visual_regression = true,
  enable_devtools_healer   = true
)
```

Then proceed to Step 4 with `mode="scaffold_and_tests"`.

---

### Step 3B — Framework present → skip scaffold

When `recommendation` is `"tests_only"`, skip Step 3A entirely.
Proceed to Step 4 with `mode="tests_only"`.

---

### Step 4 — Read existing feature file before generating

**MANDATORY before calling `generate_playwright_code`.**

If a `.feature` file already exists on disk for this work item, read it first:

```
qa-helix-writer:read_helix_file(
  helix_root    = "<helix_root>",
  relative_path = "src/test/features/<feature>.feature"
)
```

If `exists: true`, extract the following from the response and pass as generation context:

1. **Existing scenario titles** — the generator MUST use these verbatim for any scenario it generates that covers the same test intent. Never produce a title that differs only by a word ("all 6" vs "all available") — that creates a phantom duplicate.
2. **Existing step phrasings** — for steps the generator would otherwise write in a slightly different form (e.g. `the user logs in with "X" and "Y"` vs `the user logs in with username "X" and password "Y"`), use the existing phrasing exactly. Semantic equivalence is not enough; step text must match character-for-character for deduplication to work.
3. **Scenarios to skip** — if the generator would produce a scenario whose steps are semantically equivalent to an existing one (same action, same data, same assertions — just different wording), omit it entirely from the generated output. Do not generate it at all; the helix writer cannot perform semantic reasoning.

If `exists: false`, skip this step — the file will be created fresh.

---

### Step 5 — Generate test files

Run `generate_playwright_code` for the work item's Gherkin feature:

```
qa-playwright-generator:generate_playwright_code(
  gherkin_content   = <validated .feature file content>,
  page_class_name   = "<PageClass>",
  app_name          = "<app>",
  healing_strategy  = "role-label-text-ai",
  enable_visual_regression = true,
  enable_timing_healing    = true
)
```

---

### Step 6 — Write files

First retrieve the file contents from the playwright generator's cache:

```
qa-playwright-generator:get_generated_files(cache_key: "<cache_key from Step 5>")
```

Then write to Helix:

```
qa-helix-writer:write_helix_files(
  helix_root = "<helix_root>",
  files      = <files dict from get_generated_files>,
  mode       = "<recommendation from Step 2>"
)
```

The tool handles everything automatically:

**File routing:**

| Generator key | Helix destination |
|---|---|
| `src/pages/<k>/locators.ts` | `src/locators/<k>.locators.ts` |
| `src/pages/<k>/<k>.page.ts` | `src/pages/<k>.page.ts` |
| `src/test/steps/<k>.steps.ts` | `src/test/steps/<k>.steps.ts` |
| `*.feature` | `src/test/features/<k>.feature` |
| `config/cucumber.js (add this profile)` | `src/config/cucumber.config.ts` (appended) |
| `src/utils/locators/Locator*.ts` | `src/utils/locators/<file>` |

**Deduplication per file type:**

| File | Behaviour when file already exists |
|---|---|
| `locators.ts` | New const-object keys appended; existing keys skipped |
| `*.steps.ts` | New step blocks appended; steps with matching regex skipped |
| `*.page.ts` | New async methods appended; existing method names skipped |
| `*.feature` | New scenario blocks appended; scenarios with matching titles skipped (existing step wording preserved) |
| `cucumber.config.ts` | Profile block appended; duplicate profile names skipped |
| Infrastructure `*.ts` | Protected in `tests_only`; written-if-absent in `scaffold_and_tests` |

**Interface adaptation (applied automatically):**

| Generated code | Rewritten to |
|---|---|
| `repo.updateHealed(k, s, …)` | `repo.setHealed(k, s)` |
| `repo.getBBox(key)` | `null` |
| `repo.incrementSuccess/Failure(…)` | removed |
| `repo.queueSuggestion(…)` | removed |
| `repo.updateBoundingBox(…)` | removed |
| `fixture().logger` | `this.logger` |
| `fixture().locatorRepository` | `this.repo` |
| `fixture().page` | `this.page` |
| `import { Logger } from "winston"` | `import { HealerLogger }` |
| `EnvironmentManager` | Helix `environment` singleton |

---

### Step 7 — Verify the response

```json
{
  "summary": { "requested": 4, "written": 4, "skipped": 0 },
  "deduplication": {
    "src/locators/checkout.locators.ts": {
      "type": "locators",
      "added_keys":   ["checkoutBtn", "summaryTotal"],
      "skipped_keys": ["submitBtn", "cancelBtn"]
    },
    "src/test/steps/checkout.steps.ts": {
      "type": "steps",
      "added_patterns":   ["user completes checkout"],
      "skipped_patterns": ["user logs in"]
    }
  }
}
```

Report the `deduplication` summary to the user so they can confirm which
symbols were added vs skipped.

**Skipped reasons to handle:**

| Reason | Action |
|---|---|
| `"empty content"` | Normal — generator produced no content for that file |
| `"profile '…' already exists"` | Normal — no action needed |
| `"infrastructure file already exists"` | Normal in `tests_only` mode |
| `"infrastructure file — skipped in tests_only mode"` | Normal |
| Any `OSError` | Report path and error to user |

---

## Scaffold vs Regenerate Infrastructure

**First-time setup** (`framework_state` is `"absent"` or `"partial"`):

```
scaffold_locator_repository(...)   # generate 6 infra files
write_helix_files(..., mode="scaffold_and_tests")
# Writes missing infra files + test files
```

**Intentional infrastructure regeneration** (user explicitly requests it):

```
write_helix_files(..., mode="scaffold_and_tests", force_scaffold=true)
# Overwrites ALL infra files — use deliberately
```

**Never** call with `force_scaffold=true` unless the user has explicitly asked
to regenerate the healing infrastructure.

---

## Quality Gates

Before calling `write_helix_files`:

- [ ] `inspect_helix_project` has been called and `recommendation` noted.
- [ ] `list_helix_tree` has been called — existing file names recorded to prevent new-file creation.
- [ ] **Existing `*.feature` file read** (`read_helix_file`) and its scenario titles + step phrasings were passed to the generator as context (Step 4 above). If this was skipped, STOP — regenerate with the existing file as context before writing.
- [ ] **`pre_validate_cucumber_steps` called with `existing_steps_ts_contents`** — read every existing `*.steps.ts` in `src/test/steps/` via `read_helix_file` and pass as the `existing_steps_ts_contents` map. This enables check 6 (cross-file duplicate detection). Skipping this is the most common source of redundant step definitions across files.
- [ ] `pre_validate_cucumber_steps` returned `valid: true` and `errors` is empty.
- [ ] Every `*.steps.ts` in the `files` dict uses Cucumber expression syntax (not regex).
- [ ] `mode` matches `recommendation` — never override without user instruction.
- [ ] `force_scaffold` is `false` unless the user explicitly asked to regenerate.
- [ ] The `files` dict comes from `get_generated_files(cache_key)` — never manually
      constructed. `generate_playwright_code` and `scaffold_locator_repository` return
      a `cache_key`; call `get_generated_files` to retrieve file contents.
- [ ] After writing, the `deduplication` field has been reviewed and reported.

---

## Failure Recovery Procedure

When `write_helix_files` returns an error:

### 1 — Parenthesis / bracket balance error in a steps file

**Symptom:** Tool returns a message about parenthesis balance, bracket imbalance,
or validator failure on a `*.steps.ts` file.

**Root cause:** The step definitions contain regex patterns. Regex uses un-paired
parentheses (`(`, `)`) that the file validator counts as unbalanced.

**Fix:**
1. Call `pre_validate_cucumber_steps` on the failing steps file content — it identifies
   every offending pattern and provides a ready-to-paste Cucumber expression replacement.
2. Convert any regex patterns to Cucumber expressions:
   - `"([^"]*)"` → `{string}`
   - `(\d+)` → `{int}`
   - `/^…$/` wrapper → plain quoted string
3. Retry `write_helix_files` with the corrected `files` dict.
4. Do NOT call `create_file` at any point.

### 2 — `OSError` / path error

**Symptom:** Tool returns an `OSError`, `FileNotFoundError`, or similar path message.

**Fix:** Report the exact path and error to the user. Do not attempt to create the
directory or file manually unless the user explicitly instructs it.

### 3 — Any other tool error

**Fix:** Report the raw error text to the user verbatim. Ask: "How would you like
to proceed?" Do not attempt any workaround autonomously.

---

## Known Tool Limitations (Document These for Future Agent Runs)

| Limitation | Description | Workaround |
|---|---|---|
| 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 |
| `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 |
