Class: Ace::Assign::Molecules::SkillAssignSourceResolver
- Inherits:
-
Object
- Object
- Ace::Assign::Molecules::SkillAssignSourceResolver
- Defined in:
- lib/ace/assign/molecules/skill_assign_source_resolver.rb
Overview
Resolves assignment metadata from skill frontmatter.
Flow: 1) Find SKILL.md by skill name (e.g., “ace-task-work”) 2) Read workflow binding from skill.execution.workflow (fallback: assign.source) 3) Resolve workflow file from URI 4) Parse workflow assign frontmatter (sub-steps/context)
Constant Summary collapse
- ASSIGN_CAPABLE_KINDS =
%w[workflow orchestration].freeze
Class Method Summary collapse
Instance Method Summary collapse
-
#assign_capable_skill_names ⇒ Array<String>
List assign-capable canonical skills discovered from skill sources.
-
#assign_step_catalog ⇒ Array<Hash>
Build assignment step entries from canonical skills.
- #cache_signature ⇒ Object
- #clear_caches! ⇒ Object
-
#initialize(project_root: nil, skill_paths: nil, workflow_paths: nil) ⇒ SkillAssignSourceResolver
constructor
A new instance of SkillAssignSourceResolver.
-
#resolve_assign_config(skill_name) ⇒ Hash?
Resolve assign config for a skill.
-
#resolve_skill_rendering(skill_name) ⇒ Hash?
Resolve canonical skill rendering details for a skill-backed step.
-
#resolve_source_assign_config(source, step_name: nil, source_skill: nil) ⇒ Hash?
Resolve assign config from canonical source reference.
-
#resolve_source_rendering(source, step_name: nil, source_skill: nil) ⇒ Hash?
Resolve rendering details from canonical source reference.
-
#resolve_step_rendering(step_name) ⇒ Hash?
Resolve canonical rendering details for a public step name.
- #resolve_workflow_assign_config(workflow_source, step_name: nil, source_skill: nil) ⇒ Object
- #resolve_workflow_rendering(workflow_source, step_name: nil, source_skill: nil) ⇒ Object
Constructor Details
#initialize(project_root: nil, skill_paths: nil, workflow_paths: nil) ⇒ SkillAssignSourceResolver
Returns a new instance of SkillAssignSourceResolver.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 52 def initialize(project_root: nil, skill_paths: nil, workflow_paths: nil) @project_root = project_root || Ace::Support::Fs::Molecules::ProjectRootFinder.find_or_current configured_skill_paths = skill_paths || Ace::Assign.config["skill_source_paths"] configured_workflow_paths = workflow_paths || Ace::Assign.config["workflow_source_paths"] canonical_paths = discover_canonical_skill_source_paths canonical_workflow_paths = discover_canonical_workflow_source_paths override_paths = normalize_paths(configured_skill_paths || []) configured_workflow_paths = normalize_paths(configured_workflow_paths || []) @skill_paths = (canonical_paths + override_paths).uniq @workflow_paths = (canonical_workflow_paths + configured_workflow_paths).uniq @skill_index = nil @cache_signature = nil end |
Class Method Details
.cache_store ⇒ Object
31 32 33 34 35 36 37 38 39 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 31 def cache_store @cache_store ||= { frontmatter_and_body_cache: {}, skill_index_cache: {}, assign_capable_skill_names_cache: {}, assign_step_catalog_cache: {}, resolve_wfi_uri_cache: {} } end |
.clear_caches! ⇒ Object
21 22 23 24 25 26 27 28 29 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 21 def clear_caches! @cache_store = { frontmatter_and_body_cache: {}, skill_index_cache: {}, assign_capable_skill_names_cache: {}, assign_step_catalog_cache: {}, resolve_wfi_uri_cache: {} } end |
Instance Method Details
#assign_capable_skill_names ⇒ Array<String>
List assign-capable canonical skills discovered from skill sources.
Assign-capable skills are canonical skills with:
-
skill.kind: workflow|orchestration
-
workflow binding present (skill.execution.workflow or assign.source)
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 121 def assign_capable_skill_names cached = self.class.send(:cached_value, :assign_capable_skill_names_cache, cache_signature) return cached if cached names = skill_index.keys.sort.filter do |skill_name| frontmatter = skill_index_frontmatter(skill_name) next false unless assign_capable_skill_frontmatter?(frontmatter) next false unless public_discovery_skill_frontmatter?(frontmatter) next false unless frontmatter["assign"].is_a?(Hash) validate_workflow_binding!(frontmatter, skill_name) true end self.class.send(:store_cached_value, :assign_capable_skill_names_cache, cache_signature, names) names end |
#assign_step_catalog ⇒ Array<Hash>
Build assignment step entries from canonical skills.
Only steps declared under ‘assign.steps` are emitted here. This keeps canonical skills authoritative for public skill-backed assignment steps, while internal helper steps can continue to use catalog templates.
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 146 def assign_step_catalog cached = self.class.send(:cached_value, :assign_step_catalog_cache, cache_signature) return cached if cached catalog = {} each_assign_capable_skill do |skill_name, frontmatter| steps = frontmatter.dig("assign", "steps") workflow_source = workflow_binding_for_skill_frontmatter(frontmatter) next unless steps.is_a?(Array) steps.each do |step| next unless step.is_a?(Hash) step_name = step["name"]&.to_s&.strip next if step_name.nil? || step_name.empty? next if catalog.key?(step_name) entry = step.dup entry["name"] = step_name entry["source"] = "skill://#{skill_name}" entry["skill"] = skill_name entry["source_skill"] = skill_name entry["workflow"] = workflow_source if workflow_source && !workflow_source.empty? entry["description"] ||= frontmatter["description"] catalog[step_name] = entry end end catalog_values = catalog.values self.class.send(:store_cached_value, :assign_step_catalog_cache, cache_signature, catalog_values) catalog_values end |
#cache_signature ⇒ Object
74 75 76 77 78 79 80 81 82 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 74 def cache_signature @cache_signature ||= begin [ project_root, skill_path_signature, workflow_path_signature ].join("|") end end |
#clear_caches! ⇒ Object
67 68 69 70 71 72 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 67 def clear_caches! @skill_index = nil @assign_capable_skill_names = nil @assign_step_catalog = nil @cache_signature = nil end |
#resolve_assign_config(skill_name) ⇒ Hash?
Resolve assign config for a skill.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 89 def resolve_assign_config(skill_name) skill_path = skill_index[skill_name] || find_skill_by_convention(skill_name) return nil unless skill_path skill_frontmatter = cached_parse_frontmatter_from_file(skill_path) assign_block = skill_frontmatter["assign"] return nil unless assign_block.is_a?(Hash) validate_workflow_binding!(skill_frontmatter, skill_name) if assign_capable_skill_frontmatter?(skill_frontmatter) source = workflow_binding_for_skill_frontmatter(skill_frontmatter) return nil if source.nil? || source.empty? workflow_path = resolve_source_uri(source, skill_name) workflow_frontmatter = cached_parse_frontmatter_from_file(workflow_path) parsed = Atoms::AssignFrontmatterParser.parse(workflow_frontmatter) unless parsed[:valid] raise Error, "Invalid assign frontmatter in '#{workflow_path}' for skill '#{skill_name}': #{parsed[:errors].join("; ")}" end parsed[:config] end |
#resolve_skill_rendering(skill_name) ⇒ Hash?
Resolve canonical skill rendering details for a skill-backed step.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 184 def resolve_skill_rendering(skill_name) skill_path = skill_index[skill_name] || find_skill_by_convention(skill_name) return nil unless skill_path frontmatter, skill_body = cached_parse_frontmatter_and_body_from_file(skill_path) workflow_source = workflow_binding_for_skill_frontmatter(frontmatter) workflow_path = resolve_workflow_path(workflow_source, skill_name) workflow_body = workflow_path ? cached_parse_frontmatter_and_body_from_file(workflow_path).last.to_s.strip : "" { "name" => frontmatter["name"] || skill_name, "description" => frontmatter["description"], "source" => "skill://#{skill_name}", "skill" => skill_name, "workflow" => workflow_source, "workflow_path" => workflow_path, "body" => workflow_body.empty? ? skill_body.to_s.strip : workflow_body } end |
#resolve_source_assign_config(source, step_name: nil, source_skill: nil) ⇒ Hash?
Resolve assign config from canonical source reference.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 259 def resolve_source_assign_config(source, step_name: nil, source_skill: nil) source_ref = source&.to_s&.strip return nil if source_ref.nil? || source_ref.empty? if source_ref.start_with?("skill://") skill_name = source_ref.delete_prefix("skill://").strip return nil if skill_name.empty? return resolve_assign_config(skill_name) end return resolve_workflow_assign_config(source_ref, step_name: step_name, source_skill: source_skill) if source_ref.start_with?("wfi://") raise Error, "Unsupported source '#{source_ref}'. Supported: skill://..., wfi://..." end |
#resolve_source_rendering(source, step_name: nil, source_skill: nil) ⇒ Hash?
Resolve rendering details from canonical source reference.
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 228 def resolve_source_rendering(source, step_name: nil, source_skill: nil) source_ref = source&.to_s&.strip return nil if source_ref.nil? || source_ref.empty? if source_ref.start_with?("skill://") skill_name = source_ref.delete_prefix("skill://").strip return nil if skill_name.empty? return resolve_skill_rendering(skill_name) end return resolve_workflow_rendering(source_ref, step_name: step_name, source_skill: source_skill) if source_ref.start_with?("wfi://") raise Error, "Unsupported source '#{source_ref}'. Supported: skill://..., wfi://..." end |
#resolve_step_rendering(step_name) ⇒ Hash?
Resolve canonical rendering details for a public step name.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 279 def resolve_step_rendering(step_name) entry = assign_step_catalog.find { |step| step["name"] == step_name } return nil unless entry source_rendering = resolve_source_rendering( entry["source"] || entry["workflow"], step_name: entry["name"], source_skill: entry["source_skill"] || entry["skill"] ) if source_rendering merged = entry.merge(source_rendering) merged["name"] = entry["name"] if entry["name"] merged["description"] = entry["description"] if entry["description"] return merged end rendering = resolve_skill_rendering(entry["skill"]) return nil unless rendering entry.merge(rendering) end |
#resolve_workflow_assign_config(workflow_source, step_name: nil, source_skill: nil) ⇒ Object
244 245 246 247 248 249 250 251 252 253 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 244 def resolve_workflow_assign_config(workflow_source, step_name: nil, source_skill: nil) rendering = resolve_workflow_rendering(workflow_source, step_name: step_name, source_skill: source_skill) return nil unless rendering && rendering["workflow_path"] workflow_frontmatter = cached_parse_frontmatter_from_file(rendering["workflow_path"]) parsed = Atoms::AssignFrontmatterParser.parse(workflow_frontmatter) return nil unless parsed[:valid] parsed[:config] end |
#resolve_workflow_rendering(workflow_source, step_name: nil, source_skill: nil) ⇒ Object
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/ace/assign/molecules/skill_assign_source_resolver.rb', line 205 def resolve_workflow_rendering(workflow_source, step_name: nil, source_skill: nil) workflow_ref = workflow_source&.to_s&.strip return nil if workflow_ref.nil? || workflow_ref.empty? workflow_path = resolve_workflow_path(workflow_ref, step_name || source_skill || workflow_ref) return nil unless workflow_path frontmatter, body = cached_parse_frontmatter_and_body_from_file(workflow_path) { "name" => step_name || frontmatter["name"], "description" => frontmatter["description"], "source" => workflow_ref, "workflow" => workflow_ref, "workflow_path" => workflow_path, "source_skill" => source_skill, "body" => body.to_s.strip } end |