Class: Ace::Task::Molecules::TaskScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/task/molecules/task_scanner.rb

Overview

Wraps DirectoryScanner for task-format directories. Uses a custom id_extractor that matches xxx.t.yyy-slug folder names. Excludes subtask folders from primary scan results.

Constant Summary collapse

TASK_ID_EXTRACTOR =

ID extractor for task-format folders: “8pp.t.q7w-fix-login”

->(folder_name) {
  match = folder_name.match(/^([0-9a-z]{3}\.[a-z]\.[0-9a-z]{3})-(.+)$/)
  return nil unless match

  id = match[1]
  slug = match[2]
  [id, slug]
}
FILE_PATTERN =
"*.s.md"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root_dir) ⇒ TaskScanner

Returns a new instance of TaskScanner.

Parameters:

  • root_dir (String)

    Root directory containing tasks (e.g., “.ace-tasks”)



27
28
29
30
31
32
33
34
# File 'lib/ace/task/molecules/task_scanner.rb', line 27

def initialize(root_dir)
  @root_dir = root_dir
  @scanner = Ace::Support::Items::Molecules::DirectoryScanner.new(
    root_dir,
    file_pattern: FILE_PATTERN,
    id_extractor: TASK_ID_EXTRACTOR
  )
end

Instance Attribute Details

#last_folder_countsObject (readonly)

Returns the value of attribute last_folder_counts.



12
13
14
# File 'lib/ace/task/molecules/task_scanner.rb', line 12

def last_folder_counts
  @last_folder_counts
end

#last_scan_totalObject (readonly)

Returns the value of attribute last_scan_total.



12
13
14
# File 'lib/ace/task/molecules/task_scanner.rb', line 12

def last_scan_total
  @last_scan_total
end

Instance Method Details

#root_exists?Boolean

Check if root directory exists

Returns:

  • (Boolean)


116
117
118
# File 'lib/ace/task/molecules/task_scanner.rb', line 116

def root_exists?
  Dir.exist?(@root_dir)
end

#scanArray<ScanResult>

Scan for all primary tasks (excludes subtask folders).

Returns:

  • (Array<ScanResult>)

    Sorted scan results



38
39
40
# File 'lib/ace/task/molecules/task_scanner.rb', line 38

def scan
  @scanner.scan.reject { |sr| subtask_folder?(sr.folder_name) }
end

#scan_allArray<ScanResult>

Scan for all items including subtask folders.

Returns:

  • (Array<ScanResult>)

    Sorted scan results



63
64
65
# File 'lib/ace/task/molecules/task_scanner.rb', line 63

def scan_all
  @scanner.scan
end

#scan_in_folder(folder) ⇒ Array<ScanResult>

Scan and filter by special folder or virtual filter

Parameters:

  • folder (String, nil)

    Folder name, virtual filter (“next”, “all”), or nil for all

Returns:

  • (Array<ScanResult>)

    Filtered scan results



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/ace/task/molecules/task_scanner.rb', line 45

def scan_in_folder(folder)
  results = scan
  @last_scan_total = results.size
  @last_folder_counts = results.group_by(&:special_folder).transform_values(&:size)
  return results if folder.nil?

  virtual = Ace::Support::Items::Atoms::SpecialFolderDetector.virtual_filter?(folder)
  case virtual
  when :all then results
  when :next then results.select { |r| r.special_folder.nil? }
  else
    normalized = Ace::Support::Items::Atoms::SpecialFolderDetector.normalize(folder)
    results.select { |r| r.special_folder == normalized }
  end
end

#scan_subtasks(parent_dir, parent_id:) ⇒ Array<ScanResult>

Scan for subtask directories within a parent task directory. Subtask folders follow the pattern: parent_id.char-slug

Parameters:

  • parent_dir (String)

    Path to the parent task directory

  • parent_id (String)

    Formatted parent task ID (e.g., “8pp.t.q7w”)

Returns:

  • (Array<ScanResult>)

    Subtask scan results, sorted by ID



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/ace/task/molecules/task_scanner.rb', line 73

def scan_subtasks(parent_dir, parent_id:)
  return [] unless Dir.exist?(parent_dir)

  results = []
  Dir.entries(parent_dir).sort.each do |entry|
    next if entry.start_with?(".")

    full_path = File.join(parent_dir, entry)
    next unless File.directory?(full_path)

    subtask_id = nil
    slug = nil

    # Short format: "0-slug" or "a-slug"
    if (short_match = entry.match(/^([a-z0-9])-(.+)$/))
      subtask_id = "#{parent_id}.#{short_match[1]}"
      slug = short_match[2]
    else
      next
    end

    spec_files = Dir.glob(File.join(full_path, FILE_PATTERN))
    next if spec_files.empty?

    special_folder = Ace::Support::Items::Atoms::SpecialFolderDetector.detect_in_path(
      full_path, root: @root_dir
    )

    results << Ace::Support::Items::Models::ScanResult.new(
      id: subtask_id,
      slug: slug,
      folder_name: entry,
      dir_path: full_path,
      file_path: spec_files.first,
      special_folder: special_folder
    )
  end

  results.sort_by(&:id)
end