diff --git a/docusaurus/.clever.json b/docusaurus/.clever.json new file mode 100644 index 00000000..31c17f0d --- /dev/null +++ b/docusaurus/.clever.json @@ -0,0 +1,12 @@ +{ + "apps": [ + { + "app_id": "app_d274bfb2-942a-4d86-880d-17d429f29d48", + "org_id": "user_7afad073-5123-48a7-be84-bfec70acf921", + "deploy_url": "https://push-n3-par-clevercloud-customers.services.clever-cloud.com/app_d274bfb2-942a-4d86-880d-17d429f29d48.git", + "git_ssh_url": "git+ssh://git@push-n3-par-clevercloud-customers.services.clever-cloud.com/app_d274bfb2-942a-4d86-880d-17d429f29d48.git", + "name": "DevoxxGenieDoc", + "alias": "DevoxxGenieDoc" + } + ] +} \ No newline at end of file diff --git a/docusaurus/docs/features/rag.md b/docusaurus/docs/features/rag.md new file mode 100644 index 00000000..bf0aaffa --- /dev/null +++ b/docusaurus/docs/features/rag.md @@ -0,0 +1,213 @@ +--- +sidebar_position: 3 +--- + +# RAG Support + +Retrieval-Augmented Generation (RAG) is one of DevoxxGenie's most powerful features, enhancing the LLM's ability to understand and interact with your codebase by automatically finding and incorporating relevant code from your project. + +## What is RAG? + +Retrieval-Augmented Generation combines retrieval of information with text generation. In the context of DevoxxGenie: + +1. **Retrieval**: When you ask a question, DevoxxGenie searches your codebase to find the most relevant files and code snippets +2. **Augmentation**: These relevant code snippets are added to the prompt context +3. **Generation**: The LLM then generates a response informed by this contextual information + +This approach dramatically improves the quality of responses about your specific codebase, enabling the LLM to provide more accurate code suggestions, explanations, and fixes. + +## Benefits of RAG in DevoxxGenie + +- **Better code understanding**: The LLM has access to your specific implementation details +- **Project-aware responses**: Recommendations align with your existing coding style and patterns +- **Contextual debugging**: The LLM can refer to relevant parts of your codebase when helping debug issues +- **More accurate code generation**: Generated code is compatible with your existing architecture +- **Reduced hallucinations**: The LLM has real data to reference instead of guessing about your implementation + +## Using RAG in DevoxxGenie + +### Enabling RAG + +RAG is enabled by default in DevoxxGenie. To configure or adjust RAG settings: + +1. Click the settings (gear) icon in the DevoxxGenie window +2. Navigate to the "RAG Settings" section +3. Adjust parameters as needed (see Configuration Options below) + +### In Your Workflow + +When RAG is enabled, you can: + +1. Ask questions directly about your code without explicitly providing context +2. Request explanations of specific components or functionality +3. Ask for code improvements while leveraging existing patterns + +For example, queries like these will automatically trigger RAG retrieval: + +- "How does the authentication flow work in this project?" +- "Explain the data model for users" +- "Generate a new service method that follows our existing patterns" + +### Configuration Options + +DevoxxGenie offers several configuration options for RAG: + +- **Maximum documents**: Control how many relevant files are included in the context +- **Chunk size**: Adjust how code is broken down for retrieval (smaller chunks for more precise matching) +- **Search strategy**: Choose between different search algorithms +- **Similarity threshold**: Set the minimum relevance score for including a document +- **File type filters**: Include or exclude specific file types from the RAG index + +![RAG Settings](/img/rag-settings.png) + +## Advanced RAG Features + +### Retrieval Methods + +DevoxxGenie implements several retrieval methods: + +- **Semantic search**: Find contextually similar code using vector embeddings +- **Keyword search**: Match specific terms and patterns +- **Hybrid search**: Combine both approaches for better results + +### Custom Index Management + +For larger projects, DevoxxGenie allows you to: + +- Manually build and update the RAG index +- Select specific packages or directories to include +- Exclude specific files or patterns (e.g., generated code, tests) +- Save and load index configurations + +## Examples + +### Example 1: Understanding Existing Code + +**User Query**: +``` +How does our application handle database connections? +``` + +**DevoxxGenie Response**: +``` +Based on the code in your project, database connections are managed through a connection pool implemented in `DatabaseConnectionManager.java`. + +The key components are: + +1. Connection pooling using HikariCP: + ```java + public class DatabaseConnectionManager { + private static HikariDataSource dataSource; + + static { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(DatabaseProperties.getUrl()); + config.setUsername(DatabaseProperties.getUsername()); + config.setPassword(DatabaseProperties.getPassword()); + config.setMaximumPoolSize(10); + dataSource = new HikariDataSource(config); + } + + public static Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + // Other methods... + } + ``` + +2. Connection usage pattern in repositories: + ```java + public List findAllUsers() { + try (Connection conn = DatabaseConnectionManager.getConnection(); + PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users")) { + ResultSet rs = stmt.executeQuery()) { + // Process results... + } + } + ``` + +The application follows a proper resource management pattern with try-with-resources to ensure connections are always returned to the pool. +``` + +### Example 2: Generating Compatible Code + +**User Query**: +``` +Generate a new repository method to find users by email domain +``` + +**DevoxxGenie Response** (after analyzing your codebase): +``` +Based on your existing code patterns, here's a method to find users by email domain: + +```java +/** + * Finds all users with email addresses from a specific domain + * + * @param domain The email domain to search for (e.g., "example.com") + * @return List of User objects matching the domain + * @throws RepositoryException if a database error occurs + */ +public List findUsersByEmailDomain(String domain) throws RepositoryException { + List users = new ArrayList<>(); + + try (Connection conn = DatabaseConnectionManager.getConnection(); + PreparedStatement stmt = conn.prepareStatement( + "SELECT * FROM users WHERE email LIKE ?")) { + + stmt.setString(1, "%" + domain); + + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + User user = new User(); + user.setId(rs.getLong("id")); + user.setUsername(rs.getString("username")); + user.setEmail(rs.getString("email")); + user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime()); + users.add(user); + } + } + + return users; + } catch (SQLException e) { + logger.error("Error finding users by email domain: " + domain, e); + throw new RepositoryException("Failed to find users by email domain", e); + } +} +``` + +This follows your existing pattern of: +1. Using try-with-resources for connection management +2. Following your error handling approach with RepositoryException +3. Using the User mapping pattern consistent with other repository methods +``` + +## Troubleshooting + +### Common Issues + +**RAG not returning relevant context:** +- Ensure your search query is specific enough +- Try rebuilding the RAG index +- Check file type filters to ensure relevant files are included + +**Performance issues:** +- Reduce the maximum number of documents +- Increase chunk size to reduce the number of chunks +- Exclude large generated files from indexing + +**Out of memory errors:** +- Reduce context window size in settings +- Index only specific packages instead of the entire project + +## Coming Soon: GraphRAG + +In future updates, DevoxxGenie will introduce GraphRAG, an enhanced RAG implementation that: + +1. Creates a knowledge graph of your codebase +2. Maps relationships between classes, methods, and other components +3. Provides more accurate retrieval based on code structure +4. Enables more sophisticated reasoning about code architecture + +Stay tuned for updates on this exciting feature! diff --git a/docusaurus/docusaurus.config.js b/docusaurus/docusaurus.config.js index f876d3da..2a970e88 100644 --- a/docusaurus/docusaurus.config.js +++ b/docusaurus/docusaurus.config.js @@ -16,7 +16,7 @@ const config = { url: 'https://devoxx.github.io', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' - baseUrl: '/DevoxxGenieIDEAPlugin/', + baseUrl: '/', // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. diff --git a/src/main/java/com/devoxx/genie/service/analyzer/ProjectAnalyzer.java b/src/main/java/com/devoxx/genie/service/analyzer/ProjectAnalyzer.java index 5504891f..02d8f38b 100644 --- a/src/main/java/com/devoxx/genie/service/analyzer/ProjectAnalyzer.java +++ b/src/main/java/com/devoxx/genie/service/analyzer/ProjectAnalyzer.java @@ -25,6 +25,58 @@ *

