aztrx-cli 0.1.0 → 0.2.0

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 CHANGED
@@ -1,119 +1,160 @@
1
- # Aztrx
1
+ # <img src="media/logo.svg" width="28" height="32" alt="Aztrx logo" align="absmiddle" /> Aztrx AI
2
2
 
3
- > **Autonomous runtime stress-tester, deterministic bug minimizer, and self-healing engine for web applications.**
3
+ > **Autonomous runtime stress-tester, deterministic bug minimizer, human-language explainer, and self-healing engine for web applications.**
4
4
 
5
5
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green.svg?style=flat-square)](https://nodejs.org)
6
6
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
7
7
 
8
- Aztrx drives your web app like a hostile user — clicking, entering boundary data, and racing asynchronous UI states. When a runtime crash occurs (even one swallowed by a React Error Boundary), Aztrx intercepts it via the Chrome DevTools Protocol, maps it back to the exact source line, shrinks the interaction trace to the bare minimum with **ddmin**, and emits an executable, standalone **Playwright test** that proves the bug — not a log line.
8
+ Aztrx AI drives your web app like a hostile user — clicking, entering boundary data, and racing asynchronous UI states. When a runtime crash occurs (even one swallowed by a React Error Boundary), Aztrx AI intercepts it via the Chrome DevTools Protocol, maps it back to the exact source line, shrinks the interaction trace to the bare minimum with **ddmin**, and emits an executable, standalone **Playwright test** that proves the bug — not a log line.
9
9
 
10
10
  ![aztrx demo](media/demo.gif)
11
11
 
12
12
  ---
13
13
 
14
- ## Why Aztrx
14
+ ## Run it
15
15
 
16
- - **Sees swallowed errors.** Error Boundaries and `window.onerror` miss the errors your app *catches*. Aztrx reads the real throw-site stack off the `Error` object, so a crash you've never seen in your logs becomes a finding you can't ignore.
17
- - **Proves, not reports.** Every crash/error finding ships with an executable `.spec.ts` and a flake-rate verdict — `[deterministic 5/5]`, `[flaky 3/5]`, or `[unreliable]`.
18
- - **Heals, not just finds.** `--heal` generates a patch through an LLM, gates it (redaction + AST safety), compiles it, and replays it against the repro inside an isolated git worktree — the patch is verified before a human ever sees it.
19
- - **Safe by default.** A deny-by-default network guard blocks off-origin calls, and a destructive-action deny-list refuses to click "delete", "pay", or "logout".
16
+ ```bash
17
+ npx aztrx-cli run http://localhost:3000
18
+ ```
20
19
 
21
- ---
20
+ That's the whole setup — no install, no config, no account, no API key. Point it at your
21
+ running dev server and it finds the crashes. It drives Chromium through Playwright (the
22
+ first run downloads the browser automatically).
23
+
24
+ What that one command gives you:
22
25
 
23
- ## Quickstart
26
+ - **Sees swallowed errors.** Error Boundaries and `window.onerror` miss the errors your app *catches*. Aztrx AI reads the real throw-site stack off the `Error` object — a crash you've never seen in your logs becomes a finding you can't ignore.
27
+ - **Proves, not reports.** Every crash/error finding ships with an executable `.spec.ts` repro and a flake-rate verdict — `[deterministic 5/5]`, `[flaky 3/5]`, or `[unreliable]`.
28
+ - **Safe by default.** A deny-by-default network guard blocks off-origin calls, a destructive-action deny-list refuses to click "delete", "pay", or "logout", and nothing leaves your machine unless you opt in.
24
29
 
25
- Run against any running local dev server no install, no repo clone:
30
+ More ways to run all optional flags on top of the same command:
26
31
 
27
32
  ```bash
28
- npx aztrx-cli run http://localhost:3000 # deterministic walk
29
33
  npx aztrx-cli run http://localhost:3000 --fuzz # seeded chaos (replayable)
30
- npx aztrx-cli run http://localhost:3000 --fuzz --repro # + minimize → compile → validate
34
+ npx aztrx-cli run http://localhost:3000 --repro # minimize → compile → validate
35
+ npx aztrx-cli run http://localhost:3000 --http-fuzz # server-side 5xx hunt
31
36
  ```
32
37
 
33
- Install it globally once it's published:
38
+ Prefer a global install?
34
39
 
35
40
  ```bash
36
41
  npm i -g aztrx-cli
37
- aztrx-cli run http://localhost:3000 --repro
42
+ aztrx-cli run http://localhost:3000
38
43
  ```
39
44
 
40
- > **Not on npm yet?** Install from source (contributors):
41
- > ```bash
42
- > git clone https://github.com/DanisChaparov/aztrx
43
- > cd aztrx
44
- > npm install
45
- > npm run build
46
- > npm link # puts `aztrx-cli` on your PATH
47
- > ```
48
-
49
- Aztrx drives Chromium through Playwright — the first run downloads the browser
50
- automatically (`npx playwright install chromium` to force it).
51
-
52
45
  ---
53
46
 
54
- ## Features & Workflows
55
-
56
- ### 1. Initialize configuration
47
+ ## Fix it, not just find it
57
48
 
58
- Scaffold `aztrx.config.ts`, detect the framework + dev port, and seed `.aztrx/` into `.gitignore`:
49
+ The only feature that needs a key. `--fix` hands the crash to an LLM and applies a
50
+ verified patch to your working tree. Set `ANTHROPIC_API_KEY` and run:
59
51
 
60
52
  ```bash
61
- npx aztrx-cli init
53
+ export ANTHROPIC_API_KEY="your-api-key"
54
+ npx aztrx-cli run http://localhost:3000 --fix # find → explain → heal → apply
55
+ npx aztrx-cli run http://localhost:3000 --fix --yes # non-interactive (CI)
62
56
  ```
63
57
 
64
- ### 2. Autonomous healing (`--heal`)
58
+ `--fix` chains find explain → heal, then asks *"Apply the fix?"* (`y/N`). On yes, the
59
+ verified patch lands in your working tree — `git diff` shows the result. Aztrx AI never
60
+ commits.
65
61
 
66
- Locate the crash, hand the redacted context to an LLM, run a TypeScript check and Playwright validation inside an isolated worktree, and write a verified `.patch` file:
62
+ - **`--heal`** the same pipeline, but stops at a verified `.patch` file (no apply).
63
+ - **`--explain`** — a human-language "X-ray" summary of what broke, where, and whether a fix is ready. No key required: it falls back to a deterministic offline summary.
67
64
 
68
- ```bash
69
- export ANTHROPIC_API_KEY="your-api-key"
70
- npx aztrx-cli run http://localhost:3000 --fuzz --repro --heal
71
- ```
65
+ Every patch is redacted, sandboxed in a detached git worktree, compiler-checked, and gated
66
+ on your own test suite before you ever see it.
72
67
 
73
- ### 3. Live studio dashboard
68
+ ---
74
69
 
75
- Inspect real-time telemetry events and triage findings in the built-in web UI:
70
+ ## Advanced
76
71
 
77
- ```bash
78
- npx aztrx-cli studio
79
- # → listening at http://localhost:7331
80
- ```
72
+ Everything else is optional. One line each — the full table is in the [CLI reference](#cli-reference).
81
73
 
82
- ### 4. Cloud & CI ingest (`--upload`)
74
+ ### Fuzz harder
75
+ `--fuzz` breaks the *client*; `--http-fuzz` attacks the *server* — it harvests your app's
76
+ real endpoints and throws hostile requests at them (query overflow, JSON type-confusion,
77
+ header injection), turning every `5xx` into an executable repro. When a 500 body leaks a
78
+ server stack, `--heal` can even fix it by booting the patched app and replaying the repro.
83
79
 
84
- Stream sanitized, deduplicated crash fingerprints and metrics to your team's ingest server:
80
+ ### Parallel swarm
81
+ `--workers 4` fans detection into parallel workers (walk + several fuzz seeds + http-fuzz),
82
+ merged by fingerprint. `--swarm` is a hidden alias for `--workers auto`.
83
+
84
+ ### Authenticated testing
85
+ `--login` detects the login form and signs in before the pass, so every repro runs
86
+ authenticated:
85
87
 
86
88
  ```bash
87
- npx aztrx-cli run http://localhost:3000 --upload --api-key <YOUR_API_KEY> --cloud-url http://localhost:8787
89
+ AZTRX_AUTH_EMAIL=you@example.com AZTRX_AUTH_PASSWORD=secret \
90
+ npx aztrx-cli run http://localhost:3000 --login
88
91
  ```
89
92
 
90
- ### 5. GitHub Action
93
+ Or `--storage-state <path>` with a saved Playwright state. Use `--login-url` for an
94
+ explicit login page, `--allow-host auth.example.com` for a third-party auth backend.
95
+
96
+ ### Code modernizer
97
+ `npx aztrx-cli modernize src/legacy.js` rewrites legacy JS/TS into modern idiomatic syntax
98
+ (`var` → `const`, callbacks → `async`/`await`), applied only after you confirm.
99
+
100
+ ### Live studio
101
+ `npx aztrx-cli studio` — triage findings in a localhost dashboard (binds `127.0.0.1`).
102
+
103
+ ### Config & CI
104
+ `npx aztrx-cli init` scaffolds `aztrx.config.ts` and gitignores `.aztrx/`. For a per-PR
105
+ runtime gate, use the [GitHub Action](#continuous-integration-github-action). `--pr-comment`
106
+ / `--badge` write a PR comment / status badge; `--upload --api-key` streams findings to your
107
+ cloud dashboard.
91
108
 
92
- Ship a runtime gate on every PR — see [Continuous Integration](#continuous-integration-github-action) below.
109
+ ### Privacy
110
+ Off by default and strictly opt-in. `--telemetry` collects an anonymized tuple locally;
111
+ `--share-data` uploads it. `--upload` streams sanitized findings to the cloud. See
112
+ [Security & data flow](#security--data-flow) for the invariants.
93
113
 
94
114
  ---
95
115
 
96
116
  ## CLI reference
97
117
 
118
+ `aztrx-cli run --help` is grouped by intent (Detect / Prove / Fix / Report & ship / Auth);
119
+ the table below is the complete reference — including flags hidden from `--help` (aliases
120
+ and niche tuning knobs).
121
+
98
122
  | Flag | Description | Default |
99
123
  | --- | --- | --- |
100
124
  | `--fuzz` | Seeded chaos fuzzing instead of the deterministic walk | — |
125
+ | `--http-fuzz` | Server-side mutation fuzzing — hostile requests against the target origin | — |
101
126
  | `--repro` | Minimize (ddmin) → emit Playwright spec → validate flake rate | — |
102
127
  | `--heal` | Generate + verify an LLM patch (implies `--repro`) | — |
128
+ | `--fix` | Find → explain → heal → apply — the one-command fix (implies `--heal`) | — |
129
+ | `--magic-fix` | Deprecated alias for `--fix` (hidden) | — |
130
+ | `--explain` | Print a human-language summary of the findings | — |
131
+ | `--yes` / `-y` | Auto-apply verified fixes without prompting (with `--fix`) | — |
132
+ | `--lang <code>` | Language for the human-language summary (`en`, `ru`) | `en` |
103
133
  | `--upload` | Stream run findings to the cloud ingest backend | — |
104
134
  | `--api-key <key>` | Auth key for `--upload` / `--share-data` | `$AZTRX_API_KEY` |
105
135
  | `--cloud-url <url>` | Ingest server base URL | `https://api.aztrx.app` |
106
136
  | `--max-actions <n>` | Max actions per pass | `100` |
107
137
  | `--seed <n>` | PRNG seed for deterministic fuzz | `42` |
138
+ | `--workers <n>` | Number of parallel detection workers | `1` |
139
+ | `--swarm` | Hidden alias for `--workers auto` | — |
108
140
  | `--repro-runs <n>` | Flake-rate replay iterations | `3` |
109
141
  | `--heal-model <model>` | Fallback LLM tier | `claude-sonnet-5` |
110
142
  | `--heal-fast-model <model>` | Fast/cheap first tier | `claude-haiku-4-5` |
143
+ | `--test-command <cmd>` | Test command run against a healed patch | `npm test` (auto-detected) |
144
+ | `--test-timeout <ms>` | Timeout for the heal test gate | `300000` |
145
+ | `--no-test` | Skip the test gate during healing | — |
146
+ | `--start-command <cmd>` | Command to boot the app for server healing | `scripts.dev` → `scripts.start` (auto-detected) |
111
147
  | `--pr-comment [path]` | Write a GitHub PR markdown comment | `.aztrx/pr-comment.md` |
148
+ | `--badge [path]` | Write a self-contained SVG status badge | `.aztrx/badge.svg` |
112
149
  | `--telemetry` | Collect anonymized tuples locally (opt-in) | — |
113
150
  | `--share-data` | Also upload the sanitized tuples (opt-in) | — |
114
151
  | `--repo <path>` | Root path for sourcemap → source resolution | cwd |
115
152
  | `--allow-host <host>` | Add a host to the network allow-list (repeatable) | — |
116
- | `--auth <path>` / `--storage-state <path>` | Playwright storage-state for authenticated pages | — |
153
+ | `--storage-state <path>` | Playwright storage-state for authenticated pages (`--auth` is a hidden alias) | — |
154
+ | `--login` | Auto-login before the pass (needs `AZTRX_AUTH_EMAIL`/`AZTRX_AUTH_PASSWORD`) | — |
155
+ | `--login-email <email>` | Email for `--login` (hidden — prefer `$AZTRX_AUTH_EMAIL`) | `$AZTRX_AUTH_EMAIL` |
156
+ | `--login-password <pass>` | Password for `--login` (hidden — prefer `$AZTRX_AUTH_PASSWORD`) | `$AZTRX_AUTH_PASSWORD` |
157
+ | `--login-url <url>` | Explicit login page URL for `--login` (hidden) | current page |
117
158
  | `--fail-on` | Exit `1` if any crash/error finding is present | — |
118
159
  | `--dry-run` | Log planned actions without executing them | — |
119
160
  | `--crash-test` | Throw a deliberate error to verify capture | — |
@@ -132,43 +173,14 @@ Every run writes self-contained artifacts inside `.aztrx/` (gitignored):
132
173
  │ └── 458f6bf71977.spec.ts # minimal, executable Playwright repro
133
174
  ├── heal/
134
175
  │ └── fix.patch # gated, compiler-checked fix
135
- ├── events.jsonl # run log, streamed by `aztrx studio`
176
+ ├── events.jsonl # run log, streamed by `aztrx-cli studio`
136
177
  ├── telemetry/dataset.jsonl # opt-in anonymized tuple dataset
137
- └── pr-comment.md # GitHub PR markdown (with --pr-comment)
178
+ ├── pr-comment.md # GitHub PR markdown (with --pr-comment)
179
+ └── badge.svg # status badge (with --badge)
138
180
  ```
139
181
 
140
182
  ---
141
183
 
142
- ## Architecture
143
-
144
- Aztrx is a decoupled, event-driven pipeline — modules talk only through an
145
- `EventBus`; the orchestrator wires them together.
146
-
147
- ```
148
- [ CDP interceptor ] ──▶ [ action ring buffer ] ──▶ [ classifier (fingerprint) ]
149
-
150
- [ verified .patch ] ◀── [ LLM healer ] ◀── [ ddmin minimizer ] ◀── [ sourcemap resolver ]
151
-
152
- [ Playwright spec (.spec.ts) ] ──▶ [ flake-rate validator ]
153
- ```
154
-
155
- | Stage | Module | Role |
156
- | --- | --- | --- |
157
- | F1 | `interceptor.ts` | CDP interceptor — captures raw runtime errors and console/network events |
158
- | F2 | `recorder.ts` | Ring buffer of the last 25 actions; selector cascade `data-testid → text → CSS path` |
159
- | F3 | `classifier.ts` | Fingerprints + dedups findings, assigns severity; suppresses `.aztrx/baseline.json` (input) |
160
- | F4 | `resolver.ts` | Maps minified frames to source files, lines, and snippets via sourcemaps |
161
- | F5 | `fuzzer.ts` + `domWalker.ts` | Seeded chaos fuzzer; `domWalker` (F5-lite) discovers interactive elements |
162
- | F6 | `networkGuard.ts` + `domWalker.ts` | Deny-by-default network policy + destructive-action deny-list |
163
- | F7 | `minimizer.ts` | ddmin delta-debugging — eliminates irrelevant actions |
164
- | F8 | `specCompiler.ts` | Emits standalone, clean Playwright `.spec.ts` repro |
165
- | F9 | `validator.ts` | Multi-pass replays → `deterministic` / `flaky` / `unreliable` |
166
- | F10 | `heal/` | Closed-loop healing — redact → generate → AST gate → sandbox → `tsc` → verify |
167
- | F11 | `telemetry/` | Opt-in anonymized crash→repro→patch tuple collection (data flywheel) |
168
- | F12 | `cloud/` | Opt-in cloud sync — streams sanitized findings to the ingest dashboard |
169
-
170
- ---
171
-
172
184
  ## Continuous Integration (GitHub Action)
173
185
 
174
186
  Runtime gate on every PR. The action boots your dev server, runs
@@ -184,7 +196,7 @@ jobs:
184
196
  permissions: { contents: read, pull-requests: write }
185
197
  steps:
186
198
  - uses: actions/checkout@v4
187
- - uses: DanisChaparov/aztrx@main
199
+ - uses: DanisChaparov/aztrx@9b4065c71dcba1c98e20c999f54300c689cbaaa7
188
200
  with:
189
201
  url: http://localhost:3000
190
202
  start-command: npm run dev # optional — boot the app in the background
@@ -198,7 +210,7 @@ Or as a reusable workflow:
198
210
  on: pull_request
199
211
  jobs:
200
212
  aztrx:
201
- uses: DanisChaparov/aztrx/.github/workflows/aztrx-pr.yml@main
213
+ uses: DanisChaparov/aztrx/.github/workflows/aztrx-pr.yml@9b4065c71dcba1c98e20c999f54300c689cbaaa7
202
214
  with:
203
215
  url: http://localhost:3000
204
216
  start-command: npm run dev
@@ -208,6 +220,63 @@ jobs:
208
220
 
209
221
  ---
210
222
 
223
+ ## Status badge
224
+
225
+ Hang a live badge in your README that reflects your *actual* crash/error state —
226
+ not a static "protected" sticker.
227
+
228
+ ```bash
229
+ npx aztrx-cli run http://localhost:3000 --badge badge.svg
230
+ ```
231
+
232
+ ```markdown
233
+ [![aztrx](badge.svg)](https://github.com/DanisChaparov/aztrx)
234
+ ```
235
+
236
+ The badge is a self-contained SVG generated from the run's findings — green
237
+ `crash-free` or red `N findings`. It's honest because it's *earned*: regenerate it
238
+ in CI on every push to `main` and commit it back.
239
+
240
+ ```yaml
241
+ # .github/workflows/badge.yml — keep the badge honest on every push to main
242
+ on:
243
+ push:
244
+ branches: [main]
245
+ jobs:
246
+ badge:
247
+ runs-on: ubuntu-latest
248
+ permissions:
249
+ contents: write
250
+ steps:
251
+ - uses: actions/checkout@v4
252
+ - uses: actions/setup-node@v4
253
+ with:
254
+ node-version: 20
255
+ - run: npm ci
256
+ - name: Boot dev server
257
+ run: |
258
+ nohup npm run dev > /tmp/dev.log 2>&1 &
259
+ for i in $(seq 1 60); do
260
+ curl -sS -o /dev/null http://localhost:3000 && break
261
+ sleep 2
262
+ done
263
+ - name: Generate badge
264
+ run: npx --yes aztrx-cli@0.2.0 run http://localhost:3000 --badge badge.svg
265
+ - name: Commit badge
266
+ run: |
267
+ git config user.name "github-actions[bot]"
268
+ git config user.email "github-actions[bot]@users.noreply.github.com"
269
+ git add badge.svg
270
+ git commit -m "chore: update aztrx badge" || echo "no change"
271
+ git push
272
+ ```
273
+
274
+ No `--fail-on` here on purpose: the badge reflects the findings whatever they
275
+ are, and the run still exits 0 so the commit step always runs. Swap `push` for a
276
+ `schedule` cron if you'd rather regenerate daily than on every push.
277
+
278
+ ---
279
+
211
280
  ## Smart Cloud Router
212
281
 
213
282
  `--heal` is backed by a two-tier router. The fast/cheap model
@@ -217,6 +286,43 @@ aztrx falls back to `claude-sonnet-5` and tries again. Most one-line fixes never
217
286
  pay for the big model. Tiers are configurable via `AZTRX_FAST_MODEL` /
218
287
  `AZTRX_MODEL` or `--heal-fast-model` / `--heal-model`.
219
288
 
289
+ ## Security & data flow
290
+
291
+ **Local-first by default.** A run never phones home unless you pass an opt-in
292
+ flag. By default nothing leaves your machine — no telemetry, no cloud sync, no
293
+ LLM call.
294
+
295
+ | What | Leaves your machine | When |
296
+ | --- | --- | --- |
297
+ | `run` (default) | nothing | — |
298
+ | `--heal` | redacted file + redacted error/stack, to the LLM API | only with `--heal` + `ANTHROPIC_API_KEY` |
299
+ | `--share-data` | a sanitized crash→repro→patch tuple | explicit opt-in |
300
+ | `--upload` | sanitized findings + counts | explicit opt-in |
301
+
302
+ ### Invariants
303
+
304
+ - **Sourcemap containment.** A hostile sourcemap or stack URL can't read outside
305
+ your repo: every path is resolved against the repo root and rejected if it
306
+ escapes it — including through symlinks. Secret-bearing files (`.env`, `.npmrc`,
307
+ private keys) are never read into a report or PR comment.
308
+ - **Redaction before the model.** `--heal` redacts common secret patterns (keys,
309
+ tokens, credentials) from the file, error, and stack before they're sent; only
310
+ the repo-relative path and line/column are visible. Redaction is heuristic — it
311
+ is not a substitute for not committing secrets.
312
+ - **Isolated sandbox, no commits.** Patches land in a detached `git worktree` in
313
+ the OS temp dir — never your working tree. Aztrx AI never commits. A patch must
314
+ parse, add no new imports / `eval` / `child_process`, typecheck, *and* pass your
315
+ own test suite before it's offered as a `.patch` for you to review.
316
+ - **Deny-by-default network.** Only the target origin (plus explicit
317
+ `--allow-host`) is reachable; off-origin calls are blocked.
318
+ - **Destructive-action deny-list.** Never clicks delete / pay / logout.
319
+ - **Studio is localhost-only.** The dashboard binds `127.0.0.1` with no wildcard
320
+ CORS.
321
+ - **`.aztrx/` is gitignored** on `init` — repro specs, reports, and patches stay
322
+ out of history.
323
+ - **Pinned supply chain.** The GitHub Action pins `aztrx-cli@0.2.0` (never
324
+ `@latest`).
325
+
220
326
  ## Telemetry & privacy
221
327
 
222
328
  Off by default and strictly opt-in. `--telemetry` collects the anonymized tuple
@@ -227,12 +333,6 @@ strips secrets, anonymizes URLs to `<host>`, and scrubs repo paths to `<repo>`.
227
333
  Uploads are fire-and-forget, bounded by a 2s timeout, and never affect the exit
228
334
  code.
229
335
 
230
- ## Security invariants
231
-
232
- - **Deny-by-default network** — only the target origin (plus explicit `--allow-host`) is reachable.
233
- - **Destructive-action deny-list** — never clicks delete / pay / logout.
234
- - **`.aztrx/` is gitignored** on `init` — repro specs and reports stay out of history.
235
-
236
336
  ---
237
337
 
238
338
  ## Open benchmark
@@ -261,6 +361,37 @@ Full per-case table and scoring notes live in
261
361
 
262
362
  ---
263
363
 
364
+ ## Architecture
365
+
366
+ Aztrx AI is a decoupled, event-driven pipeline — modules talk only through an
367
+ `EventBus`; the orchestrator wires them together.
368
+
369
+ ```
370
+ [ CDP interceptor ] ──▶ [ action ring buffer ] ──▶ [ classifier (fingerprint) ]
371
+
372
+ [ verified .patch ] ◀── [ LLM healer ] ◀── [ ddmin minimizer ] ◀── [ sourcemap resolver ]
373
+
374
+ [ Playwright spec (.spec.ts) ] ──▶ [ flake-rate validator ]
375
+ ```
376
+
377
+ | Stage | Module | Role |
378
+ | --- | --- | --- |
379
+ | F1 | `interceptor.ts` | CDP interceptor — captures raw runtime errors and console/network events |
380
+ | F2 | `recorder.ts` | Ring buffer of the last 25 actions; selector cascade `data-testid → text → CSS path` |
381
+ | F3 | `classifier.ts` | Fingerprints + dedups findings, assigns severity; suppresses `.aztrx/baseline.json` (input) |
382
+ | F4 | `resolver.ts` | Maps minified frames to source files, lines, and snippets via sourcemaps |
383
+ | F5 | `fuzzer.ts` + `domWalker.ts` | Seeded chaos fuzzer; `domWalker` (F5-lite) discovers interactive elements |
384
+ | F6 | `networkGuard.ts` + `domWalker.ts` | Deny-by-default network policy + destructive-action deny-list |
385
+ | F7 | `minimizer.ts` | ddmin delta-debugging — eliminates irrelevant actions |
386
+ | F8 | `specCompiler.ts` | Emits standalone, clean Playwright `.spec.ts` repro |
387
+ | F9 | `validator.ts` | Multi-pass replays → `deterministic` / `flaky` / `unreliable` |
388
+ | F10 | `heal/` | Closed-loop healing — redact → generate → AST gate → sandbox → `tsc` → verify |
389
+ | F11 | `telemetry/` | Opt-in anonymized crash→repro→patch tuple collection (data flywheel) |
390
+ | F12 | `cloud/` | Opt-in cloud sync — streams sanitized findings to the ingest dashboard |
391
+ | F13 | `summarize.ts` + `heal/apply.ts` | Human-language "X-ray" report + opt-in apply of verified patches (`--fix`) |
392
+
393
+ ---
394
+
264
395
  ## Roadmap
265
396
 
266
397
  - [x] Closed-loop healing — redact → generate → gate → sandbox → verify (F10)
@@ -271,6 +402,12 @@ Full per-case table and scoring notes live in
271
402
  - [x] B2B ($29/mo) — Smart Cloud Router (haiku fast-tier → verify → Sonnet fallback)
272
403
  - [x] B2B ($29/mo) — Cloud dashboard (api.aztrx.app)
273
404
  - [x] Data flywheel — opt-in anonymized patch-tuple collection (F11)
405
+ - [x] Server-side healing — heal server `5xx` findings (verify a patch by booting the patched server; requires a leaked server stack + a resolvable start command)
406
+ - [x] Human-language "X-ray" report — `--explain` / `--lang` (LLM + offline fallback)
407
+ - [x] One-click heal & apply — `--fix` (verified patch → working tree, `y/N`, no commit)
408
+ - [x] Autonomous Swarm — parallel detection: walk + multi-seed fuzz + http-fuzz workers, merged by fingerprint
409
+ - [x] Auth auto-login — `--login` walks login forms (synthesize test tokens — next)
410
+ - [x] Code modernizer — LLM-rewrite legacy JS/TS (`modernize`; Python — next)
274
411
 
275
412
  ## Contributing
276
413
 
@@ -287,6 +424,13 @@ node dist/cli.js http://localhost:8901/crash.html --repo fixtures --repro
287
424
  # → one ● crash mapped to crash.html:13:15, minimized to 1 step
288
425
  ```
289
426
 
427
+ ## Support the project
428
+
429
+ If Aztrx AI saved you hours of debugging or helped you ship a clean release, you
430
+ can support the author directly — name a fair price on Polar.sh:
431
+
432
+ **[Donate on Polar.sh →](https://buy.polar.sh/polar_cl_f1vBaxUv3S4fJ0o28GfgzQz7gHDHXkecCQtxY0WqeFs)**
433
+
290
434
  ## License
291
435
 
292
436
  Apache-2.0 © DanisChaparov
@@ -0,0 +1,85 @@
1
+ import { Option } from "commander";
2
+ const GROUP_HEADINGS = {
3
+ detect: "Detect",
4
+ prove: "Prove",
5
+ fix: "Fix",
6
+ ship: "Report & ship",
7
+ auth: "Auth",
8
+ advanced: "Advanced options",
9
+ };
10
+ // Primary groups render first, in this order, with aligned descriptions.
11
+ const PRIMARY_GROUPS = ["detect", "prove", "fix", "ship", "auth"];
12
+ /** Build a commander Option tagged with a help group (defaults to "advanced"). */
13
+ export function opt(flags, description, group = "advanced") {
14
+ const option = new Option(flags, description);
15
+ option.__aztrxGroup = group;
16
+ return option;
17
+ }
18
+ function groupOf(option) {
19
+ return option.__aztrxGroup ?? "advanced";
20
+ }
21
+ /**
22
+ * Standalone `Help.formatHelp` override, registered via
23
+ * `runCommand.configureHelp({ formatHelp })`. Mirrors commander's built-in
24
+ * layout (Usage / Description / Arguments / Commands) but replaces the flat
25
+ * "Options:" list with named groups, and compresses the advanced flags into a
26
+ * single wrapped line of flag names.
27
+ */
28
+ export function formatHelp(cmd, helper) {
29
+ const termWidth = helper.padWidth(cmd, helper);
30
+ const helpWidth = helper.helpWidth || 80;
31
+ const itemIndentWidth = 2;
32
+ const itemSeparatorWidth = 2;
33
+ const formatItem = (term, description) => {
34
+ if (description) {
35
+ const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
36
+ return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
37
+ }
38
+ return term;
39
+ };
40
+ const formatList = (lines) => lines.join("\n").replace(/^/gm, " ".repeat(itemIndentWidth));
41
+ const output = [`Usage: ${helper.commandUsage(cmd)}`, ""];
42
+ const description = helper.commandDescription(cmd);
43
+ if (description.length > 0) {
44
+ output.push(helper.wrap(description, helpWidth, 0), "");
45
+ }
46
+ const argumentList = helper
47
+ .visibleArguments(cmd)
48
+ .map((arg) => formatItem(helper.argumentTerm(arg), helper.argumentDescription(arg)));
49
+ if (argumentList.length > 0) {
50
+ output.push("Arguments:", formatList(argumentList), "");
51
+ }
52
+ // Separate the implicit `-h, --help` so it isn't swallowed by a group.
53
+ const visible = helper.visibleOptions(cmd);
54
+ const helpOption = visible.find((o) => o.short === "-h" && o.long === "--help");
55
+ const grouped = visible.filter((o) => o !== helpOption);
56
+ const buckets = new Map();
57
+ for (const o of grouped) {
58
+ const g = groupOf(o);
59
+ if (!buckets.has(g))
60
+ buckets.set(g, []);
61
+ buckets.get(g).push(o);
62
+ }
63
+ for (const g of PRIMARY_GROUPS) {
64
+ const opts = buckets.get(g);
65
+ if (!opts || opts.length === 0)
66
+ continue;
67
+ const list = opts.map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
68
+ output.push(`${GROUP_HEADINGS[g]}:`, formatList(list), "");
69
+ }
70
+ const advanced = buckets.get("advanced");
71
+ if (advanced && advanced.length > 0) {
72
+ const names = advanced.map((o) => helper.optionTerm(o)).join(", ");
73
+ output.push(`${GROUP_HEADINGS.advanced}:`, formatList([helper.wrap(names, helpWidth - itemIndentWidth, 0)]), "");
74
+ }
75
+ if (helpOption) {
76
+ output.push(formatList([formatItem(helper.optionTerm(helpOption), helper.optionDescription(helpOption))]), "");
77
+ }
78
+ const commandList = helper
79
+ .visibleCommands(cmd)
80
+ .map((c) => formatItem(helper.subcommandTerm(c), helper.subcommandDescription(c)));
81
+ if (commandList.length > 0) {
82
+ output.push("Commands:", formatList(commandList), "");
83
+ }
84
+ return output.join("\n");
85
+ }