godot-skill-discovery

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Skill Discovery

技能发现

Skill indexing, metadata parsing, and search define efficient skill library navigation.
技能索引、元数据解析和搜索是实现高效技能库导航的核心。

Available Scripts

可用脚本

skill_index_generator.gd

skill_index_generator.gd

Expert skill indexer that parses SKILL.md files and generates searchable metadata.
专业的技能索引器,可解析SKILL.md文件并生成可搜索的元数据。

NEVER Do in Skill Discovery

技能发现中的绝对禁忌

  • NEVER rely on filename for skill identification
    filesystem-advanced.md
    vs
    SKILL.md
    ? Use frontmatter
    name
    field as source of truth, not filename.
  • NEVER skip keyword extraction — Skill without keywords? Impossible to discover via search. MUST extract from description OR maintain keyword list.
  • NEVER cache without invalidation — Skill index cached, SKILL.md updated? Stale results. Invalidate cache on file modification OR version changes.
  • NEVER use full-text search without ranking — Searching 100 skills for "input"? Returns everything. Use TF-IDF OR keyword weighting for relevance.
  • NEVER forget to handle missing frontmatter — Malformed SKILL.md without
    ---
    delimiter? Parser crash. Validate YAML frontmatter before parsing.

  • 绝对不要依赖文件名进行技能识别 —— 比如
    filesystem-advanced.md
    SKILL.md
    该选哪个?请使用前置元数据(frontmatter)中的
    name
    字段作为真实来源,而非文件名。
  • 绝对不要跳过关键词提取 —— 没有关键词的技能?根本无法通过搜索发现。必须从描述中提取关键词,或维护关键词列表。
  • 绝对不要在无失效机制的情况下缓存 —— 技能索引已缓存,但SKILL.md已更新?会导致结果过时。需在文件修改或版本变更时使缓存失效。
  • 绝对不要使用无排序的全文搜索 —— 搜索100个技能中的“input”?会返回所有相关内容。请使用TF-IDF或关键词加权来实现相关性排序。
  • 绝对不要忽略对缺失前置元数据的处理 —— 格式错误的SKILL.md没有
    ---
    分隔符?会导致解析器崩溃。解析前请先验证YAML前置元数据。

Skill Metadata Format

技能元数据格式

yaml
---
name: skill-name
description: Expert blueprint for X including [features]. Use when [scenarios]. Keywords topic, action, domain.
---
yaml
---
name: skill-name
description: Expert blueprint for X including [features]. Use when [scenarios]. Keywords topic, action, domain.
---

Indexing Pattern

索引模式

gdscript
undefined
gdscript
undefined

skill_indexer.gd

skill_indexer.gd

class_name SkillIndexer extends RefCounted
var skill_registry: Dictionary = {}
func index_skills(skills_dir: String) -> void: var dir := DirAccess.open(skills_dir) if not dir: return
dir.list_dir_begin()
var file_name := dir.get_next()

while file_name != "":
    if dir.current_is_dir():
        var skill_path := skills_dir.path_join(file_name).path_join("SKILL.md")
        if FileAccess.file_exists(skill_path):
            index_skill(skill_path)
    file_name = dir.get_next()
func index_skill(path: String) -> void: var file := FileAccess.open(path, FileAccess.READ) if not file: return
var content := file.get_as_text()
var metadata := parse_frontmatter(content)

if metadata.has("name"):
    skill_registry[metadata.name] = {
        "path": path,
        "description": metadata.get("description", ""),
        "keywords": extract_keywords(metadata.get("description", ""))
    }
func parse_frontmatter(content: String) -> Dictionary: var lines := content.split("\n") if lines[0].strip_edges() != "---": return {}
var frontmatter_lines: Array[String] = []
for i in range(1, lines.size()):
    if lines[i].strip_edges() == "---":
        break
    frontmatter_lines.append(lines[i])

var metadata := {}
for line in frontmatter_lines:
    var parts := line.split(":", true, 1)
    if parts.size() == 2:
        metadata[parts[0].strip_edges()] = parts[1].strip_edges()

return metadata
func search_skills(query: String) -> Array[Dictionary]: var results: Array[Dictionary] = [] var query_lower := query.to_lower()
for skill_name in skill_registry:
    var skill_data := skill_registry[skill_name]
    var relevance := 0.0
    
    # Check name match
    if skill_name.to_lower().contains(query_lower):
        relevance += 10.0
    
    # Check description match
    if skill_data.description.to_lower().contains(query_lower):
        relevance += 5.0
    
    # Check keyword match
    for keyword in skill_data.keywords:
        if keyword.to_lower() == query_lower:
            relevance += 20.0  # Exact keyword match
        elif keyword.to_lower().contains(query_lower):
            relevance += 3.0
    
    if relevance > 0:
        results.append({
            "name": skill_name,
            "relevance": relevance,
            "data": skill_data
        })

