Class: Crimson::SkillRouter

Inherits:
Object
  • Object
show all
Defined in:
lib/crimson/skill_router.rb

Overview

Routes user intents to skills based on trigger keyword matching. Supports auto-inject skills triggered by tool usage and domain-based priority sorting.

Defined Under Namespace

Classes: Domain

Constant Summary collapse

REPO_SKILLS_DIR =

Directory where bundled skills are stored in the repository.

File.expand_path("../../skills", __dir__)
DOMAINS =

Built-in skill domains with their priority levels.

{
  engineering: Domain.new(name: "engineering", priority: 10),
  analysis:    Domain.new(name: "analysis",    priority: 5),
  communication: Domain.new(name: "communication", priority: 5),
  safety:      Domain.new(name: "safety",      priority: 20),
}.freeze
MAX_CONDITIONAL_SKILLS =
2

Instance Method Summary collapse

Constructor Details

#initialize(skills_dirs: nil) ⇒ SkillRouter

Returns a new instance of SkillRouter.

Parameters:

  • skills_dirs (Array<String>, nil) (defaults to: nil)

    directories to search for skill markdown files



24
25
26
27
28
29
# File 'lib/crimson/skill_router.rb', line 24

def initialize(skills_dirs: nil)
  @skills_dirs = skills_dirs || [REPO_SKILLS_DIR]
  @manifests = {}
  @skill_paths = {}
  load_manifests
end

Instance Method Details

#load_skill(name) ⇒ String?

Load a skill’s content (with front matter stripped).

Parameters:

  • name (String)

    skill name

Returns:

  • (String, nil)

    skill content or nil if not found



69
70
71
72
73
74
# File 'lib/crimson/skill_router.rb', line 69

def load_skill(name)
  path = @skill_paths[name.to_sym]
  return nil unless path && File.exist?(path)
  content = File.read(path)
  strip_front_matter(content)
end

#resolve(user_message, tools_invoked: []) ⇒ Array<String>

Resolve which skills are relevant to a user message.

Parameters:

  • user_message (String)

    the user’s input

  • tools_invoked (Array<String>) (defaults to: [])

    tools used in the current turn

Returns:

  • (Array<String>)

    list of active skill names (always includes “coding”)



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/crimson/skill_router.rb', line 35

def resolve(user_message, tools_invoked: [])
  lower = user_message.to_s.downcase.strip
  matched = []

  @manifests.each do |name, manifest|
    next if manifest[:auto_inject]
    next unless triggers_match?(lower, manifest[:triggers])
    matched << { name: name, priority: manifest[:domain_priority], domain: manifest[:domain] }
  end

  matched.sort_by! { |s| -s[:priority] }

  result = ["coding"]
  seen_domains = Set.new

  matched.each do |skill|
    break if result.length >= MAX_CONDITIONAL_SKILLS + 1
    next if seen_domains.include?(skill[:domain])
    result << skill[:name].to_s
    seen_domains << skill[:domain]
  end

  @manifests.each do |name, manifest|
    next unless manifest[:auto_inject]
    next unless (tools_invoked & manifest[:auto_inject_tools]).any?
    result << name.to_s unless result.include?(name.to_s)
  end

  result
end

#skill_namesArray<String>

Returns all discovered skill names.

Returns:

  • (Array<String>)

    all discovered skill names



77
78
79
# File 'lib/crimson/skill_router.rb', line 77

def skill_names
  @manifests.keys.map(&:to_s)
end