ace-task 0.39.1 → 0.39.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/nav/protocols/skill-sources/ace-task.yml +0 -0
  3. data/.ace-defaults/nav/protocols/wfi-sources/ace-task.yml +0 -0
  4. data/.ace-defaults/task/config.yml +0 -0
  5. data/CHANGELOG.md +13 -0
  6. data/README.md +0 -0
  7. data/Rakefile +0 -0
  8. data/handbook/guides/task-definition.g.md +0 -0
  9. data/handbook/skills/as-bug-analyze/SKILL.md +0 -0
  10. data/handbook/skills/as-bug-fix/SKILL.md +0 -0
  11. data/handbook/skills/as-task-document-unplanned/SKILL.md +0 -0
  12. data/handbook/skills/as-task-draft/SKILL.md +0 -0
  13. data/handbook/skills/as-task-finder/SKILL.md +0 -0
  14. data/handbook/skills/as-task-plan/SKILL.md +0 -0
  15. data/handbook/skills/as-task-review/SKILL.md +0 -0
  16. data/handbook/skills/as-task-review-questions/SKILL.md +0 -0
  17. data/handbook/skills/as-task-update/SKILL.md +0 -0
  18. data/handbook/skills/as-task-work/SKILL.md +0 -0
  19. data/handbook/templates/task/draft.template.md +0 -0
  20. data/handbook/templates/task/file-modification-checklist.template.md +0 -0
  21. data/handbook/templates/task/technical-approach.template.md +0 -0
  22. data/handbook/workflow-instructions/bug/analyze.wf.md +0 -0
  23. data/handbook/workflow-instructions/bug/fix.wf.md +0 -0
  24. data/handbook/workflow-instructions/task/document-unplanned.wf.md +0 -0
  25. data/handbook/workflow-instructions/task/finder.wf.md +0 -0
  26. data/handbook/workflow-instructions/task/plan.wf.md +0 -0
  27. data/handbook/workflow-instructions/task/review-plan.wf.md +0 -0
  28. data/handbook/workflow-instructions/task/review-questions.wf.md +0 -0
  29. data/handbook/workflow-instructions/task/review-work.wf.md +0 -0
  30. data/handbook/workflow-instructions/task/review.wf.md +0 -0
  31. data/handbook/workflow-instructions/task/update.wf.md +0 -0
  32. data/handbook/workflow-instructions/task/work.wf.md +7 -0
  33. data/lib/ace/task/atoms/task_file_pattern.rb +0 -0
  34. data/lib/ace/task/atoms/task_id_formatter.rb +0 -0
  35. data/lib/ace/task/atoms/task_validation_rules.rb +0 -0
  36. data/lib/ace/task/cli/commands/create.rb +122 -4
  37. data/lib/ace/task/cli/commands/doctor.rb +0 -0
  38. data/lib/ace/task/cli/commands/plan.rb +0 -0
  39. data/lib/ace/task/cli/commands/show.rb +0 -0
  40. data/lib/ace/task/cli/commands/status.rb +0 -0
  41. data/lib/ace/task/models/task.rb +0 -0
  42. data/lib/ace/task/molecules/path_utils.rb +0 -0
  43. data/lib/ace/task/molecules/task_config_loader.rb +0 -0
  44. data/lib/ace/task/molecules/task_doctor_fixer.rb +15 -11
  45. data/lib/ace/task/molecules/task_doctor_reporter.rb +0 -0
  46. data/lib/ace/task/molecules/task_family_completion.rb +30 -0
  47. data/lib/ace/task/molecules/task_loader.rb +0 -0
  48. data/lib/ace/task/molecules/task_plan_cache.rb +0 -0
  49. data/lib/ace/task/molecules/task_plan_generator.rb +0 -0
  50. data/lib/ace/task/molecules/task_structure_validator.rb +0 -0
  51. data/lib/ace/task/organisms/task_doctor.rb +0 -0
  52. data/lib/ace/task/organisms/task_manager.rb +22 -29
  53. data/lib/ace/task/version.rb +1 -1
  54. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f78511a98128a2afd0cfd4d2120939912d8bc3ccc8865b3c40a032cff50fa6fd
