From 50af05062385140d4f7e48cd609761f3b8b1207b Mon Sep 17 00:00:00 2001 From: kartik Date: Tue, 3 Mar 2026 23:11:30 +0530 Subject: [PATCH] fix: pre-load @file references from external editor prompts (#20963) Signed-off-by: Kartik Angiras --- .../cli/src/ui/utils/commandUtils.test.ts | 32 +++++++++++++++---- packages/cli/src/ui/utils/commandUtils.ts | 19 ++++++++--- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/ui/utils/commandUtils.test.ts b/packages/cli/src/ui/utils/commandUtils.test.ts index 737948ce98..346eef2fc2 100644 --- a/packages/cli/src/ui/utils/commandUtils.test.ts +++ b/packages/cli/src/ui/utils/commandUtils.test.ts @@ -163,7 +163,6 @@ describe('commandUtils', () => { it('should return true when query starts with @', () => { expect(isAtCommand('@file')).toBe(true); expect(isAtCommand('@path/to/file')).toBe(true); - expect(isAtCommand('@')).toBe(true); }); it('should return true when query contains @ preceded by whitespace', () => { @@ -172,17 +171,36 @@ describe('commandUtils', () => { expect(isAtCommand(' @file')).toBe(true); }); - it('should return false when query does not start with @ and has no spaced @', () => { + it('should return true when @ is preceded by non-whitespace (external editor scenario)', () => { + // When a user composes a prompt in an external editor, @-references may + // appear after punctuation characters such as ':' or '(' without a space. + // The processor must still recognise these as @-commands so that the + // referenced files are pre-loaded before the query is sent to the model. + expect(isAtCommand('check:@file.py')).toBe(true); + expect(isAtCommand('analyze(@file.py)')).toBe(true); + expect(isAtCommand('hello@file')).toBe(true); + expect(isAtCommand('text@path/to/file')).toBe(true); + expect(isAtCommand('user@host')).toBe(true); + }); + + it('should return false when query does not contain any @ pattern', () => { expect(isAtCommand('file')).toBe(false); expect(isAtCommand('hello')).toBe(false); expect(isAtCommand('')).toBe(false); - expect(isAtCommand('email@domain.com')).toBe(false); - expect(isAtCommand('user@host')).toBe(false); + // A bare '@' with no following path characters is not an @-command. + expect(isAtCommand('@')).toBe(false); }); - it('should return false when @ is not preceded by whitespace', () => { - expect(isAtCommand('hello@file')).toBe(false); - expect(isAtCommand('text@path')).toBe(false); + it('should return false when @ is escaped with a backslash', () => { + expect(isAtCommand('\\@file')).toBe(false); + }); + + it('should return true for multi-line external editor prompts with @-references', () => { + expect(isAtCommand('Please review:\n@src/main.py\nand fix bugs.')).toBe( + true, + ); + // @file after a colon on the same line. + expect(isAtCommand('Files:@src/a.py,@src/b.py')).toBe(true); }); }); diff --git a/packages/cli/src/ui/utils/commandUtils.ts b/packages/cli/src/ui/utils/commandUtils.ts index 0d52c83863..d6fdb99f0f 100644 --- a/packages/cli/src/ui/utils/commandUtils.ts +++ b/packages/cli/src/ui/utils/commandUtils.ts @@ -10,18 +10,29 @@ import type { SlashCommand } from '../commands/types.js'; import fs from 'node:fs'; import type { Writable } from 'node:stream'; import type { Settings } from '../../config/settingsSchema.js'; +import { AT_COMMAND_PATH_REGEX_SOURCE } from '../hooks/atCommandProcessor.js'; + +// Pre-compiled regex for detecting @ patterns consistent with parseAllAtCommands. +// Uses the same AT_COMMAND_PATH_REGEX_SOURCE so that isAtCommand is true whenever +// parseAllAtCommands would find at least one atPath part. +const AT_COMMAND_DETECT_REGEX = new RegExp( + `(?' pattern that would be + * recognised by the @ command processor, regardless of what character + * precedes the '@' sign. This ensures that prompts written in an external + * editor (where '@' may follow punctuation like ':' or '(') are correctly + * identified and their referenced files pre-loaded before the query is sent + * to the model. * * @param query The input query string. * @returns True if the query looks like an '@' command, false otherwise. */ export const isAtCommand = (query: string): boolean => - // Check if starts with @ OR has a space, then @ - query.startsWith('@') || /\s@/.test(query); + AT_COMMAND_DETECT_REGEX.test(query); /** * Checks if a query string potentially represents an '/' command.