Class: Ace::Task::Organisms::TaskManager
- Inherits:
-
Object
- Object
- Ace::Task::Organisms::TaskManager
- Defined in:
- lib/ace/task/organisms/task_manager.rb
Overview
Orchestrates all task CRUD operations. Entry point for task management with config-driven root directory.
Defined Under Namespace
Classes: CreateRetriesExhaustedError
Constant Summary collapse
- CREATE_RETRY_LIMIT =
3
Instance Attribute Summary collapse
-
#last_folder_counts ⇒ Object
readonly
Returns the value of attribute last_folder_counts.
-
#last_list_total ⇒ Object
readonly
Returns the value of attribute last_list_total.
-
#last_update_note ⇒ Object
readonly
Returns the value of attribute last_update_note.
-
#root_dir ⇒ String
readonly
Get the root directory.
Instance Method Summary collapse
-
#create(title, status: nil, priority: nil, tags: [], dependencies: [], use_llm_slug: false, estimate: nil, github_issue: nil) ⇒ Models::Task
Create a new task.
-
#create_subtask(parent_ref, title, status: nil, priority: nil, tags: [], estimate: nil, github_issue: nil) ⇒ Models::Task?
Create a subtask within a parent task.
- #github_sync(ref: nil, all: false) ⇒ Object
-
#initialize(root_dir: nil, config: nil) ⇒ TaskManager
constructor
A new instance of TaskManager.
-
#list(status: nil, in_folder: "next", tags: [], filters: nil, sort: "smart") ⇒ Array<Models::Task>
List tasks with optional filtering and sorting.
-
#show(ref) ⇒ Models::Task?
Show (load) a single task by reference, including subtasks.
-
#update(ref, set: {}, add: {}, remove: {}, move_to: nil, move_as_child_of: nil) ⇒ Models::Task?
Update a task’s frontmatter fields and optionally move to a folder.
Constructor Details
#initialize(root_dir: nil, config: nil) ⇒ TaskManager
Returns a new instance of TaskManager.
26 27 28 29 30 |
# File 'lib/ace/task/organisms/task_manager.rb', line 26 def initialize(root_dir: nil, config: nil) @config = config || load_config @root_dir = root_dir || resolve_root_dir @last_update_note = nil end |
Instance Attribute Details
#last_folder_counts ⇒ Object (readonly)
Returns the value of attribute last_folder_counts.
22 23 24 |
# File 'lib/ace/task/organisms/task_manager.rb', line 22 def last_folder_counts @last_folder_counts end |
#last_list_total ⇒ Object (readonly)
Returns the value of attribute last_list_total.
22 23 24 |
# File 'lib/ace/task/organisms/task_manager.rb', line 22 def last_list_total @last_list_total end |
#last_update_note ⇒ Object (readonly)
Returns the value of attribute last_update_note.
32 33 34 |
# File 'lib/ace/task/organisms/task_manager.rb', line 32 def last_update_note @last_update_note end |
#root_dir ⇒ String (readonly)
Get the root directory.
254 255 256 |
# File 'lib/ace/task/organisms/task_manager.rb', line 254 def root_dir @root_dir end |
Instance Method Details
#create(title, status: nil, priority: nil, tags: [], dependencies: [], use_llm_slug: false, estimate: nil, github_issue: nil) ⇒ Models::Task
Create a new task.
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 74 75 76 77 |
# File 'lib/ace/task/organisms/task_manager.rb', line 42 def create( title, status: nil, priority: nil, tags: [], dependencies: [], use_llm_slug: false, estimate: nil, github_issue: nil ) ensure_root_dir ensure_github_issue_linkable!(github_issue) creator = Molecules::TaskCreator.new(root_dir: @root_dir, config: @config) attempts = 0 begin attempts += 1 created_task = creator.create( title, status: status, priority: priority, tags: , dependencies: dependencies, use_llm_slug: use_llm_slug, time: Time.now.utc + ((attempts - 1) * 2), estimate: estimate, github_issue: github_issue ) sync_linked_issues_for(created_task, reason: "create") created_task rescue Molecules::TaskCreator::IdCollisionError retry if attempts < CREATE_RETRY_LIMIT raise CreateRetriesExhaustedError, "Failed to create task: unable to generate a unique ID after #{CREATE_RETRY_LIMIT} attempts" end end |
#create_subtask(parent_ref, title, status: nil, priority: nil, tags: [], estimate: nil, github_issue: nil) ⇒ Models::Task?
Create a subtask within a parent task.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/ace/task/organisms/task_manager.rb', line 211 def create_subtask(parent_ref, title, status: nil, priority: nil, tags: [], estimate: nil, github_issue: nil) parent = show(parent_ref) return nil unless parent ensure_github_issue_linkable!(github_issue) subtask_creator = Molecules::SubtaskCreator.new(config: @config) created_subtask = subtask_creator.create( parent, title, status: status, priority: priority, tags: , estimate: estimate, github_issue: github_issue ) sync_linked_issues_for(created_subtask, reason: "create") created_subtask end |
#github_sync(ref: nil, all: false) ⇒ Object
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/ace/task/organisms/task_manager.rb', line 230 def github_sync(ref: nil, all: false) raise ArgumentError, "Provide --all or a task reference" if !all && (ref.nil? || ref.strip.empty?) if all tasks = list(in_folder: "all") linked_tasks = tasks.select { |t| linked_issue_id(t) } results = linked_tasks.map { |task| sync_linked_issues_for(task, reason: "manual-sync") } return summarize_manual_sync_results(results, skipped: tasks.length - linked_tasks.length) end task = show(ref) return nil unless task unless linked_issue_id(task) return {synced: 0, failed: 0, skipped: 1, task_id: task.id, failures: []} end result = sync_linked_issues_for(task, reason: "manual-sync") summary = summarize_manual_sync_results([result], skipped: 0) summary.merge(task_id: task.id) end |
#list(status: nil, in_folder: "next", tags: [], filters: nil, sort: "smart") ⇒ Array<Models::Task>
List tasks with optional filtering and sorting.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/ace/task/organisms/task_manager.rb', line 99 def list(status: nil, in_folder: "next", tags: [], filters: nil, sort: "smart") scanner = Molecules::TaskScanner.new(@root_dir) scan_results = scanner.scan_in_folder(in_folder) @last_list_total = scanner.last_scan_total @last_folder_counts = scanner.last_folder_counts loader = Molecules::TaskLoader.new tasks = scan_results.filter_map do |sr| loader.load(sr.dir_path, id: sr.id, special_folder: sr.special_folder) end # Apply legacy filters tasks = tasks.select { |t| t.status == status } if status tasks = (tasks, ) if .any? # Apply generic --filter specs if filters && !filters.empty? filter_specs = Ace::Support::Items::Atoms::FilterParser.parse(filters) tasks = Ace::Support::Items::Molecules::FilterApplier.apply( tasks, filter_specs, value_accessor: method(:task_value_accessor) ) end apply_sort(tasks, sort) end |
#show(ref) ⇒ Models::Task?
Show (load) a single task by reference, including subtasks.
82 83 84 85 86 87 88 89 90 |
# File 'lib/ace/task/organisms/task_manager.rb', line 82 def show(ref) scan_result = resolve_scan_result(ref) return nil unless scan_result loader = Molecules::TaskLoader.new loader.load(scan_result.dir_path, id: scan_result.id, special_folder: scan_result.special_folder) end |
#update(ref, set: {}, add: {}, remove: {}, move_to: nil, move_as_child_of: nil) ⇒ Models::Task?
Update a task’s frontmatter fields and optionally move to a folder.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 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 196 197 198 199 200 201 202 |
# File 'lib/ace/task/organisms/task_manager.rb', line 133 def update(ref, set: {}, add: {}, remove: {}, move_to: nil, move_as_child_of: nil) @last_update_note = nil scan_result = resolve_scan_result(ref) return nil unless scan_result loader = Molecules::TaskLoader.new task = loader.load(scan_result.dir_path, id: scan_result.id, special_folder: scan_result.special_folder) return nil unless task # Apply field updates if any has_field_updates = [set, add, remove].any? { |h| h && !h.empty? } desired_issue = extract_desired_github_issue(task, set: set, remove: remove) ensure_github_issue_linkable!(desired_issue, previous_task: task) if desired_issue if has_field_updates Ace::Support::Items::Molecules::FieldUpdater.update( task.file_path, set: set, add: add, remove: remove ) end # Apply move if requested current_path = task.path current_special = task.special_folder current_id = task.id if move_to if archive_move_for_subtask?(task, move_to) result = handle_subtask_archive_move(task, loader) current_path = result[:path] current_special = result[:special_folder] current_id = result[:id] else mover = Ace::Support::Items::Molecules::FolderMover.new(@root_dir) new_path = if Ace::Support::Items::Atoms::SpecialFolderDetector.move_to_root?(move_to) mover.move_to_root(task) else archive_date = parse_archive_date(task) mover.move(task, to: move_to, date: archive_date) end current_path = new_path current_special = Ace::Support::Items::Atoms::SpecialFolderDetector.detect_in_path( new_path, root: @root_dir ) end end # Reparent if requested (mutually exclusive with move_to) if move_as_child_of reparenter = Molecules::TaskReparenter.new(root_dir: @root_dir, config: @config) resolve_fn = ->(r) { show(r) } # Reload task from current path before reparenting (may have been field-updated) task_for_reparent = loader.load(current_path, id: task.id, special_folder: current_special) reparented = reparenter.reparent(task_for_reparent, target: move_as_child_of, resolve_ref: resolve_fn) sync_linked_issues_for(reparented, reason: "reparent", previous_task: task) return reparented end # Auto-archive hook: if a subtask status was set to terminal, # check if all siblings are terminal and auto-move parent to archive if set && set.key?("status") check_auto_archive(task, set["status"], loader) end # Reload and return updated task updated_task = loader.load(current_path, id: current_id, special_folder: current_special) if sync_needed_after_update?(task, updated_task, set: set, add: add, remove: remove, move_to: move_to) sync_linked_issues_for(updated_task, reason: "update", previous_task: task) end updated_task end |