4
- data.tar.gz: ca39e7634c9e5276e546f4bb333b7920babfcc00f3e62675755f128b4c0f92e2
3
+ metadata.gz: bf3a2b4bdbf7c413363c5f9b81a0f0b4161e6b50e58cce724adcb67ba03fc1c2
4
+ data.tar.gz: b08a3d0f30ad8dfb455c6232bde690e632a9028a603ac37fa289a4768c2f0312
5
5
  SHA512:
6
- metadata.gz: 69e57555803c7a2a9196559675bc1cf532955286e3704643bf1420c7484a525f41d9a1c7435e20349983ec70bcf0116d82bbd8fbfa49455cabf3fa57c86ea194
7
- data.tar.gz: d6a7abcf1878a0799d0c34f195069f61b8ac20700700e0c82e83fa8cafe1beddde4141d3d374815f7bcb4b1ee01bac8453f7ddae66791c38e607cb7b01bef7bd
6
+ metadata.gz: ea63fb03267e6eb84875128ff0ec56becc9fbc0a650a40344cf9a1b11090a61791eed9c97a203ce8a231b132534d442d0238d93cac07162ecd96f522cdd930be
7
+ data.tar.gz: 2be684d6224ec2a57dc9cc4f84eafdee43c14e1b044116e470f12b51dd1cab952267ff9b4a05d96f6e2df5eb4ef1b10d0bd52e32a31e53275a8cdca7e43b0626
File without changes
File without changes
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.39.3] - 2026-10-12
11
+
12
+ ### Fixed
13
+
14
+ - Route `ace-task create --in FOLDER` through the task manager's relocation API
15
+ and remove the newly created task artifacts when destination placement fails.
16
+
17
+ ## [0.39.2] - 2026-10-06
18
+
19
+ ### Fixed
20
+
21
+ - Keep unresolved descendants from completing or archiving task families, return children correctly after legitimate archival, and prevent doctor from inferring completion from archive location.
22
+
10
23
  ## [0.39.1] - 2026-10-04
11
24
 
12
25
  ### Fixed
data/README.md CHANGED
File without changes
data/Rakefile CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -74,6 +74,13 @@ Work through the plan checklist, step by step:
74
74
  - If a test fails: fix it before moving to the next step
75
75
  - If a test failure is undiagnosable after one attempt: stop and report
76
76
 
77
+ **Diagnostic reruns and contract corrections:**
78
+ - Before rerunning a failure, state the hypothesis and the observation that will distinguish it. Verify the runner's actual selected tests/count, effective configuration path and seed from its report or a narrow configuration probe; command arguments alone are not proof that options reached the child.
79
+ - Resolve configuration against the runner's actual working directory. If it changes directories, use a verified absolute path. A reproduction with a different seed or zero selected tests does not satisfy the intended check.
80
+ - Add bounded diagnostics at the failing boundary before repeating an opaque failure. Preserve earlier failures and distinguish product fixes, fixture corrections and diagnostic-only changes; do not rerun merely to obtain green output or silently increase deadlines.
81
+ - Poll an owned live test handle to its terminal result. Quiet output is not a reason to start a duplicate run. After a meaningful fix or new hypothesis, rerun only the affected checks, then broaden when integration risk requires it.
82
+ - When a contract changes, revise its current authoritative spec and consumer obligations together. Keep superseded decisions in history rather than leaving competing current addenda. A partial source commit cannot close an unverified acceptance criterion.
83
+
77
84
  **Task lifecycle:**
78
85
  - `draft` status: warn the user that the spec hasn't been reviewed, then continue only with explicit confirmation. In unattended/fork contexts where interactive confirmation is not possible, proceed after marking in-progress -- the assignment creation layer is responsible for blocking draft tasks before they reach this point.
79
86
  - Mark in-progress before first change, done after last verification
File without changes
File without changes
File without changes
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ace/support/cli"
4
+ require "fileutils"
4
5
 
5
6
  module Ace
6
7
  module Task
@@ -10,6 +11,9 @@ module Ace
10
11
  class Create < Ace::Support::Cli::Command
11
12
  include Ace::Support::Cli::Base
12
13
 
14
+ # "next" is a move-to-root alias and is validated separately.
15
+ BUILT_IN_DESTINATIONS = %w[maybe anytime archive].freeze
16
+
13
17
  desc <<~DESC.strip
