Class: Aidp::Skills::Composer

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/skills/composer.rb

Overview

Composes skills with templates to create complete prompts

The Composer combines skill content (WHO the agent is and WHAT capabilities they have) with template content (WHEN/HOW to execute a specific task).

Composition structure:

1. Skill content (persona, expertise, philosophy)
2. Separator
3. Template content (task-specific instructions)

Examples:

Basic composition

composer = Composer.new
prompt = composer.compose(
  skill: repository_analyst_skill,
  template: "Analyze the repository..."
)

Template-only (no skill)

prompt = composer.compose(template: "Do this task...")

Constant Summary collapse

SKILL_TEMPLATE_SEPARATOR =

Separator between skill and template content

"\n\n---\n\n"

Instance Method Summary collapse

Instance Method Details

#compose(template:, skill: nil, options: {}) ⇒ String

Compose a skill and template into a complete prompt

Parameters:

  • skill (Skill, nil) (defaults to: nil)

    Skill to include (optional)

  • template (String)

    Template content

  • options (Hash) (defaults to: {})

    Optional parameters for template variable replacement

Returns:

  • (String)

    Composed prompt



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
65
66
67
68
69
70
71
72
73
# File 'lib/aidp/skills/composer.rb', line 36

def compose(template:, skill: nil, options: {})
  Aidp.log_debug(
    "skills",
    "Composing prompt",
    skill_id: skill&.id,
    template_length: template.length,
    options_count: options.size
  )

  # Replace template variables
  rendered_template = render_template(template, options: options)

  # If no skill, return template only
  unless skill
    Aidp.log_debug("skills", "Template-only composition", template_length: rendered_template.length)
    return rendered_template
  end

  # Compose skill + template
  composed = [
    skill.content,
    SKILL_TEMPLATE_SEPARATOR,
    "# Current Task",
    "",
    rendered_template
  ].join("\n")

  Aidp.log_debug(
    "skills",
    "Composed prompt with skill",
    skill_id: skill.id,
    total_length: composed.length,
    skill_length: skill.content.length,
    template_length: rendered_template.length
  )

  composed
end

#compose_multiple(skills:, template:, options: {}) ⇒ String

Compose multiple skills with a template

Note: This is for future use when skill composition is supported. Currently raises an error as it’s not implemented in v1.

Parameters:

  • skills (Array<Skill>)

    Skills to compose

  • template (String)

    Template content

  • options (Hash) (defaults to: {})

    Template variables

Returns:

  • (String)

    Composed prompt

Raises:

  • (NotImplementedError)

    Skill composition not yet supported



117
118
119
# File 'lib/aidp/skills/composer.rb', line 117

def compose_multiple(skills:, template:, options: {})
  raise NotImplementedError, "Multiple skill composition not yet supported in v1"
end

#preview(template:, skill: nil, options: {}) ⇒ Hash

Preview what a composed prompt would look like

Returns a hash with skill content, template content, and full composition for inspection without executing.

Parameters:

  • skill (Skill, nil) (defaults to: nil)

    Skill to include

  • template (String)

    Template content

  • options (Hash) (defaults to: {})

    Template variables

Returns:

  • (Hash)

    Preview with :skill, :template, :composed, :metadata



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/aidp/skills/composer.rb', line 130

def preview(template:, skill: nil, options: {})
  rendered_template = render_template(template, options: options)
  composed = compose(skill: skill, template: template, options: options)

  {
    skill: skill ? {
      id: skill.id,
      name: skill.name,
      content: skill.content,
      length: skill.content.length
    } : nil,
    template: {
      content: rendered_template,
      length: rendered_template.length,
      variables: options.keys
    },
    composed: {
      content: composed,
      length: composed.length
    },
    metadata: {
      has_skill: !skill.nil?,
      separator_used: !skill.nil?,
      unreplaced_vars: extract_placeholders(composed)
    }
  }
end

#render_template(template, options: {}) ⇒ String

Render a template with variable substitution

Replaces {variable} placeholders with values from options hash

Parameters:

  • template (String)

    Template content

  • options (Hash) (defaults to: {})

    Variable values for substitution

Returns:

  • (String)

    Rendered template



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/aidp/skills/composer.rb', line 82

def render_template(template, options: {})
  return template if options.empty?

  # Ensure template is UTF-8 encoded
  template = template.encode("UTF-8", invalid: :replace, undef: :replace) unless template.encoding == Encoding::UTF_8
  rendered = template.dup

  options.each do |key, value|
    placeholder = "{{#{key}}}"
    rendered = rendered.gsub(placeholder, value.to_s)
  end

  # Log if there are unreplaced placeholders
  remaining_placeholders = extract_placeholders(rendered)
  if remaining_placeholders.any?
    Aidp.log_warn(
      "skills",
      "Unreplaced template variables",
      placeholders: remaining_placeholders
    )
  end

  rendered
end