Class: Aidp::Metadata::Scanner

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/metadata/scanner.rb

Overview

Scans directories for tool files and extracts metadata

Recursively finds all .md files in configured directories, parses their metadata, and returns a collection of ToolMetadata objects.

Examples:

Scanning directories

scanner = Scanner.new([".aidp/skills", ".aidp/templates"])
tools = scanner.scan_all

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(directories = [], strict: false) ⇒ Scanner

Initialize scanner with directory paths

Parameters:

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

    Directories to scan



20
21
22
23
24
# File 'lib/aidp/metadata/scanner.rb', line 20

def initialize(directories = [], strict: false)
  @directories = Array(directories)
  @strict = strict
  @parse_errors = []
end

Instance Attribute Details

#parse_errorsObject (readonly)

Returns the value of attribute parse_errors.



26
27
28
# File 'lib/aidp/metadata/scanner.rb', line 26

def parse_errors
  @parse_errors
end

Instance Method Details

#find_markdown_files(directory) ⇒ Array<String>

Find all markdown files in directory recursively

Parameters:

  • directory (String)

    Directory path

Returns:

  • (Array<String>)

    Paths to .md files



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/aidp/metadata/scanner.rb', line 96

def find_markdown_files(directory)
  pattern = File.join(directory, "**", "*.md")
  files = Dir.glob(pattern)

  Aidp.log_debug(
    "metadata",
    "Found markdown files",
    directory: directory,
    count: files.size
  )

  files
end

#scan_allArray<ToolMetadata>

Scan all configured directories

Returns:



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/aidp/metadata/scanner.rb', line 31

def scan_all
  Aidp.log_debug("metadata", "Scanning directories", directories: @directories)

  @parse_errors = []
  all_tools = []
  @directories.each do |dir|
    tools = scan_directory(dir)
    all_tools.concat(tools)
  end

  Aidp.log_info(
    "metadata",
    "Scan complete",
    directories: @directories.size,
    tools_found: all_tools.size
  )

  all_tools
end

#scan_changes(directory, previous_hashes = {}) ⇒ Hash

Scan for changes since last scan

Compares file hashes to detect changes

Parameters:

  • directory (String)

    Directory path

  • previous_hashes (Hash<String, String>) (defaults to: {})

    Map of file_path => file_hash

Returns:

  • (Hash)

    Hash with :added, :modified, :removed keys



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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/aidp/metadata/scanner.rb', line 150

def scan_changes(directory, previous_hashes = {})
  Aidp.log_debug("metadata", "Scanning for changes", directory: directory)

  current_files = find_markdown_files(directory)
  current_hashes = {}

  changes = {
    added: [],
    modified: [],
    removed: [],
    unchanged: []
  }

  # Check for added and modified files
  current_files.each do |file_path|
    content = File.read(file_path, encoding: "UTF-8")
    file_hash = Parser.compute_file_hash(content)
    current_hashes[file_path] = file_hash

    if previous_hashes.key?(file_path)
      if previous_hashes[file_path] != file_hash
        changes[:modified] << file_path
      else
        changes[:unchanged] << file_path
      end
    else
      changes[:added] << file_path
    end
  end

  # Check for removed files
  previous_hashes.keys.each do |file_path|
    changes[:removed] << file_path unless current_hashes.key?(file_path)
  end

  Aidp.log_info(
    "metadata",
    "Change detection complete",
    added: changes[:added].size,
    modified: changes[:modified].size,
    removed: changes[:removed].size,
    unchanged: changes[:unchanged].size
  )

  changes
end

#scan_directory(directory, type: nil) ⇒ Array<ToolMetadata>

Scan a single directory

Parameters:

  • directory (String)

    Directory path to scan

  • type (String, nil) (defaults to: nil)

    Tool type filter or nil for all

Returns:



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/aidp/metadata/scanner.rb', line 56

def scan_directory(directory, type: nil)
  unless Dir.exist?(directory)
    Aidp.log_warn("metadata", "Directory not found", directory: directory)
    return []
  end

  Aidp.log_debug("metadata", "Scanning directory", directory: directory, type: type)

  tools = []
  md_files = find_markdown_files(directory)

  md_files.each do |file_path|
    tool = Parser.parse_file(file_path, type: type)
    tools << tool if type.nil? || tool.type == type
  rescue Aidp::Errors::ValidationError => e
    Aidp.log_warn(
      "metadata",
      "Failed to parse file",
      file: file_path,
      error: e.message
    )
    @parse_errors << {file: file_path, error: e.message}
    raise if @strict
  end

  Aidp.log_debug(
    "metadata",
    "Directory scan complete",
    directory: directory,
    files_found: md_files.size,
    tools_parsed: tools.size
  )

  tools
end

#scan_with_filter(directory, &filter) ⇒ Array<ToolMetadata>

Scan with file filtering

Parameters:

  • directory (String)

    Directory path

  • filter (Proc)

    Filter proc that receives file_path and returns boolean

Returns:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/aidp/metadata/scanner.rb', line 115

def scan_with_filter(directory, &filter)
  unless Dir.exist?(directory)
    Aidp.log_warn("metadata", "Directory not found", directory: directory)
    return []
  end

  tools = []
  md_files = find_markdown_files(directory)

  md_files.each do |file_path|
    next unless filter.call(file_path)

    begin
      tool = Parser.parse_file(file_path)
      tools << tool
    rescue Aidp::Errors::ValidationError => e
      Aidp.log_warn(
        "metadata",
        "Failed to parse file",
        file: file_path,
        error: e.message
      )
    end
  end

  tools
end