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 +234 -90
- package/dist/cli/help.js +85 -0
- package/dist/cli.js +167 -29
- package/dist/core/auth.js +76 -0
- package/dist/core/badge.js +50 -0
- package/dist/core/fuzzer.js +1 -1
- package/dist/core/heal/apply.js +52 -0
- package/dist/core/heal/boot.js +136 -0
- package/dist/core/heal/childEnv.js +60 -0
- package/dist/core/heal/index.js +47 -6
- package/dist/core/heal/redact.js +31 -1
- package/dist/core/heal/sandbox.js +53 -1
- package/dist/core/heal/verify.js +30 -1
- package/dist/core/httpFuzzer.js +258 -0
- package/dist/core/init.js +2 -2
- package/dist/core/interceptor.js +10 -1
- package/dist/core/modernize.js +144 -0
- package/dist/core/orchestrator.js +66 -77
- package/dist/core/pr.js +44 -20
- package/dist/core/prompt.js +22 -0
- package/dist/core/replay.js +35 -3
- package/dist/core/report.js +19 -6
- package/dist/core/resolver.js +158 -5
- package/dist/core/specCompiler.js +17 -2
- package/dist/core/studio.js +3 -4
- package/dist/core/summarize.js +173 -0
- package/dist/core/swarm.js +235 -0
- package/dist/core/ui.js +2 -0
- package/dist/ui/app.js +4 -2
- package/media/demo.gif +0 -0
- package/media/logo.svg +9 -0
- package/package.json +25 -6
package/README.md
CHANGED
|
@@ -1,119 +1,160 @@
|
|
|
1
|
-
#
|
|
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
|
[](https://nodejs.org)
|
|
6
6
|
[](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
|

|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Run it
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
38
|
+
Prefer a global install?
|
|
34
39
|
|
|
35
40
|
```bash
|
|
36
41
|
npm i -g aztrx-cli
|
|
37
|
-
aztrx-cli run http://localhost:3000
|
|
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
|
-
##
|
|
55
|
-
|
|
56
|
-
### 1. Initialize configuration
|
|
47
|
+
## Fix it, not just find it
|
|
57
48
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
68
|
+
---
|
|
74
69
|
|
|
75
|
-
|
|
70
|
+
## Advanced
|
|
76
71
|
|
|
77
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
| `--
|
|
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
|
-
|
|
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@
|
|
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@
|
|
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
|
+
[](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
|
package/dist/cli/help.js
ADDED
|
@@ -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
|
+
}
|