From c643aa279db47be0eb66fadc3f2290008fed7f95 Mon Sep 17 00:00:00 2001 From: code-crusher Date: Thu, 1 Jan 2026 14:21:00 +0530 Subject: [PATCH 1/8] complete ai code reviews --- src/core/kilocode/api/codeReviewService.ts | 46 +++ src/core/webview/webviewMessageHandler.ts | 372 ++++++++++++++++++ src/package.json | 3 + src/shared/ExtensionMessage.ts | 27 +- src/shared/WebviewMessage.ts | 32 ++ webview-ui/src/codicon-custom.css | 10 + .../src/components/chat/ChatTextArea.tsx | 2 +- webview-ui/src/components/chat/ChatView.tsx | 250 ++++++++---- .../src/components/chat/CodeReviewPanel.tsx | 116 ++++++ .../components/chat/SourceControlPanel.tsx | 347 ++++++++++++++++ 10 files changed, 1113 insertions(+), 92 deletions(-) create mode 100644 src/core/kilocode/api/codeReviewService.ts create mode 100644 webview-ui/src/components/chat/CodeReviewPanel.tsx create mode 100644 webview-ui/src/components/chat/SourceControlPanel.tsx diff --git a/src/core/kilocode/api/codeReviewService.ts b/src/core/kilocode/api/codeReviewService.ts new file mode 100644 index 0000000000..286b477829 --- /dev/null +++ b/src/core/kilocode/api/codeReviewService.ts @@ -0,0 +1,46 @@ +// kilocode_change - AI Code Review Service +import { getKiloUrlFromToken } from "@roo-code/types" +import axios from "axios" +import { CodeReviewResultsPayload } from "../../../shared/WebviewMessage" + +export interface CodeReviewRequest { + git_diff: string + git_owner: string + git_repo: string + git_branch: string + git_user: string +} + +export class CodeReviewService { + constructor(private kilocodeToken: string) {} + + async requestCodeReview(request: CodeReviewRequest): Promise { + try { + const headers: Record = { + Authorization: `Bearer ${this.kilocodeToken}`, + "Content-Type": "application/json", + } + + const url = getKiloUrlFromToken("https://api.matterai.so/codereview", this.kilocodeToken) + + const response = await axios.post(url, request, { headers }) + + const data = response.data + if (data.codeChangeGeneration) { + return { + reviewBody: data.codeChangeGeneration.reviewBody, + reviewComments: data.codeChangeGeneration.reviewComments || [], + } + } + + // Fallback if structure matches CodeReviewResultsPayload directly (legacy or future proof) + return { + reviewBody: data.reviewBody || "", + reviewComments: data.reviewComments || [], + } + } catch (error) { + console.error("Code review request failed:", error) + throw new Error(`Code review failed: ${error instanceof Error ? error.message : "Unknown error"}`) + } + } +} diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index be9927bfb9..24ee8df278 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -89,6 +89,255 @@ import { getCheckpointService } from "../checkpoints" // kilocode_change import { fetchAndRefreshOrganizationModesOnStartup, refreshOrganizationModes } from "./kiloWebviewMessgeHandlerHelpers" import { ImplementPlanPayload } from "../../shared/ExtensionMessage" +// kilocode_change start: Helper functions for AI Code Review using Git API +async function getGitChanges(cwd: string): Promise<{ files: any[]; gitDiff: string }> { + try { + const { exec } = await import("child_process") + const { promisify } = await import("util") + const execAsync = promisify(exec) + + // Check if git is available + try { + await execAsync("git --version") + } catch { + console.log("Git not found") + return { files: [], gitDiff: "" } + } + + // Check if inside a git repo + try { + await execAsync("git rev-parse --is-inside-work-tree", { cwd }) + } catch { + console.log("Not inside a git work tree") + return { files: [], gitDiff: "" } + } + + const filesMap = new Map< + string, + { relPath: string; absolutePath: string; status: string; stat: { additions: number; deletions: number } } + >() + + // 1. Get stats using git diff --numstat HEAD + // This covers staged and unstaged changes for tracked files + try { + const { stdout: numstatOut } = await execAsync("git diff --numstat HEAD", { cwd }) + if (numstatOut.trim()) { + const lines = numstatOut.trim().split("\n") + for (const line of lines) { + const parts = line.split(/\s+/) + // Format: additions deletions filename + // Renames might look different, but usually git diff HEAD shows the new name + if (parts.length >= 3) { + let additions = parseInt(parts[0]) + let deletions = parseInt(parts[1]) + // Handle binary files which output - - + if (isNaN(additions)) additions = 0 + if (isNaN(deletions)) deletions = 0 + + // The path is the last part, but might contain spaces if not handled, + // but numstat separates by tab usually. + // Actually numstat separates by tab. The split(/\s+/) might be risky if whitespace in filename. + // Safer to split by tab. + } + } + // Let's re-parse properly with tabs + const numstatLines = numstatOut.trim().split("\n") + for (const line of numstatLines) { + const [adds, dels, filePath] = line.split("\t") + if (filePath) { + let additions = parseInt(adds) + let deletions = parseInt(dels) + if (isNaN(additions)) additions = 0 + if (isNaN(deletions)) deletions = 0 + + filesMap.set(filePath, { + relPath: filePath, + absolutePath: path.join(cwd, filePath), + status: "M", // Default, will update with name-status + stat: { additions, deletions }, + }) + } + } + } + } catch (e) { + console.warn("Error getting numstat:", e) + } + + // 2. Get status using git diff --name-status HEAD + try { + const { stdout: nameStatusOut } = await execAsync("git diff --name-status HEAD", { cwd }) + if (nameStatusOut.trim()) { + const lines = nameStatusOut.trim().split("\n") + for (const line of lines) { + const [status, filePath] = line.split("\t") + if (filePath && filesMap.has(filePath)) { + const entry = filesMap.get(filePath)! + // Status is like 'M', 'A', 'D', 'R100' + entry.status = status.charAt(0).toUpperCase() + } + } + } + } catch (e) { + console.warn("Error getting name-status:", e) + } + + // 3. Handle Untracked files (newly created, not yet added) + // git diff HEAD does NOT show them. git ls-files --others --exclude-standard does. + try { + const { stdout: untrackedOut } = await execAsync("git ls-files --others --exclude-standard", { cwd }) + if (untrackedOut.trim()) { + const untrackedFiles = untrackedOut.trim().split("\n") + for (const filePath of untrackedFiles) { + if (!filePath) continue + + // For untracked files, we need to count lines manually or treat as all additions + // git diff --no-index /dev/null file could work, or just read file + let additions = 0 + try { + const absPath = path.join(cwd, filePath) + const content = await fs.readFile(absPath, "utf8") + additions = content.split("\n").filter((l) => l.trim().length > 0).length // Heuristic from before, or just line count + } catch (err) { + console.log("Failed to read untracked file:", filePath) + } + + filesMap.set(filePath, { + relPath: filePath, + absolutePath: path.join(cwd, filePath), + status: "A", // Untracked is effectively Added + stat: { additions, deletions: 0 }, + }) + } + } + } catch (e) { + console.warn("Error getting untracked files:", e) + } + + // 4. Get full unified diff + let gitDiff = "" + try { + // git diff HEAD covers staged and unstaged. + // Untracked files are not in git diff HEAD. We might want to append them? + const { stdout } = await execAsync("git diff HEAD", { cwd }) + gitDiff = stdout + + // If there are untracked files, we need to generate diffs for them too if we want them in the review + // Ideally we construct "fake" diffs for them + // The original implementation generated diffs for "A" status files from scratch + const untrackedFilter = Array.from(filesMap.values()).filter( + (f) => f.status === "A" && !gitDiff.includes(f.relPath), + ) + for (const file of untrackedFilter) { + try { + const content = await fs.readFile(file.absolutePath, "utf8") + const lineCount = content.split("\n").length + const newDiff = ` +--- /dev/null ++++ b/${file.relPath} +@@ -0,0 +1,${lineCount} @@ +${content + .split("\n") + .map((l) => "+" + l) + .join("\n")} +` + gitDiff += newDiff + } catch (e) { + // ignore + } + } + } catch (e) { + console.warn("Error getting git diff:", e) + } + + const files = Array.from(filesMap.values()) + + return { files, gitDiff } + } catch (error) { + console.warn("Failed to get git changes:", error) + return { files: [], gitDiff: "" } + } +} + +async function getGitMetadata( + cwd: string, +): Promise<{ gitOwner: string; gitRepo: string; gitBranch: string; gitUser: string }> { + try { + const { exec } = await import("child_process") + const { promisify } = await import("util") + const execAsync = promisify(exec) + + const runGit = async (command: string) => { + try { + const { stdout } = await execAsync(command, { cwd }) + return stdout.trim() + } catch (e) { + console.error(`[getGitMetadata] Command failed: "${command}" in cwd: "${cwd}"`, e) + return "" + } + } + + // Get current branch + const gitBranch = (await runGit("git rev-parse --abbrev-ref HEAD")) || "unknown" + + // Get remote URL to extract owner/repo + const remoteUrl = await runGit("git config --get remote.origin.url") + let gitOwner = "unknown" + let gitRepo = "unknown" + + if (remoteUrl) { + // Parse common Git URL formats + // Matches: https://github.com/owner/repo.git or git@github.com:owner/repo.git + // And also handles non-github URLs loosely if they follow standard format + const match = remoteUrl.match(/[:/](.+?)\/(.+?)(?:\.git)?$/) + if (match) { + // Clean up owner if it contains 'git@github.com' or 'https://github.com' leftovers from loose matching + // But let's stick to the previous robust regexes if possible, or a cleaner one. + + // Previous regexes: + const httpsMatch = remoteUrl.match(/github\.com[:/](.+?)\/(.+?)(?:\.git)?$/i) + const gitMatch = remoteUrl.match(/git@github\.com:(.+?)\/(.+?)\.git$/i) + + // Let's try to be generic for any host, but prioritizing the structure owner/repo + // If we just take the last two parts of the path? + + if (httpsMatch) { + gitOwner = httpsMatch[1] + gitRepo = httpsMatch[2] + } else if (gitMatch) { + gitOwner = gitMatch[1] + gitRepo = gitMatch[2] + } else { + // Fallback for other providers (gitlab, bitbucket, or custom) + // Try to extract last two path components + const parts = remoteUrl.split("/") + if (parts.length >= 2) { + gitRepo = parts[parts.length - 1].replace(/\.git$/, "") + gitOwner = parts[parts.length - 2] + // Clean owner if it has domain info (e.g. domain.com:owner) + if (gitOwner.includes(":")) { + gitOwner = gitOwner.split(":").pop()! + } + } + } + } + } + + // Get git user from repository config + const gitUser = (await runGit("git config user.name")) || "unknown" + + return { gitOwner, gitRepo, gitBranch, gitUser } + } catch (error) { + // Return defaults if git operations fail + return { + gitOwner: "unknown", + gitRepo: "unknown", + gitBranch: "unknown", + gitUser: "unknown", + } + } +} +// kilocode_change end + export const webviewMessageHandler = async ( provider: ClineProvider, message: MaybeTypedWebviewMessage, // kilocode_change switch to MaybeTypedWebviewMessage for better type-safety @@ -727,6 +976,129 @@ export const webviewMessageHandler = async ( } as any) break } + // kilocode_change start: Get Git changes for AI Code Review (separate from pending file edits) + case "getGitChangesForReview": { + try { + // Get git changes using VS Code Git API + const { files } = await getGitChanges(provider.cwd) + + await provider.postMessageToWebview({ + type: "gitChangesForReview", + payload: { + files, + }, + } as any) + } catch (error) { + provider.log( + `Failed to get git changes for review: ${error instanceof Error ? error.message : "Unknown error"}`, + ) + await provider.postMessageToWebview({ + type: "gitChangesForReview", + payload: { + files: [], + }, + } as any) + } + break + } + // kilocode_change end + // kilocode_change start: AI Code Review handlers using Git API + case "requestCodeReview": { + try { + // Get git changes using VS Code Git API + const { files, gitDiff } = await getGitChanges(provider.cwd) + + if (files.length === 0) { + await provider.postMessageToWebview({ + type: "codeReviewResults", + payload: { + reviewBody: "No uncommitted changes found to review.", + reviewComments: [], + }, + } as any) + break + } + + // Get git metadata + const gitMetadata = await getGitMetadata(provider.cwd) + + // Call the code review API + const { CodeReviewService } = await import("../kilocode/api/codeReviewService") + const state = await provider.getState() + const codeReviewService = new CodeReviewService(state.apiConfiguration?.kilocodeToken || "") + const results = await codeReviewService.requestCodeReview({ + git_diff: gitDiff, + git_owner: gitMetadata.gitOwner, + git_repo: gitMetadata.gitRepo, + git_branch: gitMetadata.gitBranch, + git_user: gitMetadata.gitUser, + }) + + await provider.postMessageToWebview({ + type: "codeReviewResults", + payload: results, + } as any) + } catch (error) { + provider.log(`Code review request failed: ${error instanceof Error ? error.message : "Unknown error"}`) + await provider.postMessageToWebview({ + type: "codeReviewResults", + payload: { + reviewBody: `Code review failed: ${error instanceof Error ? error.message : "Unknown error"}`, + reviewComments: [], + }, + } as any) + } + break + } + case "applyCodeReviewFix": { + const { comment } = (message as any).payload + if (!comment) { + break + } + + const prompt = `Please apply the following code review fix for ${comment.path}: + +Issue: ${comment.body} + +Suggestion: +\`\`\` +${comment.suggestion} +\`\`\` +` + try { + await provider.createTask(prompt, []) + await provider.postMessageToWebview({ type: "invoke", invoke: "newChat" }) + } catch (error) { + provider.log( + `Failed to create task for code review fix: ${error instanceof Error ? error.message : "Unknown error"}`, + ) + } + break + } + case "applyAllCodeReviewFixes": { + const { comments } = (message as any).payload + if (!comments || comments.length === 0) { + break + } + + let prompt = "Please apply the following code review fixes:\n\n" + comments.forEach((c: any, i: number) => { + prompt += `Fix ${i + 1} in ${c.path}:\n` + prompt += `Issue: ${c.body}\n` + prompt += `Suggestion:\n\`\`\`\n${c.suggestion}\n\`\`\`\n\n` + }) + + try { + await provider.createTask(prompt, []) + await provider.postMessageToWebview({ type: "invoke", invoke: "newChat" }) + } catch (error) { + provider.log( + `Failed to create task for all code review fixes: ${error instanceof Error ? error.message : "Unknown error"}`, + ) + } + break + } + // kilocode_change end case "alwaysAllowExecute": await updateGlobalState("alwaysAllowExecute", message.bool ?? undefined) await provider.postStateToWebview() diff --git a/src/package.json b/src/package.json index 12a1f8be5a..3d9cbbac59 100644 --- a/src/package.json +++ b/src/package.json @@ -16,6 +16,9 @@ "extensionKind": [ "workspace" ], + "extensionDependencies": [ + "vscode.git" + ], "author": { "name": "MatterAI" }, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index d5afdce3ea..8a6ff19c94 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -1,31 +1,31 @@ import type { + ClineMessage, + CloudOrganizationMembership, + CloudUserInfo, + Experiments, GlobalSettings, - ProviderSettingsEntry, - ProviderSettings, HistoryItem, - ModeConfig, - TelemetrySetting, - Experiments, - ClineMessage, MarketplaceItem, - TodoItem, - CloudUserInfo, - CloudOrganizationMembership, + ModeConfig, OrganizationAllowList, - ShareVisibility, + ProviderSettings, + ProviderSettingsEntry, QueuedMessage, + ShareVisibility, + TelemetrySetting, + TodoItem, } from "@roo-code/types" import { GitCommit } from "../utils/git" +import { ModelRecord, RouterModels } from "./api" +import { McpDownloadResponse, McpMarketplaceCatalog } from "./kilocode/mcp" import { McpServer } from "./mcp" -import { McpMarketplaceCatalog, McpDownloadResponse } from "./kilocode/mcp" import { Mode } from "./modes" -import { ModelRecord, RouterModels } from "./api" // kilocode_change start import { - ProfileDataResponsePayload, BalanceDataResponsePayload, + ProfileDataResponsePayload, TaskHistoryResponsePayload, TasksByIdResponsePayload, } from "./WebviewMessage" @@ -475,6 +475,7 @@ export interface ClineSayTool { | "imageGenerated" | "runSlashCommand" | "planFileEdit" // kilocode_change: Plan mode file editing + | "codeReview" // kilocode_change: AI Code Review path?: string diff?: string content?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 5390643f74..b85d2e176f 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -314,6 +314,12 @@ export interface WebviewMessage { | "commitChanges" | "getPendingFileEdits" | "pendingFileEdits" + | "requestCodeReview" + | "codeReviewResults" + | "applyCodeReviewFix" + | "applyAllCodeReviewFixes" + | "getGitChangesForReview" + | "gitChangesForReview" // kilocode_change end text?: string editedMessageContent?: string @@ -528,6 +534,29 @@ export interface PendingFileEditsPayload { // The response message type - list of all pending file edits files: { relPath: string; absolutePath: string; stat: { additions: number; deletions: number } }[] } + +export interface CodeReviewComment { + path: string + body: string + suggestion: string + startLine: number + endLine: number +} + +export interface CodeReviewResultsPayload { + reviewBody: string + reviewComments: CodeReviewComment[] +} + +export interface ApplyCodeReviewFixPayload { + fixIndex: number + comment: CodeReviewComment +} + +export interface ApplyAllCodeReviewFixesPayload { + fixIndices: number[] + comments: CodeReviewComment[] +} // kilocode_change end export type WebViewMessagePayload = @@ -541,6 +570,9 @@ export type WebViewMessagePayload = | CommitChangesPayload | PendingFileEditsPayload | ImplementPlanPayload + | CodeReviewResultsPayload + | ApplyCodeReviewFixPayload + | ApplyAllCodeReviewFixesPayload // kilocode_change end | CheckpointDiffPayload | CheckpointRestorePayload diff --git a/webview-ui/src/codicon-custom.css b/webview-ui/src/codicon-custom.css index 76abe70ce9..1da6cdd1d7 100644 --- a/webview-ui/src/codicon-custom.css +++ b/webview-ui/src/codicon-custom.css @@ -17,3 +17,13 @@ -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; } + +@keyframes codicon-spin { + 100% { + transform: rotate(360deg); + } +} + +.codicon-spin { + animation: codicon-spin 1.5s linear infinite; +} diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index be2611edb7..3d323c3796 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -21,6 +21,7 @@ import { } from "@src/utils/context-mentions" import { cn } from "@/lib/utils" +import { renderMentionChip } from "@/utils/chat-render" import { MessageSquareX, Paperclip, SendHorizontal, VolumeX } from "lucide-react" import Thumbnails from "../common/Thumbnails" import KiloModeSelector from "../kilocode/KiloModeSelector" @@ -31,7 +32,6 @@ import { ImageWarningBanner } from "./ImageWarningBanner" // kilocode_change import { IndexingStatusBadge } from "./IndexingStatusBadge" import { usePromptHistory } from "./hooks/usePromptHistory" import { AcceptRejectButtons } from "./kilocode/AcceptRejectButtons" -import { renderMentionChip } from "@/utils/chat-render" // kilocode_change start: pull slash commands from Cline import SlashCommandMenu from "@/components/chat/SlashCommandMenu" diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index d9f6e51dbd..8564e31045 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -14,10 +14,20 @@ import { useDebounceEffect } from "@src/utils/useDebounceEffect" import type { ClineAsk, ClineMessage, McpServerUse } from "@roo-code/types" import { FollowUpData, SuggestionItem } from "@roo-code/types" + +// kilocode_change start: Local type definitions for Source Control Panel +interface CodeReviewComment { + path: string + body: string + suggestion: string + startLine: number + endLine: number +} +// kilocode_change end import { findLast } from "@roo/array" import { combineApiRequests } from "@roo/combineApiRequests" import { combineCommandSequences } from "@roo/combineCommandSequences" -import { ClineApiReqInfo, ClineSayBrowserAction, ClineSayTool, ExtensionMessage } from "@roo/ExtensionMessage" +import { ClineApiReqInfo, ClineSayBrowserAction, ClineSayTool } from "@roo/ExtensionMessage" import { getApiMetrics } from "@roo/getApiMetrics" import { McpServer, McpTool } from "@roo/mcp" import { getAllModes } from "@roo/modes" @@ -58,9 +68,11 @@ import KiloTaskHeader from "../kilocode/KiloTaskHeader" // kilocode_change import AutoApproveMenu from "./AutoApproveMenu" import SystemPromptWarning from "./SystemPromptWarning" // import ProfileViolationWarning from "./ProfileViolationWarning" kilocode_change: unused +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import { KilocodeNotifications } from "../kilocode/KilocodeNotifications" // kilocode_change import { CheckpointWarning } from "./CheckpointWarning" import { QueuedMessages } from "./QueuedMessages" +import { SourceControlPanel } from "./SourceControlPanel" // kilocode_change // import DismissibleUpsell from "../common/DismissibleUpsell" // kilocode_change: unused // import { useCloudUpsell } from "@src/hooks/useCloudUpsell" // kilocode_change: unused // import { Cloud } from "lucide-react" // kilocode_change: unused @@ -149,7 +161,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + const _toggleExpanded = useCallback(() => { const newState = !isExpanded setIsExpanded(newState) // Send message to extension to persist the new collapsed state @@ -206,6 +218,22 @@ const ChatViewComponent: React.ForwardRefRenderFunction(false) const [isCondensing, setIsCondensing] = useState(false) const [showAnnouncementModal, setShowAnnouncementModal] = useState(false) + // kilocode_change start: AI Code Review state + const [showSourceControl, setShowSourceControl] = useState(false) + const [codeReviewResults, setCodeReviewResults] = useState<{ + reviewBody: string + reviewComments: CodeReviewComment[] + } | null>(null) + const [_pendingFileEdits, setPendingFileEdits] = useState([]) + const [_gitChangesForReview, setGitChangesForReview] = useState([]) // Git changes for code review + const [isCodeReviewLoading, setIsCodeReviewLoading] = useState(false) + const [_hasUnreviewedChanges, setHasUnreviewedChanges] = useState(false) + // Store code review results in memory for later access + const [_storedCodeReviewResults, setStoredCodeReviewResults] = useState<{ + reviewBody: string + reviewComments: CodeReviewComment[] + } | null>(null) + // kilocode_change end const everVisibleMessagesTsRef = useRef>( new LRUCache({ max: 100, @@ -828,9 +856,61 @@ const ChatViewComponent: React.ForwardRefRenderFunction= MAX_IMAGES_PER_MESSAGE + // kilocode_change start: AI Code Review handlers + const _handleRequestCodeReview = useCallback(() => { + setIsCodeReviewLoading(true) + vscode.postMessage({ type: "requestCodeReview" }) + }, []) + + const _handleRefreshPendingEdits = useCallback(() => { + vscode.postMessage({ type: "getPendingFileEdits" }) + }, []) + + const handleRunCodeReview = useCallback(() => { + setIsCodeReviewLoading(true) + vscode.postMessage({ type: "requestCodeReview" }) + }, []) + + const _handleApplyCodeReviewFix = useCallback( + (fixIndex: number) => { + if (!codeReviewResults || !codeReviewResults.reviewComments[fixIndex]) return + const comment = codeReviewResults.reviewComments[fixIndex] + vscode.postMessage({ + type: "applyCodeReviewFix", + payload: { fixIndex, comment }, + }) + // Refresh pending edits to get updated state after applying fix + setTimeout(() => { + vscode.postMessage({ type: "getPendingFileEdits" }) + }, 500) + }, + [codeReviewResults], + ) + + const _handleApplyAllCodeReviewFixes = useCallback(() => { + if (!codeReviewResults) return + vscode.postMessage({ + type: "applyAllCodeReviewFixes", + payload: { + fixIndices: codeReviewResults.reviewComments.map((_, i) => i), + comments: codeReviewResults.reviewComments, + }, + }) + setShowSourceControl(false) + setCodeReviewResults(null) + // Clear unreviewed changes flag since all fixes were applied + setHasUnreviewedChanges(false) + }, [codeReviewResults]) + + const _handleCloseSourceControl = useCallback(() => { + setShowSourceControl(false) + // Don't clear code review results - keep them in memory for later access + }, []) + // kilocode_change end + const handleMessage = useCallback( (e: MessageEvent) => { - const message: ExtensionMessage = e.data + const message = e.data as any // kilocode_change: Type assertion for new message types switch (message.type) { case "action": @@ -881,6 +961,34 @@ const ChatViewComponent: React.ForwardRefRenderFunction 0) + } + break + // kilocode_change end + case "workspaceUpdated": + // kilocode_change: Refresh git changes for review when workspace changes + vscode.postMessage({ type: "getGitChangesForReview" }) + break } // textAreaRef.current is not explicitly required here since React // guarantees that ref will be stable across re-renders, and we're @@ -902,6 +1010,38 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + // Check for git changes when component mounts + vscode.postMessage({ type: "getGitChangesForReview" }) + + // Set up periodic checking for changes (every 5 seconds) + const interval = setInterval(() => { + vscode.postMessage({ type: "getGitChangesForReview" }) + }, 5000) + + return () => clearInterval(interval) + }, []) + + // Listen for focus events to refresh git changes + useEffect(() => { + const handleFocus = () => { + vscode.postMessage({ type: "getGitChangesForReview" }) + } + + window.addEventListener("focus", handleFocus) + document.addEventListener("visibilitychange", () => { + if (!document.hidden) { + handleFocus() + } + }) + + return () => { + window.removeEventListener("focus", handleFocus) + } + }, []) + // kilocode_change end + // NOTE: the VSCode window needs to be focused for this to work. useMount(() => textAreaRef.current?.focus()) @@ -1998,19 +2138,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction ) : (
- {/* Moved Task Bar Header Here */} - {taskHistoryFullLength !== 0 && ( -
-
- {taskHistoryFullLength < 10 && ( - {t("history:recentTasks")} - )} - -
-
- )} {!showTelemetryBanner && (
@@ -2057,19 +2184,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction : }
kilocode_change: do not show */} {/* Show the task history preview if expanded and tasks exist */} - {taskHistoryFullLength > 0 && isExpanded && ( - - )} - {/* AI Code Reviews Setup Box */} -
+
{/* Top section: Title/Subtitle left, Icons right */}

