ace-review 0.60.2 → 0.60.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a5d2a502e12992e339ce8f61fa147e9232ccaba97393af83f3e28e46d5ff67b
4
- data.tar.gz: e494d8c91a74925bf75f0a234feb8424a061d8edb2c183f191c8b1360946c57e
3
+ metadata.gz: cf22abbaecfa8e1946ec8d74145079d674550f330d80c6a11b6585daf06838c8
4
+ data.tar.gz: f4c8b3bacba1773ce6e524db739f775f0a3f24583b2b95eab82556ebe14405e7
5
5
  SHA512:
6
- metadata.gz: 1fd6cb5c9c591e6f869b54b6de6c73510a5bfbbf3bec00fabdb14eca47b9bceaa8e2242be78be9ae0278b335f3582cb214711efb0b4cda48fa2fc6c65e3a8e81
7
- data.tar.gz: 5e20675f80c039fe4dbd5601fe54b9a9827a01c08027ee2b49a20e38a5993b4be6a720ae152e3fe36cae64e9cf7e52fc06f91f36d3f4e381b09475eaa47cc023
6
+ metadata.gz: 20a97ff08b91f5ebc898e3ab6d4b251e1effb8be044473cb2875ff34b25cb101de727fb24b6190879b5c133a2bb8e0278608f230d2556b44662ab1872baf91b1
7
+ data.tar.gz: bcc904083e7d122a03ca7dbe3fce06fc9bf6934a74b8a761d9f4404a24c4ed9e6613ac7efdcc6e43f9e9d351720a1cd42b05288fdf61ccdb87a41b8be3e2dc05
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.60.3] - 2026-10-16
11
+
12
+ ### Fixed
13
+
14
+ - Keep campaign execution receipts stable across unrelated journal appends, preserve historical-head evidence joins, and make delta reports uniquely attributable without requiring a local branch checkout.
15
+
10
16
  ## [0.60.2] - 2026-10-16
11
17
 
12
18
  ### Changed
@@ -68,7 +68,10 @@ module Ace
68
68
  data["head"] == head && data["kind"] == kind && data["historical"] == historical
69
69
  raise Atoms::CampaignContract::Invalid, "accepted execution receipt identity/head/kind mismatch"
70
70
  end
71
- data
71
+ # The journal tip is a read cursor, not the identity of an accepted
72
+ # receipt. Unrelated attempts advance it without changing this proof.
73
+ # Keep the receipt digest, exact head and canonical ref in the proof.
74
+ data.except("journal_commit")
72
75
  rescue Gem::LoadError, JSON::ParserError, SystemCallError => e
73
76
  raise Atoms::CampaignContract::Invalid, "ace-assign execution authority unavailable: #{e.message}"
74
77
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "open3"
4
4
  require "yaml"
5
+ require "ace/git/atoms/server_url"
5
6
 
6
7
  module Ace
7
8
  module Review
@@ -46,6 +47,10 @@ module Ace
46
47
  return {success: false, error: "Delta reference '#{reference}' is not a commit SHA"} unless reference_head.match?(SHA_PATTERN)
47
48
  return {success: false, error: "Delta reference head #{reference_head} is not an ancestor of the current PR head #{head}; history was likely rewritten — re-run a full review round"} unless ancestor?(reference_head, head, project_root: project_root)
48
49
 
50
+ if reference_head == head && newer_local_head?(head, pr_metadata, project_root: project_root)
51
+ return {success: false, error: "PR metadata still reports the reviewed head #{head}, but the local PR branch is newer; reconcile the push and refresh PR metadata before recording an empty delta"}
52
+ end
53
+
49
54
  diff = run_git(["diff", "#{reference_head}..#{head}"], project_root: project_root)
50
55
  return diff unless diff[:success]
51
56
 
@@ -53,6 +58,48 @@ module Ace
53
58
  session_dir: session_dir, diff: diff[:stdout]}
