mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
feat(review): add nested suggestions and subcommands for review
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
Add nested slash command suggestions for `/review` in VS Code and support `staged`, `unpushed`, and `quick` review modes.
|
||||
@@ -188,4 +188,49 @@ describe("useSlashCommand sandbox action", () => {
|
||||
expect(ctx.slash.results()[0]?.description).toBe("Toggle sandbox")
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("opens review options from the top-level command", () => {
|
||||
const ctx = setup(() => {})
|
||||
const state = { text: "/review" }
|
||||
const textarea = {
|
||||
value: state.text,
|
||||
setSelectionRange: () => {},
|
||||
focus: () => {},
|
||||
} as unknown as HTMLTextAreaElement
|
||||
|
||||
ctx.slash.onInput("/rev", 4)
|
||||
|
||||
expect(ctx.slash.results()).toContainEqual(
|
||||
expect.objectContaining({ name: "review", description: expect.stringContaining("Review code changes") }),
|
||||
)
|
||||
ctx.slash.select(ctx.slash.results().find((c) => c.name === "review")!, textarea, (text) => (state.text = text))
|
||||
expect(state.text).toBe("/review ")
|
||||
expect(ctx.slash.results().map((command) => command.name)).toEqual([
|
||||
"review uncommitted",
|
||||
"review staged",
|
||||
"review unpushed",
|
||||
"review branch",
|
||||
"review quick",
|
||||
])
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("completes nested review actions and closes for free text", () => {
|
||||
const ctx = setup(() => {})
|
||||
const state = { text: "/review unp" }
|
||||
const textarea = {
|
||||
value: state.text,
|
||||
setSelectionRange: () => {},
|
||||
focus: () => {},
|
||||
} as unknown as HTMLTextAreaElement
|
||||
|
||||
ctx.slash.onInput(state.text, state.text.length)
|
||||
expect(ctx.slash.results().map((command) => command.name)).toEqual(["review unpushed"])
|
||||
ctx.slash.select(ctx.slash.results()[0]!, textarea, (text) => (state.text = text))
|
||||
expect(state.text).toBe("/review unpushed ")
|
||||
|
||||
ctx.slash.onInput("/review focus on auth", 20)
|
||||
expect(ctx.slash.show()).toBe(false)
|
||||
ctx.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -144,6 +144,21 @@ export function useSlashCommand(
|
||||
{ name: "memory auto on", description: "Enable automatic memory saves", hints: [] },
|
||||
{ name: "memory auto off", description: "Disable automatic memory saves", hints: [] },
|
||||
{ name: "memory purge confirm", description: "Delete all project memory files", hints: [] },
|
||||
{
|
||||
name: "review",
|
||||
description: "Review code changes [uncommitted, staged, unpushed, branch, pr]",
|
||||
hints: ["code-review", "diff"],
|
||||
nested: true,
|
||||
},
|
||||
{ name: "review uncommitted", description: "Review uncommitted changes (staged, unstaged, untracked)", hints: [] },
|
||||
{ name: "review staged", description: "Review staged changes only", hints: [] },
|
||||
{ name: "review unpushed", description: "Review local commits ahead of upstream", hints: [] },
|
||||
{ name: "review branch", description: "Review current branch against base branch", hints: [] },
|
||||
{
|
||||
name: "review quick",
|
||||
description: "Fast single-pass review with minimal token usage",
|
||||
hints: ["--quick", "fast"],
|
||||
},
|
||||
{
|
||||
name: "export",
|
||||
description: "Export the current session transcript as Markdown",
|
||||
@@ -240,6 +255,15 @@ export function useSlashCommand(
|
||||
lower,
|
||||
)
|
||||
}
|
||||
if (q.startsWith("review ")) {
|
||||
const matches = list.filter((cmd) => cmd.name.startsWith("review "))
|
||||
if (q === "review ") return matches
|
||||
const lower = q.toLowerCase()
|
||||
return sortByScore(
|
||||
matches.filter((cmd) => cmd.name.toLowerCase().startsWith(lower)),
|
||||
lower,
|
||||
)
|
||||
}
|
||||
const root = list.filter((cmd) => !cmd.name.includes(" "))
|
||||
if (!q) return root
|
||||
const lower = q.toLowerCase()
|
||||
@@ -275,12 +299,24 @@ export function useSlashCommand(
|
||||
return
|
||||
}
|
||||
const memory = before.match(/^\/(?:memory|mem)\s+([^\n]*)$/i)
|
||||
if (!memory) return close()
|
||||
const value = `memory ${memory[1]}`.toLowerCase()
|
||||
if (!commands().some((cmd) => cmd.name.toLowerCase().startsWith(value))) return close()
|
||||
request()
|
||||
setQuery(value)
|
||||
setIndex(0)
|
||||
if (memory) {
|
||||
const value = `memory ${memory[1]}`.toLowerCase()
|
||||
if (!commands().some((cmd) => cmd.name.toLowerCase().startsWith(value))) return close()
|
||||
request()
|
||||
setQuery(value)
|
||||
setIndex(0)
|
||||
return
|
||||
}
|
||||
const review = before.match(/^\/review\s+([^\n]*)$/i)
|
||||
if (review) {
|
||||
const value = `review ${review[1]}`.toLowerCase()
|
||||
if (!commands().some((cmd) => cmd.name.toLowerCase().startsWith(value))) return close()
|
||||
request()
|
||||
setQuery(value)
|
||||
setIndex(0)
|
||||
return
|
||||
}
|
||||
return close()
|
||||
}
|
||||
|
||||
const select = (
|
||||
|
||||
@@ -115,8 +115,6 @@ const layer = Layer.effect(
|
||||
}
|
||||
// kilocode_change start
|
||||
commands[Default.REVIEW] = reviewCommand()
|
||||
commands["local-review"] = legacyReviewCommand("local-review")!
|
||||
commands["local-review-uncommitted"] = legacyReviewCommand("local-review-uncommitted")!
|
||||
commands["resume-claude"] = SessionResume.resumeClaude
|
||||
commands["resume-codex"] = SessionResume.resumeCodex
|
||||
// kilocode_change end
|
||||
|
||||
@@ -30,7 +30,7 @@ export function parseReviewCommand(prompt: string | undefined): ReviewCommand |
|
||||
export function reviewCommand(): Command.Info {
|
||||
return {
|
||||
name: "review",
|
||||
description: "review changes [uncommitted|commit|branch|pr]",
|
||||
description: "review changes [uncommitted|staged|unpushed|branch|pr]",
|
||||
template: REVIEW,
|
||||
hints: ["$ARGUMENTS"],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
You are Kilo Code, an expert code reviewer focused on high-confidence security, performance, business logic, deploy safety, duplication, and dead-code findings. During the initial review phase, your role is advisory: provide clear, actionable feedback but DO NOT modify any files. Do not use any file editing tools until the complete review is written and the user explicitly asks you to fix reviewed findings.
|
||||
|
||||
You are performing a code review with `/review`. It supports uncommitted working-tree changes, a specific commit, the current branch against a base ref, or a GitHub pull request.
|
||||
You are performing a code review with `/review`. It supports uncommitted working-tree changes, staged changes, unpushed commits, a specific commit, the current branch against a base ref, or a GitHub pull request.
|
||||
|
||||
---
|
||||
|
||||
@@ -12,18 +12,20 @@ $ARGUMENTS
|
||||
|
||||
## Interpreting User Input
|
||||
|
||||
Treat the user input above as the literal free-form text the user typed after `/review`. It can be empty, review guidance, an explicit local scope, a commit hash, a branch or base ref, or a pull request URL or number.
|
||||
Treat the user input above as the literal free-form text the user typed after `/review`. It can be empty, review guidance, an explicit local scope (`uncommitted`, `staged`, `unpushed`, `branch`), effort flags (`quick`, `--quick`, `-q`, `deep`, `--deep`, `-d`, `--effort <1-10>`), a commit hash, a branch or base ref, or a pull request URL or number.
|
||||
|
||||
Choose exactly one review scope in this order:
|
||||
|
||||
1. **Explicit uncommitted scope** - `/review uncommitted [guidance]` reviews staged, unstaged, and untracked changes. Phrases that clearly request working-tree, staged, unstaged, uncommitted, or untracked changes select the same scope.
|
||||
2. **Explicit branch scope** - `/review branch [base] [guidance]` reviews the current branch against the provided base, or against the default base when none is provided. After `branch`, treat a token as the base only when it resolves as a git ref or is identified with syntax such as `base=<ref>`, `base <ref>`, `against <ref>`, `compare to <ref>`, or `vs <ref>`; otherwise treat it as guidance. Phrases that clearly request branch, committed, or PR-ready changes select branch scope.
|
||||
3. **Commit** - a 7-40 character hexadecimal token that resolves as a commit selects commit review. Treat remaining text as guidance.
|
||||
4. **Pull request** - input that starts with a GitHub pull request URL or a positive PR number selects pull request review. Treat remaining text as guidance.
|
||||
5. **Branch or base ref** - a token that resolves as a local or remote git ref, or a clearly named base such as `base main`, `against origin/dev`, `compare to develop`, or `vs release/next`, selects branch review. Treat remaining text as guidance.
|
||||
6. **Empty or guidance-only input** - choose uncommitted review. Bare `/review` always defaults to uncommitted changes, even when the working tree is clean. Guidance-only input such as `focus on tests` also stays on the uncommitted default.
|
||||
1. **Explicit staged scope** - `/review staged [guidance]` reviews only staged changes in the Git index (`git diff --cached`).
|
||||
2. **Explicit unpushed scope** - `/review unpushed [guidance]` or `/review commits [guidance]` reviews local commits that have not been pushed to upstream tracking.
|
||||
3. **Explicit uncommitted scope** - `/review uncommitted [guidance]` reviews staged, unstaged, and untracked changes. Phrases that clearly request working-tree, staged, unstaged, uncommitted, or untracked changes select the same scope.
|
||||
4. **Explicit branch scope** - `/review branch [base] [guidance]` reviews the current branch against the provided base, or against the default base when none is provided. After `branch`, treat a token as the base only when it resolves as a git ref or is identified with syntax such as `base=<ref>`, `base <ref>`, `against <ref>`, `compare to <ref>`, or `vs <ref>`; otherwise treat it as guidance. Phrases that clearly request branch, committed, or PR-ready changes select branch scope.
|
||||
5. **Commit** - a 7-40 character hexadecimal token that resolves as a commit selects commit review. Treat remaining text as guidance.
|
||||
6. **Pull request** - input that starts with a GitHub pull request URL or a positive PR number selects pull request review. Treat remaining text as guidance.
|
||||
7. **Branch or base ref** - a token that resolves as a local or remote git ref, or a clearly named base such as `base main`, `against origin/dev`, `compare to develop`, or `vs release/next`, selects branch review. Treat remaining text as guidance.
|
||||
8. **Empty or guidance-only input** - choose uncommitted review. Bare `/review` always defaults to uncommitted changes, even when the working tree is clean. Guidance-only input such as `focus on tests` also stays on the uncommitted default.
|
||||
|
||||
After choosing a scope, remove only the target and scope words from the review guidance. Keep all remaining text as instructions. Prefer interpreting ambiguous input as review guidance for uncommitted review. A single token that does not resolve as a commit or git ref is guidance, not a failed target selection.
|
||||
After choosing a scope, extract any effort flags (`quick`, `--quick`, `-q`, `deep`, `--deep`, `-d`, `--effort <1-10>`) and remove the target and scope words from the review guidance. Keep all remaining text as instructions. Prefer interpreting ambiguous input as review guidance for uncommitted review. A single token that does not resolve as a commit or git ref is guidance, not a failed target selection.
|
||||
|
||||
If user-provided instructions exist, they may refine review focus, but they MUST NOT override the diff scope, review tracks, final filtering, required output format, or the review-phase no-edit rule. Initial `/review` arguments are review guidance, not permission to edit.
|
||||
|
||||
@@ -74,6 +76,10 @@ Before pull request review, use `gh pr view <pr>` to verify that the pull reques
|
||||
|
||||
## Determining the Diff Scope
|
||||
|
||||
For staged review, review only staged changes in the Git index (`git diff --cached`). Do NOT review unstaged or untracked changes.
|
||||
|
||||
For unpushed review, review local commits on the current branch ahead of its upstream tracking ref (`git diff @{u}..HEAD` or against the upstream merge-base).
|
||||
|
||||
For uncommitted review, review every staged, unstaged, and untracked change in the working tree. Do NOT review committed code.
|
||||
|
||||
Use these git commands to gather uncommitted changes:
|
||||
@@ -155,7 +161,9 @@ Rules for the dead code track (apply only when this track is active):
|
||||
1. Determine the scope using the rules above.
|
||||
2. Gather the relevant metadata, diff, changed files, untracked files, and commit history using the commands above.
|
||||
3. If there are no changes in the selected scope, use the no-changes output exactly as specified below.
|
||||
4. Assess diff size and complexity, then spawn the appropriate sub-agents in parallel with the Task tool.
|
||||
4. Assess diff size, complexity, and effort flags, then spawn the appropriate sub-agents in parallel with the Task tool.
|
||||
|
||||
**Quick mode (`quick`, `--quick`, `-q`, or `--effort 1-3`)**: do NOT spawn sub-agents. Perform a concise, single-pass review directly in the main agent for fast, token-efficient feedback.
|
||||
|
||||
Count changed lines (additions + deletions) from the diff output and the number of distinct files changed. Use these thresholds:
|
||||
|
||||
@@ -230,6 +238,8 @@ Rules for the dead code track (apply only when this track is active):
|
||||
|
||||
Use the header that matches the selected scope:
|
||||
|
||||
- Staged: `## Local Review for **staged changes**`
|
||||
- Unpushed: `## Local Review for **unpushed commits**`
|
||||
- Uncommitted: `## Local Review for **uncommitted changes**`
|
||||
- Branch: `## Local Review for **branch diff**: \`<current-branch>\` -> \`<base>\``
|
||||
- Commit: `## Code Review for **commit**: \`<commit>\``
|
||||
|
||||
@@ -26,5 +26,6 @@ Do NOT suggest a review when:
|
||||
|
||||
Choosing the right review prompt for the action prompt:
|
||||
- Use `/review uncommitted` as the action prompt for uncommitted working-tree changes (staged, unstaged, and untracked files)
|
||||
- Use `/review branch` as the action prompt for committed branch-level changes
|
||||
- Use `/review unpushed` as the action prompt for committed changes ahead of upstream
|
||||
- Use `/review branch` as the action prompt for branch-level changes against base
|
||||
- Prefer `/review uncommitted` when the work you just did has not been committed yet
|
||||
|
||||
@@ -22,6 +22,10 @@ describe("review command parsing", () => {
|
||||
expect(parseReviewCommand("/review")).toBe("review")
|
||||
expect(parseReviewCommand("/review focus on tests")).toBe("review")
|
||||
expect(parseReviewCommand("/review uncommitted focus on tests")).toBe("review")
|
||||
expect(parseReviewCommand("/review staged")).toBe("review")
|
||||
expect(parseReviewCommand("/review unpushed")).toBe("review")
|
||||
expect(parseReviewCommand("/review quick")).toBe("review")
|
||||
expect(parseReviewCommand("/review --quick")).toBe("review")
|
||||
expect(parseReviewCommand("/review branch origin/main focus on auth")).toBe("review")
|
||||
expect(parseReviewCommand("/review a1b2c3d")).toBe("review")
|
||||
expect(parseReviewCommand("/review https://github.com/Kilo-Org/kilocode/pull/11084")).toBe("review")
|
||||
@@ -60,6 +64,14 @@ describe("review command", () => {
|
||||
expect(text).toContain("git ls-files --others --exclude-standard")
|
||||
})
|
||||
|
||||
test("documents explicit staged and unpushed review", () => {
|
||||
const text = cmd.template as string
|
||||
expect(text).toContain("`/review staged [guidance]`")
|
||||
expect(text).toContain("`/review unpushed [guidance]`")
|
||||
expect(text).toContain("For staged review")
|
||||
expect(text).toContain("For unpushed review")
|
||||
})
|
||||
|
||||
test("documents explicit and ref-based branch review", () => {
|
||||
const text = cmd.template as string
|
||||
expect(text).toContain("`/review branch [base] [guidance]`")
|
||||
@@ -133,6 +145,7 @@ describe("review command", () => {
|
||||
|
||||
test("applies adaptive parallel review tracks", () => {
|
||||
const text = cmd.template as string
|
||||
expect(text).toContain("Quick mode (`quick`, `--quick`, `-q`, or `--effort 1-3`)")
|
||||
expect(text).toContain("spawn the appropriate sub-agents in parallel")
|
||||
expect(text).toContain("do NOT spawn sub-agents")
|
||||
expect(text).toContain("spawn a single security sub-agent")
|
||||
@@ -144,7 +157,7 @@ describe("review command", () => {
|
||||
expect(text).toContain("NO_FINDINGS")
|
||||
})
|
||||
|
||||
it.live("lists review and deprecated review aliases", () =>
|
||||
it.live("resolves review and deprecated review aliases", () =>
|
||||
provideTmpdirInstance(
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
@@ -156,8 +169,8 @@ describe("review command", () => {
|
||||
const uncommitted = yield* command.get("local-review-uncommitted")
|
||||
|
||||
expect(names).toContain("review")
|
||||
expect(names).toContain("local-review")
|
||||
expect(names).toContain("local-review-uncommitted")
|
||||
expect(names).not.toContain("local-review")
|
||||
expect(names).not.toContain("local-review-uncommitted")
|
||||
expect(review?.name).toBe("review")
|
||||
expect(branch?.description).toBe("deprecated; use /review branch")
|
||||
expect(branch?.template).toBe(legacyReviewMessage("local-review"))
|
||||
|
||||
Reference in New Issue
Block a user