Class: Ace::Git::Worktree::Atoms::TaskIDExtractor

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/git/worktree/atoms/task_id_extractor.rb

Overview

Extracts task IDs from task data, preserving hierarchical subtask notation

This atom provides a single source of truth for task ID extraction across ace-git-worktree, ensuring consistent handling of hierarchical task IDs (e.g., “121.01” for subtasks).

Examples:

Extract from task data hash

TaskIDExtractor.extract({id: "v.0.9.0+task.121.01"}) # => "121.01"
TaskIDExtractor.extract({id: "v.0.9.0+task.121"})    # => "121"
TaskIDExtractor.extract({id: "v.0.9.0+ace-t.121.01"}) # => "121.01"
TaskIDExtractor.extract({id: "v.0.9.0+t.121"})       # => "121"

Normalize a task reference string

TaskIDExtractor.normalize("v.0.9.0+task.121.01") # => "121.01"
TaskIDExtractor.normalize("task.121")       # => "121"
TaskIDExtractor.normalize("ace-t.121")      # => "121"
TaskIDExtractor.normalize("121.01")         # => "121.01"

Class Method Summary collapse

Class Method Details

.extract(task_data) ⇒ String

Extract task ID from task data hash

Parameters:

  • task_data (Hash)

    Task data with :id and/or :task_number

Returns:

  • (String)

    Task ID (e.g., “121” or “121.01”)



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ace/git/worktree/atoms/task_id_extractor.rb', line 29

def self.extract(task_data)
  return "unknown" unless task_data

  # Regex with subtask support
  if task_data[:id]
    # B36TS full ID: "8pp.t.hy4" -> "hy4", or subtask "8pp.t.hy4.a" -> "hy4.a"
    if match = task_data[:id].match(/\A[0-9a-z]{3}\.[a-z]\.([0-9a-z]{3}(?:\.[a-z0-9])?)\z/)
      return match[1]
    end
    # Try subtask pattern first (e.g., "v.0.9.0+task.121.01" -> "121.01")
    if match = task_data[:id].match(/(?:ace-)?(?:task|t)\.(\d+)\.(\d{2})$/)
      return "#{match[1]}.#{match[2]}"
    end
    # Then simple pattern (e.g., "v.0.9.0+task.094" -> "094")
    if match = task_data[:id].match(/(?:ace-)?(?:task|t)\.(\d+)$/)
      return match[1]
    end
    # Fallback for partial patterns (e.g., "task.121.1" -> "121", ignoring invalid suffix)
    if match = task_data[:id].match(/(?:ace-)?(?:task|t)\.(\d+)/)
      return match[1]
    end
  end

  # Last resort: use task_number (doesn't include subtask suffix)
  task_data[:task_number]&.to_s || "unknown"
end

.normalize(task_ref) ⇒ String?

Normalize a task reference string to simple ID format

Parameters:

  • task_ref (String)

    Task reference (e.g., “121.01”, “v.0.9.0+task.121.01”)

Returns:

  • (String, nil)

    Normalized ID or nil if invalid



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
# File 'lib/ace/git/worktree/atoms/task_id_extractor.rb', line 60

def self.normalize(task_ref)
  ref = task_ref.to_s.strip
  return nil if ref.empty?

  # Regex patterns for recognized ACE task ID formats
  # B36TS full ID: "8pp.t.hy4" -> "hy4", or subtask "8pp.t.hy4.a" -> "hy4.a"
  if match = ref.match(/\A[0-9a-z]{3}\.[a-z]\.([0-9a-z]{3}(?:\.[a-z0-9])?)\z/)
    match[1]
  # B36TS short ref: exactly 3 lowercase alphanumeric chars (e.g., "hy4")
  elsif match = ref.match(/\A([0-9a-z]{3})\z/)
    match[1]
  # ACE task IDs are 3-digit zero-padded (081, 121, etc.)
  # Try hierarchical pattern first (e.g., "121.01", "task.121.01")
  elsif match = ref.match(/(\d{3})\.(\d{2})(?:\b|$)/)
    "#{match[1]}.#{match[2]}"
  # Try ace-task/ace-t or task/t directory at end of path (e.g., "ace-task.hy4", "/path/to/task.121")
  elsif match = ref.match(/(?:ace-)?(?:task|t)\.([0-9a-z]{3})\/?$/)
    match[1]
  # Try task./t. prefix in middle of path (e.g., ".cache/test/task.121")
  # Use negative lookbehind to skip ace-task.XXX / ace-t.XXX parent directories
  elsif match = ref.match(/(?<!ace-)(?:task|t)\.(\d{3})(?:\b|$)/)
    match[1]
  # Try bare 3-digit task ID (e.g., "121", "081")
  elsif match = ref.match(/\b(\d{3})\b/)
    match[1]
  end
end