54
59
  end
55
60
 
61
+ # Repeated provider reads can return the same stale snapshot after a
62
+ # push. A local descendant is evidence against certifying that no code
63
+ # changed. Select the PR branch explicitly; an unrelated checkout does
64
+ # not define that PR's head.
65
+ def self.newer_local_head?(head, metadata, project_root:)
66
+ branch = metadata["headRefName"].to_s
67
+ repository = metadata["head_repository_url"].to_s
68
+ return false if branch.empty? || repository.empty?
69
+ remote = run_git(["for-each-ref", "--format=%(push:remotename)%00%(push:remoteref)", "refs/heads/#{branch}"], project_root: project_root)
70
+ return false unless remote[:success]
71
+ remote_name, remote_ref = remote[:stdout].strip.split("\0", -1)
72
+ return false if remote_name.to_s.empty?
73
+ remote_ref = default_push_ref(branch, remote_name, project_root: project_root) if remote_ref.to_s.empty?
74
+ return false unless remote_ref == "refs/heads/#{branch}"
75
+ urls = run_git(["remote", "get-url", "--push", "--all", remote_name], project_root: project_root)
76
+ return false unless urls[:success] && urls[:stdout].lines.any? do |url|
77
+ Ace::Git::Atoms::ServerUrl.match?(repository, url.strip)
78
+ end
79
+ local = rev_parse("refs/heads/#{branch}", project_root: project_root)
80
+ local[:success] && local[:stdout] != head && ancestor?(head, local[:stdout], project_root: project_root)
81
+ end
82
+ private_class_method :newer_local_head?
83
+
84
+ # Git exposes push:remoteref for explicit push refspecs only. Its
85
+ # default policies use branch names/upstream, not that empty atom.
86
+ def self.default_push_ref(branch, remote, project_root:)
87
+ explicit = run_git(["config", "--get-all", "remote.#{remote}.push"], project_root: project_root)
88
+ return nil if explicit[:success] && !explicit[:stdout].empty?
89
+ policy = run_git(["config", "--get", "push.default"], project_root: project_root)
90
+ upstream = run_git(["config", "--get", "branch.#{branch}.merge"], project_root: project_root)
91
+ case policy[:stdout].to_s.strip
92
+ when "", "simple"
93
+ target = upstream[:stdout].to_s.strip
94
+ fetch = run_git(["config", "--get", "branch.#{branch}.remote"], project_root: project_root)
95
+ triangular = fetch[:success] && fetch[:stdout].strip != remote
96
+ "refs/heads/#{branch}" if triangular || target.empty? || target == "refs/heads/#{branch}"
97
+ when "current", "matching" then "refs/heads/#{branch}"
98
+ when "upstream" then upstream[:stdout].to_s.strip
99
+ end
100
+ end
101
+ private_class_method :default_push_ref
102
+
56
103
  # Find the head SHA recorded by the most recent prior session of this PR.
57
104
  #
58
105
  # @param pr_url [String] canonical PR URL stored in session metadata
@@ -23,8 +23,9 @@ module Ace
23
23
  end
24
24
  # Reviewer reports are written as review-report-<model>.md (Ruby API
25
25
  # path) or review-<model>.md (CLI provider path); prompt/context and
26
- # dev-feedback files are not reports.
27
- reports = Dir.glob(File.join(dir, "review-*.md")).select do |path|
26
+ # dev-feedback files are not reports. Single-model and no-op rounds
27
+ # use the maintained review.md output.
28
+ reports = Dir.glob(File.join(dir, "{review-*,review}.md")).select do |path|
28
29
  base = File.basename(path)
29
30
  !base.end_with?(".prompt.md", ".context.md") &&
30
31
  !%w[review-dev-feedback.md].include?(base)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Review
5
- VERSION = "0.60.2"
5
+ VERSION = "0.60.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-review
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.60.2
4
+ version: 0.60.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz