feat(memory): markdown heading extraction in RepoMapIndexer
h1-h3 headings become the file's symbols, so design docs and GDDs are embedded and retrievable alongside code. .md files now also participate in workspace fingerprinting (INDEXED_EXTENSIONS derives from pattern keys).
This commit is contained in:
@@ -84,6 +84,8 @@ class RepoMapIndexer(
|
||||
"ts" to Regex("""(?m)^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+(?<name>[A-Za-z_$][A-Za-z0-9_$]*)"""),
|
||||
// GDScript: column-0 declarations only, so indented inner-class members never match.
|
||||
"gd" to Regex("""(?m)^(?:static\s+)?(?:func|class_name|class|signal|enum)\s+(?<name>[A-Za-z_][A-Za-z0-9_]*)"""),
|
||||
// Markdown: h1–h3 headings as "symbols" — deeper levels are noise relative to retrieval value.
|
||||
"md" to Regex("""(?m)^#{1,3}\s+(?<name>.+?)\s*$"""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class RepoMapIndexerTest {
|
||||
root.resolve("build/Generated.kt").writeText("class Generated\n")
|
||||
|
||||
// Non-source file must be ignored.
|
||||
root.resolve("README.md").writeText("# hi\n")
|
||||
root.resolve("notes.txt").writeText("scratch\n")
|
||||
|
||||
val entries = RepoMapIndexer(maxDepth = 4, ignoreGlobs = listOf("build")).index(root)
|
||||
|
||||
@@ -31,7 +31,7 @@ class RepoMapIndexerTest {
|
||||
assertTrue(paths.contains("New.kt"))
|
||||
assertTrue(paths.contains("Old.kt"))
|
||||
assertFalse(paths.any { it.contains("build") }, "ignored dir leaked: $paths")
|
||||
assertFalse(paths.contains("README.md"), "non-source file leaked")
|
||||
assertFalse(paths.contains("notes.txt"), "non-source file leaked")
|
||||
|
||||
// Most-recently-modified ranks first.
|
||||
assertEquals("New.kt", entries.first().path)
|
||||
@@ -75,6 +75,30 @@ class RepoMapIndexerTest {
|
||||
assertFalse(entry.symbols.contains("help"), "indented inner method must not be matched")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extracts markdown headings as symbols`(@TempDir root: Path) {
|
||||
root.resolve("design.md").writeText(
|
||||
"""
|
||||
# Game Design Document
|
||||
intro text with an inline # hash
|
||||
|
||||
## Combat System
|
||||
body
|
||||
|
||||
### Damage Formula
|
||||
#### Too Deep To Matter
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val entry = RepoMapIndexer().index(root).single()
|
||||
assertEquals("design.md", entry.path)
|
||||
assertTrue(
|
||||
entry.symbols.containsAll(listOf("Game Design Document", "Combat System", "Damage Formula")),
|
||||
"missing headings in: ${entry.symbols}",
|
||||
)
|
||||
assertFalse(entry.symbols.any { it.contains("Too Deep") }, "h4+ headings must not be matched")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty or missing root yields empty map`(@TempDir root: Path) {
|
||||
assertTrue(RepoMapIndexer().index(root).isEmpty())
|
||||
|
||||
Reference in New Issue
Block a user