14
18
  Create a new task
15
19
 
@@ -38,7 +42,8 @@ module Ace
38
42
  option :server, type: :string, desc: "Named forge server"
39
43
  option :"default-server", type: :boolean, desc: "Use the configured default forge server"
40
44
  option :"child-of", type: :string, desc: "Parent task reference (creates subtask)"
41
- option :in, type: :string, aliases: %w[-i], desc: "Target folder (e.g. next, maybe)"
45
+ option :in, type: :string, aliases: %w[-i],
46
+ desc: "Top-level target (next, maybe, anytime, archive, or existing custom folder)"
42
47
  option :"dry-run", type: :boolean, aliases: %w[-n], desc: "Preview without writing"
43
48
 
44
49
  option :git_commit, type: :boolean, aliases: %w[--gc], desc: "Auto-commit changes"
@@ -72,6 +77,10 @@ module Ace
72
77
  end
73
78
  end
74
79
 
80
+ manager = Ace::Task::Organisms::TaskManager.new
81
+ validate_destination!(manager, in_folder) if in_folder && !child_of
82
+ placement_directories_before = directories_under(manager.root_dir) if in_folder && !child_of
83
+
75
84
  if dry_run
76
85
  puts "Would create task:"
77
86
  puts " Title: #{title}"
@@ -85,8 +94,6 @@ module Ace
85
94
  return
86
95
  end
87
96
 
88
- manager = Ace::Task::Organisms::TaskManager.new
89
-
90
97
  task = begin
91
98
  if child_of
