Skip to content

Commit a7e4e5b

Browse files
committed
code review and small improvement
1 parent 4154599 commit a7e4e5b

File tree

6 files changed

+208
-64
lines changed

6 files changed

+208
-64
lines changed

src/core/prompts/instructions/__tests__/generate-rules.test.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,6 @@ describe("ruleTypeDefinitions", () => {
116116
expect(result).toContain("If found, incorporate and improve upon their content")
117117
})
118118

119-
it("should include instructions to open files after generation", () => {
120-
const ruleInstructions: RuleInstruction[] = [ruleTypeDefinitions.general]
121-
const options: RulesGenerationOptions = {
122-
selectedRuleTypes: ["general"],
123-
addToGitignore: false,
124-
alwaysAllowWriteProtected: false,
125-
includeCustomRules: false,
126-
customRulesText: "",
127-
}
128-
129-
const result = generateRulesInstructions(ruleInstructions, options)
130-
131-
expect(result).toContain("Open the generated files")
132-
expect(result).toContain("in the editor for review after creation")
133-
})
134-
135119
it("should include proper formatting instructions", () => {
136120
const ruleInstructions: RuleInstruction[] = [ruleTypeDefinitions.general]
137121
const options: RulesGenerationOptions = {

src/core/prompts/instructions/generate-rules.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,9 @@ ${ruleInstructions
4949
5050
5. **Keep rules concise** - aim for 20 lines per file, focusing on the most important guidelines
5151
52-
6. **Open the generated files** in the editor for review after creation
53-
5452
${
5553
addToGitignore
56-
? `7. **Add the generated files to .gitignore**:
54+
? `6. **Add the generated files to .gitignore**:
5755
- After generating all rule files, add entries to .gitignore to prevent them from being committed
5856
- Add each generated file path to .gitignore (e.g., .roo/rules/coding-standards.md)
5957
- If .gitignore doesn't exist, create it

src/core/webview/webviewMessageHandler.ts

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,51 +1864,23 @@ export const webviewMessageHandler = async (
18641864
case "generateRules":
18651865
// Generate rules for the current workspace by spawning a new task
18661866
try {
1867-
const workspacePath = getWorkspacePath()
1868-
if (!workspacePath) {
1869-
vscode.window.showErrorMessage("No workspace folder open. Please open a folder to generate rules.")
1870-
break
1871-
}
1872-
18731867
// Import the rules generation service
1874-
const { createRulesGenerationTaskMessage } = await import("../../services/rules/rulesGenerator")
1875-
1876-
// Get selected rule types and options from the message
1877-
const selectedRuleTypes = message.selectedRuleTypes || ["general"]
1878-
const addToGitignore = message.addToGitignore || false
1879-
const alwaysAllowWriteProtected = message.alwaysAllowWriteProtected || false
1880-
const apiConfigName = message.apiConfigName
1881-
const includeCustomRules = message.includeCustomRules || false
1882-
const customRulesText = message.customRulesText || ""
1883-
1884-
// Switch to the selected API config if provided
1885-
if (apiConfigName) {
1886-
const currentApiConfig = getGlobalState("currentApiConfigName")
1887-
if (apiConfigName !== currentApiConfig) {
1888-
await updateGlobalState("currentApiConfigName", apiConfigName)
1889-
await provider.activateProviderProfile({ name: apiConfigName })
1890-
}
1891-
}
1892-
1893-
// Create a comprehensive message for the rules generation task using existing analysis logic
1894-
const rulesGenerationMessage = await createRulesGenerationTaskMessage(
1895-
workspacePath,
1896-
selectedRuleTypes,
1897-
addToGitignore,
1898-
alwaysAllowWriteProtected,
1899-
includeCustomRules,
1900-
customRulesText,
1868+
const { handleGenerateRules } = await import("../../services/rules/rulesGenerator")
1869+
1870+
// Call the refactored function with all necessary parameters
1871+
await handleGenerateRules(
1872+
provider,
1873+
{
1874+
selectedRuleTypes: message.selectedRuleTypes,
1875+
addToGitignore: message.addToGitignore,
1876+
alwaysAllowWriteProtected: message.alwaysAllowWriteProtected,
1877+
apiConfigName: message.apiConfigName,
1878+
includeCustomRules: message.includeCustomRules,
1879+
customRulesText: message.customRulesText,
1880+
},
1881+
getGlobalState,
1882+
updateGlobalState,
19011883
)
1902-
1903-
// Spawn a new task in code mode to generate the rules
1904-
await provider.initClineWithTask(rulesGenerationMessage)
1905-
1906-
// Automatically navigate to the chat tab to show the new task
1907-
await provider.postMessageToWebview({
1908-
type: "action",
1909-
action: "switchTab",
1910-
tab: "chat",
1911-
})
19121884
} catch (error) {
19131885
// Show error message to user
19141886
const errorMessage = error instanceof Error ? error.message : String(error)

src/services/rules/__tests__/rulesGenerator.test.ts

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
22
import * as fs from "fs/promises"
33
import * as path from "path"
4-
import { createRulesGenerationTaskMessage } from "../rulesGenerator"
4+
import * as vscode from "vscode"
5+
import { createRulesGenerationTaskMessage, handleGenerateRules } from "../rulesGenerator"
6+
import { ClineProvider } from "../../../core/webview/ClineProvider"
57

68
// Mock fs module
79
vi.mock("fs/promises", () => ({
810
mkdir: vi.fn(),
911
}))
1012

13+
// Mock vscode module
14+
vi.mock("vscode", () => ({
15+
window: {
16+
showErrorMessage: vi.fn(),
17+
},
18+
}))
19+
20+
// Mock getWorkspacePath
21+
vi.mock("../../../utils/path", () => ({
22+
getWorkspacePath: vi.fn(),
23+
}))
24+
1125
describe("rulesGenerator", () => {
1226
const mockWorkspacePath = "/test/workspace"
1327

@@ -178,4 +192,110 @@ describe("rulesGenerator", () => {
178192
expect(message).toContain(".roo/rules-docs-extractor/documentation-rules.md")
179193
})
180194
})
195+
196+
describe("handleGenerateRules", () => {
197+
let mockProvider: ClineProvider
198+
let mockGetGlobalState: any
199+
let mockUpdateGlobalState: any
200+
let mockGetWorkspacePath: any
201+
202+
beforeEach(async () => {
203+
// Mock provider
204+
mockProvider = {
205+
activateProviderProfile: vi.fn(),
206+
initClineWithTask: vi.fn(),
207+
postMessageToWebview: vi.fn(),
208+
} as any
209+
210+
// Mock global state functions
211+
mockGetGlobalState = vi.fn()
212+
mockUpdateGlobalState = vi.fn()
213+
214+
// Import and mock getWorkspacePath
215+
const pathModule = await import("../../../utils/path")
216+
mockGetWorkspacePath = vi.spyOn(pathModule, "getWorkspacePath")
217+
mockGetWorkspacePath.mockReturnValue(mockWorkspacePath)
218+
})
219+
220+
it("should show error when no workspace is open", async () => {
221+
mockGetWorkspacePath.mockReturnValue(undefined)
222+
223+
await handleGenerateRules(mockProvider, {}, mockGetGlobalState, mockUpdateGlobalState)
224+
225+
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith(
226+
"No workspace folder open. Please open a folder to generate rules.",
227+
)
228+
expect(mockProvider.initClineWithTask).not.toHaveBeenCalled()
229+
})
230+
231+
it("should switch API config when different from current", async () => {
232+
mockGetGlobalState.mockReturnValue("current-config")
233+
234+
await handleGenerateRules(
235+
mockProvider,
236+
{ apiConfigName: "new-config" },
237+
mockGetGlobalState,
238+
mockUpdateGlobalState,
239+
)
240+
241+
expect(mockUpdateGlobalState).toHaveBeenCalledWith("currentApiConfigName", "new-config")
242+
expect(mockProvider.activateProviderProfile).toHaveBeenCalledWith({ name: "new-config" })
243+
})
244+
245+
it("should not switch API config when same as current", async () => {
246+
mockGetGlobalState.mockReturnValue("current-config")
247+
248+
await handleGenerateRules(
249+
mockProvider,
250+
{ apiConfigName: "current-config" },
251+
mockGetGlobalState,
252+
mockUpdateGlobalState,
253+
)
254+
255+
expect(mockUpdateGlobalState).not.toHaveBeenCalled()
256+
expect(mockProvider.activateProviderProfile).not.toHaveBeenCalled()
257+
})
258+
259+
it("should create task and switch to chat tab", async () => {
260+
await handleGenerateRules(
261+
mockProvider,
262+
{ selectedRuleTypes: ["general"] },
263+
mockGetGlobalState,
264+
mockUpdateGlobalState,
265+
)
266+
267+
expect(mockProvider.initClineWithTask).toHaveBeenCalled()
268+
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
269+
type: "action",
270+
action: "switchTab",
271+
tab: "chat",
272+
})
273+
})
274+
275+
it("should pass all options to createRulesGenerationTaskMessage", async () => {
276+
const options = {
277+
selectedRuleTypes: ["general", "code"],
278+
addToGitignore: true,
279+
alwaysAllowWriteProtected: true,
280+
includeCustomRules: true,
281+
customRulesText: "Custom rules text",
282+
}
283+
284+
await handleGenerateRules(mockProvider, options, mockGetGlobalState, mockUpdateGlobalState)
285+
286+
// Verify the task was created with the correct message
287+
expect(mockProvider.initClineWithTask).toHaveBeenCalled()
288+
const taskMessage = vi.mocked(mockProvider.initClineWithTask).mock.calls[0][0]
289+
expect(taskMessage).toContain("Custom rules text")
290+
})
291+
292+
it("should use default values when options are not provided", async () => {
293+
await handleGenerateRules(mockProvider, {}, mockGetGlobalState, mockUpdateGlobalState)
294+
295+
expect(mockProvider.initClineWithTask).toHaveBeenCalled()
296+
// The default selectedRuleTypes should be ["general"]
297+
const taskMessage = vi.mocked(mockProvider.initClineWithTask).mock.calls[0][0]
298+
expect(taskMessage).toContain(".roo/rules/coding-standards.md")
299+
})
300+
})
181301
})

src/services/rules/rulesGenerator.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import * as fs from "fs/promises"
22
import * as path from "path"
3+
import * as vscode from "vscode"
4+
import { GlobalState } from "@roo-code/types"
35
import {
46
generateRulesInstructions,
57
ruleTypeDefinitions,
68
RulesGenerationOptions,
79
RuleInstruction,
810
} from "../../core/prompts/instructions/generate-rules"
11+
import { getWorkspacePath } from "../../utils/path"
12+
import { ClineProvider } from "../../core/webview/ClineProvider"
913

1014
/**
1115
* Creates a comprehensive task message for rules generation that can be used with initClineWithTask
@@ -55,3 +59,69 @@ export async function createRulesGenerationTaskMessage(
5559

5660
return generateRulesInstructions(ruleInstructions, options)
5761
}
62+
63+
/**
64+
* Options for generating rules
65+
*/
66+
export interface GenerateRulesOptions {
67+
selectedRuleTypes?: string[]
68+
addToGitignore?: boolean
69+
alwaysAllowWriteProtected?: boolean
70+
apiConfigName?: string
71+
includeCustomRules?: boolean
72+
customRulesText?: string
73+
}
74+
75+
/**
76+
* Handles the complete rules generation process including API config switching,
77+
* task creation, and UI navigation
78+
*/
79+
export async function handleGenerateRules(
80+
provider: ClineProvider,
81+
options: GenerateRulesOptions,
82+
getGlobalState: <K extends keyof GlobalState>(key: K) => GlobalState[K],
83+
updateGlobalState: <K extends keyof GlobalState>(key: K, value: GlobalState[K]) => Promise<void>,
84+
): Promise<void> {
85+
const workspacePath = getWorkspacePath()
86+
if (!workspacePath) {
87+
vscode.window.showErrorMessage("No workspace folder open. Please open a folder to generate rules.")
88+
return
89+
}
90+
91+
// Extract options with defaults
92+
const selectedRuleTypes = options.selectedRuleTypes || ["general"]
93+
const addToGitignore = options.addToGitignore || false
94+
const alwaysAllowWriteProtected = options.alwaysAllowWriteProtected || false
95+
const apiConfigName = options.apiConfigName
96+
const includeCustomRules = options.includeCustomRules || false
97+
const customRulesText = options.customRulesText || ""
98+
99+
// Switch to the selected API config if provided
100+
if (apiConfigName) {
101+
const currentApiConfig = getGlobalState("currentApiConfigName")
102+
if (apiConfigName !== currentApiConfig) {
103+
await updateGlobalState("currentApiConfigName", apiConfigName)
104+
await provider.activateProviderProfile({ name: apiConfigName })
105+
}
106+
}
107+
108+
// Create a comprehensive message for the rules generation task
109+
const rulesGenerationMessage = await createRulesGenerationTaskMessage(
110+
workspacePath,
111+
selectedRuleTypes,
112+
addToGitignore,
113+
alwaysAllowWriteProtected,
114+
includeCustomRules,
115+
customRulesText,
116+
)
117+
118+
// Spawn a new task in code mode to generate the rules
119+
await provider.initClineWithTask(rulesGenerationMessage)
120+
121+
// Automatically navigate to the chat tab to show the new task
122+
await provider.postMessageToWebview({
123+
type: "action",
124+
action: "switchTab",
125+
tab: "chat",
126+
})
127+
}

webview-ui/src/components/settings/ExperimentalSettings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const ExperimentalSettings = ({
7070
})}
7171
</Section>
7272

73-
<RulesSettings className="mt-6" hasUnsavedChanges={hasUnsavedChanges} />
73+
<RulesSettings className="mt-3" hasUnsavedChanges={hasUnsavedChanges} />
7474
</div>
7575
)
7676
}

0 commit comments

Comments
 (0)