- Setup Axon AI Code Reviews for PRs + Setup Automated PR Reviews

- - {/* kilocode_change start: KilocodeNotifications + Layout fixes */} + {taskHistoryFullLength > 0 && isExpanded && ( + + )}
{/* kilocode_change end */}
@@ -2172,62 +2296,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction
- {/* kilocode_change: added settings toggle for this */} {showAutoApproveMenu && }
- {/* {areButtonsVisible && ( -
- {showScrollToBottom ? ( - - { - scrollToBottomSmooth() - disableAutoScrollRef.current = false - }}> - - - - ) : ( - <> - {primaryButtonText && !isStreaming && ( - - handlePrimaryButtonClick(inputValue, selectedImages)}> - {primaryButtonText} - - - )} - - )} -
- )} */} )} @@ -2247,6 +2317,30 @@ const ChatViewComponent: React.ForwardRefRenderFunction + {!task && showSourceControl && ( +
+ setShowSourceControl(false)} + /> +
+ )} + + {!task && ( +
+ setShowSourceControl(true)} + disabled={isCodeReviewLoading}> + Run AI Code Review ({_gitChangesForReview.length}{" "} + {_gitChangesForReview.length === 1 ? "change" : "changes"}) + +
+ )} void +} + +export const CodeReviewPanel: React.FC = ({ reviewBody, reviewComments, onClose }) => { + const handleCommentClick = (comment: CodeReviewComment) => { + // Open file and highlight the code using path + line numbers + vscode.postMessage({ + type: "openFile", + text: comment.path, // Assuming openFile handler uses 'text' or 'value' for path, need to check. SourceControlPanel uses 'openFile' differently? + // Let's check SourceControlPanel handleFileClick + }) + } + + const handleApplyFix = (index: number) => { + if (!reviewComments[index]) return + const comment = reviewComments[index] + vscode.postMessage({ + type: "applyCodeReviewFix", + payload: { fixIndex: index, comment }, + }) + // Refresh pending edits + setTimeout(() => { + vscode.postMessage({ type: "getPendingFileEdits" }) + }, 500) + } + + const handleApplyAllFixes = () => { + vscode.postMessage({ + type: "applyAllCodeReviewFixes", + payload: { + fixIndices: reviewComments.map((_, i) => i), + comments: reviewComments, + }, + }) + onClose() + } + + return ( +
+
+

AI Code Review

+ + + +
+ +
+

Review Summary

+
{reviewBody}
+
+ + {reviewComments.length > 0 && ( +
+
+

+ Review Comments ({reviewComments.length}) +

+ + Fix All + +
+ +
+ {reviewComments.map((comment, index) => ( +
handleCommentClick(comment)}> +
+
+ {comment.path}:{comment.startLine}-{comment.endLine} +
+ { + e.stopPropagation() + handleApplyFix(index) + }}> + Fix + +
+
{comment.body}
+ {comment.suggestion && ( +
+ Suggestion: {comment.suggestion} +
+ )} +
+ ))} +
+
+ )} + + {reviewComments.length === 0 && ( +
+ No specific issues found. Great work! +
+ )} +
+ ) +} diff --git a/webview-ui/src/components/chat/SourceControlPanel.tsx b/webview-ui/src/components/chat/SourceControlPanel.tsx new file mode 100644 index 0000000000..925d66b1a1 --- /dev/null +++ b/webview-ui/src/components/chat/SourceControlPanel.tsx @@ -0,0 +1,347 @@ +import { vscode } from "@/utils/vscode" +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" +import React, { useEffect, useMemo, useState } from "react" +import { getIconForFilePath, getIconUrlByName } from "vscode-material-icons" + +interface FileChange { + relPath: string + absolutePath: string + stat?: { + additions: number + deletions: number + } + status?: string // 'M' for modified, 'A' for added, 'D' for deleted, etc. +} + +interface CodeReviewComment { + path: string + body: string + suggestion: string + startLine: number + endLine: number +} + +interface CodeReviewResult { + reviewBody: string + reviewComments: CodeReviewComment[] +} + +interface SourceControlPanelProps { + fileChanges: FileChange[] + codeReviewResult: CodeReviewResult | null + isLoading: boolean + onRunCodeReview: () => void + onClose: () => void +} + +// Get file name from path +const getFileName = (filePath: string): string => { + return filePath.split("/").pop() || filePath +} + +// Get directory from path +const getDirectory = (filePath: string): string => { + const parts = filePath.split("/") + if (parts.length <= 1) return "" + return parts.slice(0, -1).join("/") +} + +export const SourceControlPanel: React.FC = ({ + fileChanges, + codeReviewResult, + isLoading, + onRunCodeReview, + onClose, +}) => { + const [materialIconsBaseUri, setMaterialIconsBaseUri] = useState("") + const [isExpanded, setIsExpanded] = useState(true) + + // Get the icons base uri on mount + useEffect(() => { + const w = window as any + const baseUri = w.MATERIAL_ICONS_BASE_URI || w.vscode?.getState?.()?.MATERIAL_ICONS_BASE_URI || "" + setMaterialIconsBaseUri(baseUri) + }, []) + + const handleFileClick = (filePath: string) => { + vscode.postMessage({ + type: "openFile", + text: filePath, + }) + } + + const handleCommentClick = (comment: CodeReviewComment) => { + vscode.postMessage({ + type: "openFile", + text: comment.path, + }) + } + + // Get file icon URL using vscode-material-icons + const getFileIconUrl = (path: string): string => { + const filename = path.split("/").pop() || "" + if (filename.includes(".")) { + const iconName = getIconForFilePath(filename) + return getIconUrlByName(iconName, materialIconsBaseUri) + } + return "" + } + + // Calculate total stats + const totalStats = useMemo(() => { + return fileChanges.reduce( + (acc, file) => ({ + additions: acc.additions + (file.stat?.additions || 0), + deletions: acc.deletions + (file.stat?.deletions || 0), + }), + { additions: 0, deletions: 0 }, + ) + }, [fileChanges]) + + const handleApplyFix = (fixIndex: number) => { + if (!codeReviewResult || !codeReviewResult.reviewComments[fixIndex]) return + const comment = codeReviewResult.reviewComments[fixIndex] + vscode.postMessage({ + type: "applyCodeReviewFix", + payload: { fixIndex, comment }, + }) + // Refresh pending edits to get updated state after applying fix + setTimeout(() => { + vscode.postMessage({ type: "getPendingFileEdits" }) + }, 500) + } + + const handleApplyAllFixes = () => { + if (!codeReviewResult) return + vscode.postMessage({ + type: "applyAllCodeReviewFixes", + payload: { + fixIndices: codeReviewResult.reviewComments.map((_, i) => i), + comments: codeReviewResult.reviewComments, + }, + }) + // We can't close the panel directly from here as state is in parent, + // but typically "Apply All" implies we are done with this review session. + // For now, we just apply. + // If we want to close, we should call onClose(), but maybe user wants to see confirmation. + // Let's just apply for now. + onClose() + } + + return ( +
+ {/* Header */} +
+
+ + AI Code Review +
+
+ {fileChanges.length > 0 && !codeReviewResult && ( + + {isLoading ? ( +
+ + Analyzing... +
+ ) : ( +
+ + Run Review +
+ )} +
+ )} + + + +
+
+ + {/* File List - Expandable */} + {fileChanges.length > 0 && ( +
+ {/* Expand/Collapse Header */} +
setIsExpanded(!isExpanded)}> + + + Changes ({fileChanges.length}) + +
+ {totalStats.additions > 0 && ( + +{totalStats.additions} + )} + {totalStats.deletions > 0 && ( + -{totalStats.deletions} + )} +
+
+ + {/* File List */} + {isExpanded && ( +
+ {fileChanges.map((file, index) => { + const fileIconUrl = getFileIconUrl(file.relPath) + const fileName = getFileName(file.relPath) + const directory = getDirectory(file.relPath) + + return ( +
handleFileClick(file.absolutePath)} + title={file.absolutePath}> + {/* File Icon */} + {fileIconUrl ? ( + + ) : ( + + )} + + {/* Diff Stats */} +
+ + +{file.stat?.additions || 0} + + + -{file.stat?.deletions || 0} + +
+ + {/* File Name & Path */} +
+ + {fileName} + + {directory && ( + + {directory} + + )} +
+ + {/* Chevron on hover */} + +
+ ) + })} +
+ )} +
+ )} + + {/* Empty State */} + {fileChanges.length === 0 && !codeReviewResult && ( +
+ + All changes reviewed + No uncommitted changes to review +
+ )} + + {/* Code Review Results Section */} + {codeReviewResult && ( +
+ {/* Review Header */} +
+
+ + Review Results + {codeReviewResult.reviewComments?.length > 0 && ( + + {codeReviewResult.reviewComments.length} + + )} +
+ {codeReviewResult.reviewComments?.length > 0 && ( + + + Apply All + + )} +
+ + {/* Review Summary */} + {codeReviewResult.reviewBody && ( +
+ {codeReviewResult.reviewBody} +
+ )} + + {/* Review Comments */} + {codeReviewResult.reviewComments?.length > 0 ? ( +
+ {codeReviewResult.reviewComments.map((comment, index) => { + const commentFileIconUrl = getFileIconUrl(comment.path) + return ( +
+
+ + handleApplyFix(index)}> + + Apply + +
+
{comment.body}
+ {comment.suggestion && ( +
+
+ + Suggestion +
+ {comment.suggestion} +
+ )} +
+ ) + })} +
+ ) : ( +
+ + Looking good! + + No issues found in your changes + +
+ )} +
+ )} +
+ ) +} From 6f9281e1d161ea744b1b64a1803b0a19d2dc3673 Mon Sep 17 00:00:00 2001 From: code-crusher Date: Thu, 1 Jan 2026 14:21:47 +0530 Subject: [PATCH 2/8] update package version --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 3d9cbbac59..d2bab9eae9 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "matterai", - "version": "4.207.0", + "version": "4.210.0", "icon": "assets/icons/matterai-ic.png", "galleryBanner": { "color": "#FFFFFF", From ac8fb9a52823dd7fda384ea371587766507c4e6a Mon Sep 17 00:00:00 2001 From: code-crusher Date: Thu, 1 Jan 2026 16:03:51 +0530 Subject: [PATCH 3/8] increase code review timeout --- src/core/kilocode/api/codeReviewService.ts | 5 ++++- src/core/webview/webviewMessageHandler.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/kilocode/api/codeReviewService.ts b/src/core/kilocode/api/codeReviewService.ts index 286b477829..73df3c433c 100644 --- a/src/core/kilocode/api/codeReviewService.ts +++ b/src/core/kilocode/api/codeReviewService.ts @@ -23,7 +23,10 @@ export class CodeReviewService { const url = getKiloUrlFromToken("https://api.matterai.so/codereview", this.kilocodeToken) - const response = await axios.post(url, request, { headers }) + const response = await axios.post(url, request, { + headers, + timeout: 5 * 60 * 1000, // 5 minutes in milliseconds + }) const data = response.data if (data.codeChangeGeneration) { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 24ee8df278..b312522bc0 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1043,7 +1043,7 @@ export const webviewMessageHandler = async ( await provider.postMessageToWebview({ type: "codeReviewResults", payload: { - reviewBody: `Code review failed: ${error instanceof Error ? error.message : "Unknown error"}`, + reviewBody: `${error instanceof Error ? error.message : "Unknown error"}`, reviewComments: [], }, } as any) From 5629c60f59a64415764908c6cabeaa9a1970942a Mon Sep 17 00:00:00 2001 From: code-crusher Date: Thu, 1 Jan 2026 18:22:44 +0530 Subject: [PATCH 4/8] make ide PR reviews polling --- src/core/kilocode/api/codeReviewService.ts | 205 +++++++++++++++++++-- 1 file changed, 185 insertions(+), 20 deletions(-) diff --git a/src/core/kilocode/api/codeReviewService.ts b/src/core/kilocode/api/codeReviewService.ts index 73df3c433c..161a21aa03 100644 --- a/src/core/kilocode/api/codeReviewService.ts +++ b/src/core/kilocode/api/codeReviewService.ts @@ -1,4 +1,4 @@ -// kilocode_change - AI Code Review Service +// kilocode_change - AI Code Review Service (Async Pattern) import { getKiloUrlFromToken } from "@roo-code/types" import axios from "axios" import { CodeReviewResultsPayload } from "../../../shared/WebviewMessage" @@ -11,39 +11,204 @@ export interface CodeReviewRequest { git_user: string } +export interface CodeReviewStartResponse { + requestId: string + status: "pending" + message: string +} + +export interface CodeReviewStatusResponse { + status: "pending" | "processing" | "completed" | "failed" + result?: any + error?: string + createdAt: string + updatedAt: string +} + export class CodeReviewService { + private readonly POLLING_INTERVAL = 2000 // 2 seconds + private readonly MAX_POLLING_DURATION = 5 * 60 * 1000 // 5 minutes + private readonly REQUEST_TIMEOUT = 30 * 1000 // 30 seconds + constructor(private kilocodeToken: string) {} async requestCodeReview(request: CodeReviewRequest): Promise { + return this.requestCodeReviewWithRetry(request, 3) // Max 3 retries for initial request + } + + private async requestCodeReviewWithRetry( + request: CodeReviewRequest, + maxRetries: number, + ): Promise { + const headers: Record = { + Authorization: `Bearer ${this.kilocodeToken}`, + "Content-Type": "application/json", + } + try { - const headers: Record = { - Authorization: `Bearer ${this.kilocodeToken}`, - "Content-Type": "application/json", + // Step 1: Start the async code review + const startResponse = await this.startCodeReview(request, headers) + + // Step 2: Poll for results + const result = await this.pollForResults(startResponse.requestId, headers) + return result + } catch (error) { + // Check if this is a retryable error (timeout, network, 524, etc.) + const isRetryable = this.isRetryableError(error) + + if (isRetryable && maxRetries > 0) { + // Exponential backoff: 1s, 2s, 4s + const delay = Math.pow(2, 3 - maxRetries) * 1000 + + await new Promise((resolve) => setTimeout(resolve, delay)) + return this.requestCodeReviewWithRetry(request, maxRetries - 1) } - const url = getKiloUrlFromToken("https://api.matterai.so/codereview", this.kilocodeToken) + throw new Error(`Code review failed: ${this.getErrorMessage(error)}`) + } + } - const response = await axios.post(url, request, { - headers, - timeout: 5 * 60 * 1000, // 5 minutes in milliseconds - }) + private async startCodeReview( + request: CodeReviewRequest, + headers: Record, + ): Promise { + const url = getKiloUrlFromToken("https://api.matterai.so/codereview", this.kilocodeToken) + + const response = await axios.post(url, request, { + headers, + timeout: this.REQUEST_TIMEOUT, + }) + + return response.data + } + + private async pollForResults( + requestId: string, + headers: Record, + ): Promise { + const startTime = Date.now() + let attempt = 0 + + while (Date.now() - startTime < this.MAX_POLLING_DURATION) { + attempt++ + + try { + const statusResponse = await this.getCodeReviewStatus(requestId, headers) + + switch (statusResponse.status) { + case "pending": + break + + case "processing": + break + + case "completed": + return this.extractResults(statusResponse.result) + + case "failed": + throw new Error(`Code review failed: ${statusResponse.error || "Unknown error"}`) + + default: + throw new Error(`Unknown status: ${statusResponse.status}`) + } + + // Wait before next poll + await new Promise((resolve) => setTimeout(resolve, this.POLLING_INTERVAL)) + } catch (error) { + // If it's the final polling attempt, throw the error + if (Date.now() - startTime >= this.MAX_POLLING_DURATION) { + throw new Error( + `Code review polling timed out after ${this.MAX_POLLING_DURATION / 1000} seconds: ${this.getErrorMessage(error)}`, + ) + } - const data = response.data - if (data.codeChangeGeneration) { - return { - reviewBody: data.codeChangeGeneration.reviewBody, - reviewComments: data.codeChangeGeneration.reviewComments || [], + // For network errors during polling, wait a bit longer before retry + if (this.isRetryableError(error)) { + await new Promise((resolve) => setTimeout(resolve, 3000)) + } else { + // For non-retryable errors, don't retry + throw error } } + } + + throw new Error(`Code review polling timed out after ${this.MAX_POLLING_DURATION / 1000} seconds`) + } + + private async getCodeReviewStatus( + requestId: string, + headers: Record, + ): Promise { + const url = getKiloUrlFromToken(`https://api.matterai.so/codereview/${requestId}`, this.kilocodeToken) - // Fallback if structure matches CodeReviewResultsPayload directly (legacy or future proof) + const response = await axios.get(url, { + headers, + timeout: this.REQUEST_TIMEOUT, + }) + + return response.data + } + + private extractResults(result: any): CodeReviewResultsPayload { + // Handle the new async response format + if (result?.codeChangeGeneration) { return { - reviewBody: data.reviewBody || "", - reviewComments: data.reviewComments || [], + reviewBody: result.codeChangeGeneration.reviewBody, + reviewComments: result.codeChangeGeneration.reviewComments || [], } - } catch (error) { - console.error("Code review request failed:", error) - throw new Error(`Code review failed: ${error instanceof Error ? error.message : "Unknown error"}`) } + + // Fallback if structure matches CodeReviewResultsPayload directly + return { + reviewBody: result?.reviewBody || "", + reviewComments: result?.reviewComments || [], + } + } + + private isRetryableError(error: any): boolean { + // Check for timeout errors + if (axios.isAxiosError(error)) { + // ECONNABORTED = timeout + if (error.code === "ECONNABORTED") { + return true + } + + // 524 = Cloudflare timeout + if (error.response?.status === 524) { + return true + } + + // Network errors + if (error.code === "ENOTFOUND" || error.code === "ECONNREFUSED" || error.code === "ETIMEDOUT") { + return true + } + } + + // Check for timeout in error message + const errorMessage = error instanceof Error ? error.message : String(error) + if (errorMessage.includes("timeout") || errorMessage.includes("524")) { + return true + } + + return false + } + + private getErrorMessage(error: any): string { + if (axios.isAxiosError(error)) { + if (error.response?.status === 524) { + return "Cloudflare timeout (524) - backend took too long to respond" + } + if (error.code === "ECONNABORTED") { + return "Request timeout" + } + if (error.response?.status) { + return `HTTP ${error.response.status}: ${error.response.statusText || "Unknown error"}` + } + if (error.code) { + return `Network error: ${error.code}` + } + } + + return error instanceof Error ? error.message : String(error) } } From 284b7f384dfee35b70736aaeddd036b789899728 Mon Sep 17 00:00:00 2001 From: code-crusher Date: Fri, 2 Jan 2026 09:47:31 +0530 Subject: [PATCH 5/8] fix(searchFilesTool): normalize file patterns for ripgrep Fixes 90% miss rate by converting .ext to *.ext patterns - Automatically converts file patterns like .ts to *.ts for ripgrep's --glob - Preserves existing glob patterns like *.ts or **/*.ts - Defaults to * when no pattern provided Resolves issue where ripgrep's --glob parameter expects proper glob patterns but users were passing just file extensions --- src/services/ripgrep/index.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/services/ripgrep/index.ts b/src/services/ripgrep/index.ts index 7e0d3eb58c..cddeb2f0e0 100644 --- a/src/services/ripgrep/index.ts +++ b/src/services/ripgrep/index.ts @@ -150,7 +150,25 @@ export async function regexSearchFiles( throw new Error("Could not find ripgrep binary") } - const args = ["--json", "-e", regex, "--glob", filePattern || "*", "--context", "1", "--no-messages", directoryPath] + // Normalize file pattern to proper glob format + // Convert .ext to *.ext, but preserve existing globs like *.ts or **/*.ts + const normalizedFilePattern = filePattern + ? filePattern.startsWith(".") && !filePattern.includes("*") + ? `*${filePattern}` + : filePattern + : "*" + + const args = [ + "--json", + "-e", + regex, + "--glob", + normalizedFilePattern, + "--context", + "1", + "--no-messages", + directoryPath, + ] let output: string try { From 8656591be13a564935088ca1ac61e61e612d7bef Mon Sep 17 00:00:00 2001 From: code-crusher Date: Fri, 2 Jan 2026 09:54:10 +0530 Subject: [PATCH 6/8] ai code reviews functionality complete --- CHANGELOG.md | 18 ++++++++++++++++++ README.md | 38 ++++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a6da760e..20edd77dc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## [v4.210.0] - 2026-01-02 + +### Added + +#### AI Code Reviews + +- 1-click AI code reviews for all your agentic and manual changes +- Get all the review comments, suggestions and 1-click Apply all + +![Demo](https://github.com/MatterAIOrg/public-assets/blob/a5bf692ecb15f62a8148b5bf998917b6566991ea/axon-ide-code-reviews.gif) + +### Changed + +- Improved ripgrep with file extension normalising +- UI improvments + +--- + ## [v4.206.0] - 2025-12-29 ### Added diff --git a/README.md b/README.md index ce52baa0da..92e4787534 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,19 @@ Axon Code delivers frontier model performance directly into your development env ## ✨ Key Features -| Feature | Status | Description | -|---------|--------|-------------| -| **Agent Mode** | ✅ | Autonomous task execution with intelligent planning | -| **Plan Mode** | ✅ | Structured approach to complex coding challenges | -| **Agentic Tool Calling** | ✅ | Advanced tool integration for comprehensive workflows | -| **Advanced Reasoning** | ✅ | Deep code understanding and problem-solving | -| **Codebase Indexing** | ✅ | Intelligent code navigation and search | -| **Tab Auto-Complete** | 🔄 | Smart code completion (Coming Soon) | +| Feature | Status | Description | +| ------------------------ | ------ | ------------------------------------------------------ | +| **Agent Mode** | ✅ | Autonomous task execution with intelligent planning | +| **Plan Mode** | ✅ | Structured approach to complex coding challenges | +| \*\*AI Code Reviews | ✅ | Detailed code reviews with suggestions and 1-click fix | +| **Agentic Tool Calling** | ✅ | Advanced tool integration for comprehensive workflows | +| **Advanced Reasoning** | ✅ | Deep code understanding and problem-solving | +| **Codebase Indexing** | ✅ | Intelligent code navigation and search | +| **Tab Auto-Complete** | 🔄 | Smart code completion (Coming Soon) | + +## AI Code Reviews Demo + +![Demo](https://github.com/MatterAIOrg/public-assets/blob/a5bf692ecb15f62a8148b5bf998917b6566991ea/axon-ide-code-reviews.gif) ## 🛠️ Supported Platforms @@ -44,6 +49,7 @@ Axon Code delivers frontier model performance directly into your development env Axon Code provides a comprehensive suite of intelligent tools: ### Core Development Tools + - **`fileEdit`** - Advanced diff-patch application system - **`executeCommand`** - Secure CLI command execution - **`read_multi_file`** - Multi-file reading and analysis @@ -51,19 +57,23 @@ Axon Code provides a comprehensive suite of intelligent tools: - **`searchFiles`** - Advanced file search capabilities ### Web Integration + - **`web_search`** - Comprehensive web search - **`web_fetch`** - Targeted link data extraction ### Task Management + - **`newTask`** - Create and manage complex tasks - **`updateTodoList`** - Dynamic todo list management - **`runSlashCommand`** - Execute custom slash commands ### Code Intelligence + - **`listCodeDefinitionNames`** - Code structure analysis - **`listFiles`** - Intelligent file system navigation ### Communication + - **`askFollowupQuestion`** - Interactive user engagement - **`attemptCompletion`** - Task completion verification - **`fetchInstructions`** - Dynamic instruction retrieval @@ -72,21 +82,23 @@ Axon Code provides a comprehensive suite of intelligent tools: Choose the right model for your workflow: -| Model | Use Case | Capabilities | -|-------|----------|--------------| -| **`axon-code`** | Daily coding tasks | High intelligence, balanced performance | +| Model | Use Case | Capabilities | +| ------------------------- | --------------------- | -------------------------------------------- | +| **`axon-code`** | Daily coding tasks | High intelligence, balanced performance | | **`axon-code-2-preview`** | Complex agentic tasks | Maximum intelligence, long-running workflows | -| **`axon-mini`** | Quick tasks | Lightweight, low-effort operations | +| **`axon-mini`** | Quick tasks | Lightweight, low-effort operations | ## 📦 Installation ### VS Code Extension + 1. Open VS Code 2. Navigate to Extensions (`Ctrl+Shift+X`) 3. Search for "Axon Code" 4. Click Install ### JetBrains Plugin + 1. Open JetBrains IDE 2. Go to Settings/Preferences 3. Navigate to Plugins @@ -94,6 +106,7 @@ Choose the right model for your workflow: 5. Install and restart IDE ### CLI Installation + ```bash npm install -g @matterai/axon-code ``` @@ -121,6 +134,7 @@ Visit our [pricing page](https://www.matterai.so/pricing) for detailed plans and We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details. ### Development Setup + ```bash # Clone the repository git clone https://github.com/matterai/axon-code.git From 67701d042c8c6c15b33f8827e281a6122c3727e2 Mon Sep 17 00:00:00 2001 From: code-crusher Date: Fri, 2 Jan 2026 10:33:10 +0530 Subject: [PATCH 7/8] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92e4787534..b7e6b7019c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Axon Code delivers frontier model performance directly into your development environment, providing intelligent workflows, autonomous task execution, and deep codebase understanding. Built for enterprise-grade productivity with cutting-edge AI capabilities. -![Demo](https://res.cloudinary.com/dxvbskvxm/image/upload/v1763355416/axon-code_pe7tmc.gif) +![Demo](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExZHc0bjN0NndrNjliaHZpdnp6OHozeDBxZGthdmlhZ3JzNTZqd21nNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/07eUcY19bspsfJdbjI/giphy.gif) ## ✨ Key Features From 69366dfe3153e94aa35f086cdf713d236c037ebd Mon Sep 17 00:00:00 2001 From: Vatsal Bajpai Date: Fri, 2 Jan 2026 10:55:41 +0530 Subject: [PATCH 8/8] Update demo image link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b7e6b7019c..f4ca65fc9c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Axon Code delivers frontier model performance directly into your development environment, providing intelligent workflows, autonomous task execution, and deep codebase understanding. Built for enterprise-grade productivity with cutting-edge AI capabilities. -![Demo](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExZHc0bjN0NndrNjliaHZpdnp6OHozeDBxZGthdmlhZ3JzNTZqd21nNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/07eUcY19bspsfJdbjI/giphy.gif) +![Demo](https://github.com/MatterAIOrg/public-assets/blob/34ba86e008bae7a8b41283222947249edd822b93/axon-vscode-demo-fast.gif) ## ✨ Key Features