Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91b00e6
feat(cli): investigate recorded run attempts
gorangajic Sep 29, 2026
303133b
chore(cli): add investigation release metadata
gorangajic Sep 29, 2026
630777a
feat(cli): simplify attempt investigation commands
gorangajic Sep 29, 2026
ec7ae8c
fix(cli): harden investigation evidence redaction
gorangajic Sep 29, 2026
e6239ba
fix(cli): redact JWTs that no field name points at
gorangajic Oct 1, 2026
26dc86b
fix(cli): show each summarized action's error and selector
gorangajic Oct 1, 2026
3f03b48
docs(cli): say which entries attempt inspect --limit keeps
gorangajic Oct 1, 2026
98e1537
refactor(cli): drop attempt list now that run get lists attempt IDs
gorangajic Oct 2, 2026
17c313d
refactor(cli): read attempt artifacts through the published contract
gorangajic Oct 2, 2026
6600e7c
feat(cli): take the attempt as --attempt-id like generated commands
gorangajic Oct 2, 2026
3610df1
test(cli): give the synthetic attempt contract annotation justifications
gorangajic Oct 7, 2026
b33138f
fix(cli): read attempt artifacts through attempt.get
gorangajic Oct 7, 2026
19d60bd
refactor(cli): move the investigation parser under domains/investigation
gorangajic Oct 7, 2026
97797bc
refactor(cli): rename the investigation commands dir to attempt
gorangajic Oct 7, 2026
caa929c
fix(cli): hand the binary its parser path without touching worker setup
gorangajic Oct 7, 2026
16b6931
fix(cli): load the investigation parser from source when running unbuilt
gorangajic Oct 7, 2026
a7cde29
fix(cli): redact secrets by field-name pattern and bearer tokens
gorangajic Oct 7, 2026
0aadfcb
refactor(cli): drop unreachable investigation parser options
gorangajic Oct 7, 2026
7a5a046
docs(cli): fix the stale run log limit comment
gorangajic Oct 7, 2026
3242ad0
fix(cli): cap attempt inspect --limit at 100
gorangajic Oct 7, 2026
2b05013
fix(cli): reject unparseable inspect timestamps before calling the API
gorangajic Oct 7, 2026
8b7e322
fix(cli): reject inspect filters that do not apply to the selector
gorangajic Oct 7, 2026
279fd4c
fix(cli): allow two minutes per artifact download and report timeouts
gorangajic Oct 7, 2026
95bdca8
test(cli): cover the attempt investigate and inspect handlers
gorangajic Oct 7, 2026
1975f3d
docs(cli): say --evidence-id selects exactly one record
gorangajic Oct 7, 2026
d687eea
docs(cli): describe qawolf attempt without the dropped attempt list
gorangajic Oct 7, 2026
a6bb3e5
docs(cli): note the commands the api-contracts bump adds and removes
gorangajic Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/quiet-wolves-investigate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@qawolf/cli": minor
---

`qawolf attempt investigate --attempt-id <id>` summarizes a finished run attempt's trace and execution log, and `qawolf attempt inspect --attempt-id <id>` reads one action, request, snapshot, screenshot, console window or log record from them.