92
99
  manager.create_subtask(
@@ -119,7 +126,7 @@ module Ace
119
126
 
120
127
  # Move to folder if specified
121
128
  if in_folder && !child_of
122
- task = manager.move(task.id, to: in_folder)
129
+ task = place_created_task(manager, task, in_folder, placement_directories_before)
123
130
  end
124
131
 
125
132
  puts "Created task #{task.id}"
@@ -140,6 +147,117 @@ module Ace
140
147
 
141
148
  private
142
149
 
150
+ def validate_destination!(manager, target_folder)
151
+ detector = Ace::Support::Items::Atoms::SpecialFolderDetector
152
+ return if detector.move_to_root?(target_folder)
153
+
154
+ if detector.virtual_filter?(target_folder)
155
+ raise Ace::Support::Cli::Error,
156
+ "Destination '#{target_folder}' is a virtual list filter, not a physical folder."
157
+ end
158
+
159
+ normalized = detector.normalize(target_folder)
160
+ configured_path = File.join(manager.root_dir, normalized)
161
+ normalized_built_ins = BUILT_IN_DESTINATIONS.map { |name| detector.normalize(name) }
162
+ valid = !target_folder.strip.empty? &&
163
+ !normalized.include?(File::SEPARATOR) &&
164
+ !normalized.include?("..") &&
165
+ (normalized_built_ins.include?(normalized) || Dir.exist?(configured_path)) &&
166
+ destination_within_root?(manager.root_dir, configured_path)
167
+ return if valid
168
+
169
+ choices = (["next"] + BUILT_IN_DESTINATIONS).join(", ")
170
+ raise Ace::Support::Cli::Error,
171
+ "Destination '#{target_folder}' does not exist. " \
172
+ "Use #{choices}, or an existing custom task folder."
173
+ end
174
+
175
+ def destination_within_root?(root, destination)
176
+ return true unless File.exist?(destination)
177
+
178
+ root_path = File.realpath(root)
179
+ destination_path = File.realpath(destination)
180
+ destination_path.start_with?(root_path + File::SEPARATOR)
181
+ rescue Errno::ENOENT, Errno::EACCES
182
+ false
183
+ end
184
+
185
+ def place_created_task(manager, task, target_folder, directories_before)
186
+ placed_task = manager.update(task.id, move_to: target_folder)
187
+ expected_folder = if Ace::Support::Items::Atoms::SpecialFolderDetector.move_to_root?(target_folder)
188
+ nil
189
+ else
190
+ Ace::Support::Items::Atoms::SpecialFolderDetector.normalize(target_folder)
191
+ end
192
+
193
+ unless placed_task && placed_task.special_folder == expected_folder
194
+ actual_folder = if placed_task
195
+ placed_task.special_folder || "root"
196
+ else
197
+ "no task returned"
198
+ end
199
+ # This internal signal is wrapped below with rollback and an actionable CLI error.
200
+ raise "placement returned #{actual_folder.inspect}, expected #{expected_folder || "root"}"
201
+ end
202
+
203
+ placed_task
204
+ rescue Ace::Git::Error
205
+ # update retains linked tasks after post-placement sync failures as
206
+ # recovery records; those are not destination-placement failures.
207
+ raise
208
+ rescue StandardError => e
209
+ begin
210
+ remove_created_task_artifacts(manager, task)
211
+ remove_empty_directories_created_since(manager.root_dir, directories_before)
212
+ rescue StandardError => cleanup_error
213
+ raise Ace::Support::Cli::Error,
214
+ "Failed to create task in '#{target_folder}': #{e.message}. " \
215
+ "Cleanup also failed: #{cleanup_error.message}"
216
+ end
217
+ raise Ace::Support::Cli::Error,
218
+ "Failed to create task in '#{target_folder}': #{e.message}. " \
219
+ "No task was created; fix the destination and retry."
220
+ end
221
+
222
+ def remove_created_task_artifacts(manager, task)
223
+ root = File.expand_path(manager.root_dir)
224
+ candidates = [task.path]
225
+ candidates.concat(
226
+ Dir.glob(File.join(root, "**", "#{task.id}-*")).select { |path| File.directory?(path) }
227
+ )
228
+
229
+ candidates.uniq.each do |path|
230
+ expanded = File.expand_path(path)
231
+ next unless expanded.start_with?(root + File::SEPARATOR)
232
+
233
+ FileUtils.rm_rf(expanded)
234
+ end
235
+
236
+ remaining = Dir.glob(File.join(root, "**", "#{task.id}-*")).select { |path| File.exist?(path) }
237
+ return if remaining.empty?
238
+
239
+ raise Ace::Support::Cli::Error,
240
+ "Failed to remove partial task artifacts after placement failure: #{remaining.join(", ")}"
241
+ end
242
+
243
+ def directories_under(root)
244
+ return [] unless Dir.exist?(root)
245
+
246
+ [File.expand_path(root)] + Dir.glob(File.join(root, "**", "*"))
247
+ .select { |path| File.directory?(path) }
248
+ .map { |path| File.expand_path(path) }
249
+ end
250
+
251
+ def remove_empty_directories_created_since(root, directories_before)
252
+ # Only directories created during this short command transaction
253
+ # are candidates, and non-empty concurrent directories are retained.
254
+ (directories_under(root) - directories_before).sort_by(&:length).reverse_each do |path|
255
+ Dir.rmdir(path)
256
+ rescue Errno::ENOENT, Errno::ENOTEMPTY
257
+ next
258
+ end
259
+ end
260
+
143
261
  def resolve_title(positional_title, flag_title)
144
262
  positional_present = present_string?(positional_title)
145
263
  flag_present = present_string?(flag_title)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -8,6 +8,7 @@ require_relative "../atoms/task_id_formatter"
8
8
  require_relative "../atoms/task_validation_rules"
9
9
  require_relative "../atoms/task_frontmatter_defaults"
10
10
  require_relative "task_scanner"
11
+ require_relative "task_family_completion"
11
12
 
12
13
  module Ace
13
14
  module Task
@@ -218,7 +219,9 @@ module Ace
218
219
  end
219
220
 
220
221
  def fix_archive_status(file_path)
221
- update_frontmatter_field(file_path, "status", "done", "Updated status to 'done' (in _archive/)")
222
+ log_fix(file_path, "Skipped status repair: archive location does not prove task completion")
223
+ @skipped_count += 1
224
+ false
222
225
  end
223
226
 
224
227
  def fix_maybe_terminal(file_path)
@@ -456,6 +459,16 @@ module Ace
456
459
  description = "Moved parent task to _archive/ (all subtasks terminal)"
457
460
  end
458
461
 
462
+ source_spec = find_primary_spec(source_dir)
463
+ source_frontmatter, = Ace::Support::Items::Atoms::FrontmatterParser.parse(File.read(source_spec))
464
+ unless TaskFamilyCompletion.new(@root_dir).descendants_terminal?(
465
+ source_dir, id: source_frontmatter["id"]
466
+ )
467
+ log_fix(source_dir, "Skipped archive move: descendants are not all terminal")
468
+ @skipped_count += 1
469
+ return false
470
+ end
471
+
459
472
  if @dry_run
460
473
  log_fix(source_dir, "Would #{description.downcase}")
461
474
  @fixed_count += 1
@@ -493,16 +506,7 @@ module Ace
493
506
  end
494
507
 
495
508
  def all_siblings_terminal?(parent_dir, parent_id)
496
- scanner = TaskScanner.new(@root_dir)
497
- subtasks = scanner.scan_subtasks(parent_dir, parent_id: parent_id)
498
- return false if subtasks.empty?
499
-
500
- subtasks.all? do |scan_result|
501
- content = File.read(scan_result.file_path)
502
- frontmatter, _body = Ace::Support::Items::Atoms::FrontmatterParser.parse(content)
503
- status = frontmatter.is_a?(Hash) ? frontmatter["status"] : nil
504
- Atoms::TaskValidationRules.terminal_status?(status.to_s.downcase)
505
- end
509
+ TaskFamilyCompletion.new(@root_dir).complete?(parent_dir, id: parent_id)
506
510
  end
507
511
  end
508
512
  end
File without changes
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "task_scanner"
4
+ require_relative "../atoms/task_validation_rules"
5
+
6
+ module Ace
7
+ module Task
8
+ module Molecules
9
+ # Task-aware traversal excludes evidence folders and the parent's own status.
10
+ class TaskFamilyCompletion
11
+ def initialize(root_dir)
12
+ @scanner = TaskScanner.new(root_dir)
13
+ end
14
+
15
+ def descendants_terminal?(path, id:)
16
+ @scanner.scan_subtasks(path, parent_id: id).all? do |child|
17
+ content = File.read(child.file_path)
18
+ frontmatter, = Ace::Support::Items::Atoms::FrontmatterParser.parse(content)
19
+ Atoms::TaskValidationRules.terminal_status?(frontmatter["status"].to_s.downcase) &&
20
+ descendants_terminal?(child.dir_path, id: child.id)
21
+ end
22
+ end
23
+
24
+ def complete?(path, id:)
25
+ @scanner.scan_subtasks(path, parent_id: id).any? && descendants_terminal?(path, id: id)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -9,6 +9,7 @@ require_relative "../molecules/task_resolver"
9
9
  require_relative "../molecules/task_loader"
10
10
  require_relative "../molecules/task_creator"
11
11
  require_relative "../molecules/subtask_creator"
12
+ require_relative "../molecules/task_family_completion"
12
13
  require_relative "../molecules/task_reparenter"
13
14
  require_relative "../molecules/issue_link"
14
15
  require_relative "../atoms/task_validation_rules"
@@ -190,6 +191,13 @@ module Ace
190
191
  special_folder: scan_result.special_folder)
