Class: Aidp::StyleGuide::Selector

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/style_guide/selector.rb

Overview

Deterministic selector for STYLE_GUIDE sections based on task context

This class provides intelligent section selection from STYLE_GUIDE.md based on keywords in the task context. For providers with their own instruction files (Claude, GitHub Copilot), it skips style guide injection.

Examples:

Basic usage

selector = Selector.new(project_dir: "/path/to/project")
content = selector.select_sections(keywords: ["testing", "error"])

Check if provider needs style guide

selector.provider_needs_style_guide?("cursor")    # => true
selector.provider_needs_style_guide?("claude")    # => false

Constant Summary collapse

PROVIDERS_WITH_INSTRUCTION_FILES =

Providers that have their own instruction files and don’t need the style guide injected into prompts

%w[
  claude
  anthropic
  github_copilot
].freeze
SECTION_MAPPING =

Mapping of keywords/topics to STYLE_GUIDE.md line ranges Each entry is [start_line, end_line, description]

{
  # Core Engineering
  "code_organization" => [[25, 117, "Code Organization"]],
  "class" => [[25, 117, "Code Organization"], [217, 236, "Sandi Metz Rules"]],
  "method" => [[224, 229, "Method Design"], [217, 236, "Sandi Metz Rules"]],
  "composition" => [[29, 35, "Composition over Inheritance"]],
  "inheritance" => [[29, 35, "Composition over Inheritance"]],
  "small_objects" => [[25, 117, "Code Organization"]],
  "single_responsibility" => [[25, 117, "Code Organization"]],

  # Sandi Metz
  "sandi_metz" => [[217, 236, "Sandi Metz Rules"]],
  "parameter" => [[231, 236, "Parameter Limits"]],

  # Ruby Conventions
  "ruby" => [[259, 278, "Ruby Conventions"], [446, 498, "Ruby Version Management"]],
  "naming" => [[51, 56, "Naming Conventions"]],
  "convention" => [[259, 278, "Ruby Conventions"]],
  "style" => [[259, 278, "Ruby Conventions"]],
  "require" => [[263, 266, "Require Practices"]],
  "mise" => [[446, 498, "Ruby Version Management"]],
  "version" => [[446, 498, "Ruby Version Management"]],

  # Feature Organization
  "feature" => [[58, 116, "Feature Organization by Purpose"]],
  "workflow" => [[58, 116, "Feature Organization by Purpose"]],
  "template" => [[118, 210, "Template/Skill Separation"]],
  "skill" => [[118, 210, "Template/Skill Separation"]],

  # Logging
  "logging" => [[287, 430, "Logging Practices"]],
  "log" => [[287, 430, "Logging Practices"]],
  "debug" => [[287, 430, "Logging Practices"]],

  # ZFC - Zero Framework Cognition
  "zfc" => [[500, 797, "Zero Framework Cognition (ZFC)"]],
  "zero_framework" => [[500, 797, "Zero Framework Cognition (ZFC)"]],
  "ai_decision" => [[500, 797, "Zero Framework Cognition (ZFC)"]],
  "decision_engine" => [[500, 797, "Zero Framework Cognition (ZFC)"]],

  # AGD - AI-Generated Determinism
  "agd" => [[798, 855, "AI-Generated Determinism (AGD)"]],
  "determinism" => [[798, 855, "AI-Generated Determinism (AGD)"]],
  "config_time" => [[798, 855, "AI-Generated Determinism (AGD)"]],

  # TTY / TUI
  "tty" => [[856, 1105, "TTY Toolkit Guidelines"]],
  "tui" => [[856, 1105, "TTY Toolkit Guidelines"]],
  "ui" => [[856, 1105, "TTY Toolkit Guidelines"]],
  "prompt" => [[856, 1105, "TTY Toolkit Guidelines"], [936, 972, "TTY Output Practices"]],
  "progress" => [[856, 1105, "TTY Toolkit Guidelines"]],
  "spinner" => [[856, 1105, "TTY Toolkit Guidelines"]],
  "table" => [[856, 1105, "TTY Toolkit Guidelines"]],
  "select" => [[856, 1105, "TTY Toolkit Guidelines"]],

  # Testing
  "testing" => [[1873, 2112, "Testing Guidelines"]],
  "test" => [[1873, 2112, "Testing Guidelines"], [1550, 1800, "Test Coverage Philosophy"]],
  "spec" => [[1873, 2112, "Testing Guidelines"]],
  "rspec" => [[1873, 2112, "Testing Guidelines"]],
  "mock" => [[1873, 2112, "Testing Guidelines"], [1930, 2012, "Dependency Injection"]],
  "stub" => [[1873, 2112, "Testing Guidelines"]],
  "coverage" => [[1550, 1800, "Test Coverage Philosophy"]],
  "pending" => [[1816, 1870, "Pending Specs Policy"]],
  "expect_script" => [[1173, 1234, "expect Scripts for TUI"]],
  "tmux" => [[1203, 1295, "tmux Testing"]],
  "time_test" => [[1656, 1690, "Time-based Testing"]],
  "fork" => [[1718, 1778, "Forked Process Testing"]],
  "encoding" => [[1780, 1813, "String Encoding"]],
  "dependency_injection" => [[1930, 2012, "Dependency Injection"]],

  # Error Handling
  "error" => [[2113, 2168, "Error Handling"], [280, 286, "Error Handling Basics"]],
  "exception" => [[2113, 2168, "Error Handling"], [2130, 2140, "Error Class Pattern"]],
  "rescue" => [[280, 286, "Error Handling Basics"], [2113, 2168, "Error Handling"]],

  # Concurrency
  "concurrency" => [[2170, 2185, "Concurrency & Threads"]],
  "thread" => [[2170, 2185, "Concurrency & Threads"]],
  "async" => [[2170, 2185, "Concurrency & Threads"]],

  # Performance
  "performance" => [[2206, 2232, "Performance"]],
  "optimization" => [[2206, 2232, "Performance"]],
  "cache" => [[2206, 2232, "Performance"]],

  # Security
  "security" => [[2233, 2272, "Security & Safety"]],
  "safety" => [[2233, 2272, "Security & Safety"]],
  "validation" => [[2233, 2272, "Security & Safety"]],

  # Backward Compatibility / Pre-release
  "backward_compatibility" => [[2273, 2472, "Backward Compatibility"]],
  "deprecation" => [[2273, 2472, "Backward Compatibility"]],
  "legacy" => [[2273, 2472, "Backward Compatibility"]],

  # Commit Hygiene
  "commit" => [[2476, 2482, "Commit Hygiene"]],
  "git" => [[2476, 2482, "Commit Hygiene"]],

  # Prompt Optimization
  "prompt_optimization" => [[2523, 2854, "Prompt Optimization"]],
  "fragment" => [[2523, 2854, "Prompt Optimization"]],

  # Task Filing
  "task" => [[2856, 2890, "Task Filing"]],
  "tasklist" => [[2856, 2890, "Task Filing"]]
}.freeze
CORE_SECTIONS =

