Next Release#2774
Open
github-actions[bot] wants to merge 63 commits into
Open
Conversation
…pest, paratest, ecs, pint) Consolidates the PHP-tooling work from three upstream PRs plus a new Pint module, leaving phpt to its own PR (#1503). - rtk php / rtk artisan: syntax check (-l) and Laravel artisan wrapper. - rtk phpunit: structured-state parser, aggregate counts, bounded failure list. Uses runner::run_filtered. - rtk phpstan: typed serde::Deserialize parser for --error-format=json, groups errors by file, sorts by count desc. Utility commands (--version, list, clear-result-cache) pass through unchanged. - rtk pest / rtk paratest: shared test_output helper. - rtk ecs / rtk pint: code-style fixers; pint uses --format=json for structured per-file rule counts. Composer custom-bin-dir detection: composer_bin_dirs() reads COMPOSER_BIN_DIR and composer.json config.bin-dir, so tools/bin/phpunit classifies identically to vendor/bin/phpunit. registry.rs normalizes tool paths before matching. Sources: - #1246 (aaronflorey, self-closed): php, artisan, ecs, pest, paratest, test_output, utils, composer_bin_dirs, registry normalization. - #874 (Beninho, open): phpunit state-machine parser. - #1110 (LucianoVandi, open): phpstan typed parser. - New: pint_cmd.rs. Tests: discover::registry 253 pass; cmds::php 36 pass; cargo build --release 0 errors; cargo fmt --check clean.
`Commands::Run` used `status.code().unwrap_or(1)` which silently maps signal kills (SIGTERM, SIGKILL, OOM) to exit code 1 instead of the POSIX-conventional 128+signal. Use the existing `exit_code_from_status()` helper already used by Commands::Proxy. Fixes #2680
fix(run): propagate signal exit code instead of unwrap_or(1)
Upstream develop changed `rewrite_command` to take 3 args (cmd, excluded, transparent_prefixes). The PHP tooling tests that don't care about transparent prefixes now use the existing `rewrite_command_no_prefixes` helper instead.
The PHP runners (phpunit, pest/paratest, ecs, phpstan, pint) only
applied their compact filters when rtk itself launched the process.
Output produced elsewhere — most commonly a tool run inside a Docker
container and piped back to the host — bypassed them entirely, since
the visible command is `docker ...`, not the PHP tool.
Wire the existing filter functions into `rtk pipe`:
- resolve_filter: phpunit, pest|paratest|php-test, ecs, phpstan, pint
- phpstan/pint pipe wrappers sniff JSON-vs-text by content, since the
runners force --format=json but piped output may be either
- auto_detect_filter: route the "by Sebastian Bergmann" banner to the
phpunit filter (no -f needed)
- bump the four backing fns to pub(crate)
Also fix filter_phpstan_text matching the summary line case-sensitively
("found"), which missed phpstan's actual "[ERROR] Found N errors".
Tests: phpunit banner auto-detect, phpstan case-insensitive summary.
pint: Pint >=1.14 renamed JSON keys name->path and appliedFixers->fixers. The struct fields were required with no aliases, so serde rejected current output and the filter fell back to raw (no compression). Add backward- compatible aliases so both schemas parse. phpstan: the "ok" gate and summary line read totals.errors, which counts only non-file-specific (global) errors. A normal failing run reports errors=0 with the count in file_errors, so runs with real errors were reported as "phpstan: ok", silently hiding failures. Gate on both counts and report file_errors in the summary. Both regressions slipped past the suite because the fixtures set errors == file_errors (phpstan) and used the old key names (pint). Added regression tests using the current-version schemas. Reported by @evaldnet (verified against Pint 1.27.1, PHPStan 2.1.40). Co-authored-by: Aaron Florey <azza@jcaks.net> Co-authored-by: Eli White <1153183+EliW@users.noreply.github.com> Co-authored-by: Benjamin LETELLIER <bletellier@audencia.com> Co-authored-by: Luciano <vandi.luciano@gmail.com>
…ecs/pint `./vendor/bin/<tool>` is the common Laravel invocation form. classify_command normalizes the leading `./`, so these classify as supported, but the rewrite strips literal `rewrite_prefixes` from the raw command and the five rules only carried `vendor/bin/<tool>` and bare `<tool>`. So `./vendor/bin/pint` ran raw with no compression while `vendor/bin/pint` rewrote to `rtk pint`. phpstan already carried `./vendor/bin/phpstan`. Add the `./vendor/bin/` prefix to the other five, with a regression test. Reported by @evaldnet (verified against pint 1.29.1, phpstan 2.1.40). Co-authored-by: Aaron Florey <azza@jcaks.net> Co-authored-by: Eli White <1153183+EliW@users.noreply.github.com> Co-authored-by: Benjamin LETELLIER <bletellier@audencia.com> Co-authored-by: Luciano <vandi.luciano@gmail.com>
develop's `test_every_subcommand_is_classified` requires every CLI subcommand to appear in `RTK_META_COMMANDS` or `PASSTHROUGH`. The consolidated php subcommands (php, phpunit, phpstan, pest, paratest, ecs, pint) wrap real tools, so they belong in `PASSTHROUGH`. Without this the PR-into-develop merge fails the test.
The `.semgrep.yml` `dynamic-command-execution` rule forbids `Command::new` on a variable. `php_tool_command` built the command directly from the resolved tool path; route it through `resolved_command` (the sanctioned PATHEXT-aware constructor) instead. Clears both blocking findings with no behavior change.
… path compaction)
- run() now uses php_tool_command("phpstan") instead of hardcoding
vendor/bin/phpstan, so COMPOSER_BIN_DIR and composer.json's config.bin-dir
are respected, matching every other php tool in this module.
- filter_phpstan_text only treats shell-level "binary missing" lines as a
failure. The previous contains("not found") matched real analysis messages
("Class X not found") and swallowed the summary; also dropped a stray Ruby
LoadError string ("cannot load such file"). Adds a regression test.
- compact_php_path reduced to last-two-components for all frameworks, dropping
the Laravel-specific prefix list. Tests updated to the compacted output.
is_numbered_failure_heading matched any line starting with a digit and containing ')', which split a failure block on detail lines like "5 of 10 assertions passed in Foo::bar()". Anchor to `^\d+\) \S` via a lazy_static regex. Adds a regression test.
short_path() called current_dir() on every file (up to MAX_FILES_SHOWN per invocation). Hoist the cwd prefix out of the loop and strip it inline.
…ewrite The six Composer-tool rules had inconsistent patterns, and their rewrite_prefixes only worked because classify normalizes the command while rewrite stripped literal prefixes off the raw text. A form the pattern accepted but the prefix list missed (e.g. ./bin/phpunit) would classify yet silently fail to rewrite. rewrite_segment_inner now normalizes the leading invocation for these tools (php wrapper, ./, vendor/bin, composer bin-dir) the same way classify does, so the prefix list collapses to the residual canonical forms: bin/<tool> + bare name for phpunit/phpstan, bare name for pest/paratest/ecs/pint. Patterns standardized to ^(?:php\s+)?(?:\./)?(?:(?:vendor/)?bin/)?<tool> for phpunit/phpstan and ^(?:\./)?(?:vendor/bin/)?<tool> for the rest. Adds a form-coverage test asserting every accepted spelling maps to one rewrite.
When no explicit permission rules exist in ~/.cursor/cli-config.json (the default for fresh installs), every command gets Default verdict which maps to AskRewrite. The Cursor hook only handled AllowRewrite, silently dropping all rewrites and making RTK non-functional. Now treat AskRewrite as allow when no rules are configured — Cursor has no ask-the-user UX, so deferring is indistinguishable from dropping. When explicit rules exist, preserve the conservative behavior of deferring mixed/unmatched commands. Also fix the legacy shell script (rtk-rewrite.sh) which treated exit code 3 (ask) as failure via || short-circuit. Fixes #2372
Align cursor_has_explicit_rules() with the inline has_rules check in run_cursor_inner_with_rules() — both now consider deny, ask, and allow rules when determining whether the user has configured explicit permission policies.
Address PR #2609 review feedback: - RC=3 in shell script now emits "permission": "ask" (future-proof) - Add cursor_ask() and use it for all AskRewrite decisions - Remove has_rules guard (unnecessary with ask semantics) - Remove dead cursor_has_explicit_rules() function
…cation Wire TOML-covered commands into the transparent hook rewrite path, and make TOML truncation reversible via the shared tee hints. Part 1 — hook wiring (#2179): The hook's rewrite decision only consulted the static RULES table, so a command covered solely by a TOML filter (jj, jq, just, ssh, ty, nx, turbo, …) was passed through unfiltered by the hook even though `rtk <cmd>` filtered it directly. `rewrite_segment_inner` now consults the TOML registry on a RULES miss (Unsupported) and rewrites to `rtk <cmd>`, which re-enters via run_fallback → the TOML tier. Guards: honors RTK_NO_TOML, respects the exclude list, never rewrites Ignored (denylisted) commands, and a collision guard (is_rtk_reserved_command) prevents rewriting to a name Clap would route to its own subcommand. RTK_META_COMMANDS moved to core::constants as the shared source. Part 2 — reversibility: TOML truncation on success previously dropped lines with a bare "... omitted" marker and no recovery pointer. apply_filter_with_info now reports a Lossiness (Tail / Whole / None); run_fallback emits the line-offset tail hint (`[see remaining: tail -n +N …]`) for a contiguous tail-drop, the full-output hint otherwise, and — when tee is unavailable — shows the full raw rather than an unrecoverable marker. Fixes the symmetric <500B failure-path gap and an inaccurate MIN_TEE_SIZE comment in curl_cmd. All hosts (claude/gemini/copilot/cursor) and `rtk rewrite` are covered via the shared rewrite_command. No hook JSON / integrity-hash changes. The mirrored RULES rows are kept (they feed discover/session/gain metadata). Builds on the approach of #2226. Note: the TOML registry load now lands on the Unsupported hook hot path (~3-8ms native, one-time per process); the Supported path is unaffected. A match-only RegexSet to cut that cost is a documented follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PHP-CS-Fixer omits the fixers key in dry-run/diff modes (it is optional
in the JSON reporter), so a valid report like {"files":[{"name":"x.php"}]}
failed the whole parse and fell back to raw output. Mark the field
#[serde(default)] so a missing key deserializes to an empty vec.
Interpret all RTK rewrite exit codes and respond appropriately: - Exit 0 (Allow): auto-apply rewrite - Exit 1 (Passthrough): no RTK equivalent, pass through unchanged - Exit 2 (Deny): block the call entirely - Exit 3 (Ask): rewrite available, require user approval via requireApproval with allow-once/deny decisions Uses execFileSync (not execSync) to avoid shell injection. Returns a [string | null, RewriteVerdict?] tuple from tryRewrite so the before_tool_call hook can react to each verdict type. "allow-always" omitted from allowedDecisions because OpenClaw does not auto-persist approval for plugin hooks — see: https://docs.openclaw.ai/plugins/plugin-permission-requests#troubleshooting Closes #2202
filter_phpunit_output matched on raw bytes, so `--colors=always` output
(colorized "OK ("/"FAILURES!"/"Tests:" lines) defeated every anchor and
counts were lost. Strip ANSI first, mirroring the other PHP filters.
Also report PHPUnit errors (thrown exceptions) distinctly from failures
(assertion mismatches) instead of lumping both under "failures".
…format `phpstan -c phpstan.neon analyse src/` put a global option before the subcommand, so the first-arg-only check missed it and the run fell back to unfiltered passthrough. Scan all args for analyse/analyze. Replace the has_custom_format bool with a 3-state ErrorFormat enum so an explicit `--error-format=json` is preserved without appending a second `--error-format json`. Strip ANSI in the text fallback for `--ansi`.
Pest has no root `pest.php` file — its bootstrap lives at tests/Pest.php and its canonical marker is the vendor/bin/pest binary. The root-level check never matched real Pest projects and false-positived on unrelated utility files, misrouting PHPUnit-only projects to the Pest filter.
Matching only "by Sebastian Bergmann" misrouted any LICENSE, composer metadata, or git log mentioning the author to the phpunit filter. Require the input to start with "PHPUnit " as well.
composer_bin_dirs() re-read composer.json and the env on every call; the rewrite hot path queries it several times per command segment via normalize_php_tool_command (both classify_command and rewrite_segment_inner). Resolution is constant for a process, so cache it in a OnceLock.
feat: add uv run support
- Fix documentation, Cursor now uses native rust binary hook - Fix rtk-rewrite.sh shell script to fail on any exit code except 0 and 3 - Test that "continue": true is also present for "permission": "ask" and sync shell rewrite hook
…_object extract_json_object() used char indices from enumerate() as byte offsets when slicing the input string. For multi-byte UTF-8 characters (CJK, emoji), this causes a 'not a char boundary' panic because char index != byte offset. Switch from Vec<char> + enumerate() to char_indices() which yields correct byte offsets. Add regression tests for CJK values, emoji values, CJK prefixes, and mixed multi-byte nested JSON. Fixes #2509
paratest_cmd.rs lacked the mandatory #[cfg(test)] module, failing the check-test-presence CI gate. Add tests exercising the ParaTest-specific path in filter_test_runner_output: banner + "Random Seed:" + dot progress stripped, result summary and failure details preserved.
# Conflicts: # src/main.rs
.claude/rules/cli-testing.md marks token-savings verification as a 🔴 requirement for filters. Add a ≥60% savings assertion over a realistic paratest run (banner + config + parallel-worker progress → one-line summary), alongside the existing behavioral tests.
fix(cicd): next release pr target fork compatible
|
sasha seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
feat(php): consolidated PHP tooling (php, artisan, phpunit, phpstan, pest, paratest, ecs, pint)
…n flags Trust-gate both filter files; `rtk init` prompts to enable detected filters, or --trust-filters/--no-trust-filters for scripts.
fix(parser): use byte offsets instead of char indices in extract_json_object
Move the user-facing trust docs into docs/guide (config) and out of CODING_PRACTICES.
…ggling at the moment
The warning printed to stderr on every rewritten command, adding tokens; trust is surfaced only in `rtk init`/`rtk trust`.
Concise filter list + [y/N] (--yes for non-interactive); drops the full-file dump and silent auto-trust. Shared prompt helper with init.
The rewrite path only needs a yes/no match, not the full compiled pipeline find_matching_filter forces on every not-in-RULES command (~10ms/call saved; selection still uses find_matching_filter in run_fallback).
Recoverable loss for a fired head/max cut (never a bare marker), schema-gate the hook match set, parse-error probe for trust; + tests.
…udly when non-interactive
rtk gain --history panicked with "byte index 22 is not a char boundary" when a tracked command contained non-ASCII text (Cyrillic, CJK, emoji). The same byte-slicing pattern existed in the --failures display (top commands, recent failures, timestamp prefix) and in the session id column of rtk session, all reproduced as real panics. Replace the ad-hoc byte-based truncations with the shared char-safe core::utils::truncate() helper, and use .get(..n).unwrap_or() for the timestamp and session id prefix slices so they fall back to the full string instead of panicking. Output is byte-identical to the previous behavior for pure-ASCII data. Fixes #2787
Native Windows has had full auto-rewrite support since v0.37.2 via the `rtk hook claude` native binary command (no bash/jq/Unix shell). The README still described the pre-v0.37.2 "CLAUDE.md fallback" behavior, which is now inaccurate and a recurring source of confusion (#330). - Rewrite the Windows section: native binary hook is the primary path, WSL secondary; drop the "limited support / CLAUDE.md fallback" framing - Add an upgrade note (re-run `rtk init -g` to migrate off rtk-rewrite.sh) - Add a ripgrep prerequisite note (`rg` on PATH) - Fix the install note and Supported AI Tools table (bash -> native binary) Refs #330 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(windows): document native binary hook support
Strip the diffstat decorations and cap the file list; savings are measured against the real command, not an inflated -p patch.
…-panic fix(analytics): truncate display strings on UTF-8 char boundaries
Shorten "N files changed, X insertions(+), Y deletions(-)" to "N changed, X +, Y -"; numbers preserved, no signal loss.
fix(git): compact git stash show instead of forcing -p
feat(hook): wire TOML filters + better tee tail hint
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Feats
Fix
Other