ace-task 0.39.1 → 0.39.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f78511a98128a2afd0cfd4d2120939912d8bc3ccc8865b3c40a032cff50fa6fd
4
- data.tar.gz: ca39e7634c9e5276e546f4bb333b7920babfcc00f3e62675755f128b4c0f92e2
3
+ metadata.gz: 425d8beebe8b18f6d816767d85d9f1b0a29c68a470b8c6cf4590629b2028bddd
4
+ data.tar.gz: a28fc844b081cc3f9b969e2ab15a674df2dca08bb30fc48cd90b1741d3024663
5
5
  SHA512:
6
- metadata.gz: 69e57555803c7a2a9196559675bc1cf532955286e3704643bf1420c7484a525f41d9a1c7435e20349983ec70bcf0116d82bbd8fbfa49455cabf3fa57c86ea194
7
- data.tar.gz: d6a7abcf1878a0799d0c34f195069f61b8ac20700700e0c82e83fa8cafe1beddde4141d3d374815f7bcb4b1ee01bac8453f7ddae66791c38e607cb7b01bef7bd
6
+ metadata.gz: c2be6996b2753ec7ff709cba139a8533e2b746186e7ba3c29b26e51d9361d163e99a022bc5c05a39db372e71ee5a843c64f4f06fbe5b66298e7488b922da27d5
7
+ data.tar.gz: b976035a868e0d4d433b956d41520d5b7a2670aff609754bd975797a6bc6a0417ca1e3fe364d429c65dfe9edfc8c4e9609ad20accfb0db4f2743c8b26ec304a5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.39.2] - 2026-10-06
11
+
12
+ ### Fixed
13
+
14
+ - Keep unresolved descendants from completing or archiving task families, return children correctly after legitimate archival, and prevent doctor from inferring completion from archive location.
15
+
10
16
  ## [0.39.1] - 2026-10-04
11
17
 
12
18
  ### Fixed
@@ -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
@@ -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
@@ -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
@@ -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.2"
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.2
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-08 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