sela-core 0.1.0-alpha.2 → 0.1.0-alpha.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 +809 -56
- package/dist/config/ConfigLoader.d.ts +5 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +89 -19
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +3 -1
- package/dist/services/LLMService.d.ts.map +1 -1
- package/dist/services/LLMService.js +40 -4
- package/dist/services/MutationApplier.d.ts +2 -0
- package/dist/services/MutationApplier.d.ts.map +1 -1
- package/dist/services/MutationApplier.js +7 -0
- package/dist/utils/TerminalLink.d.ts +2 -0
- package/dist/utils/TerminalLink.d.ts.map +1 -0
- package/dist/utils/TerminalLink.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,103 +1,856 @@
|
|
|
1
|
-
# Sela
|
|
1
|
+
# Sela Documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Self-Healing Reliability Engine for Playwright**
|
|
4
|
+
> _Version 1.0.8_
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
1. [Introduction - The Problem & The Solution](#1-introduction)
|
|
11
|
+
2. [The Safety-First Philosophy](#2-the-safety-first-philosophy)
|
|
12
|
+
3. [Core Concepts](#3-core-concepts)
|
|
13
|
+
4. [Quick Start](#4-quick-start)
|
|
14
|
+
5. [CLI Reference](#5-cli-reference)
|
|
15
|
+
6. [CI/CD Integration & Sharding](#6-cicd-integration--sharding)
|
|
16
|
+
7. [Reporting & Insights](#7-reporting--insights)
|
|
17
|
+
8. [Advanced Configuration](#8-advanced-configuration)
|
|
18
|
+
9. [Error Reference](#9-error-reference)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Introduction
|
|
23
|
+
|
|
24
|
+
### The Problem: Tests That Break Without Warning
|
|
25
|
+
|
|
26
|
+
Selector fragility is the silent killer of test suite ROI. A frontend refactor ships on Friday, and by Monday your CI pipeline is red - not because the application broke, but because a `div` got an `id` rename, a class was reorganised, or a component hierarchy shifted by one level. Your engineers spend the morning triaging failures that have nothing to do with bugs.
|
|
27
|
+
|
|
28
|
+
At scale, this compounds fast. A team with 2,000 end-to-end tests might absorb dozens of locator-maintenance hours per sprint - engineers doing rote, zero-insight work instead of shipping features.
|
|
29
|
+
|
|
30
|
+
### What is Sela?
|
|
31
|
+
|
|
32
|
+
Sela is a **Self-Healing Reliability Engine** for Playwright. It intercepts locator failures at runtime, uses a multi-stage AI pipeline to recover the correct selector, and - after passing a rigorous multi-gate safety review - surgically patches your source code. The broken test passes. Your spec file is updated. No human intervention required.
|
|
33
|
+
|
|
34
|
+
Sela is **not** a test generator or a code scaffolding tool. It is a reliability layer that runs inside your existing Playwright setup, invisible during green runs, and active only when something breaks. Think of it as an immune system for your test suite.
|
|
35
|
+
|
|
36
|
+
### Why Sela?
|
|
37
|
+
|
|
38
|
+
Most "AI testing" tools either generate new tests from scratch or produce brittle selector suggestions you have to manually review. Sela is different in three fundamental ways:
|
|
39
|
+
|
|
40
|
+
| Dimension | Typical AI Tools | Sela |
|
|
41
|
+
| ------------------- | -------------------------- | ------------------------------------- |
|
|
42
|
+
| **When it acts** | Test generation (pre-run) | Failure recovery (runtime) |
|
|
43
|
+
| **What it changes** | Generates new files | Surgically patches existing source |
|
|
44
|
+
| **Safety model** | None / "review everything" | 8-gate automated safety pipeline |
|
|
45
|
+
| **CI integration** | Standalone | Native Playwright Reporter & sharding |
|
|
46
|
+
| **Source of truth** | The AI | Your codebase + DNA fingerprints |
|
|
47
|
+
|
|
48
|
+
The key differentiator is **safety with autonomy**. Sela is designed to touch your source code unattended in CI - which means its safety guarantees have to be enterprise-grade. Every heal goes through a multi-gate verification pipeline before a single byte is written to disk.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. The Safety-First Philosophy
|
|
53
|
+
|
|
54
|
+
> _Sela will never make your test suite less trustworthy in order to make it green._
|
|
55
|
+
|
|
56
|
+
This is the core design constraint. A self-healing engine that silently swaps a **Login** button for a **Logout** button would be worse than no healing at all. Sela's entire architecture is built around preventing exactly this class of failure.
|
|
57
|
+
|
|
58
|
+
### How Sela Verifies Every Fix Before Touching Your Codebase
|
|
59
|
+
|
|
60
|
+
When a locator fails, Sela goes through the following sequence. Each stage is a gate - a failure at any gate stops the process and records the event for your review.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Locator Failure Detected
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
┌───────────────────────┐
|
|
67
|
+
│ 1. AI Selector Fix │ LLM proposes a candidate selector + confidence score
|
|
68
|
+
└───────────┬───────────┘
|
|
69
|
+
│
|
|
70
|
+
▼
|
|
71
|
+
┌───────────────────────┐
|
|
72
|
+
│ 2. Live DOM Probe │ Sela finds the element live on the page (1.5s timeout)
|
|
73
|
+
└───────────┬───────────┘
|
|
74
|
+
│
|
|
75
|
+
▼
|
|
76
|
+
┌───────────────────────┐
|
|
77
|
+
│ 3. DNA Capture │ Atomic snapshot of the candidate element's full identity
|
|
78
|
+
└───────────┬───────────┘
|
|
79
|
+
│
|
|
80
|
+
▼
|
|
81
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
82
|
+
│ SafetyGuard Pipeline │
|
|
83
|
+
│ Gate 1: Visibility Check (ghost / occluded element?) │
|
|
84
|
+
│ Gate 2: Hard Confidence Stop (AI confidence < threshold?) │
|
|
85
|
+
│ Gate 3: Zero-Trust Content Verification │
|
|
86
|
+
│ Gate 4: Structural-Only Change Check │
|
|
87
|
+
│ Gate 5: Binary Semantic-Opposite Guard │
|
|
88
|
+
│ Gate 6: Functional Role Classification │
|
|
89
|
+
│ Gate 6.5: Dangerous-Sink Transition Rule │
|
|
90
|
+
│ Gate 7: Intent Auditor (second AI model - haiku) │
|
|
91
|
+
│ Gate 8: Confidence Tier Decision │
|
|
92
|
+
└───────────────────────────────────────────────────────────────┘
|
|
93
|
+
│
|
|
94
|
+
▼
|
|
95
|
+
APPLY / HOLD / REJECT
|
|
96
|
+
│
|
|
97
|
+
▼ (APPLY only)
|
|
98
|
+
┌───────────────────────┐
|
|
99
|
+
│ AST Source Mutation │ Surgical, comment-preserving, type-checked write
|
|
100
|
+
└───────────────────────┘
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> 💡 **Suggested diagram:** Replace the ASCII art above with an interactive vertical flow diagram showing each gate as a coloured node (green = pass, red = reject, amber = hold).
|
|
104
|
+
|
|
105
|
+
### The 8-Gate SafetyGuard Pipeline - In Detail
|
|
106
|
+
|
|
107
|
+
#### Gate 1: Visibility Check
|
|
108
|
+
|
|
109
|
+
Before anything else, Sela checks whether the candidate element is actually visible and interactive. Six "ghost conditions" are evaluated:
|
|
110
|
+
|
|
111
|
+
- `G1` - `display: none`
|
|
112
|
+
- `G2` - `visibility: hidden`
|
|
113
|
+
- `G3` - `opacity < 0.05`
|
|
114
|
+
- `G4` - `pointer-events: none` with no interactable child
|
|
115
|
+
- `G5` - zero width and height
|
|
116
|
+
- `G6` - `content-visibility: hidden`
|
|
117
|
+
|
|
118
|
+
A ghost element results in an immediate `FAIL_HARD` - there is no scenario where healing to an invisible element is correct. Occlusion (another element covering the candidate at its centre point) is also checked: on `main`/`master` branches it is `FAIL_HARD`; on feature branches it is `REQUIRES_REVIEW`.
|
|
119
|
+
|
|
120
|
+
#### Gate 2: Hard Confidence Stop
|
|
121
|
+
|
|
122
|
+
The AI healing model returns a numeric confidence score (0–100). If this score falls below `confidenceHardStop` (default: **85**), the heal is blocked unconditionally. This gate is intentionally aggressive - a low-confidence suggestion should never reach your source.
|
|
123
|
+
|
|
124
|
+
#### Gate 3: Zero-Trust Content Verification
|
|
125
|
+
|
|
126
|
+
Sela compares the live `innerText` of the candidate element against the DNA baseline text captured during the original test run. If the AI omitted a `contentChange` assessment but the text has changed, Sela synthesises one and forces a full inversion check. Nothing is assumed correct.
|
|
127
|
+
|
|
128
|
+
#### Gate 4: Structural-Only Change Check
|
|
129
|
+
|
|
130
|
+
If the selector changed but no text content changed, the risk profile drops significantly. A purely structural heal (e.g., an `id` rename with identical label text) is fast-tracked to `SAFE` if confidence is high enough, or `REQUIRES_REVIEW` if it falls below `confidenceReviewThreshold`.
|
|
131
|
+
|
|
132
|
+
#### Gate 5: Binary Semantic-Opposite Guard
|
|
133
|
+
|
|
134
|
+
A hardcoded vocabulary of semantic antonym pairs - `welcome`/`denied`, `login`/`logout`, `enable`/`disable`, and others - is checked against the old and new element text. A match means the element's meaning has inverted. This results in `FAIL_HARD`, regardless of AI confidence.
|
|
135
|
+
|
|
136
|
+
#### Gate 6: Functional Role Classification
|
|
137
|
+
|
|
138
|
+
Sela classifies elements into functional roles using word-boundary regex matching:
|
|
139
|
+
|
|
140
|
+
| Role | Examples |
|
|
141
|
+
| ----------------- | ------------------------------ |
|
|
142
|
+
| `SUBMIT` | Submit, Confirm, Save, Apply |
|
|
143
|
+
| `CANCEL` | Cancel, Discard, Abort |
|
|
144
|
+
| `DELETE` | Delete, Remove, Destroy |
|
|
145
|
+
| `NAVIGATE` | Back, Next, Go to |
|
|
146
|
+
| `AUTH` | Sign in, Log out, Authenticate |
|
|
147
|
+
| `STATUS_POSITIVE` | Success, Approved, Active |
|
|
148
|
+
| `STATUS_NEGATIVE` | Error, Rejected, Inactive |
|
|
149
|
+
|
|
150
|
+
Cross-role swaps (e.g., healing from a `SUBMIT` button to a `CANCEL` button) trigger escalated responses:
|
|
151
|
+
|
|
152
|
+
- In **assertion mode**: `BLOCKED`
|
|
153
|
+
- In **action mode**: `REQUIRES_REVIEW`
|
|
154
|
+
- `STATUS_POSITIVE` ↔ `STATUS_NEGATIVE`: always `FAIL_HARD`
|
|
155
|
+
|
|
156
|
+
#### Gate 6.5: Dangerous-Sink Transition Rule
|
|
157
|
+
|
|
158
|
+
A specialised rule that watches for dynamic role mutations. If the new element falls into the `DELETE` or `AUTH` role and the old element did not, the pipeline sets a `forceAudit` flag - bypassing all confidence-based shortcuts and forcing the Intent Auditor (Gate 7) to evaluate the change regardless of score.
|
|
159
|
+
|
|
160
|
+
#### Gate 7: The Intent Auditor
|
|
161
|
+
|
|
162
|
+
When a heal is flagged for deeper review, a second AI model (Claude Haiku) acts as an independent auditor. It receives the DNA before and after snapshots and evaluates whether the semantic intent of the locator is preserved. It returns one of three verdicts:
|
|
163
|
+
|
|
164
|
+
- **`CONSISTENT`** - The element serves the same purpose. Proceed.
|
|
165
|
+
- **`SUSPICIOUS`** - Intent is uncertain. Blocked in assertion mode; held for review in action mode.
|
|
166
|
+
- **`INCONSISTENT`** - The element's role has changed. `FAIL_HARD`.
|
|
167
|
+
|
|
168
|
+
The raw auditor confidence is then adjusted structurally:
|
|
169
|
+
|
|
170
|
+
- **Ancestry drift penalty** (up to −30 pts): if the parent elements have changed significantly, confidence is penalised.
|
|
171
|
+
- **Anchor bonus** (up to +15 pts): if the closest accessible label or row anchor text is an exact match, confidence is boosted.
|
|
172
|
+
|
|
173
|
+
An adjusted `CONSISTENT` verdict below 50% is downgraded to `SUSPICIOUS`.
|
|
174
|
+
|
|
175
|
+
#### Gate 8: Confidence Tier Decision
|
|
176
|
+
|
|
177
|
+
With all gates passed, the final confidence score determines the outcome:
|
|
178
|
+
|
|
179
|
+
- `≥ confidenceReviewThreshold` (default: **95**) → **`SAFE`** → heal is applied
|
|
180
|
+
- `< confidenceReviewThreshold` → **`REQUIRES_REVIEW`** → heal is quarantined
|
|
181
|
+
|
|
182
|
+
### The Three Dispositions
|
|
183
|
+
|
|
184
|
+
Every heal exits the SafetyGuard with one of three dispositions:
|
|
185
|
+
|
|
186
|
+
| Disposition | Safety Level | Behaviour |
|
|
187
|
+
| ------------ | ----------------------- | -------------------------------------------------------------------- |
|
|
188
|
+
| **`APPLY`** | `SAFE` | Source mutation is written to disk |
|
|
189
|
+
| **`HOLD`** | `REQUIRES_REVIEW` | Heal is quarantined; source is untouched; `PROTECTED` event recorded |
|
|
190
|
+
| **`REJECT`** | `BLOCKED` / `FAIL_HARD` | Change is refused; codebase is completely untouched |
|
|
191
|
+
|
|
192
|
+
`PROTECTED` events surface prominently in the Sela Insights report, auto-expanded and annotated with the specific safety rule that blocked the change. This is a feature, not a failure - it means Sela caught a potential regression that would have silently corrupted your test suite.
|
|
193
|
+
|
|
194
|
+
### The Internal-Selector Invariant
|
|
195
|
+
|
|
196
|
+
Playwright uses `internal:` engine selectors (e.g., `internal:role=button[name="Submit"i]`) as a private serialisation format. Sela **never** writes these into your source code - they are fragile, undocumented, and produce poor developer experience.
|
|
197
|
+
|
|
198
|
+
At every write-head, Sela intercepts internal selectors and translates them to idiomatic public Playwright methods:
|
|
199
|
+
|
|
200
|
+
- `internal:role=…` → `getByRole(...)`
|
|
201
|
+
- `internal:label=…` → `getByLabel(...)`
|
|
202
|
+
|
|
203
|
+
When a constant (e.g., `const SEL = "#submit"`) is being updated and `getByRole(...)` would be out of scope at the declaration site, Sela writes a clean `role=button[name="Submit"]` public locator string instead, and surfaces a `SEMANTIC_UPGRADE_SUGGESTED` advisory recommending the full method refactor as an optional next step.
|
|
204
|
+
|
|
205
|
+
### Outbound Data Sanitization
|
|
206
|
+
|
|
207
|
+
Before any context is sent to an AI provider, Sela sanitises the payload. The following are detected and masked:
|
|
208
|
+
|
|
209
|
+
- Email addresses
|
|
210
|
+
- Access tokens, secrets, and credentials
|
|
211
|
+
- Password-like values
|
|
212
|
+
- Credit card numbers
|
|
213
|
+
- Custom project-defined sensitive patterns
|
|
214
|
+
|
|
215
|
+
Sanitisation is **enabled by default**, covers all DOM snapshots, DNA payloads, and healing context, and is fail-secure: if the sanitisation configuration fails to load, masking remains active. It can be disabled via configuration only if your project explicitly requires it.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 3. Core Concepts
|
|
220
|
+
|
|
221
|
+
### The DNA System - Element Fingerprints
|
|
222
|
+
|
|
223
|
+
The foundation of Sela's healing intelligence is the **DNA snapshot** - a structured JSON fingerprint of each element captured at the moment it first passes a test. Stored in `sela-snapshots/<stableId>.json`, a DNA record contains:
|
|
224
|
+
|
|
225
|
+
**Identity**
|
|
226
|
+
|
|
227
|
+
- Tag name, `id`, `role`, class list, and all HTML attributes
|
|
228
|
+
- `innerText` / `textContent` (trimmed)
|
|
229
|
+
- Bounding rect (`x`, `y`, `width`, `height`)
|
|
230
|
+
- Schema version (`v2`), timestamp
|
|
231
|
+
|
|
232
|
+
**Ancestry (up to 5 levels)**
|
|
233
|
+
|
|
234
|
+
- Each ancestor: tag, `id`, classes, and a structural fingerprint
|
|
235
|
+
- Walk stops at Shadow DOM boundaries
|
|
236
|
+
|
|
237
|
+
**Visibility State**
|
|
238
|
+
|
|
239
|
+
- Ghost conditions (G1–G6)
|
|
240
|
+
- Occlusion status
|
|
241
|
+
- Opacity value
|
|
242
|
+
|
|
243
|
+
**Contextual Anchors**
|
|
244
|
+
|
|
245
|
+
- `closestLabel`: resolved via `aria-labelledby` → `aria-label` → `label[for]` → implicit wrapper → nearest visible sibling
|
|
246
|
+
- `rowAnchor`: longest unique sibling cell text when the element lives in a table row
|
|
247
|
+
- `neighborTexts`: up to five direct-sibling texts
|
|
248
|
+
|
|
249
|
+
**Shadow DOM & Frame Awareness**
|
|
250
|
+
|
|
251
|
+
- `isInShadowDom: boolean`
|
|
252
|
+
- Frame traversal path for iframe-hosted elements
|
|
253
|
+
|
|
254
|
+
The `stableId` is an MD5 hash of `file:line:action[:idx]` - stable across renames but unique per action, preventing cross-call-site poisoning.
|
|
255
|
+
|
|
256
|
+
### The Heal Pipeline
|
|
257
|
+
|
|
258
|
+
When a Playwright locator throws, Sela's `SelaEngine.heal()` runs the following sequence:
|
|
259
|
+
|
|
260
|
+
1. Load the element's DNA snapshot (auto-migrates `v1` → `v2` on read)
|
|
261
|
+
2. Extract an AI-optimised neighbourhood DOM via `DOMUtils.getNeighborhoodDom` (drills iframes and Shadow DOM, strips noise)
|
|
262
|
+
3. Inject DNA context into the LLM prompt (text, role, anchor labels, row anchor, frame-path hint)
|
|
263
|
+
4. Request a fix from the LLM - receives candidate selector chain + confidence + content-change assessment
|
|
264
|
+
5. Live `innerText()` probe (1.5s timeout, frame-aware) - zero-trust verification baseline
|
|
265
|
+
6. Atomic DNA capture of the candidate element
|
|
266
|
+
7. SafetyGuard evaluation (8 gates - see §2)
|
|
267
|
+
8. On `APPLY`: surgical AST source mutation via the Trace-Back Pipeline (primary) or regex fallback
|
|
268
|
+
9. Capture `git diff --unified=1 HEAD` for the report
|
|
269
|
+
10. Buffer `HealedEvent` / `ProtectedEvent` / `FailedEvent` for the end-of-run dashboard
|
|
270
|
+
|
|
271
|
+
### The AST Trace-Back Pipeline
|
|
272
|
+
|
|
273
|
+
Rather than blindly overwriting a call-site string, Sela traces a failing locator back to its **source declaration** and mutates it there - preserving your code architecture.
|
|
274
|
+
|
|
275
|
+
**Example:** If `await hi.nth(3).click()` fails and `hi` was declared as `const hi = page.locator('#old-id')` in a `beforeEach`, Sela updates the declaration, not the call-site. Other tests consuming `hi` are untouched unless analysis proves the old selector is globally broken.
|
|
276
|
+
|
|
277
|
+
The pipeline has four composable stages:
|
|
278
|
+
|
|
279
|
+
| Stage | Component | Purpose |
|
|
280
|
+
| ----- | ----------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
281
|
+
| 1 | `AnchorResolver` | Identifies nodes by structural signature (not line numbers), surviving concurrent edits |
|
|
282
|
+
| 2 | `TraceBackEngine` | Climbs the call chain to the source declaration; handles POM classes, `beforeEach` patterns, Shadow DOM chains |
|
|
283
|
+
| 3 | `DecisionEngine` | Chooses `MUTATE_IN_PLACE` (single consumer) vs. `FORK_AT_TEST` (multi-consumer) vs. `PROMPT_INTERACTIVE` |
|
|
284
|
+
| 4 | `MutationApplier` | Zero-layout-noise splice over exact character spans; type-safety gate with rollback |
|
|
285
|
+
|
|
286
|
+
The `MutationApplier` re-parses the file after mutation and runs TypeScript's `getPreEmitDiagnostics`. If the change introduces a new type error, it rolls back automatically and leaves the file untouched.
|
|
287
|
+
|
|
288
|
+
**Developer Opt-Out** - Pin any statement against Sela's pipeline with:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// sela-fail-fast
|
|
292
|
+
await page.locator("#legacy-broken").click();
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Sela detects this directive before making any LLM call and records a `FailedEvent` with category `"Skipped by Developer"`.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 4. Quick Start
|
|
300
|
+
|
|
301
|
+
### Prerequisites
|
|
302
|
+
|
|
303
|
+
- Node.js 18+
|
|
304
|
+
- An existing Playwright project
|
|
305
|
+
- An `ANTHROPIC_API_KEY` (set in your environment or `.env` file)
|
|
306
|
+
|
|
307
|
+
### Installation
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
npm install sela-core --save-dev
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Time to Value: Under 5 Minutes
|
|
314
|
+
|
|
315
|
+
**Step 1 - Initialise Sela**
|
|
316
|
+
|
|
317
|
+
Run this from your project root:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npx sela init
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
This command:
|
|
324
|
+
|
|
325
|
+
- Creates a `sela.config.ts` with sensible defaults
|
|
326
|
+
- Verifies your `ANTHROPIC_API_KEY` is accessible
|
|
327
|
+
- Rewrites `@playwright/test` imports to `sela-core` in your spec files
|
|
328
|
+
|
|
329
|
+
> ⚠️ Sela replaces the `@playwright/test` import surface. The full Playwright API (`test`, `expect`, `defineConfig`, `chromium`, `firefox`, `webkit`, `devices`, `selectors`) is re-exported from `sela-core` unchanged - your tests require no further modification.
|
|
330
|
+
|
|
331
|
+
**Step 2 - Add the Reporter**
|
|
332
|
+
|
|
333
|
+
In your `playwright.config.ts`:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { defineConfig } from "sela-core";
|
|
337
|
+
|
|
338
|
+
export default defineConfig({
|
|
339
|
+
reporter: [["sela-core/reporter"]],
|
|
340
|
+
// ... rest of your config
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Step 3 - Run Your Tests**
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
npx playwright test
|
|
348
|
+
```
|
|
6
349
|
|
|
7
|
-
|
|
350
|
+
When a locator failure occurs, Sela intercepts it automatically. At the end of the run, you'll see a summary table in your terminal:
|
|
8
351
|
|
|
9
|
-
|
|
352
|
+
```
|
|
353
|
+
┌────────────────────┬───────┐
|
|
354
|
+
│ 🔍 Tests Scanned │ 248 │
|
|
355
|
+
│ ⚠️ Errors Detected │ 3 │
|
|
356
|
+
│ 🛠️ Successful Heals│ 3 │
|
|
357
|
+
│ ⏭️ Skipped Files │ 0 │
|
|
358
|
+
└────────────────────┴───────┘
|
|
359
|
+
```
|
|
10
360
|
|
|
11
|
-
|
|
12
|
-
- [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
|
|
361
|
+
**Step 4 - Review the Report**
|
|
13
362
|
|
|
363
|
+
```bash
|
|
364
|
+
npx sela show-report
|
|
14
365
|
```
|
|
15
|
-
|
|
16
|
-
git
|
|
17
|
-
|
|
18
|
-
|
|
366
|
+
|
|
367
|
+
This opens `sela-report.html` in your default browser - an interactive dashboard showing healed selectors, safety verdicts, git diffs, and ROI metrics.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 5. CLI Reference
|
|
372
|
+
|
|
373
|
+
### Top-Level Commands
|
|
374
|
+
|
|
375
|
+
#### `sela init`
|
|
376
|
+
|
|
377
|
+
Bootstrap Sela in your project.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
npx sela init
|
|
19
381
|
```
|
|
20
382
|
|
|
21
|
-
|
|
383
|
+
- Writes `sela.config.ts` to the project root
|
|
384
|
+
- Validates `ANTHROPIC_API_KEY`
|
|
385
|
+
- Migrates `@playwright/test` imports to `sela-core`
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
#### `sela status`
|
|
22
390
|
|
|
23
|
-
|
|
391
|
+
Print an ecosystem health report for your DNA index.
|
|
24
392
|
|
|
25
|
-
|
|
393
|
+
```bash
|
|
394
|
+
npx sela status
|
|
395
|
+
npx sela status --json # Machine-readable JSON output
|
|
396
|
+
```
|
|
26
397
|
|
|
27
|
-
|
|
28
|
-
- [Create a new merge request](https://docs.gitlab.com/user/project/merge_requests/creating_merge_requests/)
|
|
29
|
-
- [Automatically close issues from merge requests](https://docs.gitlab.com/user/project/issues/managing_issues/#closing-issues-automatically)
|
|
30
|
-
- [Enable merge request approvals](https://docs.gitlab.com/user/project/merge_requests/approvals/)
|
|
31
|
-
- [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
|
|
398
|
+
Output includes: total DNA records, healed elements, unverified heals, orphaned snapshots (source file no longer exists), heal-to-record ratio, and top healed files.
|
|
32
399
|
|
|
33
|
-
|
|
400
|
+
---
|
|
34
401
|
|
|
35
|
-
|
|
402
|
+
#### `sela list`
|
|
36
403
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
404
|
+
List DNA records with optional filters.
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
npx sela list
|
|
408
|
+
npx sela list --filter "submit" # Filter by text/selector substring
|
|
409
|
+
npx sela list --source "checkout" # Filter by source file path
|
|
410
|
+
npx sela list --orphaned # Show only orphaned DNA records
|
|
411
|
+
npx sela list --limit 20 --json
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
| Flag | Description |
|
|
415
|
+
| ----------------- | ----------------------------------------------- |
|
|
416
|
+
| `--filter <text>` | Substring match on selector or element text |
|
|
417
|
+
| `--source <path>` | Filter by source file path |
|
|
418
|
+
| `--orphaned` | Show only records whose source no longer exists |
|
|
419
|
+
| `--limit <n>` | Limit output rows |
|
|
420
|
+
| `--json` | Emit structured JSON to stdout |
|
|
42
421
|
|
|
43
422
|
---
|
|
44
423
|
|
|
45
|
-
|
|
424
|
+
#### `sela show-report`
|
|
46
425
|
|
|
47
|
-
|
|
426
|
+
Open the latest `sela-report.html` in your default browser, or print its absolute path.
|
|
48
427
|
|
|
49
|
-
|
|
428
|
+
```bash
|
|
429
|
+
npx sela show-report
|
|
430
|
+
```
|
|
50
431
|
|
|
51
|
-
|
|
432
|
+
---
|
|
52
433
|
|
|
53
|
-
|
|
434
|
+
#### `sela merge <glob>`
|
|
54
435
|
|
|
55
|
-
|
|
436
|
+
Aggregate sharded JSON payloads from parallel CI runners into a single unified report.
|
|
56
437
|
|
|
57
|
-
|
|
438
|
+
```bash
|
|
439
|
+
npx sela merge ".sela/shard-outputs/*.json"
|
|
440
|
+
```
|
|
58
441
|
|
|
59
|
-
|
|
442
|
+
Use this as the final step in a CI workflow after parallel shards complete. See [CI/CD Integration](#6-cicd-integration--sharding) for the full pattern.
|
|
60
443
|
|
|
61
|
-
|
|
444
|
+
---
|
|
62
445
|
|
|
63
|
-
|
|
446
|
+
### `sela dna` Subcommands
|
|
64
447
|
|
|
65
|
-
|
|
448
|
+
#### `sela dna find`
|
|
66
449
|
|
|
67
|
-
|
|
450
|
+
Search your DNA index by selector, text, or source file.
|
|
68
451
|
|
|
69
|
-
|
|
452
|
+
```bash
|
|
453
|
+
npx sela dna find --selector "#submit-btn"
|
|
454
|
+
npx sela dna find --text "Add to Cart"
|
|
455
|
+
npx sela dna find --source-file "checkout.spec.ts"
|
|
456
|
+
npx sela dna find --json
|
|
457
|
+
```
|
|
70
458
|
|
|
71
|
-
|
|
459
|
+
---
|
|
72
460
|
|
|
73
|
-
|
|
461
|
+
#### `sela dna refactor [key]`
|
|
74
462
|
|
|
75
|
-
|
|
463
|
+
Launch an interactive wizard to manually edit a DNA record, preview the diff, and commit the change to both the snapshot and the source file.
|
|
76
464
|
|
|
77
|
-
|
|
465
|
+
```bash
|
|
466
|
+
npx sela dna refactor
|
|
467
|
+
npx sela dna refactor <stableId>
|
|
468
|
+
```
|
|
78
469
|
|
|
79
|
-
|
|
470
|
+
---
|
|
80
471
|
|
|
81
|
-
|
|
472
|
+
#### `sela dna bulk-update --old --new`
|
|
82
473
|
|
|
83
|
-
|
|
474
|
+
Replace a selector string across all matching DNA records and their source files.
|
|
84
475
|
|
|
85
|
-
|
|
476
|
+
```bash
|
|
477
|
+
npx sela dna bulk-update --old "#legacy-btn" --new "[data-testid='submit']"
|
|
478
|
+
npx sela dna bulk-update --old "#legacy-btn" --new "[data-testid='submit']" --dry-run
|
|
479
|
+
npx sela dna bulk-update --old "#legacy-btn" --new "[data-testid='submit']" --confirm
|
|
480
|
+
```
|
|
86
481
|
|
|
87
|
-
|
|
482
|
+
| Flag | Description |
|
|
483
|
+
| ----------- | ---------------------------------------- |
|
|
484
|
+
| `--dry-run` | Preview changes without writing to disk |
|
|
485
|
+
| `--confirm` | Apply changes without interactive prompt |
|
|
88
486
|
|
|
89
|
-
|
|
487
|
+
---
|
|
90
488
|
|
|
91
|
-
|
|
489
|
+
#### `sela dna sync-source <key>`
|
|
92
490
|
|
|
93
|
-
|
|
491
|
+
Rewrite the locator in a spec file at the exact line recorded in the DNA, to match the stored selector. Useful after manual DNA edits.
|
|
94
492
|
|
|
95
|
-
|
|
493
|
+
```bash
|
|
494
|
+
npx sela dna sync-source <stableId>
|
|
495
|
+
```
|
|
96
496
|
|
|
97
|
-
|
|
497
|
+
---
|
|
98
498
|
|
|
99
|
-
|
|
499
|
+
### Global Flags
|
|
500
|
+
|
|
501
|
+
| Flag | Description |
|
|
502
|
+
| ----------- | --------------------------------------------------------------------------------------- |
|
|
503
|
+
| `--json` | Suppress spinners; redirect diagnostic output to stderr; emit structured JSON to stdout |
|
|
504
|
+
| `--verbose` | Enable debug-level logging (raw LLM payloads, AST transformations, DOM extractions) |
|
|
505
|
+
|
|
506
|
+
**Environment variables:**
|
|
507
|
+
|
|
508
|
+
| Variable | Effect |
|
|
509
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------- |
|
|
510
|
+
| `SELA_DEBUG=1` | Equivalent to `--verbose` |
|
|
511
|
+
| `SELA_DRY_RUN=1` | Full heal pipeline runs but no files are written to disk; report marks cards as "Dry Run · No Write" |
|
|
512
|
+
| `SELA_NO_CACHE=1` | Bypass healing cache; every heal hits the LLM directly |
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## 6. CI/CD Integration & Sharding
|
|
517
|
+
|
|
518
|
+
### The Playwright Reporter
|
|
519
|
+
|
|
520
|
+
The `sela-core/reporter` integrates Sela's full heal-and-report pipeline at the Playwright reporter layer. It activates regardless of whether you are using the fixture-based `test` import.
|
|
521
|
+
|
|
522
|
+
**Reporter lifecycle:**
|
|
523
|
+
|
|
524
|
+
| Hook | What Sela does |
|
|
525
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
526
|
+
| `onBegin` | Sets `SELA_REPORTER_ACTIVE=1`; clears stale shard files from prior crashed runs |
|
|
527
|
+
| `onTestEnd` | For `failed`/`timedOut` results: extracts the first PNG attachment, base64-encodes it into memory, and attaches it to the corresponding `FailedEvent` |
|
|
528
|
+
| `onEnd` | Flushes pending screenshots → compiles report → writes history → emits HTML + JSON |
|
|
529
|
+
|
|
530
|
+
### State Persistence in Ephemeral CI Environments
|
|
531
|
+
|
|
532
|
+
Sela maintains a 14-run rolling history window in `.sela-history.json`. In ephemeral CI environments where the filesystem resets between runs, Sela implements an **Artifact-by-last-run** strategy: the history file is fetched from the previous run's artifacts before test execution begins.
|
|
533
|
+
|
|
534
|
+
Add `.sela-history.json` to your CI artifact upload/download steps to preserve the historical trend across runs.
|
|
535
|
+
|
|
536
|
+
`.sela-history.json` and report files should be excluded from git via `.gitignore`:
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
# .gitignore
|
|
540
|
+
.sela-history.json
|
|
541
|
+
sela-report.html
|
|
542
|
+
sela-report.json
|
|
543
|
+
.sela/
|
|
544
|
+
sela-snapshots/
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
To remove any accidentally committed cache pointers:
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
git rm --cached .sela-history.json
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Parallel Sharding
|
|
554
|
+
|
|
555
|
+
Sela supports Playwright's native `--shard=K/N` parallelism with no additional configuration.
|
|
556
|
+
|
|
557
|
+
**How it works:**
|
|
558
|
+
|
|
559
|
+
- Each shard runner writes an isolated payload file to `.sela/report-shards/sela-shard-<pid>-<ts>.json`. There is zero contention between runners.
|
|
560
|
+
- The final CI step runs `sela merge` to aggregate all shards into one unified report and one historical row.
|
|
561
|
+
|
|
562
|
+
**GitHub Actions example:**
|
|
563
|
+
|
|
564
|
+
```yaml
|
|
565
|
+
# .github/workflows/test.yml
|
|
566
|
+
jobs:
|
|
567
|
+
test:
|
|
568
|
+
strategy:
|
|
569
|
+
matrix:
|
|
570
|
+
shard: [1, 2, 3, 4]
|
|
571
|
+
steps:
|
|
572
|
+
- run: npx playwright test --shard=${{ matrix.shard }}/4
|
|
573
|
+
|
|
574
|
+
- name: Upload shard output
|
|
575
|
+
uses: actions/upload-artifact@v4
|
|
576
|
+
with:
|
|
577
|
+
name: sela-shard-${{ matrix.shard }}
|
|
578
|
+
path: .sela/report-shards/
|
|
579
|
+
|
|
580
|
+
report:
|
|
581
|
+
needs: test
|
|
582
|
+
steps:
|
|
583
|
+
- name: Download all shard outputs
|
|
584
|
+
uses: actions/download-artifact@v4
|
|
585
|
+
with:
|
|
586
|
+
pattern: sela-shard-*
|
|
587
|
+
merge-multiple: true
|
|
588
|
+
path: .sela/report-shards/
|
|
589
|
+
|
|
590
|
+
- name: Merge Sela report
|
|
591
|
+
run: npx sela merge ".sela/report-shards/*.json"
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Shard merge guarantees:**
|
|
595
|
+
|
|
596
|
+
- **Clock-skew immunity:** Event deduplication is keyed on `kind|stableId|normalizedPath|line|title` - timestamps are intentionally excluded, making the dedup safe across multi-region runners with clock drift.
|
|
597
|
+
- **Partial-failure resilience:** If a subset of shards failed (e.g., a worker crash), `sela merge` aggregates the surviving data, produces the unified report, and surfaces a prominent diagnostic warning. It does not abort the reporting pipeline.
|
|
598
|
+
|
|
599
|
+
### CI Deduplication
|
|
600
|
+
|
|
601
|
+
When a CI job is retried (native Playwright retry or manual re-run), Sela detects the environment signature (`GITHUB_RUN_ID` or `CI_PIPELINE_ID`) and performs an **in-place overwrite** of the existing history entry for that run - preventing dataset inflation from retries.
|
|
602
|
+
|
|
603
|
+
### Integrity Guard
|
|
604
|
+
|
|
605
|
+
`ReportMergeService` validates the structure of `.sela-history.json` on read. If a corrupt or truncated state file is detected, it throws `HISTORY_CORRUPT` and aborts - preserving the last known good state. A corrupted history file will not silently clobber valid historical data.
|
|
606
|
+
|
|
607
|
+
---
|
|
100
608
|
|
|
101
|
-
##
|
|
609
|
+
## 7. Reporting & Insights
|
|
610
|
+
|
|
611
|
+
### The Sela Insights Report
|
|
612
|
+
|
|
613
|
+
`sela-report.html` is a self-contained, enterprise-grade dark-mode dashboard written to disk after every run (and accessible via `sela show-report`). It has two views toggled at the top of the page:
|
|
614
|
+
|
|
615
|
+
#### The Management ROI Dashboard
|
|
616
|
+
|
|
617
|
+
Designed for engineering leads and senior management:
|
|
618
|
+
|
|
619
|
+
- **Status Donut Chart** - Live distribution of `Passed`, `Healed`, `Protected`, and `Failed` tests. Click any slice to jump to the Engineering Deep Dive, pre-filtered to that state.
|
|
620
|
+
- **Executive KPI Cards** - Time saved today (12 min per heal, 18 min per project win, 4 min per detected failure), cumulative bugs prevented, suite success rate, and average AI healing confidence.
|
|
621
|
+
- **14-Run Historical Trend Chart** - Area chart of cumulative developer hours recovered (cyan gradient) overlaid with a line tracking total self-healed selectors per run. Clean zero-heal runs appear as benchmark performance points, not gaps.
|
|
622
|
+
- **Recent Runs Ledger** - Tabular audit trail of historical runs with confidence ratios and saved metrics.
|
|
623
|
+
|
|
624
|
+
#### The Engineering Deep Dive
|
|
625
|
+
|
|
626
|
+
Designed for the developers who own the specs:
|
|
627
|
+
|
|
628
|
+
- **Dynamic filter chips** and a search bar supporting automated focus shortcuts
|
|
629
|
+
- **Healing Cards** - When expanded, each healed test shows:
|
|
630
|
+
- A colour-coded git diff (`--unified=1`) with exact line numbers
|
|
631
|
+
- An Intent Auditor Gauge: circular progress UI with safety breakdown score, ancestry penalties, anchor bonuses, and the final verdict
|
|
632
|
+
- AI Chain-of-Thought: collapsible logical steps (DNA recovery, layout strategies, safety bounds)
|
|
633
|
+
- A DNA Evidence Table: old-vs-new attribute comparison with match/mismatch indicators
|
|
634
|
+
- **Action Panel:** one-click copy of rollback CLI script (`npx sela dna refactor --rollback`), copy of alternative candidate locators, and a VS Code deep-link (`vscode://file/path/to/spec.ts:42`) to jump directly to the mutated line
|
|
635
|
+
- **Protected Event Cards** - Auto-expanded on load; annotated with the specific safety rule that blocked the change and the semantic inversion or role violation detected
|
|
636
|
+
|
|
637
|
+
### Failure Screenshots
|
|
638
|
+
|
|
639
|
+
The reporter automatically captures the first PNG screenshot from Playwright's test attachments on failure, base64-encodes it into memory (surviving Playwright's temp-folder cleanup), and embeds it inline in the failure card in the HTML report. No additional configuration is required.
|
|
640
|
+
|
|
641
|
+
### PR Automation
|
|
642
|
+
|
|
643
|
+
Sela can optionally open pull requests or commits for healed selectors. Configure via `sela.config.ts`:
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
prAutomation: {
|
|
647
|
+
enabled: true,
|
|
648
|
+
strategy: 'PR', // 'directCommit' | 'PR' | 'draftPR'
|
|
649
|
+
minAiConfidence: 90,
|
|
650
|
+
minAuditorConfidence: 80,
|
|
651
|
+
onBugDetected: true, // Open separate issue/PR for PROTECTED events
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Sela evaluates the configured strategy at runtime and **downgrades** it automatically when constraints are not met (e.g., insufficient confidence, branch policy mismatch), logging the reason.
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## 8. Advanced Configuration
|
|
660
|
+
|
|
661
|
+
### `sela.config.ts` - Full Reference
|
|
662
|
+
|
|
663
|
+
```typescript
|
|
664
|
+
import { defineConfig } from "sela-core";
|
|
665
|
+
|
|
666
|
+
export default defineConfig({
|
|
667
|
+
// 'balanced' | 'strict' | 'loose' - preset that sets threshold defaults
|
|
668
|
+
policy: "balanced",
|
|
669
|
+
|
|
670
|
+
// How Sela writes heals to disk
|
|
671
|
+
updateStrategy: "inPlace", // 'inPlace' | 'commentOnly' | 'branch'
|
|
672
|
+
autoCommit: false, // Commit to current branch after healing
|
|
673
|
+
|
|
674
|
+
// SafetyGuard thresholds
|
|
675
|
+
thresholds: {
|
|
676
|
+
confidenceHardStop: 85, // Below this → BLOCKED unconditionally
|
|
677
|
+
confidenceReviewThreshold: 95, // Below this → REQUIRES_REVIEW
|
|
678
|
+
auditorConfidenceMin: 75, // Minimum adjusted auditor confidence
|
|
679
|
+
allowSuspicious: false, // Allow SUSPICIOUS heals through in action mode
|
|
680
|
+
auditorStrictness: "balanced", // 'strict' | 'balanced' | 'loose'
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
// Per-branch threshold overrides
|
|
684
|
+
branchOverrides: {
|
|
685
|
+
main: {
|
|
686
|
+
policy: "strict",
|
|
687
|
+
thresholds: { confidenceHardStop: 90, confidenceReviewThreshold: 98 },
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
// Write-scope allowlist (glob patterns); empty = unrestricted
|
|
692
|
+
allowedPaths: ["tests/**/*.spec.ts", "e2e/**/*.ts"],
|
|
693
|
+
|
|
694
|
+
// Healing budget - null = unlimited
|
|
695
|
+
maxHealsPerRun: 50,
|
|
696
|
+
|
|
697
|
+
// Outbound data sanitisation
|
|
698
|
+
sanitization: {
|
|
699
|
+
enabled: true,
|
|
700
|
+
customPatterns: [
|
|
701
|
+
/ACME-[A-Z0-9]{16}/g, // Custom token format
|
|
702
|
+
],
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
// PR automation
|
|
706
|
+
prAutomation: {
|
|
707
|
+
enabled: false,
|
|
708
|
+
strategy: "PR",
|
|
709
|
+
minAiConfidence: 90,
|
|
710
|
+
minAuditorConfidence: 80,
|
|
711
|
+
onBugDetected: true,
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Update Strategy Modes
|
|
717
|
+
|
|
718
|
+
| Mode | Behaviour |
|
|
719
|
+
| ------------- | ---------------------------------------------------------------------------------------- |
|
|
720
|
+
| `inPlace` | Writes the healed selector directly to your spec (default) |
|
|
721
|
+
| `commentOnly` | Inserts `// TODO [Sela]: <new selector>` above the failing line; source is never mutated |
|
|
722
|
+
| `branch` | Creates a `sela/heal-<timestamp>` git branch and commits the change there |
|
|
723
|
+
|
|
724
|
+
`commentOnly` is useful when you want Sela to analyse and propose fixes in CI, but reserve the actual write for a human review step.
|
|
725
|
+
|
|
726
|
+
### Branch Overrides
|
|
727
|
+
|
|
728
|
+
Apply tighter constraints on protected branches:
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
branchOverrides: {
|
|
732
|
+
main: {
|
|
733
|
+
policy: 'strict',
|
|
734
|
+
thresholds: {
|
|
735
|
+
confidenceHardStop: 92,
|
|
736
|
+
confidenceReviewThreshold: 98,
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
'release/*': {
|
|
740
|
+
policy: 'strict',
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Sela detects the current branch via `git rev-parse --abbrev-ref HEAD` and merges the matching override at runtime.
|
|
746
|
+
|
|
747
|
+
### Write Scope Protection
|
|
748
|
+
|
|
749
|
+
Restrict which files Sela is permitted to modify using glob patterns:
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
allowedPaths: [
|
|
753
|
+
'tests/**/*.spec.ts',
|
|
754
|
+
'e2e/pages/**/*.ts',
|
|
755
|
+
],
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
- All write-heads (`SourceUpdater`, `ASTSourceUpdater`, `InitializerUpdater`, `MutationApplier`) validate the target path against this list before writing
|
|
759
|
+
- Path traversal (`../`) and symlink escapes are blocked unless explicitly matched by an absolute pattern
|
|
760
|
+
- An empty `allowedPaths` array means unrestricted writes (backward-compatible default)
|
|
761
|
+
|
|
762
|
+
### Heal Budget
|
|
763
|
+
|
|
764
|
+
Limit the number of AI-generated heals per run to control costs or enforce a gradual rollout:
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
maxHealsPerRun: 25,
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
- Each AI heal consumes one budget slot
|
|
771
|
+
- Cache hits do **not** consume budget
|
|
772
|
+
- Once exhausted, additional heal attempts are skipped for that run
|
|
773
|
+
- Budget state is process-safe and shared across all workers
|
|
774
|
+
|
|
775
|
+
### Dry Run Mode
|
|
776
|
+
|
|
777
|
+
Run the full healing pipeline - including LLM calls, SafetyGuard evaluation, and AST mutation - without writing anything to disk:
|
|
778
|
+
|
|
779
|
+
```bash
|
|
780
|
+
SELA_DRY_RUN=1 npx playwright test
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
The report marks healed cards as `"Dry Run · No Write"`. Use this to audit what Sela would change before enabling it in production CI.
|
|
784
|
+
|
|
785
|
+
### The `sela-fail-fast` Directive
|
|
786
|
+
|
|
787
|
+
Exempt individual statements from healing entirely:
|
|
788
|
+
|
|
789
|
+
```typescript
|
|
790
|
+
// sela-fail-fast
|
|
791
|
+
await page.locator("#legacy-element").click();
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
Also accepted as `// @sela-fail-fast` or `/* sela-fail-fast */`. Sela detects this directive **before** making any LLM call - it never reaches the AI pipeline, so there is zero cost for opted-out selectors.
|
|
795
|
+
|
|
796
|
+
---
|
|
797
|
+
|
|
798
|
+
## 9. Error Reference
|
|
799
|
+
|
|
800
|
+
All Sela errors follow a canonical structure:
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
{
|
|
804
|
+
subsystem: string, // e.g. "SafetyGuard", "ASTUpdater", "Report"
|
|
805
|
+
code: string, // stable machine-readable code
|
|
806
|
+
reason: string, // human-readable explanation
|
|
807
|
+
context: object, // structured diagnostic context
|
|
808
|
+
message: string,
|
|
809
|
+
stack: string,
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### SafetyGuard Codes
|
|
814
|
+
|
|
815
|
+
| Code | Trigger |
|
|
816
|
+
| -------------------------------- | ------------------------------------------------------- |
|
|
817
|
+
| `BLOCKED_LOW_CONFIDENCE` | AI confidence below `confidenceHardStop` |
|
|
818
|
+
| `BLOCKED_AUDITOR_SUSPICIOUS` | Intent Auditor returned `SUSPICIOUS` in assertion mode |
|
|
819
|
+
| `BLOCKED_ROLE_SWAP_ASSERTION` | Cross-role swap detected in assertion mode |
|
|
820
|
+
| `FAIL_HARD_VISIBILITY_GHOST` | Candidate element is invisible (G1–G6) |
|
|
821
|
+
| `FAIL_HARD_VISIBILITY_OCCLUDED` | Candidate element is covered on main/master |
|
|
822
|
+
| `FAIL_HARD_SEMANTIC_INVERSION` | Binary semantic opposite detected |
|
|
823
|
+
| `FAIL_HARD_STATUS_INVERSION` | Status role inversion (POSITIVE ↔ NEGATIVE) |
|
|
824
|
+
| `FAIL_HARD_AUDITOR_INCONSISTENT` | Intent Auditor returned `INCONSISTENT` |
|
|
825
|
+
| `FAIL_HARD_DANGEROUS_SINK` | Dangerous-sink forced audit resulted in rejection |
|
|
826
|
+
| `SG_HELD_FOR_REVIEW` | Heal quarantined (`REQUIRES_REVIEW`) - source untouched |
|
|
827
|
+
| `GENERIC_REJECTION` | Catch-all rejection |
|
|
828
|
+
|
|
829
|
+
### CI Integrity Codes
|
|
830
|
+
|
|
831
|
+
| Code | Trigger |
|
|
832
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
833
|
+
| `HISTORY_CORRUPT` | `.sela-history.json` failed structural validation - pipeline aborted to preserve last-known-good state |
|
|
834
|
+
| `SHARD_UNREADABLE` | A shard payload failed to parse - shard is skipped; aggregate-and-flag behaviour applies |
|
|
835
|
+
|
|
836
|
+
### Write-Scope & Budget Codes
|
|
837
|
+
|
|
838
|
+
| Code | Trigger |
|
|
839
|
+
| ---------------------- | ---------------------------------------------------- |
|
|
840
|
+
| `PATH_SCOPE_VIOLATION` | Attempted mutation outside configured `allowedPaths` |
|
|
841
|
+
| `HEAL_BUDGET_EXCEEDED` | `maxHealsPerRun` limit reached for this execution |
|
|
842
|
+
|
|
843
|
+
### AST Codes
|
|
844
|
+
|
|
845
|
+
| Code | Trigger |
|
|
846
|
+
| -------------------------------- | ------------------------------------------------------------------ |
|
|
847
|
+
| `AST_HEAL_DISABLED_BY_DIRECTIVE` | `// sela-fail-fast` directive detected above the failing statement |
|
|
848
|
+
|
|
849
|
+
### Exit Code Policy
|
|
850
|
+
|
|
851
|
+
- **Test runs** (`npx playwright test`): Playwright owns the exit code. A healed test passes → `0`. An unhealed genuine failure → `1`. Sela never converts a recoverable file-level failure into a worker crash.
|
|
852
|
+
- **CLI subcommands**: Fatal infrastructure failures (missing config, history corruption) → non-zero exit. Recoverable file-level failures in batch commands → `0` (execution continued).
|
|
853
|
+
|
|
854
|
+
---
|
|
102
855
|
|
|
103
|
-
|
|
856
|
+
_Sela is designed to earn your trust incrementally. Start with `commentOnly` mode, review a week of proposed heals in the Insights report, then graduate to `inPlace` when you are confident. The safety pipeline is always active regardless of write mode._
|
|
@@ -5,6 +5,11 @@ export declare class ConfigLoader {
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the fully resolved config. First call does all I/O; subsequent
|
|
7
7
|
* calls return the cached result at zero cost.
|
|
8
|
+
*
|
|
9
|
+
* Throws when:
|
|
10
|
+
* - `SELA_CONFIG_PATH` is set but points to a non-existent file.
|
|
11
|
+
* - A config file is found but cannot be loaded or has invalid keys.
|
|
12
|
+
* - `SELA_DRY_RUN` / `SELA_NO_CACHE` contain an unrecognised value.
|
|
8
13
|
*/
|
|
9
14
|
static getInstance(): ResolvedConfig;
|
|
10
15
|
private static logEnvBanners;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAc,cAAc,EAAiB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAc,cAAc,EAAiB,MAAM,cAAc,CAAC;AAwLzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAE5C;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAmCpC,OAAO,CAAC,MAAM,CAAC,aAAa;IAW5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAKjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAIrB"}
|
|
@@ -43,10 +43,41 @@ const path = __importStar(require("path"));
|
|
|
43
43
|
const child_process_1 = require("child_process");
|
|
44
44
|
const SelaConfig_1 = require("./SelaConfig");
|
|
45
45
|
const logger_1 = require("../utils/logger");
|
|
46
|
+
// All public top-level keys of SelaConfig — used to catch typos at load time.
|
|
47
|
+
const KNOWN_CONFIG_KEYS = new Set([
|
|
48
|
+
"healingPolicy",
|
|
49
|
+
"thresholds",
|
|
50
|
+
"agenticDiscovery",
|
|
51
|
+
"updateStrategy",
|
|
52
|
+
"autoCommit",
|
|
53
|
+
"prAutomation",
|
|
54
|
+
"dnaStoragePath",
|
|
55
|
+
"allowedPaths",
|
|
56
|
+
"maxHealsPerRun",
|
|
57
|
+
"sanitization",
|
|
58
|
+
"branchOverrides",
|
|
59
|
+
]);
|
|
46
60
|
// ─────────────────────────────────────────────────────────────────
|
|
47
61
|
// FILE DISCOVERY
|
|
48
62
|
// ─────────────────────────────────────────────────────────────────
|
|
63
|
+
/**
|
|
64
|
+
* Resolves the config file path.
|
|
65
|
+
*
|
|
66
|
+
* Resolution order:
|
|
67
|
+
* 1. `SELA_CONFIG_PATH` env var — absolute or cwd-relative; throws immediately
|
|
68
|
+
* if set but the file does not exist (typo in CI config is a hard error).
|
|
69
|
+
* 2. Upward traversal from `startDir`, checking both .ts and .js extensions,
|
|
70
|
+
* stopping at the filesystem root.
|
|
71
|
+
*/
|
|
49
72
|
function findConfigFile(startDir) {
|
|
73
|
+
const envPath = process.env.SELA_CONFIG_PATH;
|
|
74
|
+
if (envPath) {
|
|
75
|
+
const resolved = path.resolve(envPath);
|
|
76
|
+
if (fs.existsSync(resolved))
|
|
77
|
+
return resolved;
|
|
78
|
+
throw new Error(`[Sela] SELA_CONFIG_PATH points to a non-existent file: "${resolved}". ` +
|
|
79
|
+
`Check the env var in your CI configuration.`);
|
|
80
|
+
}
|
|
50
81
|
let dir = startDir;
|
|
51
82
|
while (true) {
|
|
52
83
|
for (const name of ["sela.config.ts", "sela.config.js"]) {
|
|
@@ -65,13 +96,11 @@ function findConfigFile(startDir) {
|
|
|
65
96
|
// ─────────────────────────────────────────────────────────────────
|
|
66
97
|
function getCurrentBranch() {
|
|
67
98
|
try {
|
|
68
|
-
// Fail fast if not a git repo or git is unavailable
|
|
69
99
|
const branch = (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD", {
|
|
70
100
|
encoding: "utf-8",
|
|
71
101
|
stdio: ["pipe", "pipe", "pipe"],
|
|
72
102
|
}).trim();
|
|
73
103
|
if (branch === "HEAD") {
|
|
74
|
-
// Detached HEAD (common in CI with explicit SHA checkout)
|
|
75
104
|
logger_1.logger.warn("Detached HEAD detected - branch overrides skipped");
|
|
76
105
|
return null;
|
|
77
106
|
}
|
|
@@ -83,25 +112,64 @@ function getCurrentBranch() {
|
|
|
83
112
|
}
|
|
84
113
|
}
|
|
85
114
|
// ─────────────────────────────────────────────────────────────────
|
|
115
|
+
// STRUCTURAL VALIDATION
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────
|
|
117
|
+
function validateConfigKeys(config, filePath) {
|
|
118
|
+
const unknown = Object.keys(config).filter((k) => !KNOWN_CONFIG_KEYS.has(k));
|
|
119
|
+
if (unknown.length > 0) {
|
|
120
|
+
throw new Error(`[Sela] Unknown key(s) in ${path.basename(filePath)}: ` +
|
|
121
|
+
`${unknown.map((k) => `"${k}"`).join(", ")}. ` +
|
|
122
|
+
`Valid top-level keys: ${[...KNOWN_CONFIG_KEYS].join(", ")}.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ─────────────────────────────────────────────────────────────────
|
|
86
126
|
// CONFIG FILE LOADING
|
|
87
127
|
// ─────────────────────────────────────────────────────────────────
|
|
128
|
+
/**
|
|
129
|
+
* Loads and validates the raw config from `configPath`.
|
|
130
|
+
*
|
|
131
|
+
* Load strategy (in order):
|
|
132
|
+
* 1. `configPath` as-is — works when a TS loader is registered
|
|
133
|
+
* (Playwright, ts-node, tsx).
|
|
134
|
+
* 2. `.js` sibling — for compiled CI builds where only the transpiled file
|
|
135
|
+
* is present alongside the source `.ts`.
|
|
136
|
+
*
|
|
137
|
+
* Throws (never silently returns {}) when the file is found but every load
|
|
138
|
+
* attempt fails, so CI surfaces the root cause instead of running on defaults.
|
|
139
|
+
*/
|
|
88
140
|
function loadRawConfig(configPath) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
93
|
-
const mod = require(configPath);
|
|
94
|
-
const config = mod?.default ?? mod ?? {};
|
|
95
|
-
if (typeof config !== "object" || config === null) {
|
|
96
|
-
logger_1.logger.warn(`sela.config did not export an object - using defaults`);
|
|
97
|
-
return {};
|
|
98
|
-
}
|
|
99
|
-
return config;
|
|
141
|
+
const candidates = [configPath];
|
|
142
|
+
if (configPath.endsWith(".ts")) {
|
|
143
|
+
candidates.push(configPath.replace(/\.ts$/, ".js"));
|
|
100
144
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
145
|
+
let lastError = null;
|
|
146
|
+
for (const candidate of candidates) {
|
|
147
|
+
if (!fs.existsSync(candidate))
|
|
148
|
+
continue;
|
|
149
|
+
try {
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
151
|
+
const mod = require(candidate);
|
|
152
|
+
const config = mod?.default ?? mod ?? {};
|
|
153
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
154
|
+
throw new Error(`${path.basename(candidate)} must export a plain object — ` +
|
|
155
|
+
`got ${Array.isArray(config) ? "array" : typeof config}`);
|
|
156
|
+
}
|
|
157
|
+
validateConfigKeys(config, candidate);
|
|
158
|
+
return config;
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
// Key-validation failures are definitive — skip fallback candidates.
|
|
162
|
+
if (err.message?.startsWith("[Sela]"))
|
|
163
|
+
throw err;
|
|
164
|
+
lastError = err;
|
|
165
|
+
}
|
|
104
166
|
}
|
|
167
|
+
// Every attempt failed — throw with a diagnostic instead of silently
|
|
168
|
+
// falling back to defaults. Missing sela.config → defaults is intentional
|
|
169
|
+
// (handled at the call site); found-but-broken is always a hard error.
|
|
170
|
+
throw new Error(`[Sela] Failed to load ${path.basename(configPath)}: ${lastError?.message ?? "unknown error"}. ` +
|
|
171
|
+
`Ensure a TypeScript loader (Playwright, ts-node, tsx) is active, ` +
|
|
172
|
+
`or provide a compiled sela.config.js alongside sela.config.ts.`);
|
|
105
173
|
}
|
|
106
174
|
// ─────────────────────────────────────────────────────────────────
|
|
107
175
|
// DEEP MERGE (plain objects only - arrays are replaced, not merged)
|
|
@@ -134,20 +202,23 @@ class ConfigLoader {
|
|
|
134
202
|
/**
|
|
135
203
|
* Returns the fully resolved config. First call does all I/O; subsequent
|
|
136
204
|
* calls return the cached result at zero cost.
|
|
205
|
+
*
|
|
206
|
+
* Throws when:
|
|
207
|
+
* - `SELA_CONFIG_PATH` is set but points to a non-existent file.
|
|
208
|
+
* - A config file is found but cannot be loaded or has invalid keys.
|
|
209
|
+
* - `SELA_DRY_RUN` / `SELA_NO_CACHE` contain an unrecognised value.
|
|
137
210
|
*/
|
|
138
211
|
static getInstance() {
|
|
139
212
|
if (ConfigLoader.resolved)
|
|
140
213
|
return ConfigLoader.resolved;
|
|
141
214
|
const configPath = findConfigFile(process.cwd());
|
|
142
215
|
if (!configPath) {
|
|
143
|
-
// Zero-breaking-change guarantee: no config → pure defaults
|
|
144
216
|
ConfigLoader.resolved = (0, SelaConfig_1.resolveConfig)({});
|
|
145
217
|
ConfigLoader.logEnvBanners(ConfigLoader.resolved);
|
|
146
218
|
return ConfigLoader.resolved;
|
|
147
219
|
}
|
|
148
220
|
logger_1.logger.debug(`Loaded config from: ${path.relative(process.cwd(), configPath)}`);
|
|
149
221
|
let rawConfig = loadRawConfig(configPath);
|
|
150
|
-
// Apply branch overrides using exact-string matching
|
|
151
222
|
const detectedBranch = getCurrentBranch();
|
|
152
223
|
ConfigLoader.branch = detectedBranch;
|
|
153
224
|
if (rawConfig.branchOverrides && detectedBranch) {
|
|
@@ -171,7 +242,6 @@ class ConfigLoader {
|
|
|
171
242
|
}
|
|
172
243
|
/** Returns the detected Git branch name, or null if detection failed. */
|
|
173
244
|
static getBranch() {
|
|
174
|
-
// Ensure getInstance() has been called at least once
|
|
175
245
|
if (!ConfigLoader.resolved)
|
|
176
246
|
ConfigLoader.getInstance();
|
|
177
247
|
return ConfigLoader.branch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAexC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AA0FhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;;IAuBnD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA0DnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAexC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AA0FhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;;IAuBnD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA0DnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAirBlB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAerB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,CAAC,yBAAyB;IAgDjC,OAAO,CAAC,kBAAkB;IA8D1B,OAAO,CAAC,oBAAoB;IAyD5B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAgLtB,aAAa;IAsB3B,OAAO,CAAC,0BAA0B;IA+BlC,OAAO,CAAC,WAAW;IA6Bb,wBAAwB,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;CAqBnB;AAED,OAAO,EAAE,CAAC"}
|
|
@@ -481,6 +481,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
481
481
|
// branch / autoCommit / dry-run keep the legacy git-aware path).
|
|
482
482
|
// Falls back to the legacy regex/semantic engine on ABORT / PROMPT.
|
|
483
483
|
let updateResult;
|
|
484
|
+
let composedMicroDiff;
|
|
484
485
|
const plainInPlace = (!this.config.updateStrategy ||
|
|
485
486
|
this.config.updateStrategy === "inPlace") &&
|
|
486
487
|
!this.config.autoCommit;
|
|
@@ -500,6 +501,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
500
501
|
composed.outcome?.status === "applied" &&
|
|
501
502
|
(strat === "MUTATE_IN_PLACE" || strat === "FORK_AT_TEST")) {
|
|
502
503
|
WorkspaceSnapshotService_1.sharedWorkspaceSnapshot.postWrite(resolvedForCompose);
|
|
504
|
+
composedMicroDiff = composed.outcome?.microDiff;
|
|
503
505
|
const wl = composed.writtenLine ?? line;
|
|
504
506
|
logger_1.logger.stage("ANALYZING AST", `composeHeal primary path (${strat})`);
|
|
505
507
|
updateResult = {
|
|
@@ -550,7 +552,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
|
|
|
550
552
|
// any developer dirty edits sit identically in both buffers and
|
|
551
553
|
// therefore cancel out. Falls back to "" when the snapshot is
|
|
552
554
|
// missing (cross-file healing into an untracked path, etc.).
|
|
553
|
-
const gitUnifiedDiff = this._captureIsolatedDiff(mutatedFilePath);
|
|
555
|
+
const gitUnifiedDiff = composedMicroDiff || this._captureIsolatedDiff(mutatedFilePath);
|
|
554
556
|
const firstChange = this._extractFirstChangeFromDiff(gitUnifiedDiff);
|
|
555
557
|
// Backward-compat: keep oldCodeLine/newCodeLine populated from the
|
|
556
558
|
// first -/+ pair inside the diff. When git produced no diff (file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAWtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1C;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAEpD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;IAC9B;;;OAGG;IACH,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;;OAEG;IACH,aAAa,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACpC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACxC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"LLMService.d.ts","sourceRoot":"","sources":["../../src/services/LLMService.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAWtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1C;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAEpD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;IAC9B;;;OAGG;IACH,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;;OAEG;IACH,aAAa,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACpC,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACxC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAyND;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,IAAI,GAAE,iBAAsB;IAsBxC,OAAO,CAAC,SAAS;IAejB;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IA6FpB,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;CAqLxD"}
|
|
@@ -109,7 +109,17 @@ intended element on the next test run. Use this rubric:
|
|
|
109
109
|
- 90-100: stable id, data-testid, or unique role+name - zero ambiguity in the DOM.
|
|
110
110
|
- 70-89: strong semantic match, but some structural drift or competing candidates.
|
|
111
111
|
- 50-69: best-guess match - multiple plausible candidates required disambiguation.
|
|
112
|
-
-
|
|
112
|
+
- 20-49: speculative match - return this only when no better candidate exists.
|
|
113
|
+
- 1-19: SEMANTIC MISMATCH - the best candidate in the DOM has a different action
|
|
114
|
+
type or intent than the original (e.g. original was "Delete" but you can
|
|
115
|
+
only find "Update", or the element is completely absent from the DOM).
|
|
116
|
+
Use this range whenever the semantic role/purpose of the match differs
|
|
117
|
+
from what the DNA describes.
|
|
118
|
+
CRITICAL: The 90-100 range is ONLY valid when the match is unambiguous AND semantically
|
|
119
|
+
identical to the original element. If there is ANY doubt about semantic equivalence
|
|
120
|
+
(different button text implying a different action, different role, different purpose),
|
|
121
|
+
you MUST score below 70. A high confidence on a semantically wrong match is a
|
|
122
|
+
critical failure - it will silently corrupt a test suite.
|
|
113
123
|
NEVER omit this field. NEVER return it as a string, boolean, or null.
|
|
114
124
|
Reports rendered downstream will show "n/a" when this field is missing - which
|
|
115
125
|
is a clear failure signal to the developer reviewing the heal.
|
|
@@ -117,10 +127,23 @@ is a clear failure signal to the developer reviewing the heal.
|
|
|
117
127
|
### MANDATORY OUTPUT FORMAT
|
|
118
128
|
Return ONLY valid JSON.
|
|
119
129
|
|
|
120
|
-
Example output -
|
|
130
|
+
Example output A - Unambiguous match via stable testid (confidence 90-100):
|
|
121
131
|
{
|
|
122
132
|
"status": "FIXED",
|
|
123
|
-
"confidence":
|
|
133
|
+
"confidence": 95,
|
|
134
|
+
"chainSegments": [
|
|
135
|
+
{ "type": "getByTestId", "testId": "submit-order-btn" }
|
|
136
|
+
],
|
|
137
|
+
"segments": [
|
|
138
|
+
{ "type": "element", "selector": "[data-testid=\"submit-order-btn\"]" }
|
|
139
|
+
],
|
|
140
|
+
"explanation": "Element found by stable data-testid - zero ambiguity."
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Example output B - Semantic drift with structural change (confidence 70-89):
|
|
144
|
+
{
|
|
145
|
+
"status": "FIXED",
|
|
146
|
+
"confidence": 74,
|
|
124
147
|
"chainSegments": [
|
|
125
148
|
{ "type": "locator", "selector": ".user-item" },
|
|
126
149
|
{ "type": "filter", "hasText": "Bob Smith" },
|
|
@@ -132,7 +155,20 @@ Example output - Identifying a specific button in a list row:
|
|
|
132
155
|
"selector": ".user-item >> internal:has-text=\"Bob Smith\"i >> internal:role=button[name=\"Modify Bob\"i]"
|
|
133
156
|
}
|
|
134
157
|
],
|
|
135
|
-
"explanation": "
|
|
158
|
+
"explanation": "Button name changed from 'Edit Bob' to 'Modify Bob' - semantically equivalent action, structural drift in container class."
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
Example output C - Semantic mismatch, original element absent (confidence 1-19):
|
|
162
|
+
{
|
|
163
|
+
"status": "FIXED",
|
|
164
|
+
"confidence": 12,
|
|
165
|
+
"chainSegments": [
|
|
166
|
+
{ "type": "getByRole", "role": "button", "name": "Update System" }
|
|
167
|
+
],
|
|
168
|
+
"segments": [
|
|
169
|
+
{ "type": "element", "selector": "internal:role=button[name=\"Update System\"i]" }
|
|
170
|
+
],
|
|
171
|
+
"explanation": "Original 'Delete System' button is absent from the DOM. Nearest candidate is 'Update System' which performs a different action - semantic mismatch, very low confidence."
|
|
136
172
|
}`;
|
|
137
173
|
// ─────────────────────────────────────────────────────────────────
|
|
138
174
|
// HELPERS
|
|
@@ -33,6 +33,8 @@ export interface ApplyOutcome {
|
|
|
33
33
|
skipped: Array<{
|
|
34
34
|
reason: string;
|
|
35
35
|
}>;
|
|
36
|
+
/** Isolated per-batch unified diff — only set when status === "applied". */
|
|
37
|
+
microDiff?: string;
|
|
36
38
|
}
|
|
37
39
|
export interface ApplyOptions {
|
|
38
40
|
/** When false, compute + gate but never touch disk. Default true. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MutationApplier.d.ts","sourceRoot":"","sources":["../../src/services/MutationApplier.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MutationApplier.d.ts","sourceRoot":"","sources":["../../src/services/MutationApplier.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,SAAS,EAET,UAAU,EACX,MAAM,kBAAkB,CAAC;AAY1B;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAeD,qBAAa,eAAe;IAC1B,gEAAgE;IAChE,MAAM,CAAC,YAAY,CACjB,OAAO,EAAE,SAAS,EAAE,EACpB,IAAI,GAAE,YAAiB,GACtB,YAAY,EAAE;IAIjB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,cAAc,CACnB,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,YAAiB,GACtB,YAAY;IAqCf,OAAO,CAAC,MAAM,CAAC,sBAAsB;CAiLtC;AA2CD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB5D"}
|
|
@@ -60,7 +60,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
60
60
|
exports.MutationApplier = void 0;
|
|
61
61
|
exports.isLocatorReturningExpr = isLocatorReturningExpr;
|
|
62
62
|
const fs = __importStar(require("fs"));
|
|
63
|
+
const path = __importStar(require("path"));
|
|
63
64
|
const ts_morph_1 = require("ts-morph");
|
|
65
|
+
const IsolatedDiff_1 = require("../utils/IsolatedDiff");
|
|
64
66
|
const AnchorResolver_1 = require("./AnchorResolver");
|
|
65
67
|
const DecisionEngine_1 = require("./DecisionEngine");
|
|
66
68
|
const TraceBackEngine_1 = require("./TraceBackEngine");
|
|
@@ -236,6 +238,10 @@ class MutationApplier {
|
|
|
236
238
|
skipped,
|
|
237
239
|
};
|
|
238
240
|
}
|
|
241
|
+
const microDiff = (0, IsolatedDiff_1.generateUnifiedDiff)(original, newContent, {
|
|
242
|
+
filePath: path.relative(process.cwd(), filePath),
|
|
243
|
+
context: 3,
|
|
244
|
+
}) || undefined;
|
|
239
245
|
// ── Type-safety gate: baseline-diff diagnostics ──────────────────
|
|
240
246
|
const afterSf = project.createSourceFile(filePath, newContent, {
|
|
241
247
|
overwrite: true,
|
|
@@ -286,6 +292,7 @@ class MutationApplier {
|
|
|
286
292
|
written: write && !dryRun,
|
|
287
293
|
finalContent: newContent,
|
|
288
294
|
skipped,
|
|
295
|
+
microDiff,
|
|
289
296
|
};
|
|
290
297
|
}
|
|
291
298
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TerminalLink.d.ts","sourceRoot":"","sources":["../../src/utils/TerminalLink.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,UAGzD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.terminalLink = terminalLink;
|
|
4
|
+
function terminalLink(text, command) {
|
|
5
|
+
// הפורמט הזה אומר לטרמינל: "זה טקסט, וזה הלינק/פקודה שצריך להריץ"
|
|
6
|
+
return `\u001b]8;;${command}\u001b\\${text}\u001b]8;;\u001b\\`;
|
|
7
|
+
}
|
package/package.json
CHANGED