From 81a21bfe57be62fd1de8525d9ce5649a5e10cb75 Mon Sep 17 00:00:00 2001 From: kami Date: Thu, 11 Jun 2026 14:32:04 +0400 Subject: [PATCH] feat(memory): GDScript symbol extraction in RepoMapIndexer Adds 'gd' to SYMBOL_PATTERNS (func/class_name/class/signal/enum at column 0, optional static prefix). Because INDEXED_EXTENSIONS derives from the pattern keys, .gd files now also participate in WorkspaceStateProbe fingerprinting, making fp: state keys content-sensitive for Godot repos. --- .../apps/server/memory/RepoMapIndexer.kt | 2 ++ .../apps/server/memory/RepoMapIndexerTest.kt | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/memory/RepoMapIndexer.kt b/apps/server/src/main/kotlin/com/correx/apps/server/memory/RepoMapIndexer.kt index d027ba8f..71d30b44 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/memory/RepoMapIndexer.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/memory/RepoMapIndexer.kt @@ -82,6 +82,8 @@ class RepoMapIndexer( "go" to Regex("""(?m)^\s*(?:func\s+(?:\([^)]*\)\s*)?|type\s+)(?[A-Za-z_][A-Za-z0-9_]*)"""), "js" to Regex("""(?m)^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class)\s+(?[A-Za-z_$][A-Za-z0-9_$]*)"""), "ts" to Regex("""(?m)^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+(?[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+(?[A-Za-z_][A-Za-z0-9_]*)"""), ) } } diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/memory/RepoMapIndexerTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/memory/RepoMapIndexerTest.kt index dc91f4a3..9bf79d90 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/memory/RepoMapIndexerTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/memory/RepoMapIndexerTest.kt @@ -41,6 +41,40 @@ class RepoMapIndexerTest { assertTrue(entries.first { it.path == "Old.kt" }.symbols.contains("Old")) } + @Test + fun `extracts GDScript top-level symbols`(@TempDir root: Path) { + root.resolve("player.gd").writeText( + """ + class_name Player + extends CharacterBody2D + + signal health_changed(new_health) + + enum State { IDLE, RUNNING } + + static func create() -> Player: + return Player.new() + + func take_damage(amount: int) -> void: + health -= amount + + class InnerHelper: + func help(): + pass + """.trimIndent(), + ) + + val entry = RepoMapIndexer().index(root).single() + assertEquals("player.gd", entry.path) + assertTrue( + entry.symbols.containsAll( + listOf("Player", "health_changed", "State", "create", "take_damage", "InnerHelper"), + ), + "missing symbols in: ${entry.symbols}", + ) + assertFalse(entry.symbols.contains("help"), "indented inner method must not be matched") + } + @Test fun `empty or missing root yields empty map`(@TempDir root: Path) { assertTrue(RepoMapIndexer().index(root).isEmpty())