# Sort by relevance
results.sort_custom(func(a, b): return a.relevance > b.relevance)
return results
func extract_keywords(description: String) -> Array[String]: var keywords: Array[String] = []
# Extract from "Keywords X, Y, Z" pattern
var keyword_marker := "Keywords "
var keyword_index := description.find(keyword_marker)
if keyword_index != -1:
    var keyword_section := description.substr(keyword_index + keyword_marker.length())
    var parts := keyword_section.split(".", true, 1)
    var keyword_str := parts[0] if parts.size() > 0 else keyword_section
    
    for word in keyword_str.split(","):
        keywords.append(word.strip_edges())

return keywords
undefined
class_name SkillIndexer extends RefCounted
var skill_registry: Dictionary = {}
func index_skills(skills_dir: String) -> void: var dir := DirAccess.open(skills_dir) if not dir: return
dir.list_dir_begin()
var file_name := dir.get_next()

while file_name != "":
    if dir.current_is_dir():
        var skill_path := skills_dir.path_join(file_name).path_join("SKILL.md")
        if FileAccess.file_exists(skill_path):
            index_skill(skill_path)
    file_name = dir.get_next()
func index_skill(path: String) -> void: var file := FileAccess.open(path, FileAccess.READ) if not file: return
var content := file.get_as_text()
var metadata := parse_frontmatter(content)

if metadata.has("name"):
    skill_registry[metadata.name] = {
        "path": path,
        "description": metadata.get("description", ""),
        "keywords": extract_keywords(metadata.get("description", ""))
    }
func parse_frontmatter(content: String) -> Dictionary: var lines := content.split("\n") if lines[0].strip_edges() != "---": return {}
var frontmatter_lines: Array[String] = []
for i in range(1, lines.size()):
    if lines[i].strip_edges() == "---":
        break
    frontmatter_lines.append(lines[i])

var metadata := {}
for line in frontmatter_lines:
    var parts := line.split(":", true, 1)
    if parts.size() == 2:
        metadata[parts[0].strip_edges()] = parts[1].strip_edges()

return metadata
func search_skills(query: String) -> Array[Dictionary]: var results: Array[Dictionary] = [] var query_lower := query.to_lower()
for skill_name in skill_registry:
    var skill_data := skill_registry[skill_name]
    var relevance := 0.0
    
    # Check name match
    if skill_name.to_lower().contains(query_lower):
        relevance += 10.0
    
    # Check description match
    if skill_data.description.to_lower().contains(query_lower):
        relevance += 5.0
    
    # Check keyword match
    for keyword in skill_data.keywords:
        if keyword.to_lower() == query_lower:
            relevance += 20.0  # Exact keyword match
        elif keyword.to_lower().contains(query_lower):
            relevance += 3.0
    
    if relevance > 0:
        results.append({
            "name": skill_name,
            "relevance": relevance,
            "data": skill_data
        })

# Sort by relevance
results.sort_custom(func(a, b): return a.relevance > b.relevance)
return results
func extract_keywords(description: String) -> Array[String]: var keywords: Array[String] = []
# Extract from "Keywords X, Y, Z" pattern
var keyword_marker := "Keywords "
var keyword_index := description.find(keyword_marker)
if keyword_index != -1:
    var keyword_section := description.substr(keyword_index + keyword_marker.length())
    var parts := keyword_section.split(".", true, 1)
    var keyword_str := parts[0] if parts.size() > 0 else keyword_section
    
    for word in keyword_str.split(","):
        keywords.append(word.strip_edges())

return keywords
undefined

Best Practices

最佳实践

  1. Version Skill Index — Include skill version in metadata for compatibility checks
  2. Cache Aggressively — Parse SKILL.md on index build, cache results for fast search
  3. Support Fuzzy Matching — Allow typos in search (e.g., Levenshtein distance)
  4. Category Grouping — Organize skills by category for browsing (2D, 3D, Genre, etc.)
  1. 为技能索引添加版本控制 —— 在元数据中包含技能版本,用于兼容性检查
  2. 积极缓存 —— 在索引构建时解析SKILL.md,缓存结果以实现快速搜索
  3. 支持模糊匹配 —— 允许搜索中的拼写错误(例如,使用Levenshtein距离算法)
  4. 按类别分组 —— 按类别(2D、3D、类型等)组织技能,便于浏览

Reference

参考

  • Related:
    godot-project-foundations
    ,
    godot-gdscript-mastery
  • 相关内容:
    godot-project-foundations
    ,
    godot-gdscript-mastery

Related

相关

  • Master Skill: godot-master
  • 主技能:godot-master