191
192
  return nil unless task
192
193
 
194
+ if Ace::Support::Items::Atoms::SpecialFolderDetector.normalize(move_to) == "_archive" &&
195
+ !task.subtask? && !family_completion.descendants_terminal?(task.path, id: task.id)
196
+ @last_update_note = "Task #{task.id} was not archived because descendants are not all terminal; " \
197
+ "resolve blocked or unfinished subtasks before archiving. No requested fields were changed."
198
+ return task
199
+ end
200
+
193
201
  # Apply field updates if any
194
202
  has_field_updates = [set, add, remove].any? { |h| h && !h.empty? }
195
203
  reject_issue_metadata_update!(set, add, remove)
@@ -258,11 +266,11 @@ module Ace
258
266
  # Auto-archive hook: if a subtask status was set to terminal,
259
267
  # check if all siblings are terminal and auto-move parent to archive
260
268
  if set && set.key?("status")
261
- check_auto_archive(task, set["status"], loader)
269
+ check_auto_archive(task, set["status"])
262
270
  end
263
271
 
264
272
  # Reload and return updated task
265
- updated_task = loader.load(current_path, id: current_id, special_folder: current_special)
273
+ updated_task = show(current_id)
266
274
  if sync_needed_after_update?(task, updated_task, set: set, add: add, remove: remove, move_to: move_to)
