Skip to content

Commit 44a6cd7

Browse files
committed
feat: implement /init command for project documentation generation
- Add /init command handler in Task.ts that intercepts the command - Create ProjectScanner module to analyze project structure and technologies - Create DocumentationGenerator to generate comprehensive ROO.md file - Add unit tests for both ProjectScanner and DocumentationGenerator - Integrate with existing RooIgnore patterns for file filtering Closes #6153
1 parent 1e17b3b commit 44a6cd7

File tree

5 files changed

+1050
-0
lines changed

5 files changed

+1050
-0
lines changed
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
import { ProjectInfo, Technology, ConfigFile, DirectoryInfo } from "./ProjectScanner"
2+
3+
export class DocumentationGenerator {
4+
async generateDocumentation(projectInfo: ProjectInfo): Promise<string> {
5+
const sections: string[] = []
6+
7+
// Header
8+
sections.push(`# ${projectInfo.name}`)
9+
sections.push("")
10+
sections.push(projectInfo.description)
11+
sections.push("")
12+
13+
// Project Overview
14+
sections.push("## Project Overview")
15+
sections.push("")
16+
sections.push(
17+
`This document provides a comprehensive overview of the **${projectInfo.name}** project structure, technologies, and setup instructions. This file is automatically generated by Roo Code's \`/init\` command to help maintain consistent project documentation.`,
18+
)
19+
sections.push("")
20+
21+
// Table of Contents
22+
sections.push("## Table of Contents")
23+
sections.push("")
24+
sections.push("- [Technologies](#technologies)")
25+
sections.push("- [Project Structure](#project-structure)")
26+
sections.push("- [Configuration Files](#configuration-files)")
27+
sections.push("- [Dependencies](#dependencies)")
28+
sections.push("- [Available Scripts](#available-scripts)")
29+
sections.push("- [Development Setup](#development-setup)")
30+
sections.push("- [Architecture Patterns](#architecture-patterns)")
31+
if (projectInfo.gitInfo.hasGit) {
32+
sections.push("- [Git Information](#git-information)")
33+
}
34+
sections.push("")
35+
36+
// Technologies
37+
sections.push("## Technologies")
38+
sections.push("")
39+
sections.push("This project uses the following technologies:")
40+
sections.push("")
41+
42+
const techByType = this.groupTechnologiesByType(projectInfo.technologies)
43+
for (const [type, techs] of Object.entries(techByType)) {
44+
sections.push(`### ${this.formatTechType(type)}`)
45+
sections.push("")
46+
for (const tech of techs) {
47+
const version = tech.version ? ` (${tech.version})` : ""
48+
const config = tech.configFile ? ` - Config: \`${tech.configFile}\`` : ""
49+
sections.push(`- **${tech.name}**${version}${config}`)
50+
}
51+
sections.push("")
52+
}
53+
54+
// Project Structure
55+
sections.push("## Project Structure")
56+
sections.push("")
57+
sections.push("```")
58+
sections.push(this.generateTreeStructure(projectInfo.structure.directories))
59+
sections.push("```")
60+
sections.push("")
61+
62+
// File statistics
63+
sections.push("### File Statistics")
64+
sections.push("")
65+
sections.push(`- Total files: ${projectInfo.structure.fileCount}`)
66+
sections.push(`- File types:`)
67+
const sortedFileTypes = Object.entries(projectInfo.structure.fileTypes)
68+
.sort(([, a], [, b]) => b - a)
69+
.slice(0, 10)
70+
for (const [ext, count] of sortedFileTypes) {
71+
const extName = ext || "(no extension)"
72+
sections.push(` - ${extName}: ${count} files`)
73+
}
74+
sections.push("")
75+
76+
// Configuration Files
77+
sections.push("## Configuration Files")
78+
sections.push("")
79+
sections.push("The following configuration files are present in the project:")
80+
sections.push("")
81+
82+
const configByType = this.groupConfigsByType(projectInfo.configFiles)
83+
for (const [type, configs] of Object.entries(configByType)) {
84+
sections.push(`### ${this.formatConfigType(type)}`)
85+
sections.push("")
86+
for (const config of configs) {
87+
sections.push(`- \`${config.path}\``)
88+
}
89+
sections.push("")
90+
}
91+
92+
// Dependencies
93+
if (
94+
Object.keys(projectInfo.dependencies.production).length > 0 ||
95+
Object.keys(projectInfo.dependencies.development).length > 0
96+
) {
97+
sections.push("## Dependencies")
98+
sections.push("")
99+
100+
if (Object.keys(projectInfo.dependencies.production).length > 0) {
101+
sections.push("### Production Dependencies")
102+
sections.push("")
103+
sections.push("```json")
104+
sections.push(JSON.stringify(projectInfo.dependencies.production, null, 2))
105+
sections.push("```")
106+
sections.push("")
107+
}
108+
109+
if (Object.keys(projectInfo.dependencies.development).length > 0) {
110+
sections.push("### Development Dependencies")
111+
sections.push("")
112+
sections.push("```json")
113+
sections.push(JSON.stringify(projectInfo.dependencies.development, null, 2))
114+
sections.push("```")
115+
sections.push("")
116+
}
117+
}
118+
119+
// Available Scripts
120+
if (Object.keys(projectInfo.scripts).length > 0) {
121+
sections.push("## Available Scripts")
122+
sections.push("")
123+
sections.push("The following scripts are available:")
124+
sections.push("")
125+
126+
for (const [name, command] of Object.entries(projectInfo.scripts)) {
127+
if (command === "Makefile target") {
128+
sections.push(`- \`${name}\` - Makefile target`)
129+
} else {
130+
sections.push(`- \`npm run ${name}\` - ${command}`)
131+
}
132+
}
133+
sections.push("")
134+
}
135+
136+
// Development Setup
137+
sections.push("## Development Setup")
138+
sections.push("")
139+
sections.push("To set up this project for development:")
140+
sections.push("")
141+
142+
const setupSteps = this.generateSetupSteps(projectInfo)
143+
setupSteps.forEach((step, index) => {
144+
sections.push(`${index + 1}. ${step}`)
145+
})
146+
sections.push("")
147+
148+
// Architecture Patterns
149+
if (
150+
projectInfo.patterns.architecture ||
151+
projectInfo.patterns.testingFramework ||
152+
projectInfo.patterns.buildTool ||
153+
projectInfo.patterns.cicd
154+
) {
155+
sections.push("## Architecture Patterns")
156+
sections.push("")
157+
158+
if (projectInfo.patterns.architecture) {
159+
sections.push(`- **Architecture**: ${projectInfo.patterns.architecture}`)
160+
}
161+
if (projectInfo.patterns.testingFramework) {
162+
sections.push(`- **Testing Framework**: ${projectInfo.patterns.testingFramework}`)
163+
}
164+
if (projectInfo.patterns.buildTool) {
165+
sections.push(`- **Build Tool**: ${projectInfo.patterns.buildTool}`)
166+
}
167+
if (projectInfo.patterns.packageManager) {
168+
sections.push(`- **Package Manager**: ${projectInfo.patterns.packageManager}`)
169+
}
170+
if (projectInfo.patterns.cicd && projectInfo.patterns.cicd.length > 0) {
171+
sections.push(`- **CI/CD**: ${projectInfo.patterns.cicd.join(", ")}`)
172+
}
173+
sections.push("")
174+
}
175+
176+
// Git Information
177+
if (projectInfo.gitInfo.hasGit) {
178+
sections.push("## Git Information")
179+
sections.push("")
180+
if (projectInfo.gitInfo.branch) {
181+
sections.push(`- **Current Branch**: ${projectInfo.gitInfo.branch}`)
182+
}
183+
if (projectInfo.gitInfo.remote) {
184+
sections.push(`- **Remote Repository**: ${projectInfo.gitInfo.remote}`)
185+
}
186+
sections.push("")
187+
}
188+
189+
// Footer
190+
sections.push("---")
191+
sections.push("")
192+
sections.push(`*This documentation was automatically generated by Roo Code on ${new Date().toISOString()}*`)
193+
sections.push("")
194+
195+
return sections.join("\n")
196+
}
197+
198+
private groupTechnologiesByType(technologies: Technology[]): Record<string, Technology[]> {
199+
const grouped: Record<string, Technology[]> = {}
200+
for (const tech of technologies) {
201+
if (!grouped[tech.type]) {
202+
grouped[tech.type] = []
203+
}
204+
grouped[tech.type].push(tech)
205+
}
206+
return grouped
207+
}
208+
209+
private groupConfigsByType(configs: ConfigFile[]): Record<string, ConfigFile[]> {
210+
const grouped: Record<string, ConfigFile[]> = {}
211+
for (const config of configs) {
212+
if (!grouped[config.type]) {
213+
grouped[config.type] = []
214+
}
215+
grouped[config.type].push(config)
216+
}
217+
return grouped
218+
}
219+
220+
private formatTechType(type: string): string {
221+
const typeMap: Record<string, string> = {
222+
language: "Languages",
223+
framework: "Frameworks",
224+
tool: "Tools",
225+
database: "Databases",
226+
service: "Services",
227+
}
228+
return typeMap[type] || type
229+
}
230+
231+
private formatConfigType(type: string): string {
232+
const typeMap: Record<string, string> = {
233+
npm: "NPM Configuration",
234+
typescript: "TypeScript Configuration",
235+
eslint: "ESLint Configuration",
236+
prettier: "Prettier Configuration",
237+
webpack: "Webpack Configuration",
238+
vite: "Vite Configuration",
239+
jest: "Jest Configuration",
240+
vitest: "Vitest Configuration",
241+
git: "Git Configuration",
242+
docker: "Docker Configuration",
243+
environment: "Environment Configuration",
244+
python: "Python Configuration",
245+
go: "Go Configuration",
246+
rust: "Rust Configuration",
247+
}
248+
return typeMap[type] || type
249+
}
250+
251+
private generateTreeStructure(directories: DirectoryInfo[], prefix: string = ""): string {
252+
const lines: string[] = []
253+
254+
// Add root indicator
255+
if (prefix === "") {
256+
lines.push(".")
257+
}
258+
259+
// Sort directories by name
260+
const sorted = directories.sort((a, b) => a.name.localeCompare(b.name))
261+
262+
for (let i = 0; i < sorted.length; i++) {
263+
const dir = sorted[i]
264+
const isLast = i === sorted.length - 1
265+
const connector = isLast ? "└── " : "├── "
266+
const extension = isLast ? " " : "│ "
267+
268+
lines.push(`${prefix}${connector}${dir.name}/`)
269+
270+
// Add file count if directory has files
271+
if (dir.fileCount > 0) {
272+
lines.push(`${prefix}${extension} (${dir.fileCount} files)`)
273+
}
274+
}
275+
276+
return lines.join("\n")
277+
}
278+
279+
private generateSetupSteps(projectInfo: ProjectInfo): string[] {
280+
const steps: string[] = []
281+
282+
// Clone repository if git remote exists
283+
if (projectInfo.gitInfo.remote) {
284+
steps.push(`Clone the repository: \`git clone ${projectInfo.gitInfo.remote}\``)
285+
steps.push(`Navigate to the project directory: \`cd ${projectInfo.name}\``)
286+
}
287+
288+
// Install dependencies based on package manager
289+
if (projectInfo.patterns.packageManager) {
290+
const pm = projectInfo.patterns.packageManager
291+
if (pm === "npm") {
292+
steps.push("Install dependencies: `npm install`")
293+
} else if (pm === "yarn") {
294+
steps.push("Install dependencies: `yarn install`")
295+
} else if (pm === "pnpm") {
296+
steps.push("Install dependencies: `pnpm install`")
297+
}
298+
} else if (projectInfo.technologies.some((t) => t.name === "Node.js")) {
299+
steps.push("Install dependencies: `npm install`")
300+
}
301+
302+
// Python setup
303+
if (projectInfo.technologies.some((t) => t.name === "Python")) {
304+
steps.push("Create a virtual environment: `python -m venv venv`")
305+
steps.push("Activate the virtual environment:")
306+
steps.push(" - On Windows: `venv\\Scripts\\activate`")
307+
steps.push(" - On macOS/Linux: `source venv/bin/activate`")
308+
if (projectInfo.configFiles.some((c) => c.name === "requirements.txt")) {
309+
steps.push("Install Python dependencies: `pip install -r requirements.txt`")
310+
}
311+
}
312+
313+
// Go setup
314+
if (projectInfo.technologies.some((t) => t.name === "Go")) {
315+
steps.push("Download Go dependencies: `go mod download`")
316+
}
317+
318+
// Rust setup
319+
if (projectInfo.technologies.some((t) => t.name === "Rust")) {
320+
steps.push("Build the project: `cargo build`")
321+
}
322+
323+
// Environment setup
324+
if (projectInfo.configFiles.some((c) => c.name === ".env.example")) {
325+
steps.push("Copy the example environment file: `cp .env.example .env`")
326+
steps.push("Update the `.env` file with your local configuration")
327+
}
328+
329+
// Add common development scripts
330+
if (projectInfo.scripts["dev"]) {
331+
steps.push("Start the development server: `npm run dev`")
332+
} else if (projectInfo.scripts["start"]) {
333+
steps.push("Start the development server: `npm run start`")
334+
}
335+
336+
if (projectInfo.scripts["test"]) {
337+
steps.push("Run tests: `npm run test`")
338+
}
339+
340+
return steps
341+
}
342+
}

0 commit comments

Comments
 (0)