Skip to content

Commit 896d3d9

Browse files
authored
feat: enhance prompts (#11)
1 parent 4fab2d9 commit 896d3d9

6 files changed

Lines changed: 619 additions & 277 deletions

File tree

src/main/kotlin/mcp/code/analysis/service/CodeAnalyzer.kt

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ data class CodeAnalyzer(
3737
val relativePath = file.absolutePath.substring(repoDir.absolutePath.length + 1)
3838
val lang = getLanguageFromExtension(file.extension)
3939
val safeContent = file.readLines().joinToString("\n")
40-
"---\n --- File: $relativePath\n~~~$lang\n$safeContent\n~~~"
40+
"---\nFile: $relativePath\n~~~$lang\n$safeContent\n~~~"
4141
}
4242
.toList()
4343
.also { snippets ->
4444
logger.info("Collected ${snippets.size} code snippets from ${repoDir.absolutePath}")
45-
logger.info("Snippets Found:\n${snippets.joinToString("\n")}")
45+
logger.debug("Snippets Found:\n${snippets.joinToString("\n")}")
4646
}
4747

4848
/**
@@ -59,11 +59,11 @@ data class CodeAnalyzer(
5959

6060
return if (readmeFile != null) {
6161
val content = readmeFile.readText()
62-
logger.info("Readme file content: $content")
6362
logger.info("Readme file found: ${readmeFile.absolutePath}")
63+
logger.debug("Readme file content: $content")
6464
content
6565
} else {
66-
logger.info("No readme file found in ${repoDir.absolutePath}")
66+
logger.warn("No readme file found in ${repoDir.absolutePath}")
6767
"No README content available."
6868
}
6969
}
@@ -72,22 +72,25 @@ data class CodeAnalyzer(
7272
// Skip hidden directories and common directories to ignore
7373
val dirsToIgnore =
7474
setOf(".git", "node_modules", "venv", "__pycache__", "target", "build", "dist", ".idea", ".vscode")
75-
if (dir.isHidden || dir.name in dirsToIgnore) return emptyMap()
76-
77-
val files = dir.listFiles() ?: return emptyMap()
7875

79-
return files.fold(mutableMapOf()) { structure, file ->
80-
val relativePath = file.absolutePath.substring(rootPath.length + 1)
76+
if (dir.isHidden || dir.name in dirsToIgnore) return emptyMap()
77+
val files = dir.listFiles()?.toList() ?: return emptyMap()
78+
val fileEntries = files.filterNot(File::isDirectory).associate { file -> file.name to analyzeFile(file) }
79+
80+
val directoryEntries =
81+
files
82+
.filter(File::isDirectory)
83+
.flatMap { subDir ->
84+
val dirStructure = processDirectory(subDir, rootPath)
85+
if (dirStructure.isEmpty()) emptyList()
86+
else {
87+
val relativePath = subDir.absolutePath.substring(rootPath.length + 1)
88+
listOf(relativePath to dirStructure)
89+
}
90+
}
91+
.toMap()
8192

82-
if (file.isDirectory) {
83-
val dirStructure = processDirectory(file, rootPath)
84-
if (dirStructure.isNotEmpty()) structure[relativePath] = dirStructure
85-
} else {
86-
// For code files, add metadata
87-
structure[relativePath] = analyzeFile(file)
88-
}
89-
structure
90-
}
93+
return fileEntries + directoryEntries
9194
}
9295

9396
private fun analyzeFile(file: File): Map<String, Any> {
@@ -166,7 +169,7 @@ data class CodeAnalyzer(
166169

167170
// If more than the threshold percentage of the first 1000 bytes is null, likely binary
168171
nullCount > bytes.size * binaryDetectionThreshold
169-
} catch (e: Exception) {
172+
} catch (_: Exception) {
170173
false
171174
}
172175
}

src/main/kotlin/mcp/code/analysis/service/ModelContextService.kt

Lines changed: 66 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ package mcp.code.analysis.service
33
import io.ktor.client.*
44
import io.ktor.client.call.*
55
import io.ktor.client.engine.cio.*
6-
import io.ktor.client.plugins.HttpTimeout
6+
import io.ktor.client.plugins.*
77
import io.ktor.client.plugins.contentnegotiation.*
8-
import io.ktor.client.plugins.timeout
98
import io.ktor.client.request.*
109
import io.ktor.client.statement.*
1110
import io.ktor.http.*
1211
import io.ktor.serialization.kotlinx.json.*
1312
import kotlin.time.Duration.Companion.minutes
1413
import kotlinx.serialization.Serializable
15-
import kotlinx.serialization.json.*
14+
import kotlinx.serialization.json.Json
1615
import mcp.code.analysis.config.AppConfig
1716
import org.slf4j.Logger
1817
import org.slf4j.LoggerFactory
@@ -46,7 +45,11 @@ data class ModelContextService(
4645
*/
4746
suspend fun generateResponse(prompt: String): String {
4847
return try {
49-
logger.info("Sending request to Ollama with prompt: ${prompt}...")
48+
logger.info(
49+
"""Sending request to Ollama with prompt:
50+
|${prompt}..."""
51+
.trimIndent()
52+
)
5053
val request = OllamaRequest(model = config.modelName, prompt = prompt)
5154
val ollamaApiUrl = "${config.modelApiUrl}/generate"
5255
val httpResponse = sendRequest(ollamaApiUrl, request)
@@ -57,6 +60,11 @@ data class ModelContextService(
5760
"API error (${httpResponse.status}): $errorBody"
5861
} else {
5962
val response = httpResponse.body<OllamaResponse>()
63+
logger.info(
64+
"""Received response from Ollama:
65+
|${response.response}"""
66+
.trimIndent()
67+
)
6068
response.response ?: "No response generated"
6169
}
6270
} catch (e: Exception) {
@@ -66,58 +74,68 @@ data class ModelContextService(
6674
}
6775

6876
/**
69-
* Generate a summary of the codebase based on the provided information.
77+
* Build a prompt for the model context based on the provided README file.
7078
*
71-
* @param codeStructure Map representing the structure of the codebase
72-
* @param insights List of insights extracted from code analysis
73-
* @param readmeContent Content of the README file from the repository
74-
* @return A comprehensive summary of the codebase
75-
*/
76-
suspend fun generateSummary(codeStructure: Map<String, Any>, insights: List<String>, readmeContent: String): String {
77-
return try {
78-
val prompt = buildSummaryPrompt(codeStructure, insights, readmeContent)
79-
generateResponse(prompt)
80-
} catch (e: Exception) {
81-
logger.error("Error generating summary: ${e.message}", e)
82-
"Error generating summary: ${e.message}"
83-
}
84-
}
85-
86-
/**
87-
* Build a prompt for the model context based on the provided code snippets.
88-
*
89-
* @param codeSnippets List of code snippets from the repository to analyze
79+
* @param readme List of code snippets from the repository to analyze
9080
* @return A structured prompt for the model
9181
*/
92-
fun buildPrompt(codeSnippets: List<String>): String =
82+
fun buildInsightsPrompt(readme: String) =
9383
"""
94-
You are an expert code analyzer with deep knowledge of multiple programming languages including Java, Kotlin, Python, Go, Scala, JavaScript, TypeScript, C++, Rust, Ruby, and more. Analyze the following code repository snippets and provide insights about:
95-
96-
1. The overall architecture of the application
97-
2. Primary programming languages used and their interactions
98-
3. Key components and their relationships
99-
4. Design patterns used
100-
5. Potential code quality issues or improvements
101-
6. Security considerations
102-
7. Performance considerations
103-
8. Language-specific best practices and conventions
104-
105-
Code snippets:
106-
107-
${codeSnippets.joinToString("\n\n")}
108-
109-
Provide detailed analysis with specific references to the code where possible. Address language-specific concerns and identify cross-language integration points if multiple languages are used.
110-
"""
84+
|You are an expert codebase analyst with deep knowledge of software architecture, secure and scalable design, and programming languages including Java, Kotlin, Python, Go, Scala, JavaScript, TypeScript, C++, Rust, Ruby, and more.
85+
|
86+
|You are given the README file of a repository. Analyze it thoroughly and provide a detailed breakdown addressing the following aspects **based on the README content alone**:
87+
|
88+
|1. **Overall architecture** of the application (inferred from descriptions, diagrams, or setup instructions)
89+
|2. **Primary programming languages** used and how they may interact
90+
|3. **Key components** and their relationships or dependencies
91+
|4. **Design patterns** mentioned or implied
92+
|5. **Potential code quality issues** or areas for improvement (based on tooling, structure, or conventions described)
93+
|6. **Security considerations** (e.g., exposed credentials, missing practices)
94+
|7. **Performance considerations** (e.g., use of caching, parallelism hints)
95+
|8. **Language-specific best practices and conventions**
96+
|
97+
|If any of the above are not directly stated, make well-reasoned inferences and clearly label them as such.
98+
|
99+
|Format your response using sections and markdown. Provide specific references to the README text where applicable. If multiple languages are used, highlight any cross-language integration points.
100+
|
101+
|README Content:
102+
|~~~markdown
103+
|${readme.replace("```","~~~")}
104+
|~~~"""
105+
.trimIndent()
111106

112107
/**
113-
* Parse the model output to extract insights.
108+
* Build a summary prompt for the model context based on the provided code structure and snippets.
114109
*
115-
* @param modelOutput The raw output from the model
116-
* @return List of extracted insights
110+
* @param codeStructure Map representing the structure of the codebase
111+
* @param codeSnippets List of code snippets from the repository
112+
* @param insights List of insights generated from the README analysis
113+
* @return A structured prompt for the model
117114
*/
118-
fun parseInsights(modelOutput: String): List<String> {
119-
return modelOutput.split(Regex("\\n\\s*\\d+\\.\\s+|\\n\\s*-\\s+")).map { it.trim() }.filter { it.isNotEmpty() }
120-
}
115+
fun buildSummaryPrompt(codeStructure: Map<String, Any>, codeSnippets: List<String>, insights: String): String =
116+
"""
117+
|You are analyzing a software code repository. You are provided with:
118+
|
119+
|Code Snippets:
120+
|${codeSnippets.joinToString("\n\n")}
121+
|
122+
|Key Insights:
123+
|$insights
124+
|
125+
|Using this information, generate a comprehensive yet accessible summary of the codebase. Your goal is to help a new developer quickly understand the project.
126+
|
127+
|Your summary should include:
128+
|
129+
|1. **Main purpose** of the project
130+
|2. **Core architecture and components**
131+
|3. **Technologies and languages** used
132+
|4. **Key functionality and workflows**
133+
|5. **Potential areas for improvement or refactoring**
134+
|
135+
|Where helpful, include **small illustrative code snippets** from the provided examples to clarify important concepts, structures, or patterns.
136+
|
137+
|Format your response with clear section headings and concise explanations. Assume the reader is technically proficient but unfamiliar with this specific codebase."""
138+
.trimIndent()
121139

122140
private suspend fun sendRequest(url: String, request: OllamaRequest): HttpResponse {
123141
return httpClient.post(url) {
@@ -131,33 +149,6 @@ Provide detailed analysis with specific references to the code where possible. A
131149
}
132150
}
133151

134-
private fun buildSummaryPrompt(
135-
codeStructure: Map<String, Any>,
136-
insights: List<String>,
137-
readmeContent: String,
138-
): String =
139-
"""
140-
You are analyzing a code repository. Based on the following information:
141-
142-
README Content:
143-
${readmeContent.replace("```","~~~")}
144-
145-
Code Structure:
146-
${codeStructure.entries.joinToString("\n") { "${it.key}: ${it.value}" }}
147-
148-
Key Insights:
149-
${insights.joinToString("\n")}
150-
151-
Create a comprehensive summary of this codebase, including:
152-
1. Main purpose of the project
153-
2. Core architecture and components
154-
3. Technologies used
155-
4. Key functionality
156-
5. Potential areas for improvement
157-
158-
Make a summary with code snippets that can help a new developer to understand this codebase.
159-
"""
160-
161152
companion object {
162153
/** Creates a default HTTP client with the appropriate configuration. */
163154
fun defaultHttpClient(): HttpClient =

src/main/kotlin/mcp/code/analysis/service/RepositoryAnalysisService.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ data class RepositoryAnalysisService(
1616
suspend fun analyzeRepository(repoUrl: String, branch: String): String {
1717
return try {
1818
val repoDir = gitService.cloneRepository(repoUrl, branch)
19+
20+
val readmeContent = codeAnalyzer.findReadmeFile(repoDir)
1921
val codeStructure = codeAnalyzer.analyzeStructure(repoDir)
2022
val codeSnippets = codeAnalyzer.collectAllCodeSnippets(repoDir)
21-
val prompt = modelContextService.buildPrompt(codeSnippets)
22-
val response = modelContextService.generateResponse(prompt)
23-
val insights = modelContextService.parseInsights(response)
24-
val readmeContent = codeAnalyzer.findReadmeFile(repoDir)
25-
modelContextService.generateSummary(codeStructure, insights, readmeContent)
23+
24+
val insightsPrompt = modelContextService.buildInsightsPrompt(readmeContent)
25+
val insightsResponse = modelContextService.generateResponse(insightsPrompt)
26+
27+
val summaryPrompt = modelContextService.buildSummaryPrompt(codeStructure, codeSnippets, insightsResponse)
28+
modelContextService.generateResponse(summaryPrompt)
2629
} catch (e: Exception) {
2730
throw Exception("Error analyzing repository: ${e.message}", e)
2831
}

0 commit comments

Comments
 (0)