267
275
  sync_failed_definitively = false
268
276
  with_issue_identity_lock(linked_issue(updated_task) || {}) do
@@ -847,25 +855,23 @@ module Ace
847
855
 
848
856
  # Auto-archive: if a subtask reaches terminal status and all siblings
849
857
  # in the parent directory are also terminal, move the parent to archive.
850
- def check_auto_archive(task, new_status, loader)
851
- terminal = Ace::Support::Items::Atoms::FolderCompletionDetector::TERMINAL_STATUSES
852
- return unless terminal.include?(new_status.to_s.downcase)
858
+ def check_auto_archive(task, new_status)
859
+ return unless Atoms::TaskValidationRules.terminal_status?(new_status.to_s.downcase)
860
+ return unless task.subtask?
853
861
 
854
- # Only applies to subtasks (task dir is nested inside a parent dir)
855
- parent_dir = File.dirname(task.path)
856
- return if File.expand_path(parent_dir) == File.expand_path(@root_dir)
862
+ current = show(task.id)
863
+ return unless current && current.special_folder != "_archive"
857
864
 
858
- # Check if all specs in the parent dir (recursive for subtask subdirs) are terminal
859
- return unless Ace::Support::Items::Atoms::FolderCompletionDetector.all_terminal?(
860
- parent_dir, recursive: true
861
- )
862
-
863
- parent = load_parent_from_directory(parent_dir, loader)
864
- return unless parent
865
+ parent = show(current.parent_id)
866
+ return unless parent && family_completion.complete?(parent.path, id: parent.id)
865
867
 
866
868
  archive_parent_via_update(parent)
867
869
  end
868
870
 
871
+ def family_completion
872
+ Molecules::TaskFamilyCompletion.new(@root_dir)
873
+ end
874
+
869
875
  def parse_archive_date(task)
870
876
  raw = task.metadata["completed_at"] || task.metadata["created_at"]
871
877
  return nil unless raw
@@ -897,10 +903,7 @@ module Ace
897
903
  }
898
904
  end
899
905
 
900
- parent_with_subtasks = loader.load(parent.path, id: parent.id, special_folder: parent.special_folder)
901
- subtasks = parent_with_subtasks&.subtasks || []
902
- all_terminal = subtasks.any? &&
903
- subtasks.all? { |st| Atoms::TaskValidationRules.terminal_status?(st.status.to_s.downcase) }
906
+ all_terminal = family_completion.complete?(parent.path, id: parent.id)
904
907
 
905
908
  unless all_terminal
906
909
  @last_update_note = "Subtask #{task.id} was not archived because sibling subtasks are not all terminal."
@@ -941,16 +944,6 @@ module Ace
941
944
  archived_parent
942
945
  end
943
946
 
944
- def load_parent_from_directory(parent_dir, loader)
945
- parent_id = File.basename(parent_dir).split("-", 2).first
946
- return nil unless parent_id
947
-
948
- parent_special = Ace::Support::Items::Atoms::SpecialFolderDetector.detect_in_path(
949
- parent_dir, root: @root_dir
950
- )
951
- loader.load(parent_dir, id: parent_id, special_folder: parent_special)
952
- end
953
-
954
947
  # Value accessor for FilterApplier
955
948
  def task_value_accessor(item, key)
956
949
  case key
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Task
5
- VERSION = "0.39.1"
5
+ VERSION = "0.39.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-task
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.39.1
4
+ version: 0.39.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-03 00:00:00.000000000 Z
10
+ date: 2026-10-12 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-core
@@ -204,6 +204,7 @@ files:
204
204
  - lib/ace/task/molecules/task_display_formatter.rb
205
205
  - lib/ace/task/molecules/task_doctor_fixer.rb
206
206
  - lib/ace/task/molecules/task_doctor_reporter.rb
207
+ - lib/ace/task/molecules/task_family_completion.rb
207
208
  - lib/ace/task/molecules/task_frontmatter_validator.rb
208
209
  - lib/ace/task/molecules/task_loader.rb
209
210
  - lib/ace/task/molecules/task_plan_cache.rb