Skip to content

Commit e09afaa

Browse files
committed
initial working
1 parent d513b9c commit e09afaa

File tree

7 files changed

+458
-0
lines changed

7 files changed

+458
-0
lines changed

src/core/webview/webviewMessageHandler.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,40 @@ export const webviewMessageHandler = async (
18831883
})
18841884
}
18851885
break
1886+
case "generateRules":
1887+
// Generate rules for the current workspace
1888+
try {
1889+
const workspacePath = getWorkspacePath()
1890+
if (!workspacePath) {
1891+
await provider.postMessageToWebview({
1892+
type: "rulesGenerationStatus",
1893+
success: false,
1894+
error: "No workspace folder open",
1895+
})
1896+
break
1897+
}
1898+
1899+
// Import the rules generation service
1900+
const { generateRulesForWorkspace } = await import("../../services/rules/rulesGenerator")
1901+
1902+
// Generate the rules
1903+
const rulesPath = await generateRulesForWorkspace(workspacePath)
1904+
1905+
// Send success message back to webview
1906+
await provider.postMessageToWebview({
1907+
type: "rulesGenerationStatus",
1908+
success: true,
1909+
text: rulesPath,
1910+
})
1911+
} catch (error) {
1912+
// Send error message back to webview
1913+
await provider.postMessageToWebview({
1914+
type: "rulesGenerationStatus",
1915+
success: false,
1916+
error: error instanceof Error ? error.message : String(error),
1917+
})
1918+
}
1919+
break
18861920
case "humanRelayResponse":
18871921
if (message.requestId && message.text) {
18881922
vscode.commands.executeCommand(getCommand("handleHumanRelayResponse"), {
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import * as fs from "fs/promises"
2+
import * as path from "path"
3+
import * as vscode from "vscode"
4+
import { fileExistsAtPath } from "../../utils/fs"
5+
import { getProjectRooDirectoryForCwd } from "../roo-config/index"
6+
7+
interface ProjectConfig {
8+
type: "typescript" | "javascript" | "python" | "java" | "go" | "rust" | "unknown"
9+
hasTypeScript: boolean
10+
hasESLint: boolean
11+
hasPrettier: boolean
12+
hasJest: boolean
13+
hasVitest: boolean
14+
hasPytest: boolean
15+
packageManager: "npm" | "yarn" | "pnpm" | "bun" | null
16+
dependencies: string[]
17+
devDependencies: string[]
18+
scripts: Record<string, string>
19+
}
20+
21+
/**
22+
* Analyzes the project configuration files to determine project type and tools
23+
*/
24+
async function analyzeProjectConfig(workspacePath: string): Promise<ProjectConfig> {
25+
const config: ProjectConfig = {
26+
type: "unknown",
27+
hasTypeScript: false,
28+
hasESLint: false,
29+
hasPrettier: false,
30+
hasJest: false,
31+
hasVitest: false,
32+
hasPytest: false,
33+
packageManager: null,
34+
dependencies: [],
35+
devDependencies: [],
36+
scripts: {},
37+
}
38+
39+
// Check for package.json
40+
const packageJsonPath = path.join(workspacePath, "package.json")
41+
if (await fileExistsAtPath(packageJsonPath)) {
42+
try {
43+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"))
44+
45+
// Determine package manager
46+
if (await fileExistsAtPath(path.join(workspacePath, "yarn.lock"))) {
47+
config.packageManager = "yarn"
48+
} else if (await fileExistsAtPath(path.join(workspacePath, "pnpm-lock.yaml"))) {
49+
config.packageManager = "pnpm"
50+
} else if (await fileExistsAtPath(path.join(workspacePath, "bun.lockb"))) {
51+
config.packageManager = "bun"
52+
} else if (await fileExistsAtPath(path.join(workspacePath, "package-lock.json"))) {
53+
config.packageManager = "npm"
54+
}
55+
56+
// Extract dependencies
57+
config.dependencies = Object.keys(packageJson.dependencies || {})
58+
config.devDependencies = Object.keys(packageJson.devDependencies || {})
59+
config.scripts = packageJson.scripts || {}
60+
61+
// Check for specific tools
62+
const allDeps = [...config.dependencies, ...config.devDependencies]
63+
config.hasTypeScript =
64+
allDeps.includes("typescript") || (await fileExistsAtPath(path.join(workspacePath, "tsconfig.json")))
65+
config.hasESLint =
66+
allDeps.includes("eslint") ||
67+
(await fileExistsAtPath(path.join(workspacePath, ".eslintrc.js"))) ||
68+
(await fileExistsAtPath(path.join(workspacePath, ".eslintrc.json")))
69+
config.hasPrettier =
70+
allDeps.includes("prettier") || (await fileExistsAtPath(path.join(workspacePath, ".prettierrc")))
71+
config.hasJest = allDeps.includes("jest")
72+
config.hasVitest = allDeps.includes("vitest")
73+
74+
// Determine project type
75+
if (config.hasTypeScript) {
76+
config.type = "typescript"
77+
} else {
78+
config.type = "javascript"
79+
}
80+
} catch (error) {
81+
console.error("Error parsing package.json:", error)
82+
}
83+
}
84+
85+
// Check for Python project
86+
if (
87+
(await fileExistsAtPath(path.join(workspacePath, "pyproject.toml"))) ||
88+
(await fileExistsAtPath(path.join(workspacePath, "setup.py"))) ||
89+
(await fileExistsAtPath(path.join(workspacePath, "requirements.txt")))
90+
) {
91+
config.type = "python"
92+
config.hasPytest =
93+
(await fileExistsAtPath(path.join(workspacePath, "pytest.ini"))) ||
94+
(await fileExistsAtPath(path.join(workspacePath, "pyproject.toml")))
95+
}
96+
97+
// Check for other project types
98+
if (await fileExistsAtPath(path.join(workspacePath, "go.mod"))) {
99+
config.type = "go"
100+
} else if (await fileExistsAtPath(path.join(workspacePath, "Cargo.toml"))) {
101+
config.type = "rust"
102+
} else if (
103+
(await fileExistsAtPath(path.join(workspacePath, "pom.xml"))) ||
104+
(await fileExistsAtPath(path.join(workspacePath, "build.gradle")))
105+
) {
106+
config.type = "java"
107+
}
108+
109+
return config
110+
}
111+
112+
/**
113+
* Generates rules content based on project analysis
114+
*/
115+
function generateRulesContent(config: ProjectConfig, workspacePath: string): string {
116+
const sections: string[] = []
117+
118+
// Header
119+
sections.push("# Project Rules")
120+
sections.push("")
121+
sections.push(`Generated on: ${new Date().toISOString()}`)
122+
sections.push(`Project type: ${config.type}`)
123+
sections.push("")
124+
125+
// Build and Development
126+
sections.push("## Build and Development")
127+
sections.push("")
128+
129+
if (config.packageManager) {
130+
sections.push(`- Package manager: ${config.packageManager}`)
131+
sections.push(`- Install dependencies: \`${config.packageManager} install\``)
132+
133+
if (config.scripts.build) {
134+
sections.push(`- Build command: \`${config.packageManager} run build\``)
135+
}
136+
if (config.scripts.test) {
137+
sections.push(`- Test command: \`${config.packageManager} run test\``)
138+
}
139+
if (config.scripts.dev || config.scripts.start) {
140+
const devScript = config.scripts.dev || config.scripts.start
141+
sections.push(
142+
`- Development server: \`${config.packageManager} run ${config.scripts.dev ? "dev" : "start"}\``,
143+
)
144+
}
145+
}
146+
147+
sections.push("")
148+
149+
// Code Style and Linting
150+
sections.push("## Code Style and Linting")
151+
sections.push("")
152+
153+
if (config.hasESLint) {
154+
sections.push("- ESLint is configured for this project")
155+
sections.push("- Run linting: `npm run lint` (if configured)")
156+
sections.push("- Follow ESLint rules and fix any linting errors before committing")
157+
}
158+
159+
if (config.hasPrettier) {
160+
sections.push("- Prettier is configured for code formatting")
161+
sections.push("- Format code before committing")
162+
sections.push("- Run formatting: `npm run format` (if configured)")
163+
}
164+
165+
if (config.hasTypeScript) {
166+
sections.push("- TypeScript is used in this project")
167+
sections.push("- Ensure all TypeScript errors are resolved before committing")
168+
sections.push("- Use proper type annotations and avoid `any` types")
169+
sections.push("- Run type checking: `npm run type-check` or `tsc --noEmit`")
170+
}
171+
172+
sections.push("")
173+
174+
// Testing
175+
sections.push("## Testing")
176+
sections.push("")
177+
178+
if (config.hasJest || config.hasVitest) {
179+
const testFramework = config.hasVitest ? "Vitest" : "Jest"
180+
sections.push(`- ${testFramework} is used for testing`)
181+
sections.push("- Write tests for new features and bug fixes")
182+
sections.push("- Ensure all tests pass before committing")
183+
sections.push(`- Run tests: \`${config.packageManager || "npm"} run test\``)
184+
185+
if (config.hasVitest) {
186+
sections.push("- Vitest specific: Test files should use `.test.ts` or `.spec.ts` extensions")
187+
sections.push("- The `describe`, `test`, `it` functions are globally available")
188+
}
189+
}
190+
191+
if (config.hasPytest && config.type === "python") {
192+
sections.push("- Pytest is used for testing")
193+
sections.push("- Write tests in `test_*.py` or `*_test.py` files")
194+
sections.push("- Run tests: `pytest`")
195+
}
196+
197+
sections.push("")
198+
199+
// Project Structure
200+
sections.push("## Project Structure")
201+
sections.push("")
202+
sections.push("- Follow the existing project structure and naming conventions")
203+
sections.push("- Place new files in appropriate directories")
204+
sections.push("- Use consistent file naming (kebab-case, camelCase, or PascalCase as per project convention)")
205+
206+
sections.push("")
207+
208+
// Language-specific rules
209+
if (config.type === "typescript" || config.type === "javascript") {
210+
sections.push("## JavaScript/TypeScript Guidelines")
211+
sections.push("")
212+
sections.push("- Use ES6+ syntax (const/let, arrow functions, destructuring, etc.)")
213+
sections.push("- Prefer functional programming patterns where appropriate")
214+
sections.push("- Handle errors properly with try/catch blocks")
215+
sections.push("- Use async/await for asynchronous operations")
216+
sections.push("- Follow existing import/export patterns")
217+
sections.push("")
218+
}
219+
220+
if (config.type === "python") {
221+
sections.push("## Python Guidelines")
222+
sections.push("")
223+
sections.push("- Follow PEP 8 style guide")
224+
sections.push("- Use type hints where appropriate")
225+
sections.push("- Write docstrings for functions and classes")
226+
sections.push("- Use virtual environments for dependency management")
227+
sections.push("")
228+
}
229+
230+
// General Best Practices
231+
sections.push("## General Best Practices")
232+
sections.push("")
233+
sections.push("- Write clear, self-documenting code")
234+
sections.push("- Add comments for complex logic")
235+
sections.push("- Keep functions small and focused")
236+
sections.push("- Follow DRY (Don't Repeat Yourself) principle")
237+
sections.push("- Handle edge cases and errors gracefully")
238+
sections.push("- Write meaningful commit messages")
239+
sections.push("")
240+
241+
// Dependencies
242+
if (config.dependencies.length > 0 || config.devDependencies.length > 0) {
243+
sections.push("## Key Dependencies")
244+
sections.push("")
245+
246+
// List some key dependencies
247+
const keyDeps = [...config.dependencies, ...config.devDependencies]
248+
.filter((dep) => !dep.startsWith("@types/"))
249+
.slice(0, 10)
250+
251+
keyDeps.forEach((dep) => {
252+
sections.push(`- ${dep}`)
253+
})
254+
255+
sections.push("")
256+
}
257+
258+
return sections.join("\n")
259+
}
260+
261+
/**
262+
* Generates rules for the workspace and saves them to a file
263+
*/
264+
export async function generateRulesForWorkspace(workspacePath: string): Promise<string> {
265+
// Analyze the project
266+
const config = await analyzeProjectConfig(workspacePath)
267+
268+
// Generate rules content
269+
const rulesContent = generateRulesContent(config, workspacePath)
270+
271+
// Ensure .roo/rules directory exists
272+
const rooDir = getProjectRooDirectoryForCwd(workspacePath)
273+
const rulesDir = path.join(rooDir, "rules")
274+
await fs.mkdir(rulesDir, { recursive: true })
275+
276+
// Generate filename with timestamp
277+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, -5)
278+
const rulesFileName = `generated-rules-${timestamp}.md`
279+
const rulesPath = path.join(rulesDir, rulesFileName)
280+
281+
// Write rules file
282+
await fs.writeFile(rulesPath, rulesContent, "utf-8")
283+
284+
// Open the file in VSCode
285+
const doc = await vscode.workspace.openTextDocument(rulesPath)
286+
await vscode.window.showTextDocument(doc)
287+
288+
return rulesPath
289+
}

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export interface ExtensionMessage {
105105
| "shareTaskSuccess"
106106
| "codeIndexSettingsSaved"
107107
| "codeIndexSecretStatus"
108+
| "rulesGenerationStatus"
108109
text?: string
109110
payload?: any // Add a generic payload for now, can refine later
110111
action?:

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ export interface WebviewMessage {
194194
| "checkRulesDirectoryResult"
195195
| "saveCodeIndexSettingsAtomic"
196196
| "requestCodeIndexSecretStatus"
197+
| "generateRules"
197198
text?: string
198199
editedMessageContent?: string
199200
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account"

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { SetExperimentEnabled } from "./types"
1212
import { SectionHeader } from "./SectionHeader"
1313
import { Section } from "./Section"
1414
import { ExperimentalFeature } from "./ExperimentalFeature"
15+
import { RulesSettings } from "./RulesSettings"
1516

1617
type ExperimentalSettingsProps = HTMLAttributes<HTMLDivElement> & {
1718
experiments: Experiments
@@ -66,6 +67,8 @@ export const ExperimentalSettings = ({
6667
)
6768
})}
6869
</Section>
70+
71+
<RulesSettings className="mt-6" />
6972
</div>
7073
)
7174
}

0 commit comments

Comments
 (0)