Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Run Parsers

prx run <command> wraps CLI tools and returns structured JSON with only actionable information. A passing cargo test suite that produces 50,000 tokens of raw output becomes ~200 tokens through prx. On suites with failures, you get exactly the failures — nothing else.

The Problem

Test runners, build tools, and infrastructure CLIs produce output designed for human eyes. A typical cargo test run on a medium-sized project outputs thousands of lines: test names, timing, progress dots, success messages. An agent running tests needs one thing: what failed and why.

The same applies to kubectl describe, terraform plan, docker build, and npm list. Each tool produces verbose output where the signal is buried in noise.

Architecture

command string → detect_tool() → execute() → parse_output() → JSON envelope
                 ↓                            ↓
              tool name                  ParsedResult {
              (string match)               summary, passed, failed, skipped,
                                           failures: Vec<Diagnostic>,
                                           warnings: Vec<Diagnostic>,
                                           tail: Option<String>
                                         }

detect_tool() matches the command string to a parser name. execute() spawns the process and captures stdout and stderr. parse_output() dispatches to the tool-specific parser. The fallback parser handles unknown commands (truncated tail + exit code).

Detection order matters: more specific patterns must match first. cargo llvm-cov must match before cargo test, and kubectl logs before kubectl.

Run parsers operate on command output (text logs, compiler diagnostics), not source code. Tree-sitter is used elsewhere in prx for code parsing. The one future exception — enriching error locations with function context — is deferred.

Parser Catalog

Test Runners

ParserCommandsExtractsDropsSavings
cargo_testcargo testpass/fail counts, failed test names and outputpassing test lines95-99%
pytestpytest, python -m pytestpass/fail/skip counts, failed test namespassing test dots, collection output95-99%
go_testgo testpass/fail counts, failed test outputpassing --- PASS lines90-95%
jestjest, vitest, npm testpass/fail/skip counts, failed test outputpassing test lines, transform output90-95%
dotnetdotnet test, dotnet buildCS-prefixed errors/warnings, test failuresrestore output, dependency noise75-85%

Build and Lint Tools

ParserCommandsExtractsDropsSavings
cargo_buildcargo build, cargo check, cargo clippyerrors and warnings with file:line:colhelp text, notes, duplicate messages80-90%
mypymypy, python -m mypyfile:line: error: lines, error countnotes without errors, success messages50%
tsctsc, npx tscTypeScript errors with file:line:colhelp suggestions, project config noise70-80%
eslinteslintlint errors/warnings with file:linepassing file notifications, fix suggestions60-80%
mvnmvn, mvnwcompilation errors, Surefire failures, build resultdownload spam, dependency resolution90%
gradlegradle, gradlewFAILED tasks, compile errors, test summarydaemon startup, download progress85%

Coverage Tools

ParserCommandsExtractsDropsSavings
cargo_llvm_covcargo llvm-covcoverage summary, low-coverage filesper-line coverage data90-95%
pytest_covpytest --cov, coverage reporttotal %, low-coverage filesper-line miss data, branch detail80-90%
go_covergo test -cover, go tool covertotal %, per-package coverageper-line annotations70-80%
jest_covjest --coverage, c8, istanbultotal %, uncovered files tableper-line detail, branch maps80-90%

Infrastructure and DevOps

ParserCommandsExtractsDropsSavings
terraformterraform plan, terraform applychanged resources, plan summary(known after apply), unchanged attrs75-85%
kubectlkubectl describe, kubectl getwarning events, non-Ready conditionsnormal events, managed fields80-90%
kubectl_logskubectl logs, docker logsERROR/WARN/FATAL + context, dedupedINFO/DEBUG lines, repeated lines70-90%
docker_builddocker build, docker buildxfailed step + context, image infolayer cache, download progress80%
npm_lsnpm list, npm lstop-level deps, conflicts, warningsnested transitive dependencies95%
git_loggit logcompact hash+subject+author tablefull messages, diffs, stats50-60%

Fallback

ParserCommandsExtractsDropsSavings
fallbackanything elseexit code, truncated tail (last 50-100 lines)bulk of output50-90%

Tool Detection

detect_tool() matches the command string against a list of patterns in priority order. More specific patterns come first.

#![allow(unused)]
fn main() {
fn detect_tool(command: &str) -> &'static str {
    if command.contains("llvm-cov") { return "cargo_llvm_cov"; }
    if command.starts_with("cargo test") { return "cargo_test"; }
    if command.starts_with("cargo") { return "cargo_build"; }
    if command.starts_with("pytest") { return "pytest"; }
    // ...
    "fallback"
}
}

The detection is string matching, not shell parsing. This is intentional: it’s fast, predictable, and covers the common cases without the complexity of a full shell parser.

JSON Auto-Detection (--auto-json)

Several tools support structured output natively. When --auto-json is passed, prx injects the appropriate JSON flag before running the command:

  • kubectl get → adds -o json
  • terraform plan → adds -json
  • npm ls → adds --json
  • eslint → adds --format json
  • mypy → adds --output json

When the tool produces JSON output, prx parses it structurally instead of using regex. This is more reliable and handles edge cases that regex parsers miss.

If you pass --json yourself in the command, prx detects the JSON response and parses it structurally without needing --auto-json.

Token Savings

On a passing test suite, the savings are dramatic:

  • cargo test on a 200-test suite: ~50,000 tokens raw → ~200 tokens via prx (99% reduction)
  • pytest on a 500-test suite: ~30,000 tokens raw → ~150 tokens via prx (99.5% reduction)

On a suite with failures, prx returns exactly the failures. A 200-test suite with 3 failures returns the 3 failure messages plus a summary line — typically 300-500 tokens regardless of how many tests passed.

Adding a New Parser

Each parser is a module in src/runner/. To add a parser:

  1. Create src/runner/mytool.rs with a parse(output: &str) -> ParsedResult function.
  2. Add a detection pattern to detect_tool() in src/runner/mod.rs. Place it before any more general patterns it should take priority over.
  3. Register the parser in the dispatch table in parse_output().
  4. Add inline tests with at least three cases: all-passing output, output with failures, and an edge case (empty output, mixed warnings, or a tool-specific quirk).

Test fixtures are string literals of representative command output. Keep them short (10-30 lines) — enough to exercise the regex patterns without bloating the test file.

File Layout

src/runner/
├── mod.rs              # detect_tool, parse_output, execute, ParsedResult
├── cargo_build.rs      # cargo build/clippy
├── cargo_llvm_cov.rs   # cargo llvm-cov
├── cargo_test.rs       # cargo test
├── docker_build.rs     # docker build
├── dotnet.rs           # dotnet build/test
├── eslint.rs           # eslint
├── fallback.rs         # unknown commands
├── git_log.rs          # git log
├── go_cover.rs         # go test -cover
├── go_test.rs          # go test
├── gradle.rs           # gradle/gradlew
├── jest.rs             # jest/vitest
├── jest_cov.rs         # jest --coverage / c8
├── kubectl.rs          # kubectl describe/get
├── kubectl_logs.rs     # kubectl/docker logs
├── mvn.rs              # mvn/mvnw
├── mypy.rs             # mypy
├── npm_ls.rs           # npm list/ls
├── pytest.rs           # pytest
├── pytest_cov.rs       # pytest --cov / coverage
├── terraform.rs        # terraform plan/apply
└── tsc.rs              # tsc