*/ public class ProjectAnalyzer { + + public static final String POM_XML = "pom.xml"; + public static final String BUILD = "build"; + public static final String COMMANDS = "commands"; + public static final String BUILD_GRADLE = "build.gradle"; + public static final String BUILD_GRADLE_KTS = "build.gradle.kts"; + public static final String PACKAGE_JSON = "package.json"; + public static final String KOTLIN = "Kotlin"; + public static final String JAVA = "Java"; + public static final String JAVA_SCRIPT_TYPE_SCRIPT = "JavaScript/TypeScript"; + public static final String CMAKE_LISTS_TXT = "CMakeLists.txt"; + public static final String C_C_PLUS_PLUS = "C/C++"; + public static final String PRETTIERRC = ".prettierrc"; + public static final String ESLINTRC_JS = ".eslintrc.js"; + public static final String CHECKSTYLE_XML = "checkstyle.xml"; + public static final String PHPCS_XML = "phpcs.xml"; + public static final String CLANG_FORMAT = ".clang-format"; + public static final String RUST = "Rust"; + public static final String CARGO_TOML = "Cargo.toml"; + public static final String GO_MOD = "go.mod"; + public static final String GO = "Go"; + public static final String PYTHON = "Python"; + public static final String PHP = "PHP"; + public static final String ESLINT = "eslint"; + public static final String PRETTIER = "prettier"; + public static final String CHECKSTYLE = "checkstyle"; + public static final String PHPCS = "phpcs"; + public static final String CLANG_FORMAT1 = "clang-format"; + public static final String TEST_JAVA_PATTERN = "**/*Test.java"; + public static final String J_UNIT = "JUnit"; + public static final String TEST_PHP_PATTERN = "**/*Test.php"; + public static final String PHP_UNIT = "PHPUnit"; + public static final String TEST_PY_PATTERN = "**/*_test.py"; + public static final String PYTEST = "pytest"; + public static final String TEST_GO_PATTERN = "**/*_test.go"; + public static final String GO_TESTING = "Go testing"; + public static final String SPEC_JS_PATTERN = "**/*spec.js"; + public static final String JEST = "Jest"; + public static final String TEST_RS_PATTERN = "**/test_*.rs"; + public static final String RUST_TESTING = "Rust testing"; + public static final String SPRING_BOOT_JAR = "**/spring-boot*.jar"; + public static final String SPRING_BOOT = "Spring Boot"; + public static final String LARAVEL_PHP = "**/laravel*.php"; + public static final String LARAVEL = "Laravel"; + public static final String DJANGO_PY = "**/django/*.py"; + public static final String DJANGO = "Django"; + public static final String REACT_JS = "**/react.js"; + public static final String REACT = "React"; + public static final String TESTING = "testing"; + public static final String WEB = "web"; + public static final String OTHER = "other"; + private final Project project; private final VirtualFile baseDir; private final GitignoreParser gitignoreParser; @@ -34,9 +86,7 @@ public ProjectAnalyzer(Project project, VirtualFile baseDir) { this.baseDir = baseDir; // Initialize the GitignoreParser with support for nested .gitignore files -// ProjectScannerLogPanel.log("Initializing GitignoreParser with support for nested .gitignore files..."); this.gitignoreParser = new GitignoreParser(baseDir); -// ProjectScannerLogPanel.log("GitignoreParser initialization complete"); } public Map scanProject() { @@ -64,99 +114,86 @@ public Map scanProject() { Map buildInfo = new HashMap<>(); // Check for common build files - if (baseDir.findChild("pom.xml") != null) { + if (baseDir.findChild(POM_XML) != null) { buildInfo.put("type", "Maven"); // Add basic Maven commands Map commands = new HashMap<>(); - commands.put("build", "mvn clean install"); + commands.put(BUILD, "mvn clean install"); commands.put("test", "mvn test"); - buildInfo.put("commands", commands); + buildInfo.put(COMMANDS, commands); } - else if (baseDir.findChild("build.gradle") != null || baseDir.findChild("build.gradle.kts") != null) { + else if (baseDir.findChild(BUILD_GRADLE) != null || baseDir.findChild(BUILD_GRADLE_KTS) != null) { buildInfo.put("type", "Gradle"); // Add basic Gradle commands Map commands = new HashMap<>(); - commands.put("build", "./gradlew build"); + commands.put(BUILD, "./gradlew build"); commands.put("test", "./gradlew test"); - buildInfo.put("commands", commands); + buildInfo.put(COMMANDS, commands); } - else if (baseDir.findChild("CMakeLists.txt") != null) { + else if (baseDir.findChild(CMAKE_LISTS_TXT) != null) { buildInfo.put("type", "CMake"); Map commands = new HashMap<>(); - commands.put("build", "cmake --build build"); + commands.put(BUILD, "cmake --build build"); commands.put("test", "ctest"); - buildInfo.put("commands", commands); + buildInfo.put(COMMANDS, commands); } - else if (baseDir.findChild("package.json") != null) { + else if (baseDir.findChild(PACKAGE_JSON) != null) { buildInfo.put("type", "npm/yarn"); Map commands = new HashMap<>(); - commands.put("build", "npm run build"); + commands.put(BUILD, "npm run build"); commands.put("test", "npm test"); - buildInfo.put("commands", commands); + buildInfo.put(COMMANDS, commands); } else if (baseDir.findChild("composer.json") != null) { buildInfo.put("type", "Composer"); Map commands = new HashMap<>(); commands.put("install", "composer install"); commands.put("test", "composer test"); - buildInfo.put("commands", commands); + buildInfo.put(COMMANDS, commands); } - else if (baseDir.findChild("Cargo.toml") != null) { + else if (baseDir.findChild(CARGO_TOML) != null) { buildInfo.put("type", "Cargo"); Map commands = new HashMap<>(); - commands.put("build", "cargo build"); + commands.put(BUILD, "cargo build"); commands.put("test", "cargo test"); - buildInfo.put("commands", commands); + buildInfo.put(COMMANDS, commands); } return buildInfo; } private @NotNull Map detectLanguages() { -// ProjectScannerLogPanel.log("Starting language detection for project: " + project.getName()); -// ProjectScannerLogPanel.log("Base directory: " + baseDir.getPath()); - Map languages = new HashMap<>(); Set detectedLanguages = new HashSet<>(); Map languageFileCount = new HashMap<>(); // Check for language-specific project files first -// ProjectScannerLogPanel.log("Checking for language-specific project files..."); - if (baseDir.findChild("Cargo.toml") != null) { -// ProjectScannerLogPanel.log("Found Cargo.toml - Rust project detected"); - detectedLanguages.add("Rust"); - languageFileCount.put("Rust", 1); // Start with 1 for the project file - } - if (baseDir.findChild("pom.xml") != null) { -// ProjectScannerLogPanel.log("Found pom.xml - Java project detected"); - detectedLanguages.add("Java"); - languageFileCount.put("Java", 1); - } - if (baseDir.findChild("build.gradle") != null || baseDir.findChild("build.gradle.kts") != null) { -// ProjectScannerLogPanel.log("Found Gradle build file - Java/Kotlin project detected"); - detectedLanguages.add("Java"); - detectedLanguages.add("Kotlin"); - languageFileCount.put("Java", languageFileCount.getOrDefault("Java", 0) + 1); - languageFileCount.put("Kotlin", languageFileCount.getOrDefault("Kotlin", 0) + 1); - } - if (baseDir.findChild("go.mod") != null) { -// ProjectScannerLogPanel.log("Found go.mod - Go project detected"); - detectedLanguages.add("Go"); - languageFileCount.put("Go", 1); - } - if (baseDir.findChild("package.json") != null) { -// ProjectScannerLogPanel.log("Found package.json - JavaScript/TypeScript project detected"); - detectedLanguages.add("JavaScript/TypeScript"); - languageFileCount.put("JavaScript/TypeScript", 1); - } - if (baseDir.findChild("CMakeLists.txt") != null) { -// ProjectScannerLogPanel.log("Found CMakeLists.txt - C/C++ project detected"); - detectedLanguages.add("C/C++"); - languageFileCount.put("C/C++", 1); - } - - // File extension-based detection -// ProjectScannerLogPanel.log("Starting file extension-based detection..."); + if (baseDir.findChild(CARGO_TOML) != null) { + detectedLanguages.add(RUST); + languageFileCount.put(RUST, 1); // Start with 1 for the project file + } + if (baseDir.findChild(POM_XML) != null) { + detectedLanguages.add(JAVA); + languageFileCount.put(JAVA, 1); + } + if (baseDir.findChild(BUILD_GRADLE) != null || baseDir.findChild(BUILD_GRADLE_KTS) != null) { + detectedLanguages.add(JAVA); + detectedLanguages.add(KOTLIN); + languageFileCount.put(JAVA, languageFileCount.getOrDefault(JAVA, 0) + 1); + languageFileCount.put(KOTLIN, languageFileCount.getOrDefault(KOTLIN, 0) + 1); + } + if (baseDir.findChild(GO_MOD) != null) { + detectedLanguages.add(GO); + languageFileCount.put(GO, 1); + } + if (baseDir.findChild(PACKAGE_JSON) != null) { + detectedLanguages.add(JAVA_SCRIPT_TYPE_SCRIPT); + languageFileCount.put(JAVA_SCRIPT_TYPE_SCRIPT, 1); + } + if (baseDir.findChild(CMAKE_LISTS_TXT) != null) { + detectedLanguages.add(C_C_PLUS_PLUS); + languageFileCount.put(C_C_PLUS_PLUS, 1); + } VfsUtil.visitChildrenRecursively(baseDir, new VirtualFileVisitor() { @Override @@ -164,9 +201,6 @@ public boolean visitFile(@NotNull VirtualFile file) { // Check if the file should be ignored based on .gitignore rules String relativePath = getRelativePath(baseDir, file); if (relativePath != null && gitignoreParser.shouldIgnore(relativePath, file.isDirectory())) { -// if (file.isDirectory()) { -// ProjectScannerLogPanel.log("Skipping directory based on .gitignore: " + relativePath); -// } return true; // Skip this file and its children } @@ -180,36 +214,36 @@ public boolean visitFile(@NotNull VirtualFile file) { if (extension != null) { switch (extension) { case "java" -> { - detectedLanguages.add("Java"); - languageFileCount.put("Java", languageFileCount.getOrDefault("Java", 0) + 1); + detectedLanguages.add(JAVA); + languageFileCount.put(JAVA, languageFileCount.getOrDefault(JAVA, 0) + 1); } case "kt" -> { - detectedLanguages.add("Kotlin"); - languageFileCount.put("Kotlin", languageFileCount.getOrDefault("Kotlin", 0) + 1); + detectedLanguages.add(KOTLIN); + languageFileCount.put(KOTLIN, languageFileCount.getOrDefault(KOTLIN, 0) + 1); } case "php" -> { - detectedLanguages.add("PHP"); - languageFileCount.put("PHP", languageFileCount.getOrDefault("PHP", 0) + 1); + detectedLanguages.add(PHP); + languageFileCount.put(PHP, languageFileCount.getOrDefault(PHP, 0) + 1); } case "py" -> { - detectedLanguages.add("Python"); - languageFileCount.put("Python", languageFileCount.getOrDefault("Python", 0) + 1); + detectedLanguages.add(PYTHON); + languageFileCount.put(PYTHON, languageFileCount.getOrDefault(PYTHON, 0) + 1); } case "js", "ts" -> { - detectedLanguages.add("JavaScript/TypeScript"); - languageFileCount.put("JavaScript/TypeScript", languageFileCount.getOrDefault("JavaScript/TypeScript", 0) + 1); + detectedLanguages.add(JAVA_SCRIPT_TYPE_SCRIPT); + languageFileCount.put(JAVA_SCRIPT_TYPE_SCRIPT, languageFileCount.getOrDefault(JAVA_SCRIPT_TYPE_SCRIPT, 0) + 1); } case "cpp", "h", "c" -> { - detectedLanguages.add("C/C++"); - languageFileCount.put("C/C++", languageFileCount.getOrDefault("C/C++", 0) + 1); + detectedLanguages.add(C_C_PLUS_PLUS); + languageFileCount.put(C_C_PLUS_PLUS, languageFileCount.getOrDefault(C_C_PLUS_PLUS, 0) + 1); } case "rs" -> { - detectedLanguages.add("Rust"); - languageFileCount.put("Rust", languageFileCount.getOrDefault("Rust", 0) + 1); + detectedLanguages.add(RUST); + languageFileCount.put(RUST, languageFileCount.getOrDefault(RUST, 0) + 1); } case "go" -> { - detectedLanguages.add("Go"); - languageFileCount.put("Go", languageFileCount.getOrDefault("Go", 0) + 1); + detectedLanguages.add(GO); + languageFileCount.put(GO, languageFileCount.getOrDefault(GO, 0) + 1); } } } @@ -223,12 +257,6 @@ public boolean visitFile(@NotNull VirtualFile file) { if (!detectedLanguages.isEmpty()) { languages.put("detected", new ArrayList<>(detectedLanguages)); - // Log all detected languages with their file counts -// ProjectScannerLogPanel.log("File counts by language:"); -// for (Map.Entry entry : languageFileCount.entrySet()) { -// ProjectScannerLogPanel.log(" - " + entry.getKey() + ": " + entry.getValue() + " files"); -// } - // Find the language with the most files String primaryLanguage = detectedLanguages.iterator().next(); int maxFiles = 0; @@ -241,7 +269,6 @@ public boolean visitFile(@NotNull VirtualFile file) { } languages.put("primary", primaryLanguage); -// ProjectScannerLogPanel.log("Primary language detected: " + primaryLanguage); } return languages; @@ -255,24 +282,26 @@ public boolean visitFile(@NotNull VirtualFile file) { if (editorConfig != null) { try { styleInfo.put("editorconfig", VfsUtil.loadText(editorConfig)); - } catch (IOException ignored) {} + } catch (IOException ignored) { + // ignore + } } // Check for common code style tools - if (baseDir.findChild(".eslintrc.js") != null || baseDir.findChild(".eslintrc.json") != null) { - styleInfo.put("eslint", true); + if (baseDir.findChild(ESLINTRC_JS) != null || baseDir.findChild(".eslintrc.json") != null) { + styleInfo.put(ESLINT, true); } - if (baseDir.findChild(".prettierrc") != null || baseDir.findChild(".prettierrc.js") != null) { - styleInfo.put("prettier", true); + if (baseDir.findChild(PRETTIERRC) != null || baseDir.findChild(".prettierrc.js") != null) { + styleInfo.put(PRETTIER, true); } - if (baseDir.findChild("checkstyle.xml") != null) { - styleInfo.put("checkstyle", true); + if (baseDir.findChild(CHECKSTYLE_XML) != null) { + styleInfo.put(CHECKSTYLE, true); } - if (baseDir.findChild("phpcs.xml") != null) { - styleInfo.put("phpcs", true); + if (baseDir.findChild(PHPCS_XML) != null) { + styleInfo.put(PHPCS, true); } - if (baseDir.findChild(".clang-format") != null) { - styleInfo.put("clang-format", true); + if (baseDir.findChild(CLANG_FORMAT) != null) { + styleInfo.put(CLANG_FORMAT1, true); } return styleInfo; @@ -285,42 +314,42 @@ public boolean visitFile(@NotNull VirtualFile file) { List otherFrameworks = new ArrayList<>(); // Check for testing frameworks in various languages - if (containsFile(baseDir, "**/*Test.java") || containsFile(baseDir, "**/JUnit*.java")) { - testingFrameworks.add("JUnit"); + if (containsFile(baseDir, TEST_JAVA_PATTERN) || containsFile(baseDir, "**/JUnit*.java")) { + testingFrameworks.add(J_UNIT); } - if (containsFile(baseDir, "**/*Test.php") || containsFile(baseDir, "**/PHPUnit*.php")) { - testingFrameworks.add("PHPUnit"); + if (containsFile(baseDir, TEST_PHP_PATTERN) || containsFile(baseDir, "**/PHPUnit*.php")) { + testingFrameworks.add(PHP_UNIT); } - if (containsFile(baseDir, "**/*_test.py") || containsFile(baseDir, "**/pytest*.py")) { - testingFrameworks.add("pytest"); + if (containsFile(baseDir, TEST_PY_PATTERN) || containsFile(baseDir, "**/pytest*.py")) { + testingFrameworks.add(PYTEST); } - if (containsFile(baseDir, "**/*_test.go")) { - testingFrameworks.add("Go testing"); + if (containsFile(baseDir, TEST_GO_PATTERN)) { + testingFrameworks.add(GO_TESTING); } - if (containsFile(baseDir, "**/*spec.js") || containsFile(baseDir, "**/jest.config.js")) { - testingFrameworks.add("Jest"); + if (containsFile(baseDir, SPEC_JS_PATTERN) || containsFile(baseDir, "**/jest.config.js")) { + testingFrameworks.add(JEST); } - if (containsFile(baseDir, "**/test_*.rs")) { - testingFrameworks.add("Rust testing"); + if (containsFile(baseDir, TEST_RS_PATTERN)) { + testingFrameworks.add(RUST_TESTING); } // Web frameworks detection - if (containsFile(baseDir, "**/spring-boot*.jar") || containsFile(baseDir, "**/SpringBoot*.java")) { - webFrameworks.add("Spring Boot"); + if (containsFile(baseDir, SPRING_BOOT_JAR) || containsFile(baseDir, "**/SpringBoot*.java")) { + webFrameworks.add(SPRING_BOOT); } - if (containsFile(baseDir, "**/laravel*.php") || findInFile(baseDir, "composer.json", "laravel/framework")) { - webFrameworks.add("Laravel"); + if (containsFile(baseDir, LARAVEL_PHP) || findInFile(baseDir, "composer.json", "laravel/framework")) { + webFrameworks.add(LARAVEL); } - if (containsFile(baseDir, "**/django/*.py") || findInFile(baseDir, "requirements.txt", "django")) { - webFrameworks.add("Django"); + if (containsFile(baseDir, DJANGO_PY) || findInFile(baseDir, "requirements.txt", "django")) { + webFrameworks.add(DJANGO); } - if (containsFile(baseDir, "**/react.js") || findInFile(baseDir, "package.json", "react")) { - webFrameworks.add("React"); + if (containsFile(baseDir, REACT_JS) || findInFile(baseDir, PACKAGE_JSON, "react")) { + webFrameworks.add(REACT); } - frameworks.put("testing", testingFrameworks); - frameworks.put("web", webFrameworks); - frameworks.put("other", otherFrameworks); + frameworks.put(TESTING, testingFrameworks); + frameworks.put(WEB, webFrameworks); + frameworks.put(OTHER, otherFrameworks); return frameworks; } @@ -398,7 +427,7 @@ private boolean findInFile(@NotNull VirtualFile dir, String fileName, String con List> dependencies = new ArrayList<>(); // Check for Gradle dependencies (Kotlin DSL) - VirtualFile buildGradleKts = baseDir.findChild("build.gradle.kts"); + VirtualFile buildGradleKts = baseDir.findChild(BUILD_GRADLE_KTS); if (buildGradleKts != null) { try { String content = VfsUtil.loadText(buildGradleKts); @@ -444,7 +473,7 @@ private boolean findInFile(@NotNull VirtualFile dir, String fileName, String con } // Check for Gradle dependencies (Groovy DSL) - VirtualFile buildGradle = baseDir.findChild("build.gradle"); + VirtualFile buildGradle = baseDir.findChild(BUILD_GRADLE); if (buildGradle != null) { try { String content = VfsUtil.loadText(buildGradle); @@ -457,7 +486,7 @@ private boolean findInFile(@NotNull VirtualFile dir, String fileName, String con } // Check for Maven dependencies - VirtualFile pomXml = baseDir.findChild("pom.xml"); + VirtualFile pomXml = baseDir.findChild(POM_XML); if (pomXml != null) { try { String content = VfsUtil.loadText(pomXml); @@ -480,7 +509,7 @@ private boolean findInFile(@NotNull VirtualFile dir, String fileName, String con } // Check for NPM dependencies - VirtualFile packageJson = baseDir.findChild("package.json"); + VirtualFile packageJson = baseDir.findChild(PACKAGE_JSON); if (packageJson != null) { try { String content = VfsUtil.loadText(packageJson); diff --git a/src/main/java/com/devoxx/genie/service/analyzer/ProjectTreeGenerator.java b/src/main/java/com/devoxx/genie/service/analyzer/ProjectTreeGenerator.java deleted file mode 100644 index 0f317663..00000000 --- a/src/main/java/com/devoxx/genie/service/analyzer/ProjectTreeGenerator.java +++ /dev/null @@ -1,352 +0,0 @@ -package com.devoxx.genie.service.analyzer; - -import com.devoxx.genie.service.analyzer.util.CachedProjectScanner; -import com.devoxx.genie.service.projectscanner.FileScanner; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.*; - -/** - * A utility class to generate a project tree and append it to the DEVOXXGENIE.md file. - * - * @deprecated Use the FileScanner-based implementation in DevoxxGenieGenerator.appendProjectTreeUsingFileScanner - * for better performance and consistency with file scanning logic. - */ -@Deprecated -@Slf4j -public class ProjectTreeGenerator { - - private static final String SECTION_HEADER = "\n\n### Project Tree\n\n"; - private static final String TREE_PREFIX = "```\n"; - private static final String TREE_SUFFIX = "\n```\n"; - - private static final Set IGNORED_DIRS = new HashSet<>(Arrays.asList( - ".git", ".idea", ".gradle", "build", "out", "target", "node_modules" - )); - - /** - * Generates a project tree and appends it to the DEVOXXGENIE.md file. - * - * @param baseDir The base directory of the project - * @param treeDepth The maximum depth of the tree - * @deprecated Use FileScanner-based implementation instead - */ - @Deprecated - public static void appendProjectTreeToDevoxxGenie(@NotNull VirtualFile baseDir, - Integer treeDepth) { - long startTime = System.currentTimeMillis(); - log.info("Starting project tree generation for: " + baseDir.getPath()); - - try { - CachedProjectScanner scanner = new CachedProjectScanner(baseDir); - List files = scanner.scanDirectoryWithCache(); - - long scanDuration = System.currentTimeMillis() - startTime; - log.info(String.format("Found %d files in project structure (scan took %d ms)", files.size(), scanDuration)); - - String treeContent = generateTreeContent(baseDir, files, treeDepth); - - log.info("Generated tree content with length: " + treeContent.length()); - - // Append to DEVOXXGENIE.md - appendToGenieMd(baseDir, treeContent); - } catch (Exception e) { - log.error("Error in appendProjectTreeToDevoxxGenie", e); - } - } - - /** - * Generates a project tree and appends it to the DEVOXXGENIE.md file using FileScanner. - * This method provides an alternative implementation to the original using FileScanner. - * - * @param project The project - * @param baseDir The base directory of the project - * @param treeDepth The maximum depth of the tree - */ - public static void appendProjectTreeUsingFileScanner(@NotNull Project project, - @NotNull VirtualFile baseDir, - int treeDepth) { - long startTime = System.currentTimeMillis(); - log.info("Starting project tree generation using FileScanner for: " + baseDir.getPath()); - - try { - // Initialize a FileScanner instance - FileScanner fileScanner = new FileScanner(); - - // Initialize the gitignore parser - fileScanner.initGitignoreParser(project, baseDir); - - // Generate tree content using FileScanner - String treeContent = fileScanner.generateSourceTreeRecursive(baseDir, 0); - - long duration = System.currentTimeMillis() - startTime; - log.info(String.format("Generated tree content in %d ms, length: %d", duration, treeContent.length())); - - // Format the content with markdown code block - String formattedContent = TREE_PREFIX + treeContent + TREE_SUFFIX; - - // Append to DEVOXXGENIE.md - appendToGenieMd(baseDir, formattedContent); - } catch (Exception e) { - log.error("Error in appendProjectTreeUsingFileScanner", e); - } - } - - /** - * Generates a tree-like text representation of the project structure. - * - * @param baseDir The base directory of the project - * @param files List of files found by the scanner - * @return A formatted string representation of the project tree - */ - private static @NotNull String generateTreeContent(@NotNull VirtualFile baseDir, - @NotNull List files, - Integer treeDepth) { - long startTime = System.currentTimeMillis(); - - // Create a tree structure from the files - TreeNode root = new TreeNode(baseDir.getName(), true); - String basePath = baseDir.getPath(); - - log.info("Base path for tree generation: " + basePath); - - // Sort files for consistent output - files.sort(Comparator.comparing(VirtualFile::getPath)); - - for (VirtualFile file : files) { - // Skip ignored directories - boolean shouldSkip = false; - String path = file.getPath(); - for (String ignoredDir : IGNORED_DIRS) { - if (path.contains("/" + ignoredDir + "/") || path.endsWith("/" + ignoredDir)) { - shouldSkip = true; - break; - } - } - if (shouldSkip) continue; - - // Get the relative path - String relativePath = path.substring(basePath.length()); - if (relativePath.startsWith("/")) { - relativePath = relativePath.substring(1); - } - - // Skip empty paths (this is the root directory) - if (relativePath.isEmpty()) continue; - - // Add to tree - addFileToTree(root, relativePath, file.isDirectory(), treeDepth); - } - - // Generate the string representation - StringBuilder sb = new StringBuilder(); - sb.append(TREE_PREFIX); - sb.append(root.name).append("/\n"); - printTree(root, "", sb); - sb.append(TREE_SUFFIX); - - log.info("Tree content generated, sample: " + (sb.length() > 100 ? sb.substring(0, 100) + "..." : sb.toString())); - - return sb.toString(); - } - - /** - * Helper method to add a file to the tree structure. - * - * @param root The root tree node - * @param path The relative path of the file - * @param isDirectory Whether the file is a directory - */ - private static void addFileToTree(TreeNode root, @NotNull String path, boolean isDirectory, Integer treeDepth) { - // Skip paths that are too long to avoid processing issues - if (path.length() > 500) { - log.warn("Skipping excessively long path: " + path.substring(0, 100) + "..."); - return; - } - String[] parts = path.split("/"); - TreeNode current = root; - - int depth = 0; - - // Protect against paths with too many parts - int maxParts = Math.min(parts.length, 50); - for (int i = 0; i < maxParts; i++) { - String part = parts[i]; - if (part.isEmpty()) continue; - - depth++; - if (depth > treeDepth && i < parts.length - 1) { - // Skip deeper levels but still count directories - current.incrementChildCount(); - return; - } - - boolean isLeafDirectory = isDirectory && i == parts.length - 1; - boolean isFile = !isDirectory && i == parts.length - 1; - - // Try to find existing child - TreeNode child = null; - for (TreeNode node : current.children) { - if (node.name.equals(part)) { - child = node; - break; - } - } - - if (child == null) { - // Create new node if not found - child = new TreeNode(part, isLeafDirectory || (!isFile && i < parts.length - 1)); - current.children.add(child); - } - - current = child; - } - } - - /** - * Helper method to print the tree recursively. - * - * @param node The current tree node - * @param prefix The prefix for the current line - * @param sb The StringBuilder to append to - */ - private static void printTree(TreeNode node, String prefix, @NotNull StringBuilder sb) { - // Protect against excessively large trees by limiting total nodes processed - if (sb.length() > 100000) { - sb.append(prefix).append("... (truncated due to size)\n"); - return; - } - List children = new ArrayList<>(node.children); - - // Sort directories first, then files, both alphabetically - children.sort((a, b) -> { - if (a.isDirectory && !b.isDirectory) return -1; - if (!a.isDirectory && b.isDirectory) return 1; - return a.name.compareTo(b.name); - }); - - for (int i = 0; i < children.size(); i++) { - TreeNode child = children.get(i); - boolean isLast = i == children.size() - 1; - - sb.append(prefix) - .append(isLast ? "└── " : "├── ") - .append(child.name) - .append(child.isDirectory ? "/" : "") - .append("\n"); - - if (child.isDirectory) { - if (child.children.isEmpty() && child.childCount > 0) { - // Show a placeholder for directories with children beyond MAX_DEPTH - sb.append(prefix) - .append(isLast ? " " : "│ ") - .append("└── ") - .append("... (") - .append(child.childCount) - .append(" more items)") - .append("\n"); - } else { - printTree(child, prefix + (isLast ? " " : "│ "), sb); - } - } - } - } - - /** - * Appends the project tree to the DEVOXXGENIE.md file. - * - * @param baseDir The base directory of the project - * @param treeContent The generated tree content - */ - private static void appendToGenieMd(VirtualFile baseDir, String treeContent) { - try { - VirtualFile devoxxGenieMdFile = baseDir.findChild("DEVOXXGENIE.md"); - - if (devoxxGenieMdFile == null) { - log.warn("DEVOXXGENIE.md file not found, cannot append project tree"); - return; - } - - // Read current content - String currentContent = VfsUtil.loadText(devoxxGenieMdFile); - log.info("Read current DEVOXXGENIE.md content, length: " + currentContent.length()); - - // Check if it already has a project tree section - if (currentContent.contains(SECTION_HEADER)) { - // Remove existing project tree section - int sectionStart = currentContent.indexOf(SECTION_HEADER); - int sectionEnd = currentContent.indexOf(TREE_SUFFIX, sectionStart); - - log.info("Found existing project tree section: start=" + sectionStart + ", end=" + sectionEnd); - - if (sectionEnd > sectionStart) { - // Replace the existing section - String beforeSection = currentContent.substring(0, sectionStart); - String afterSection = sectionEnd + TREE_SUFFIX.length() < currentContent.length() ? - currentContent.substring(sectionEnd + TREE_SUFFIX.length()) : ""; - - currentContent = beforeSection + SECTION_HEADER + treeContent + afterSection; - log.info("Replaced existing project tree section"); - } else { - // If section is malformed, just append - currentContent += SECTION_HEADER + treeContent; - log.info("Existing project tree section appears malformed, appending new section"); - } - } else { - // Append new section - currentContent += SECTION_HEADER + treeContent; - log.info("Appending new project tree section"); - } - - // Update the file - final String finalContent = currentContent; - - // Run in a write action on the EDT - log.info("Attempting to save updated content to DEVOXXGENIE.md"); - - // Using invokeAndWait to ensure we switch to the EDT thread - try { - ApplicationManager.getApplication().invokeAndWait(() -> - ApplicationManager.getApplication().runWriteAction(() -> { - try { - VfsUtil.saveText(devoxxGenieMdFile, finalContent); - log.info("Project tree appended to DEVOXXGENIE.md successfully"); - } catch (IOException e) { - log.error("Error updating DEVOXXGENIE.md with project tree", e); - } - })); - } catch (Exception e) { - log.error("Error while waiting for EDT to save DEVOXXGENIE.md", e); - } - } catch (Exception e) { - log.error("Error appending project tree to DEVOXXGENIE.md", e); - } - } - - /** - * A simple tree node class for building the directory structure. - */ - private static class TreeNode { - String name; - boolean isDirectory; - List children; - int childCount; - - TreeNode(String name, boolean isDirectory) { - this.name = name; - this.isDirectory = isDirectory; - this.children = new ArrayList<>(); - this.childCount = 0; - } - - void incrementChildCount() { - childCount++; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/devoxx/genie/service/analyzer/languages/rust/RustProjectScannerExtension.java b/src/main/java/com/devoxx/genie/service/analyzer/languages/rust/RustProjectScannerExtension.java index 1f52b78c..14a4ea91 100644 --- a/src/main/java/com/devoxx/genie/service/analyzer/languages/rust/RustProjectScannerExtension.java +++ b/src/main/java/com/devoxx/genie/service/analyzer/languages/rust/RustProjectScannerExtension.java @@ -50,7 +50,7 @@ public void enhanceProjectInfo(@NotNull Project project, @NotNull Map buildSystem = (Map) projectInfo.get("buildSystem"); + // Map buildSystem = (Map) projectInfo.get("buildSystem"); // Add Rust information to project info projectInfo.put("rust", rustInfo); diff --git a/src/main/java/com/devoxx/genie/ui/panel/mcp/MCPLogPanel.java b/src/main/java/com/devoxx/genie/ui/panel/mcp/MCPLogPanel.java index e86109ff..b343032d 100644 --- a/src/main/java/com/devoxx/genie/ui/panel/mcp/MCPLogPanel.java +++ b/src/main/java/com/devoxx/genie/ui/panel/mcp/MCPLogPanel.java @@ -5,7 +5,6 @@ import com.devoxx.genie.ui.settings.DevoxxGenieStateService; import com.devoxx.genie.ui.topic.AppTopics; import com.devoxx.genie.util.MessageBusUtil; -import com.intellij.json.JsonFileType; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; @@ -247,7 +246,9 @@ public boolean canClose(String inputString) { maxLogEntries = newValue; pruneLogsToMaxSize(); } - } catch (NumberFormatException ignored) {} + } catch (NumberFormatException ignored) { + // Ignore + } } } @@ -373,7 +374,7 @@ private void openLogInEditor(LogEntry logEntry) { String finalContent = content; ApplicationManager.getApplication().invokeLater(() -> { // Create virtual file - LightVirtualFile virtualFile = new LightVirtualFile(fileName, JsonFileType.INSTANCE, finalContent); + LightVirtualFile virtualFile = new LightVirtualFile(fileName, finalContent); // Open the file in the editor FileEditorManager.getInstance(project).openFile(virtualFile, true); @@ -440,7 +441,6 @@ public String toString() { */ private static class LogEntryRenderer extends DefaultListCellRenderer { // Colors for different types of messages - private static final Color TIMESTAMP_COLOR = new Color(127, 127, 127); private static final Color INCOMING_COLOR = new Color(76, 175, 80); // Green private static final Color OUTGOING_COLOR = new Color(33, 150, 243); // Blue diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8ebf1007..4793f87a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -40,6 +40,9 @@
  • Fix #567 : At least one message is required by @stephanj
  • Fix #569 : Fix to support again attached images by @stephanj
  • Fix #571 : Don't show default editor file when using MCP by @stephanj
  • +
  • Fix #576 : LMStudio and Ollama do not return a response by @stephanj
  • +
  • Feat #574 : Edit custom prompt via a dialog by @mydeveloperplanet
  • +
  • Fix #580 : IDEA Compatibility problem Json dependency
  • v0.5.0