feat: add /review slash command and nanocoder review CLI command - #1099
feat: add /review slash command and nanocoder review CLI command#1099soumojit-D48 wants to merge 10 commits into
Conversation
|
@will-lamerton @akramcodez @Avtrkrb, Hi Guys Kindly Review this PR and let me know, Thanku.. |
will-lamerton
left a comment
There was a problem hiding this comment.
Nice shape overall: it follows the /commit dependency-injection pattern, registers lazily with progressLabel on both the command and the registry entry, reuses the existing git utils, and updates README/docs/help together. I checked the branch out locally: tsc --noEmit passes, biome is clean, all 13 tests pass.
Three things need fixing before this can land.
1. nanocoder review is a no-op whenever stdout is not a TTY (pipes, redirects, CI).
cli.tsx sets nonInteractivePrompt = '/review main', but plainAuto enables plain mode whenever !process.stdout.isTTY || ciDetected, and runPlainShell puts the prompt straight into the conversation as {role: 'user', content: prompt} (source/plain/shell.ts:180). There is no slash-command dispatch anywhere in source/plain/. So nanocoder review 42 > out.md or any CI run just sends the literal text /review 42 to the model as chat. The Ink path works only because handleMessageSubmit dispatches commands. Options: teach the plain shell to dispatch built-in commands, call the review logic directly from cli.tsx, or hard-error when review lands in plain mode.
2. The review system prompt never loads in an installed build, so it silently degrades to the one-line fallback.
loadReviewPrompt() resolves join(__dirname, '../app/prompts/sections/review.md'). After tsc, __dirname is dist/commands, so it looks for dist/app/prompts/sections/review.md. tsc does not emit .md and the build script copies only contributors.json, so that path never exists in a built tree. The npm files list ships source/app/prompts/sections, which is exactly why prompt-builder.ts:15 uses ../../source/app/prompts/sections. Net effect: every real install hits the catch {} and gets the one-sentence fallback prompt, losing the point of the PR, with nothing logged. The test passes only because AVA runs from source/. Please match the prompt-builder.ts path (or export a loadPromptSection(name) helper there and reuse it, including its basename traversal guard), log in the catch, and add a test asserting the file resolved rather than the fallback.
3. Target semantics are inverted relative to the documented examples.
getBranchDiff always runs git diff <defaultBranch>...<target>, so the headline example /review main computes git diff main...main and always reports "No changes found". The docs say "fetches the diff against the default branch", which is what a user reviewing their own branch expects. Suggest defaulting the target to the current branch and diffing <target>...HEAD, or at minimum special-casing target === defaultBranch. The empty-diff message is also wrong: it reports between "${currentBranch}" and "${targetDescription}" when currentBranch never appears in the diff command.
Medium
args.findIndex(arg => arg === 'review')matches anywhere in argv and unconditionally overwritesnonInteractivePrompt, sonanocoder run please review this file(unquoted prompts are supported) silently becomes/review this file. Anchor onargs[0]likecopilot login, and error whenrunandrevieware both present.- The copied flag filter handles
--mode=xbut not the two-token--mode planthat therunloop skips, sonanocoder review --mode plan mainsets the target to--mode. Good case for extracting therunfilter into one shared helper instead of duplicating it. - When
ghis missing or fails, a numeric target falls back to being treated as a branch,git rev-parse --verify 42fails, and the user sees a raw git error. The barecatch {}also discards why gh failed (not authenticated, PR not found, wrong repo). An explicit "PR review requires the gh CLI" plus the surfaced gh error would be much clearer.
Minor
truncateDiff(diff, 1000)keeps the first and last 500 lines and drops the middle, which is the worst part to lose for a review.truncated.truncatedis computed but never used: surface "reviewed first/last N of M lines" so the user knows the review is partial.targetis not validated before going into git argv. No shell is involved so there is no shell injection, but a leading-is argument injection (/review --ext-diff). Reject targets starting with-, or pass--before the ref.- On the PR path the user message says
Reviewing changes from PR #42 into "feature", which is inaccurate; the PR diff has no relation to the current branch. ReviewDependenciesdeclaresisGhAvailable/execGhas required, but 8 of the 13 tests construct it without them. That only compiles because tsconfig excludes*.spec.*fromtsc --noEmitand biome excludes specs from lint. Make them optional, or acceptPartial<ReviewDependencies>merged overdefaultDependencies.- No tests for the new
cli.tsxparsing, even thoughsource/cli.spec.tsalready has an established pattern for it. All three CLI-level bugs above would be caught there. - No changeset. Please add one naming
@nanocollective/nanocoder(a barenanocoderpasses PR checks and then breaks release-prepare on main). - Docs say PR review "requires gh CLI", but the code silently falls back instead.
Design note
The command is a single one-shot client.chat over a diff, with no file reads or tool loop. That matches /commit, but it caps review quality: the model cannot open surrounding code to check whether a finding is real, which is what the prompt's "no hallucinated issues" instruction actually needs. Worth confirming with #1002 whether diff-only review is the intended scope or a first step.
51222fa to
412306a
Compare
6a95365 to
f08dc1e
Compare
|
@will-lamerton @akramcodez @Avtrkrb |
will-lamerton
left a comment
There was a problem hiding this comment.
Thanks for the updates. Most of the previous points are genuinely fixed, but one of the fixes introduced a regression, and the tests that would have caught it were rewritten to match it. Still needs another pass.
Blocker (new): run no longer works with flags before it
My medium point asked to anchor review on args[0]. That was also applied to run (source/cli.tsx:321), and args is raw process.argv.slice(2) with no normalisation. Every documented invocation with a flag before run now breaks:
nanocoder --plain run "summarize README.md"(README and cli.tsx's own Examples block)nanocoder --plain --json run "..." | jq .finalText(README)nanocoder --mode plan run "audit the auth module"(README)nanocoder --trust-directory run "analyze src/app.ts"(cli.tsx help)nanocoder --provider ollama --model llama3.1 --context-max 128k run "analyze src"(docs/getting-started)
Verified on this branch:
$ npx tsx source/cli.tsx --plain run "say hi"
--plain requires the `run` subcommand in this version. Try: nanocoder --plain run "..."
The error tells you to run the command that produced it. On 4143e0ef the same invocation works. --json and --trust-directory before run hit their own guards at cli.tsx:456 and :464 the same way.
Two existing tests that covered this were edited rather than kept, which is why all 88 tests pass:
CLI parsing: handles mixed arguments with run command:['--vscode', 'run', ...]changed to['run', ...]plain mode: explicit --plain enables it on a TTY without CI:['--plain', 'run', 'hi']changed to['run', 'hi', '--plain'], title changed to "when run is args[0]"
Please leave run on findIndex, anchor only review on args[0], and restore both tests. Related: the isRunCommand && isReviewCommand guard at cli.tsx:420 is unreachable, since args[0] cannot be both.
Previous blocking points
- Plain mode - addressed via the hard-error route. One problem: the message suggests
nanocoder review <target> 2>&1 | tee out.md, but| teemakes stdout a pipe and trips the same guard. There is currently no way to get review output into a file, so the CLI half of the feature is interactive-only. Worth confirming that is the intended end state. - Prompt path - addressed.
review.ts:24matchesprompt-builder.ts:15, resolves correctly from bothsource/commandsanddist/commands, the catch logs, and the test asserts real prompt content rather than the fallback. Nits: the sharedloadPromptSectionhelper was not extracted, so the path derivation now exists twice; andbasename(normalize('review')...)sanitises a hardcoded literal, so that guard protects against nothing. - Target semantics - addressed. No-arg reviews current vs default,
target === defaultBranchis special-cased, and the empty-diff message usestargetDescription.
Medium
- Two-token
--mode planis handled, but by copying the filter. That flag list now exists four times:runandreviewin cli.tsx,parsePromptandparseReviewArgsin the spec. Extracting it would also have prevented the regression above. - gh handling is properly fixed, with an explicit message, gh's error surfaced, non-GitHub remotes caught, and tests for all three.
Minor
- Truncation note arithmetic is wrong.
review.tsusesMath.ceil(truncated.totalLines / 2), buttruncateDiffkeepsMath.ceil(maxLines / 2)= 500 head and 500 tail. For an 1100-line diff the note reads "reviewed first and last 550 of 1100 lines", claiming full coverage while 100 lines were dropped, and it contradicts the accurate markertruncateDiffalready embeds. The test only assertsof 1100 lines, so it passes. Fix to500, or drop the note since the marker is already in the diff. - Leading-
-rejection,--no-ext-diff, the PR-path message, optionalisGhAvailable/execGh, the changeset naming, and the docs/gh consistency are all done. - The new cli.tsx tests follow the existing duplicate-the-helper convention, which is fine, but nothing covers the two new CLI behaviours: the non-TTY review guard and the
run/reviewconflict. nanocoder reviewwith no target exits 1 with usage, while/reviewwith no args reviews the current branch. Same command, two contracts.reviewArgs.join(' ')letsnanocoder review a bbecome/review a b; the handler readsargs[0]and dropsbsilently.
Design note
Still unanswered from last time. This remains a single one-shot client.chat over a diff with no tool loop, so the prompt's "no hallucinated issues" instruction has nothing to verify findings against. Fine as a first step, but worth confirming against #1002.
7e0febb to
3deffe9
Compare
|
@will-lamerton Hi, Pls review the PR, and let me know, Thank you! |
will-lamerton
left a comment
There was a problem hiding this comment.
Fourth pass. The blocker from last time is genuinely fixed and I verified it. What is left is one medium that was moved rather than fixed, the two test gaps I asked for, three carried-over nits, and two design questions that have now gone unanswered across three reviews. Requesting changes mainly for the last two: I do not want to merge a feature whose scope is still undecided.
Checked out 3deffe9f in an isolated worktree: tsc --noEmit clean, biome clean, 95 tests pass across cli.spec.ts, review.spec.tsx, review-cli.spec.ts.
Blocker: fixed, verified
run is back on findIndex (cli.tsx:323) and only review anchors on args[0], via the extracted parseReviewCliArgs in source/commands/review-cli.ts. plainRequested, plainAuto, and the --json / --trust-directory guards all read isRunCommand rather than nonInteractiveMode, so every documented flag-before-run invocation works again:
$ npx tsx source/cli.tsx --plain run "say hi"
Directory ... is not trusted. Pass --trust-directory ...
Both tests are restored byte-for-byte to their main versions, titles included, and three new cases cover flags before run. The unreachable isRunCommand && isReviewCommand guard is gone. Thanks for taking that one seriously.
Needs another change
1. nanocoder review a b still drops b silently. This was not fixed, it was relocated. parseReviewCliArgs returns reviewArgs[0] and discards the rest, and parseReviewCliArgs: only uses first positional arg now locks that behaviour in as intended. Silently ignoring an argument the user typed is the thing I was asking you to remove. Either error on more than one positional, or accept the extra args and reject them in the handler with a message.
2. No tests for either new CLI behaviour. review-cli.spec.ts covers only parseReviewCliArgs, which is good as far as it goes. Nothing covers the non-TTY review guard or a run / review collision, which are the two behaviours that are new in cli.tsx. cli.spec.ts already has resolvePlainMode as a pattern to copy. I verified the guard by hand instead:
$ npx tsx source/cli.tsx review main < /dev/null | cat
Error: `nanocoder review` requires an interactive terminal (TTY).
3. Docs do not mention that nanocoder review is TTY-only. docs/getting-started/index.md and the README both present it in a bash block next to run, which does support piping and redirection. Anyone who writes nanocoder review main > review.md gets an error the docs gave them no reason to expect. One sentence in the Code Review section covers it.
4. The prompt-fallback test renames a tracked source file. review.spec.tsx:507 does renameSync on source/app/prompts/sections/review.md and restores in a finally. A killed or crashed run leaves review.md.bak in the working tree and the real prompt missing, which then silently degrades every subsequent local review to the fallback. Mock readFileSync, or point the loader at a temp dir.
Carried over, still open
loadPromptSectionwas not extracted. The path is now derived in two places,source/utils/prompt-builder.ts:15andsource/commands/review.ts:24, and they have to stay in sync by hand.basename(normalize('review').replace(...))still sanitises a hardcoded string literal. It protects against nothing. Either take a real parameter or drop it.- The flag list is still triplicated: the
runloop incli.tsx:331,parseReviewCliArgs, andparsePromptincli.spec.ts. The three agree today. They also agreed before the last regression.
Smaller things from this pass
await import('./commands/review-cli')sits atcli.tsx:202, directly beneath the comment saying--acp/--plain/ auth must stay on the lightweight path. The module has no imports so the cost is near zero, but it runs on every single invocation for a subcommand that is almost never the one being used. Move it inside anargs[0] === 'review'check.nanocoder review --json mainon a TTY passes the--jsonguard, becausereviewsetsnonInteractiveMode = true, and then--jsonis silently ignored on the Ink path.- Truncation arithmetic is correct now, but the
500inreview.tsis hardcoded and only right because the call site passes1000. Change that number and the note lies again with no test failure. Deriving it from the same expressiontruncateDiffuses would close it.
Design, third ask
Both of these are still unanswered, and they are the reason this is request-changes rather than approve.
Is diff-only review the intended scope? This is still a single one-shot client.chat over a diff with no tool loop. The system prompt tells the model not to hallucinate issues, but it has no way to open the surrounding code and check whether a finding is real, which is precisely what that instruction requires. That is a defensible v1, but I want it stated as a deliberate first step against #1002 rather than left implicit.
Is TTY-only the intended end state for the CLI half? The hard-error route is a reasonable fix for the plain-shell gap, and the message no longer suggests the self-defeating | tee. But the result is that nanocoder review cannot write its output anywhere, which is most of the reason to have a CLI form at all. If that is the plan for now, say so in the PR and in the docs and I will take it. If the intent is to teach the plain shell to dispatch built-in commands later, an issue linking back here would be enough.
Answer those two and fix 1 through 4 and I think this lands.
…ts, docs, fallback test
0506b03 to
8fe68ae
Compare
Description
Add a dedicated
/reviewslash command andnanocoder review <branch|pr-number>CLI command for AI-powered code review. This implements the feature requested in issue #1002.Code review is a massive use case for AI. Previously, users had to manually prompt "review the git diff" which produced inconsistent results without a strong system prompt. This PR adds a first-class review command that provides architect-level analysis of branch and PR diffs.
Usage
Slash command (interactive TUI):
CLI command (non-interactive):
How it works
gh pr diffwhen the GitHub CLI is availableWhat the review identifies
Type of Change
Changeset
pnpm changeset) describing this change for the changelogDocs-only or internal chores need no changeset (or run
pnpm changeset --emptyto note that intentionally).Testing
Automated Tests
.spec.ts/tsxfilespnpm test:allcompletes successfully)9 test cases covering:
Manual Testing
Architecture Decisions
ReviewDependenciesinterface for testability, following the same pattern as/commitlazy-registry.tsto avoid loading at startupgh pr diffwhen available, falls back to branch diff when gh CLI is missingChecklist