Skip to content

Commit 7fc44aa

Browse files
committed
Regex fixes
1 parent 84910b8 commit 7fc44aa

File tree

3 files changed

+53
-38
lines changed

3 files changed

+53
-38
lines changed

src/__tests__/command-mentions.spec.ts

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,20 @@ describe("Command Mentions", () => {
6161
expect(result).toContain("Please help me set up the project")
6262
})
6363

64-
it("should only handle command at start of message", async () => {
65-
mockGetCommand.mockResolvedValue({
64+
it("should handle only first command in message", async () => {
65+
mockGetCommand.mockResolvedValueOnce({
6666
name: "setup",
6767
content: "# Setup instructions",
6868
source: "project",
6969
filePath: "/project/.roo/commands/setup.md",
7070
})
7171

72-
// Only the first command should be recognized
72+
// Only first command should be recognized
7373
const input = "/setup the project\nThen /deploy later"
7474
const result = await callParseMentions(input)
7575

7676
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "setup")
77-
expect(mockGetCommand).toHaveBeenCalledTimes(1) // Only called once
77+
expect(mockGetCommand).toHaveBeenCalledTimes(1) // Only first command called
7878
expect(result).toContain('<command name="setup">')
7979
expect(result).toContain("# Setup instructions")
8080
expect(result).not.toContain('<command name="deploy">') // Second command not processed
@@ -88,7 +88,7 @@ describe("Command Mentions", () => {
8888

8989
expect(mockGetCommand).toHaveBeenCalledWith("/test/cwd", "nonexistent")
9090
expect(result).toContain('<command name="nonexistent">')
91-
expect(result).toContain("not found")
91+
expect(result).toContain("Command 'nonexistent' not found")
9292
expect(result).toContain("</command>")
9393
})
9494

@@ -172,8 +172,8 @@ npm install
172172
})
173173

174174
describe("command mention regex patterns", () => {
175-
it("should match valid command mention patterns at start of message", () => {
176-
const commandRegex = /^\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
175+
it("should match valid command mention patterns anywhere", () => {
176+
const commandRegex = /\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
177177

178178
const validPatterns = ["/setup", "/build-prod", "/test_suite", "/my-command", "/command123"]
179179

@@ -184,40 +184,44 @@ npm install
184184
})
185185
})
186186

187-
it("should not match command patterns in middle of text", () => {
188-
const commandRegex = /^\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
187+
it("should match command patterns in middle of text", () => {
188+
const commandRegex = /\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
189189

190-
const invalidPatterns = ["Please /setup", "Run /build now", "Use /deploy here"]
190+
const validPatterns = ["Please /setup", "Run /build now", "Use /deploy here"]
191191

192-
invalidPatterns.forEach((pattern) => {
192+
validPatterns.forEach((pattern) => {
193193
const match = pattern.match(commandRegex)
194-
expect(match).toBeFalsy()
194+
expect(match).toBeTruthy()
195+
expect(match![0]).toMatch(/^\/[a-zA-Z0-9_\.-]+$/)
195196
})
196197
})
197198

198-
it("should NOT match commands at start of new lines", () => {
199-
const commandRegex = /^\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
199+
it("should match commands at start of new lines", () => {
200+
const commandRegex = /\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
200201

201202
const multilineText = "First line\n/setup the project\nAnother line\n/deploy when ready"
202203
const matches = multilineText.match(commandRegex)
203204

204-
// Should not match any commands since they're not at the very start
205-
expect(matches).toBeFalsy()
205+
// Should match both commands now
206+
expect(matches).toBeTruthy()
207+
expect(matches).toHaveLength(2)
208+
expect(matches![0]).toBe("/setup")
209+
expect(matches![1]).toBe("/deploy")
206210
})
207211

208-
it("should only match command at very start of message", () => {
209-
const commandRegex = /^\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
212+
it("should match only first command in message", () => {
213+
const commandRegex = /^\/([a-zA-Z0-9_\.-]+)(?=\s|$)/m
210214

211-
const validText = "/setup the project\nThen do other things"
212-
const matches = validText.match(commandRegex)
215+
const validText = "/setup the project\nThen /deploy later"
216+
const match = validText.match(commandRegex)
213217

214-
expect(matches).toBeTruthy()
215-
expect(matches).toHaveLength(1)
216-
expect(matches![0]).toBe("/setup")
218+
expect(match).toBeTruthy()
219+
expect(match![0]).toBe("/setup")
220+
expect(match![1]).toBe("setup") // Captured group
217221
})
218222

219223
it("should not match invalid command patterns", () => {
220-
const commandRegex = /^\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
224+
const commandRegex = /\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
221225

222226
const invalidPatterns = ["/ space", "/with space", "/with/slash", "//double", "/with@symbol"]
223227

@@ -239,37 +243,48 @@ npm install
239243
expect(result).toContain("Command 'setup' (see below for command content)")
240244
})
241245

242-
it("should only process first command in message", async () => {
246+
it("should process only first command in message", async () => {
247+
mockGetCommand.mockResolvedValueOnce({
248+
name: "setup",
249+
content: "# Setup instructions",
250+
source: "project",
251+
filePath: "/project/.roo/commands/setup.md",
252+
})
253+
243254
const input = "/setup the project\nThen /deploy later"
244255
const result = await callParseMentions(input)
245256

246257
expect(result).toContain("Command 'setup' (see below for command content)")
247-
expect(result).not.toContain("Command 'deploy'") // Second command not processed
258+
expect(result).not.toContain("Command 'deploy' (see below for command content)")
248259
})
249260

250-
it("should only match commands at very start of message", async () => {
261+
it("should match commands at start of lines only", async () => {
262+
mockGetCommand.mockResolvedValue({
263+
name: "build",
264+
content: "# Build instructions",
265+
source: "project",
266+
filePath: "/project/.roo/commands/build.md",
267+
})
268+
251269
// At the beginning - should match
252270
let input = "/build the project"
253271
let result = await callParseMentions(input)
254272
expect(result).toContain("Command 'build'")
255273

256-
// In the middle - should NOT match
274+
// In the middle - should NOT match with new regex
257275
input = "Please /build and test"
258276
result = await callParseMentions(input)
259277
expect(result).not.toContain("Command 'build'")
260-
expect(result).toContain("Please /build and test") // Original text preserved
261278

262-
// At the end - should NOT match
279+
// At the end - should NOT match with new regex
263280
input = "Run the /build"
264281
result = await callParseMentions(input)
265282
expect(result).not.toContain("Command 'build'")
266-
expect(result).toContain("Run the /build") // Original text preserved
267283

268-
// At start of new line - should NOT match
284+
// At start of new line - should match
269285
input = "Some text\n/build the project"
270286
result = await callParseMentions(input)
271-
expect(result).not.toContain("Command 'build'")
272-
expect(result).toContain("Some text\n/build the project") // Original text preserved
287+
expect(result).toContain("Command 'build'")
273288
})
274289
})
275290
})

src/core/mentions/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from "path"
44
import * as vscode from "vscode"
55
import { isBinaryFile } from "isbinaryfile"
66

7-
import { mentionRegexGlobal, commandRegexGlobal, unescapeSpaces } from "../../shared/context-mentions"
7+
import { mentionRegexGlobal, commandRegex, unescapeSpaces } from "../../shared/context-mentions"
88

99
import { getCommitInfo, getWorkingState } from "../../utils/git"
1010
import { getWorkspacePath } from "../../utils/path"
@@ -89,7 +89,7 @@ export async function parseMentions(
8989
const commandMentions: Set<string> = new Set()
9090

9191
// First pass: extract command mentions (starting with /)
92-
let parsedText = text.replace(commandRegexGlobal, (match, commandName) => {
92+
let parsedText = text.replace(commandRegex, (match, commandName) => {
9393
commandMentions.add(commandName)
9494
return `Command '${commandName}' (see below for command content)`
9595
})

src/shared/context-mentions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ export const mentionRegex =
5757
/(?<!\\)@((?:\/|\w+:\/\/)(?:[^\s\\]|\\ )+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
5858
export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")
5959

60-
// Regex to match command mentions like /command-name (only at start of message)
61-
export const commandRegexGlobal = /\/([a-zA-Z0-9_\.-]+)(?=\s|$)/g
60+
// Regex to match command mentions like /command-name at start of lines
61+
export const commandRegex = /^\/([a-zA-Z0-9_\.-]+)(?=\s|$)/m
6262

6363
export interface MentionSuggestion {
6464
type: "file" | "folder" | "git" | "problems"

0 commit comments

Comments
 (0)