Pins `@qawolf/api-contracts` 0.83.0. That removes `qawolf run getAttemptArtifacts`; use `qawolf attempt get --attempt-id <id>` for an attempt's signed artifact links. It also adds `qawolf environment discard`, `qawolf environment promote`, `qawolf promotion find` and `qawolf promotion get`.
75 changes: 75 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,81 @@
]
}
},
{
"files": ["src/domains/investigation/**/*.ts"],
"rules": {
"eslint/no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["../*"],
"message": "Use \"~/\" path alias instead of relative parent imports."
},
{
"group": ["bun", "bun:*"],
"message": "Bun module imports are not available in the nodejs build \u2014 use a portable alternative."
},
{
"group": ["~/commands/*", "~/commands/**"],
"message": "domains/ must not import from commands/."
},
{
"group": ["~/domains/auth/*", "~/domains/auth/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["~/domains/config/*", "~/domains/config/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["~/domains/doctor/*", "~/domains/doctor/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["~/domains/emails/*", "~/domains/emails/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["~/domains/flows/*", "~/domains/flows/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["~/domains/install/*", "~/domains/install/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["~/domains/publicApi/*", "~/domains/publicApi/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": [
"~/domains/interactiveRunner/*",
"~/domains/interactiveRunner/**"
],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["~/domains/runner/*", "~/domains/runner/**"],
"message": "Cross-domain import: shared types belong in core/ or shell/."
},
{
"group": ["node:fs", "fs", "node:fs/promises", "fs/promises"],
"message": "domains/ must not import node:fs \u2014 file I/O belongs in shell/."
},
{
"group": ["node:child_process", "child_process"],
"message": "domains/ must not import node:child_process \u2014 use SpawnFn from shell/spawn.js."
},
{
"group": ["node:readline", "readline"],
"message": "domains/ must not import node:readline \u2014 use UI from shell/ui/."
}
]
}
]
}
},
{
"files": ["src/domains/runner/**/*.ts"],
"rules": {
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ src/
│ ├── init/ # init handler + templates
│ ├── install/ # installBrowsers, installBrowserList
│ ├── interactiveRunner/ # remote runners: launch, stop, keepalive, runFlow, journal, screenshot, act, exec
│ ├── investigation/ # attempt investigate/inspect: artifact download; parser/ is a lazily loaded bundle (dist/investigation-parser.js)
│ ├── runner/ # the LOCAL execution engine: flowsRun, runWebFlow, runAndroidFlow, worker dispatch + pool
│ └── updateCheck/ # startUpdateCheck: new-version notice after commands
├── commands/ # Thin CLI glue — Commander registration + composite root
├── context.ts # withContext() / withAuthContext() action wrappers
├── program.ts # createProgram() factory
├── attempt/ # attempt investigate/inspect (domains/investigation)
├── auth/ # login, logout, whoami handlers
├── doctor/ # doctor handler
├── flows/ # flows run/list/pull handlers; runDefaults composite root
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,36 @@ qawolf flows run examples/example.flow.ts
| `qawolf auth` | [Authenticate with QA Wolf](https://docs.qawolf.com/qawolf/local-execution/authenticate) |
| `qawolf flows` | [Run flows locally](https://docs.qawolf.com/qawolf/local-execution/run-flows-locally), [pull flows](https://docs.qawolf.com/qawolf/local-execution/pull-flows) |
| `qawolf run` | Trigger and manage QA Wolf runs on the platform (public API) |
| `qawolf attempt` | Summarize and inspect a finished run attempt's trace and logs |
| `qawolf install` | [Install runtime dependencies](https://docs.qawolf.com/qawolf/local-execution/install-dependencies) |
| `qawolf init` | [Set up a local-only project](https://docs.qawolf.com/qawolf/local-execution/set-up-a-project) |
| `qawolf doctor` | [Diagnose problems](https://docs.qawolf.com/qawolf/local-execution/diagnose-problems) |

Run any command with `--help` for its flags and options. `qawolf help ref <command>` prints the help of a command and of every command under it, as Markdown.

### Investigate a run attempt

Start with a run ID, pick an attempt ID from its failed flows, then narrow the evidence only when the summary points to it:

```bash
qawolf run get --run-id <run-id> --flow-statuses failed
qawolf attempt investigate --attempt-id <attempt-id>
qawolf attempt inspect --attempt-id <attempt-id> --timeline --limit 20
qawolf attempt inspect --attempt-id <attempt-id> --request <request-id>
```

`attempt inspect` requires exactly one of `--action`, `--timeline`, `--network`, `--request`, `--snapshot`, `--screenshot`, `--console`, or `--log`. Use `--evidence-id` with `--console` or `--log` to return exactly that one evidence record; the other filters are listed in `qawolf attempt inspect --help`.

Snapshot HTML can be returned directly or written to a new file. Screenshots require a file. The CLI refuses to overwrite an existing path:

```bash
qawolf attempt inspect --attempt-id <attempt-id> --snapshot <snapshot-id> --format html
qawolf attempt inspect --attempt-id <attempt-id> --snapshot <snapshot-id> --format html --output-file snapshot.html
qawolf attempt inspect --attempt-id <attempt-id> --screenshot <screenshot-id> --output-file screenshot.jpeg
```

The trace and logs are downloaded from the signed links `qawolf attempt get` returns and parsed on your machine. Downloads, output text, HTML, request bodies, and log retention are bounded; `incomplete`, `truncated`, `malformed`, and artifact status fields describe evidence that could not be read in full.

## Agent integration

This repository ships a [`qawolf-cli` Agent Skill](skills/qawolf-cli/SKILL.md) that follows the open [Agent Skills specification](https://agentskills.io/specification). It works with compatible harnesses such as Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, and OpenCode.
Expand Down
Loading
Loading