Default sections to always include (core rules)

[
  [25, 117, "Code Organization"],
  [217, 236, "Sandi Metz Rules"],
  [287, 430, "Logging Practices"]
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_dir:) ⇒ Selector

Returns a new instance of Selector.



147
148
149
150
151
# File 'lib/aidp/style_guide/selector.rb', line 147

def initialize(project_dir:)
  @project_dir = project_dir
  @style_guide_content = nil
  @style_guide_lines = nil
end

Instance Attribute Details

#project_dirObject (readonly)

Returns the value of attribute project_dir.



145
146
147
# File 'lib/aidp/style_guide/selector.rb', line 145

def project_dir
  @project_dir
end

Instance Method Details

#available_keywordsArray<String>

Get all available section names

Returns:

  • (Array<String>)

    List of all section mapping keys



216
217
218
# File 'lib/aidp/style_guide/selector.rb', line 216

def available_keywords
  SECTION_MAPPING.keys.sort
end

#extract_keywords(context) ⇒ Array<String>

Extract keywords from task context

Parameters:

  • context (Hash, String)

    Task context (description, affected files, etc.)

Returns:

  • (Array<String>)

    Extracted keywords



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/aidp/style_guide/selector.rb', line 195

def extract_keywords(context)
  text = context.is_a?(Hash) ? context.values.join(" ") : context.to_s
  text_lower = text.downcase

  keywords = []

  SECTION_MAPPING.keys.each do |keyword|
    # Convert keyword format (snake_case to spaces/variations)
    patterns = build_patterns(keyword)
    keywords << keyword if patterns.any? { |p| text_lower.include?(p) }
  end

  Aidp.log_debug("style_guide_selector", "keywords_extracted",
    input_length: text.length, keywords_found: keywords.size)

  keywords.uniq
end

#preview_selection(keywords) ⇒ Array<Hash>

Get information about what sections would be selected for given keywords

Parameters:

  • keywords (Array<String>)

    Keywords to check

Returns:

  • (Array<Hash>)

    Section info with line ranges and descriptions



231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/aidp/style_guide/selector.rb', line 231

def preview_selection(keywords)
  sections = gather_sections(keywords, false)
  merged = merge_and_sort_ranges(sections)

  merged.map do |start_line, end_line, description|
    {
      start_line: start_line,
      end_line: end_line,
      description: description,
      estimated_lines: end_line - start_line + 1
    }
  end
end

#provider_needs_style_guide?(provider_name) ⇒ Boolean

Check if a provider needs style guide injection

Parameters:

  • provider_name (String)

    Name of the provider

Returns:

  • (Boolean)

    true if style guide should be injected



157
158
159
160
161
162
# File 'lib/aidp/style_guide/selector.rb', line 157

def provider_needs_style_guide?(provider_name)
  return true if provider_name.nil?

  normalized = provider_name.to_s.downcase.strip
  !PROVIDERS_WITH_INSTRUCTION_FILES.include?(normalized)
end

#select_sections(keywords: [], include_core: true, max_lines: nil) ⇒ String

Select relevant sections from STYLE_GUIDE.md based on keywords

Parameters:

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

    Keywords to match against

  • include_core (Boolean) (defaults to: true)

    Whether to include core sections

  • max_lines (Integer, nil) (defaults to: nil)

    Maximum lines to return (nil for unlimited)

Returns:

  • (String)

    Combined content of selected sections



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/aidp/style_guide/selector.rb', line 170

def select_sections(keywords: [], include_core: true, max_lines: nil)
  Aidp.log_debug("style_guide_selector", "selecting_sections",
    keywords: keywords, include_core: include_core, max_lines: max_lines)

  return "" unless style_guide_exists?

  # Gather all matching sections
  sections = gather_sections(keywords, include_core)

  # Merge overlapping ranges and sort
  merged_ranges = merge_and_sort_ranges(sections)

  # Extract content from ranges
  content = extract_content(merged_ranges, max_lines)

  Aidp.log_debug("style_guide_selector", "sections_selected",
    section_count: merged_ranges.size, content_lines: content.lines.count)

  content
end

#style_guide_exists?Boolean

Check if style guide file exists

Returns:

  • (Boolean)


223
224
225
# File 'lib/aidp/style_guide/selector.rb', line 223

def style_guide_exists?
  File.exist?(style_guide_path)
end