Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a876d5c
feat: implement issue #2079 — dev-lead: resolve a bot review thread o…
Oct 9, 2026
04c91bc
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
9742f93
fix(reviews): harden acv_latest_nochange_disposition (hedges, em-dash…
don-petry Oct 9, 2026
a9d1152
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
6f0cfda
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
f9026aa
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
a05c718
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
207e12d
fix(bot): address code quality findings
don-petry Oct 9, 2026
7971234
fix(shellcheck): remove unused variable req_disposition
don-petry Oct 9, 2026
36aa3db
fix(reviews): no-change path blocks only newer REQUIRED/bot comments
Oct 9, 2026
567df3a
fix(reviews): skip-nochange neutralises only the no-change phrase's R…
don-petry Oct 9, 2026
bd55e44
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
dfe7626
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
8bcd390
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
caf8618
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
70b4d78
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
289c948
fix(#2079 AC1): integrate no-change disposition into resolve_deferred…
don-petry Oct 9, 2026
1a91bf2
fix(reviews): require latest maintainer comment to affirm no-change; …
don-petry Oct 9, 2026
1fe504a
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
b006fa8
fix(reviews): fail closed on same-timestamp ties and truncated commen…
don-petry Oct 9, 2026
47fac64
fix(reviews): stamp pass replies so dev-lead skip notes never read as…
Oct 9, 2026
5ead692
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
fdbbeb7
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
3c393e2
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
7e8627e
fix(reviews): attribute pass replies by recorded id and gate no-chang…
Oct 9, 2026
b93d9de
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
008be41
fix(reviews): unset DEV_LEAD_NOCHANGE_EPOCH explicitly in the unset-e…
don-petry Oct 9, 2026
9b41edc
fix(reviews): require hasNextPage==false on no-change fresh re-read
Oct 9, 2026
852b026
Merge branch 'main' into dev-lead/issue-2079-20261009-0149
don-petry Oct 9, 2026
6adb065
fix(reviews): persist unstamped replies across passes and record all …
Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions scripts/lib/addressed-claim-verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,29 @@ readonly _ACV_CLAIM_SUFFIX=' -->'
# merge" shape) rather than any human chatter, so a neutral "thanks" never blocks.
readonly _ACV_DISPOSITION_RE_UPPER='(REQUIRED|MUST BE (FIXED|ADDRESSED|RESOLVED|CHANGED)|CHANGES REQUIRED|BLOCKING|REQUEST(ING|ED)? CHANGES)'

# A maintainer "no change needed" disposition (#1743, #2079) — the mirror image of the
# blocking disposition above. Matched case-insensitively against a marker-less
# maintainer comment. It PERMITS resolution of a false-positive bot thread, the
# missing half of the #1692 REQUIRED gate (which WITHHOLDS resolution). Kept as
# conservative as the blocking regex: it recognises an explicit false-positive /
# wontfix / not-applicable assertion, not any human chatter, so a neutral "thanks"
# never clears a thread. Note "no change required" contains the substring REQUIRED
# and so also matches _ACV_DISPOSITION_RE_UPPER; the caller must resolve that
# collision in favour of the no-change intent.
readonly _ACV_NOCHANGE_RE_UPPER='(NO (CODE )?CHANGES? (NEEDED|REQUIRED|NECESSARY|WARRANTED|IS NEEDED|ARE NEEDED)|FALSE[ -]?POSITIVE|WON.?T ?FIX|WONTFIX|WORKING AS INTENDED|NOT A (REAL )?(BUG|ISSUE|PROBLEM|CONCERN|DEFECT)|NOT APPLICABLE|BY DESIGN|INTENDED BEHAVIOU?R)'
Comment thread
don-petry marked this conversation as resolved.
Resolved

# A NEGATED (or quoted-then-rejected) no-change phrase is NOT an affirmative no-change
# disposition (#1799). The broad phrase match above treats any occurrence of e.g.
# "FALSE POSITIVE" as authorization, so "this is not a false positive" or "I wouldn't
# call it working as intended" would wrongly clear a bot thread a maintainer actually
# wants fixed. This recognises a negator within a few words before a no-change phrase;
# acv_latest_nochange_disposition checks it FIRST and lets such a comment fall through
# to "not a disposition" (fail closed -> the thread stays open for the maintainer). A
# phrase that itself begins with a negator ("NOT A BUG", "NO CHANGE NEEDED") only
# trips the guard when ANOTHER negator precedes it, so the bare affirmative forms are
# never clobbered.
readonly _ACV_NOCHANGE_NEGATION_RE_UPPER='(CANNOT|CAN.?T|COULD ?NOT|COULDN.?T|WILL NOT|WON.?T|WOULD ?NOT|WOULDN.?T|SHOULD ?NOT|SHOULDN.?T|IS ?NOT|ISN.?T|ARE ?NOT|AREN.?T|WAS ?NOT|WASN.?T|WERE ?NOT|WEREN.?T|DOES ?NOT|DOESN.?T|DO ?NOT|DON.?T|DID ?NOT|DIDN.?T|NEVER|NOT)[,[:space:]]+([A-Z,'"'"']+[[:space:]]+){0,6}(FALSE[ -]?POSITIVE|WON.?T ?FIX|WONTFIX|WORKING AS INTENDED|BY DESIGN|INTENDED BEHAVIOU?R|NO (CODE )?CHANGES? (NEEDED|REQUIRED|NECESSARY|WARRANTED|IS NEEDED|ARE NEEDED)|NOT A (REAL )?(BUG|ISSUE|PROBLEM|CONCERN|DEFECT)|NOT APPLICABLE)'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

# Post-marker BOT-comment classification (#1735 AC2/AC4). A review bot that replies
# AFTER our addressed-marker either ACKNOWLEDGES (accepts our refutation / records a
# custom rule — the codeant-ai "✅ Customized review instruction saved!" shape) or
Expand Down Expand Up @@ -374,14 +397,102 @@ acv_latest_maintainer_disposition() {
fi
done <<<"$rows"

# Fail closed: any matching disposition with an unparseable timestamp wins over a
# valid latest one, since its true order against the others is unknown (#2079 AC3).
if [[ "$saw_unparseable" -eq 1 ]]; then
echo "unparseable"
return 2
fi
if [[ -n "$latest" ]]; then
echo "$latest"
return 0
fi
echo ""
return 1
}

# acv_latest_nochange_disposition <comments_json>
# Scan ALL comments in a thread (#1743, #2079) for a standing maintainer "no change
# needed" disposition — the mirror of acv_latest_maintainer_disposition.
# <comments_json> is a JSON array of
# {author:{login,__typename}, authorAssociation, body, createdAt}.
# A comment counts as a no-change disposition when it is: a User (not a Bot), from
# a repo maintainer (authorAssociation OWNER|MEMBER|COLLABORATOR — anything else,
# including missing, fails closed), MARKER-LESS per review_thread_is_agent_authored,
# and asserts an affirmative no-change disposition (_ACV_NOCHANGE_RE_UPPER, with
# negated and question forms rejected).
# Authorship is decided by MARKER, never by login: dev-lead posts as the same
# account the maintainer uses, so skipping that login would discard the
# maintainer's own verdict (the #1799 defect), while the marker is what makes the
# agent unable to dismiss a finding on its own (#1743 AC4).
# Returns:
# 0 + echoes the latest such comment's ISO createdAt (a no-change disposition applies)
# 1 + echoes "" (no no-change disposition found)
# 2 + echoes "unparseable" (a no-change disposition was
# found but a createdAt is
# missing/unparseable ->
# fail closed, leave open)
# Pure — no gh/git/network.
acv_latest_nochange_disposition() {
Comment thread
don-petry marked this conversation as resolved.
Resolved
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
local comments_json="${1:-}"

# Single jq pass serialises every comment as one row (login, typename, createdAt,
# association, base64(body)). Fields are joined with the ASCII Unit Separator
# (U+001F), NOT a tab: tab is IFS-whitespace, so `read` would collapse a run of
# tabs and DROP an empty middle field (an empty createdAt — the exact fail-closed
# case — would shift the next field into its slot). The body is base64-encoded so
# it can never contain the separator or a newline.
local rows
rows=$(jq -r '
if type == "array" then
.[]
| [ (.author.login // ""), (.author.__typename // ""), (.createdAt // ""), (.authorAssociation // ""), ((.body // "") | @base64) ]
| join("\u001f")
else empty end
' <<<"$comments_json" 2>/dev/null) || return 1
[[ -z "$rows" ]] && return 1

local latest="" saw_unparseable=0
local login typename created assoc body_b64 body up
while IFS=$'\x1f' read -r login typename created assoc body_b64; do
[[ -z "$login$typename$created$assoc$body_b64" ]] && continue
[[ "$typename" != "User" ]] && continue
# Only a repo maintainer may authorize resolution; a missing/unknown association
# fails closed (CWE-863).
case "$assoc" in OWNER|MEMBER|COLLABORATOR) ;; *) continue ;; esac
body=$(base64 --decode <<<"$body_b64" 2>/dev/null || printf '')
Comment thread
don-petry marked this conversation as resolved.
Resolved
# Marker-less is the discriminator: an agent-authored comment (carrying one of
# our markers) is ours, never a maintainer disposition — even from the same login.
review_thread_is_agent_authored "$body" && continue
Comment thread
don-petry marked this conversation as resolved.
Resolved
up="${body^^}"
# A negated phrase ("this is not a false positive") is not an affirmative
# disposition — checked FIRST so it falls through (fail closed).
[[ "$up" =~ $_ACV_NOCHANGE_NEGATION_RE_UPPER ]] && continue
[[ "$up" =~ $_ACV_NOCHANGE_RE_UPPER ]] || continue
# A question or an expression of doubt/disagreement is not an affirmative verdict
# ("Could this be a false positive?", "I disagree with calling this a false
# positive") -> fail closed.
[[ "$up" == *'?'* ]] && continue
[[ "$up" =~ (DISAGREE|DOUBT|UNCLEAR|NOT[[:space:]]SURE|UNSURE|NOT[[:space:]]CONVINCED) ]] && continue
Comment thread
don-petry marked this conversation as resolved.
Resolved
Comment thread
don-petry marked this conversation as resolved.
Resolved
# A disposition with no parseable timestamp cannot be ordered against a newer
# REQUIRED disposition or bot finding -> fail closed.
if [[ -z "$created" ]] || ! _acv_is_iso8601 "$created"; then
Comment thread
don-petry marked this conversation as resolved.
Resolved
saw_unparseable=1
continue
fi
if [[ -z "$latest" || "$created" > "$latest" ]]; then
latest="$created"
fi
done <<<"$rows"

if [[ "$saw_unparseable" -eq 1 ]]; then
echo "unparseable"
return 2
fi
if [[ -n "$latest" ]]; then
echo "$latest"
return 0
fi
echo ""
return 1
}
Expand Down
Loading
Loading