How RTK Manages JavaScript/TypeScript Tooling Output: tsc and eslint Handling Explained
RTK executes TypeScript and ESLint as subprocesses through a streaming runner that detects local binaries, filters noisy output via BlockStreamFilter, and prints concise, token-efficient summaries grouped by error codes and files.
The RTK CLI (rtk-ai/rtk) provides a unified interface for JavaScript and TypeScript development workflows by wrapping external tooling like the TypeScript compiler (tsc) and ESLint. Instead of executing these commands directly, RTK intercepts their output streams, parses diagnostic messages in real-time, and presents condensed summaries that highlight critical issues while reducing token noise for AI-assisted development.
How RTK Wraps External JavaScript/TypeScript Tools
RTK treats every external tool as a subprocess executed through its core runner. For JavaScript and TypeScript workflows, the system handles two primary commands: the TypeScript compiler and the ESLint linter. Both follow a consistent four-step pipeline:
- Binary Detection – Prefer locally-installed binaries (
tsc,eslint) and fall back to package-manager wrappers (npx tsc,npx eslint) when binaries are missing. - Stream Processing – Pipe raw output line-by-line through a
BlockStreamFilterthat implements theBlockHandlertrait. - Message Parsing – Group messages by file, error code, or rule to eliminate redundancy.
- Summarized Output – Pass filtered results to an optional tee subsystem for telemetry, then print a compact summary to the terminal.
TypeScript Compiler (tsc) Handling
RTK’s TypeScript integration lives in src/cmds/js/tsc_cmd.rs. The module wraps the compiler in a streaming handler that reduces verbose diagnostic output into actionable summaries.
Binary Detection and Command Construction
RTK first checks for a local TypeScript installation before invoking the compiler. According to the source in src/cmds/js/tsc_cmd.rs (lines 18-26), the system uses tool_exists("tsc") to verify availability:
- If
tscexists locally, RTK callsresolved_command("tsc")to obtain the absolute path. - If missing, RTK constructs a package-manager fallback using
npx tsc.
let tsc_exists = tool_exists("tsc");
let mut cmd = if tsc_exists {
resolved_command("tsc")
} else {
// Fallback to npx tsc
};
The system then forwards all user arguments verbatim (lines 28-30) and logs the exact command when verbose mode (-v) is enabled (lines 32-35).
Streaming Output Processing
Rather than buffering the entire compiler output, RTK uses incremental stream processing. The run_streamed function in src/core/runner.rs executes the command while piping stdout through a BlockStreamFilter configured with a TscHandler (lines 37-42 in tsc_cmd.rs):
runner::run_streamed(
...,
Box::new(BlockStreamFilter::new(TscHandler::new())),
...
)
The TscHandler implements the BlockHandler trait defined in src/core/stream.rs. It recognizes error block boundaries using the TSC_ERROR regex (lines 11-15), enabling real-time filtering without waiting for process completion.
Error Aggregation and Summary Generation
As the stream processes, TscHandler maintains counters for error frequencies and affected files. Once the subprocess exits, the handler formats a compact summary (lines 62-86) that includes:
- Total error count and affected file count.
- Top error codes by frequency (e.g., TS2322, TS2345).
- Grouped messages to prevent repetitive file listings.
The final output displays only the essential diagnostic information, with full details optionally tee'd to telemetry files.
ESLint Handling
ESLint integration resides in src/cmds/js/lint_cmd.rs, which handles multiple linters but contains specific logic for ESLint’s JSON output format.
Linter Detection and Command Execution
RTK automatically detects whether the user intends to run ESLint through the detect_linter function (lines 74-87). The system strips package-manager prefixes like npx or pnpm exec, defaulting to eslint when the first non-flag argument lacks path-like characteristics.
Command construction uses package_manager_exec("eslint") (lines 99-103), which expands to the appropriate package-manager invocation (npx eslint, pnpm exec eslint, etc.). RTK automatically forces JSON output by appending -f json when the user hasn't specified a custom format (lines 106-109):
cmd.arg("-f").arg("json");
Unlike the streaming approach used for tsc, ESLint execution uses exec_capture (lines 70-74) to capture the complete JSON payload before processing.
JSON Parsing and Result Summarization
The filter_eslint_json function (lines 25-78) deserializes the ESLint JSON output and computes:
- Total error and warning counts.
- Frequency tables for violated rules (e.g.,
no-unused-vars,prefer-const). - Issue counts per file.
The results render as a token-efficient summary showing top rules and affected files, followed by telemetry recording.
Core Runner and Streaming Infrastructure
Both tsc and eslint handlers rely on infrastructure defined in src/core/runner.rs and src/core/stream.rs:
runner.rs– Orchestrates subprocess execution, sets up tee channels for raw output capture, and manages the streaming lifecycle.stream.rs– Defines theBlockHandlertrait andBlockStreamFilterstruct used to process output incrementally without buffering the entire stream in memory.
The BlockStreamFilter works by scanning for block start patterns (like TypeScript error headers), accumulating lines until the block ends, then passing the complete block to the handler's format_summary method.
Practical Usage Examples
Run TypeScript compilation with automatic binary detection and filtered output:
# Compiles TypeScript and shows only the summary
rtk tsc src/**/*.ts
Example output:
───────────────────────────────────────
TypeScript: 12 errors in 4 files
Top codes: TS2322 (5x), TS2345 (4x), TS2339 (3x)
Execute ESLint with automatic JSON formatting and package-manager handling:
# Lints the src/ directory with concise output
rtk lint src/
# Explicitly using pnpm (RTK strips the prefix automatically)
rtk lint pnpm exec eslint .
Example output:
───────────────────────────────────────
ESLint: 3 errors, 7 warnings in 2 files
Top rules:
no-unused-vars (4x)
prefer-const (2x)
eqeqeq (1x)
Top files:
src/utils.ts (5 issues)
src/api.ts (5 issues)
Summary
- RTK wraps JavaScript/TypeScript tools as subprocesses through a unified runner in
src/core/runner.rs, providing consistent behavior acrosstscandeslint. - Binary detection prefers local installations (
tsc,eslint) and falls back tonpxorpnpm execwrappers when needed, implemented insrc/cmds/js/tsc_cmd.rs(lines 18-26) andsrc/cmds/js/lint_cmd.rs(lines 99-103). - TypeScript uses streaming filters via
BlockStreamFilterandTscHandlerto process compiler output incrementally, recognizing error blocks with regex patterns and aggregating statistics in real-time. - ESLint uses JSON capture; RTK forces
-f jsonformat (lines 106-109 insrc/cmds/js/lint_cmd.rs), then parses the output throughfilter_eslint_json(lines 25-78) to produce grouped summaries by rule and file. - Both tools support telemetry through the
timer.track()subsystem, enabling performance monitoring and output logging without cluttering the terminal.
Frequently Asked Questions
How does RTK choose between a local binary and npx for TypeScript?
RTK checks for the existence of a local tsc binary using tool_exists("tsc") before executing. If found, it resolves the absolute path via resolved_command("tsc"); otherwise, it constructs a command using npx tsc as a fallback. This logic appears in src/cmds/js/tsc_cmd.rs at lines 18-26.
Why does RTK force JSON output for ESLint but not for TypeScript?
ESLint provides structured JSON output via the -f json flag, which RTK automatically appends (lines 106-109 in src/cmds/js/lint_cmd.rs) to enable reliable parsing through filter_eslint_json. TypeScript's tsc does not offer JSON streaming; instead, RTK parses human-readable error blocks using the TscHandler regex (TSC_ERROR) and BlockStreamFilter to handle the streaming text output.
Can I see the full raw output from tsc or eslint instead of the summary?
Yes. RTK includes a tee subsystem (implemented in src/core/tee.rs) that writes raw output to files for later analysis. While the terminal displays the filtered summary, the complete diagnostic stream can be captured for debugging or detailed review without interfering with the concise display.
Does RTK support package managers other than npm for running these tools?
Yes. RTK abstracts package-manager commands through package_manager_exec() in src/core/utils.rs. When detecting linter commands, it strips prefixes like npx, pnpm exec, or bunx via detect_linter (lines 74-87 in src/cmds/js/lint_cmd.rs), allowing seamless usage across npm, pnpm, Yarn, and Bun ecosystems.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →