Skip to content

Commit ac4b42d

Browse files
committed
feat: Support loading Cursor Rules
1 parent d0a1cda commit ac4b42d

File tree

5 files changed

+92
-6
lines changed

5 files changed

+92
-6
lines changed

build.sbt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ lazy val root = project
1212
"io.circe" %% "circe-core" % "0.14.5",
1313
"io.circe" %% "circe-generic" % "0.14.5",
1414
"io.circe" %% "circe-parser" % "0.14.5",
15-
"org.jline" % "jline" % "3.21.0"
15+
"org.jline" % "jline" % "3.21.0",
16+
"com.github.scopt" %% "scopt" % "4.1.0"
1617
)
1718
)
1819

1920
enablePlugins(JavaAppPackaging)
2021
packageName := "supercoder"
21-
mainClass := Some("com.supercoder.run")
22+
mainClass := Some("com.supercoder.Main")
2223
maintainer := "[email protected]"

src/main/scala/com/supercoder/Main.scala

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,19 @@ package com.supercoder
22

33
import com.supercoder.ui.TerminalChat
44
import com.supercoder.agents.CoderAgent
5+
import com.supercoder.config.ArgsParser
6+
import com.supercoder.lib.CursorRulesLoader
57

68
object Main {
79
def main(args: Array[String]): Unit = {
8-
val agent = new CoderAgent()
9-
TerminalChat.run(agent)
10+
ArgsParser.parse(args) match {
11+
case Some(config) =>
12+
val additionalPrompt = if config.useCursorRules then CursorRulesLoader.loadRules() else ""
13+
val agent = new CoderAgent(additionalPrompt)
14+
TerminalChat.run(agent)
15+
case None =>
16+
// invalid options, usage error message is already printed by scopt
17+
sys.exit(1)
18+
}
1019
}
1120
}

src/main/scala/com/supercoder/agents/CoderAgent.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ The discussion is about the code of the current project/folder. Always use the r
2121
project if you are unsure before giving answer.
2222
"""
2323

24-
class CoderAgent()
25-
extends BaseChatAgent(coderAgentPrompt) {
24+
class CoderAgent(additionalPrompt: String = "")
25+
extends BaseChatAgent(coderAgentPrompt + additionalPrompt) {
2626

2727
final val availableTools = List(
2828
CodeSearchTool,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.supercoder.config
2+
3+
import scopt.OParser
4+
5+
case class Config(useCursorRules: Boolean = false)
6+
7+
object ArgsParser {
8+
def parse(args: Array[String]): Option[Config] = {
9+
val builder = OParser.builder[Config]
10+
val parser = {
11+
import builder._
12+
OParser.sequence(
13+
programName("SuperCoder"),
14+
opt[String]('c', "use-cursor-rules")
15+
.action((x, c) => c.copy(useCursorRules = (x == "true")))
16+
.text("use Cursor rules for the agent"),
17+
help("help").text("prints this usage text")
18+
)
19+
}
20+
OParser.parse(parser, args, Config())
21+
}
22+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.supercoder.lib
2+
3+
import java.nio.file.{Files, Paths, Path}
4+
import java.nio.charset.StandardCharsets
5+
import scala.jdk.CollectionConverters._
6+
7+
object CursorRulesLoader {
8+
/**
9+
* Loads all *.mdc files in the project's .cursor/rules directory, then reads the .cursorrules file in the project's root,
10+
* combines them and returns them as a single string.
11+
*
12+
* The returned string consists of the content of all .mdc files (separated by newlines) followed by a newline and the
13+
* content of the .cursorrules file.
14+
*/
15+
def loadRules(): String = {
16+
// Determine the project root directory
17+
val projectRoot = Paths.get(System.getProperty("user.dir"))
18+
19+
// Define the path for .cursorrules file in the project's root
20+
val cursorRulesFile: Path = projectRoot.resolve(".cursorrules")
21+
22+
// Define the directory path for .cursor/rules
23+
val rulesDir: Path = projectRoot.resolve(Paths.get(".cursor", "rules"))
24+
25+
// Initialize content for .mdc files
26+
val mdcContent: String = if (Files.exists(rulesDir) && Files.isDirectory(rulesDir)) {
27+
// List all files in the rulesDir ending with .mdc
28+
val mdcFiles = Files.list(rulesDir).iterator().asScala
29+
.filter(path => Files.isRegularFile(path) && path.getFileName.toString.endsWith(".mdc"))
30+
.toList
31+
// Read each file and concatenate contents separated by newline
32+
mdcFiles.map { path =>
33+
new String(Files.readAllBytes(path), StandardCharsets.UTF_8)
34+
}.mkString("\n")
35+
} else {
36+
""
37+
}
38+
39+
// Read the .cursorrules file if it exists
40+
val mainRulesContent: String = if (Files.exists(cursorRulesFile) && Files.isRegularFile(cursorRulesFile)) {
41+
new String(Files.readAllBytes(cursorRulesFile), StandardCharsets.UTF_8)
42+
} else {
43+
""
44+
}
45+
46+
// Combine the content from .mdc files and .cursorrules file
47+
// Separating the two parts with a newline if both are non-empty
48+
if (mdcContent.nonEmpty && mainRulesContent.nonEmpty) {
49+
mdcContent + "\n" + mainRulesContent
50+
} else {
51+
mdcContent + mainRulesContent
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)