Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
73e733d to
79113d0
Compare
Awesome! Minor issues & questions1.
|
bf5a66f to
60e0226
Compare
|
Thanks — all five are addressed, and I put the fixes through a two-round cross-review, which found seven more. Also rebased onto 1. 2. 3. MCP hint — live rather than hypothetical: both MCP tools already accept 4. 5. Resolved vs. raw — both hint builders take a struct of already-resolved values, so the suggestion and the The cross-review's largest finding was that the trigger asked about the spelling rather than the answer: Tests: +21 in One question before merge: squash the three commits into one, or keep them as they are (hint fix / MCP surface / review fixes)? Happy either way. |
|
@vlsi nice work on this. The shell-quoting story is airtight — the Two things, both in the same predicate. 1. A glob that matches a single directory silences the hint. let inspects_several_files =
|p: &PathBuf| p.is_dir() || path_glob::expand_existing(p).len() > 1;This never inspects what the glob matched. $ ast-bro map src > /tmp/a # 312423 bytes, hinted
$ ast-bro map 'sr*' > /tmp/b # 312423 bytes, quiet
$ cmp /tmp/a /tmp/b && echo identical
identicalThat's exactly the arbitrariness the doc comment above the function rules out (" let inspects_several_files = |p: &PathBuf| {
let m = path_glob::expand_existing(p);
p.is_dir() || m.len() > 1 || m.iter().any(|e| e.is_dir())
};2. A directory holding one file is hinted as a "multi-file answer". Same predicate, other direction: Everything else I poked at held up: Happy to merge once those two are settled. |
…n act on The hint from aeroxy#35 shipped, but three things kept it from doing its job: it named whichever alias the caller entered through, so `digest <dir> --detail full` suggested the nonsense `digest … --preset digest`; JSON callers paid the same size for the same question and got no hint at all, because the `--json` branch returned before the text renderer reached it; and the behaviour was documented nowhere. Two rules changed as a result of review. The suppression rule now asks what the call renders, not how it was spelled. `digest <dir> --include-private --include-fields --json` produces byte for byte what `map <dir> --detail names --json` produces, yet only the second was hinted. It is derived from the resolved axes — bare-name detail, nothing private, no fields, a cap of 50 or tighter — so an inflated digest call is hinted like the map call it equals. The hint is emitted once after both renderers instead of once per renderer, which is what keeps text and JSON from drifting apart again. The suggestion replaces the call it qualifies, so it now reproduces it: every path rather than the first directory, the caller's `--glob`, `--json`, `--compact`, and a `--max-members` tighter than the suggested cap. It is also built to survive the trip through a shell — values are quoted, the glob takes the attached `--glob=` form, and paths move behind a `--` separator when one starts with a dash, since quoting alone leaves clap reading it as a flag. The threshold is unchanged and now says what it measures: the payload the caller has to read, so the same directory crosses it under `--json` while staying quiet in text. A separate JSON threshold would silence genuinely oversized payloads. Twelve tests cover the rules, two of them by running the suggested command through /bin/sh and asserting it exits 0. wiki/architecture.md documents the hint beside the rest of the CLI contract, where aeroxy#32/aeroxy#33/aeroxy#36/aeroxy#37 already were. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The hint from aeroxy#35 reached only the CLI. An MCP client is the caller it was written for and heard nothing: the server has no stderr, so the branch that prints it was unreachable from `tools/call`. The decision moves into `map_hint_is_due`, shared by both surfaces, and each surface words it for its own caller. A shell command is the wrong suggestion to hand a client that speaks JSON-RPC, so the MCP text names the `digest` tool call that shrinks the answer instead. It rides the response the way JSON, since a prepended line would break the parser on the other end. The field carries the message bare — the marker is a text-mode convention. Both MCP tools ask the same question the CLI asks. Calling `digest` is not by itself the digest answer: with include_private, include_fields, or a loosened cap it renders what `map` renders, and it is hinted accordingly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nswer Five review comments from the pull request, then seven more from a two-round cross-review of the fixes. From the review: - `shell_quote` gained `%`, which is inert in a POSIX word. It deliberately still quotes `~`: a path that reaches this function spelled `~/project` is a directory literally named `~`, because a real tilde is expanded before the process starts, so emitting it bare would send the next shell to $HOME. Adding it, as suggested, would have been the bug. - A new `kb_ceil` rounds the displayed size up, so 25 001 bytes past a 25 KB bar no longer announce themselves as 24 KB. - Both hint builders take a struct of already-resolved values, so the suggestion and the `already_digest` test cannot be computed from two sources if preset resolution ever moves. - `with_hint` now says so when the rendered payload fails to parse back, rather than dropping the hint silently. - The MCP hint no longer hardcodes its arguments. It spells out the tool call to send, and because the `digest` tool has no display filters to receive, it names `map` with the digest axes — the same thing the CLI suggests. From the cross-review, the suggestion now reproduces the call it replaces in three more ways: it repeats a path that did not resolve, so following it keeps the missing-path note; it carries `--no-attrs` and `--no-lines`; and it stays quiet altogether for a path that is not UTF-8, which also removed a panic on the MCP side. The trigger stopped being about the spelling. `map alpha` and `map 'alpha/*.rs'` render byte for byte the same answer, so both are hinted; a call that inspected one file, and one that enumerated its files explicitly, stay quiet. The message says "multi-file answer" because "directory" was no longer true of every trigger. On MCP the bar is measured before `missing_paths` is injected, since the suggested call keeps every missing path and cannot shrink that. cli_ergonomics is up to 54 tests and mcp_e2e to 15. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…spelled Two maintainer comments, then eight more findings from a two-round cross-review of the fixes. The predicate asked the arguments how many entries they expanded to, which answered two identical calls differently: `map 'sr*'` was silent beside a byte-identical `map src`, and a directory holding one file announced itself as a multi-file answer. It now asks the walk — `results.len()` — which is the one form of the question no spelling can answer. Every way of naming the same files gets the same answer, so an enumerated file list is hinted like the directory it equals; the previous test asserting the opposite was itself spelling-dependent and is rewritten. That made the hint grow with the argument count: a shell-expanded `map src/**/*.rs` echoed 117 paths into a 2.5 KB line about a 40 KB answer, which is the property the hint exists to protect. Past four paths it names their count instead. The MCP side keeps every path regardless, because `paths` is its one argument without a default and an object missing it is not a call an agent can send — the CLI can shorten only because its suggestion is prose the reader completes from a command line they still have. The spelled-out form is now withheld, rather than rendered wrong, whenever it cannot be faithful: a path that is not UTF-8, and a path holding a backtick, which would close the hint's own delimiter from inside the shell quotes that correctly protect it. Both fall back to the counted form, which names no paths. The MCP `map` tool compared against a literal 50 where the CLI and the sibling `digest` tool read `crate::defaults::MAX_MEMBERS` — the divergence the repo already recorded once, and one no `defaults` guard can see inside an expression. Tests: the suggestion is now asserted to settle rather than merely to run, on both surfaces, and the cap boundary, the backtick path, and a directory holding one file are pinned. cli_ergonomics 59, mcp_e2e 19. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60e0226 to
0a2529f
Compare
|
@vlsi both of mine are fixed, and Three more, all in the suggestion builder rather than the trigger. 1. The counted form can loop. The spelled-out form is a fresh command, so it drops whatever the caller had and always settles. The counted form says add these flags to what you already typed — and when the caller has already set every axis the preset would change, adding them is a no-op: $ ast-bro map <20 files> --detail names --include-private --include-fields --max-members 8
# hint: this was a multi-file answer (32 KB); adding `--preset digest --max-members 8` to the same 20 paths answers the same question in a fraction of the size
$ # append exactly that:
$ ast-bro map <20 files> --detail names --include-private --include-fields --max-members 8 --preset digest
# hint: this was a multi-file answer (32 KB); adding `--preset digest --max-members 8` to the same 20 paths answers the same question in a fraction of the sizeByte-identical 32302-byte output, byte-identical hint, forever. Explicit flags beat the preset on all four axes, so there is nothing left for the suggestion to change.
2. A newline in a path splits the hint across lines. Reproduced with a directory literally named — an unbalanced single quote inside an unbalanced backtick. A line-oriented reader takes that as the whole suggestion, and pasting it hangs the shell waiting for the close. Same class as the backtick guard two lines up, and the repo's own helper is line-oriented ( 3. Non-UTF-8 drops the hint rather than falling back.
Lowest severity of the three: APFS rejects the filename outright, so it's Linux-only and I couldn't reproduce it here — code reading only. Everything else I went after held. The CLI and MCP really do share the decision, the suggestion carries the scope/display/format flags, the dash-prefixed (1) is the only one I'd hold the merge for. |
…n be read Three maintainer findings, all in the suggestion builder, and one from a Codex pass over the fix. The counted form said "adding these flags to what you typed". Against a caller who had already set every axis the preset changes that is a no-op — explicit flags beat the preset — so the same hint printed forever on byte-identical output. Appending the fragment verbatim is worse: the repeated `--max-members` is a clap rejection. Both forms now describe a whole call, so following either one lands on the digest answer. The spelled-out form is one backtick-delimited line, and three kinds of path break it: one that is not UTF-8 has no faithful spelling, one holding a backtick closes the delimiter from inside the shell quotes that correctly protect it, and one holding a newline splits the hint across lines, where every reader of it is line-oriented — this repository's tests included. Only the second was handled, and the first lost the hint outright rather than falling back. All three now take the counted form, which names no paths and so has nothing to break. `println!` writes a newline the size check never counted, so at exactly the threshold the emitted answer cleared a bar the predicate said it had not. That byte is also what the negative tests assert against, so stdout and the predicate now agree on one number. Tests: the counted form is asserted to settle, alongside the two readings the wording rules out; the unspellable cases are one table over backtick, newline and tab. cli_ergonomics 60. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cross-review of the previous commit, which closed "a path cannot be written
into the hint's one backtick-delimited line" — and screened only the paths.
The glob is the other caller-controlled string in that same span, so all
three failure modes stayed reachable through it: `map <dir> --glob
'{*.rs,zz`zz}'` printed a hint carrying three backticks, and a newline glob
split it across two stderr lines. The counted form broke too, which the wiki
had just claimed it could not.
The rule is now stated once and applied to every interpolated string. A path
that fails it still falls back to the counted form; a glob that fails it
withholds the hint, because the counted form carries the glob as well and
dropping it would prescribe a broader question than the one being qualified.
Two of the previous commit's fixes were also asserted by nothing. The
emitted-newline change moves the firing point by exactly one byte, and every
threshold assertion in the suite clears the bar by 50 KB — my claim that the
negative tests sat on that boundary was wrong. A fixture that pads a function
name one character at a time lands stdout on 25 000 and 25 001 bytes and pins
both sides. And the non-UTF-8 fallback, the half that turned a lost hint into
a delivered one, could not be reached end to end at all: APFS rejects the
filename, so it is a unit test over the builder.
Each of the three tests was checked to fail against the code it replaced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
All three fixed in 1. The counted form looped. Both forms now describe a whole call — 2. Newline in a path. Generalized rather than added to the list — the guard is now any control character, and the test is one table over a backtick, a newline and a tab. 3. Non-UTF-8 drops the hint. It falls back like the others now. It needed a unit test on the builder: APFS rejects the filename, and an argument naming nothing is a rejected call before the hint is built. A Codex pass on top of that found the Then the cross-review found I had closed the class only halfway. The rule was "a string the hint interpolates must survive one backtick-delimited line", and I screened the paths but not One correction to my own commit message: I wrote that the negative tests sit on the threshold boundary. They do not — every threshold assertion in the suite clears the bar by 50 KB, so reverting the
Still happy to squash the five commits into one before merge if you prefer; say the word. |
|
@vlsi all three are properly closed, and you found two I'd walked straight past — the glob being the other caller-controlled string in that same span is the one I should have caught, since screening the paths and leaving the glob open covers none of the three failure modes in practice. Widening the test from backtick to "any control character" is the better rule too; I'd have patched Verified the fixes at One thing left, and it's small. The suggested cap Which is the same class you called out yourself one commit earlier, about the literal 50 — and the And a nit, no action needed: Genuinely excellent work on this one — four rounds, and every round the fix got more general than the finding that prompted it. That's rarer than it sounds. Enjoy the week off, and good luck with whatever you're off to do :) Nothing here is urgent, so leave it exactly as it is — pick it back up whenever you feel like it, or don't, and I'll take the cap literal myself if it starts bothering me before then. Thanks again. |
Why
The oversized-directory hint from #35 shipped, but it could not do its job.
It named whichever alias the caller entered through, so
ast-bro digest <dir> --detail fullprinted a suggestion readingdigest … --preset digest— nonsense after #37 made the two one command. JSON callers paid the same size for the same question and got no hint at all, because the--jsonbranch returns before the text renderer ever reaches it. And an MCP client — the caller the hint was written for — heard nothing at all: the server has no stderr, so the branch that prints it was unreachable fromtools/call. The behavior was documented nowhere:wiki/architecture.mdcovers #32, #33, #36 and #37, but not this one.What
The suppression rule asks what the call renders, not how it was spelled.
digest <dir> --include-private --include-fields --jsonproduces byte for byte whatmap <dir> --detail names --jsonproduces, yet only the second was hinted. It is now derived from the resolved axes — bare-name detail, nothing private, no fields, a cap of 50 or tighter — so an inflated digest call is hinted like the map call it equals. The hint is emitted once after both renderers rather than once per renderer, which is what keeps text and JSON from drifting apart again.The suggestion reproduces the call it replaces. Every path rather than the first directory; the caller's
--glob,--json,--compact; and a--max-memberstighter than the suggested cap. It is built to survive the trip through a shell: values are quoted, the glob takes the attached--glob=form, and paths move behind a--separator when one starts with a dash, since quoting alone leaves clap reading it as a flag.The threshold is unchanged and now says what it measures — the payload the caller has to read, so the same directory crosses it under
--jsonwhile staying quiet in text. A separate JSON threshold would silence genuinely oversized payloads.The MCP server makes the same decision and words it for its own caller.
map_hint_is_dueis shared; the message is not. A shell command is the wrong thing to hand a client that speaks JSON-RPC, so the MCP text names thedigesttool call that shrinks the answer. It rides the response the way #33's notes already do: a leading# hint:line on text, ahintfield on JSON, since a prepended line would break the parser on the other end.No change to the part of #35 that matters most:
map <dir>is still answered as asked. The hint is a pointer beside the result, never a redirect.How to verify
cargo test— the full suite.cli_ergonomicsgrew from 38 to 49 tests andmcp_e2efrom 10 to 14; two of the new ones run the suggested command through/bin/shand assert it exits 0.By hand, on a build of this branch:
Over MCP,
tools/callonmapwith{"paths": ["src"]}returns a leading# hint:line, and the same call with"json": truereturns a payload whosehintfield carries the message.Reviewed by a two-reviewer cross-review over three rounds; seven findings, all fixed.
Closes #35.
🤖 Generated with Claude Code