From 07c8db9939dae018d0113c00e51ef593a0ba10e6 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl <304681515+codex-bot-andresmgsl@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:57:11 +0000 Subject: [PATCH] fix: union repeated blocked-by declarations --- .../issueflow-reconcile.sh | 34 +++++++++---------- changelog.d/184.md | 3 ++ test/issueflow-reconcile.test.sh | 8 +++++ 3 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 changelog.d/184.md diff --git a/actions/issueflow-reconcile/issueflow-reconcile.sh b/actions/issueflow-reconcile/issueflow-reconcile.sh index c5ab519..c69cb11 100644 --- a/actions/issueflow-reconcile/issueflow-reconcile.sh +++ b/actions/issueflow-reconcile/issueflow-reconcile.sh @@ -178,26 +178,26 @@ issue_references() { # text on stdin -> LOCAL/CROSSreference blocked_reference_records() { # body on stdin -> classified reference records # Dependency declarations sometimes soft-wrap after a comma. Continue - # through the first sentence terminator; if prose omits one, conservatively - # retain later references so ambiguity can keep an issue blocked, never - # promote it prematurely. + # each occurrence through its sentence terminator. Unioning every occurrence + # can over-retain historical prose, but a stale block is safer than falsely + # promoting work whose live dependency was dropped (#184). awk ' { - line = $0 - lower = tolower(line) - if (!active) { - marker = "blocked by" - start = index(lower, marker) - if (!start) next - line = substr(line, start + length(marker)) - active = 1 + body = body (NR == 1 ? "" : "\n") $0 + } + END { + marker = "blocked by" + rest = body + while ((start = index(tolower(rest), marker))) { + clause = substr(rest, start + length(marker)) + if (match(clause, /[.;]/)) { + print substr(clause, 1, RSTART - 1) + rest = substr(clause, RSTART + 1) + } else { + print clause + break + } } - if (line ~ /[.;]/) { - sub(/[.;].*/, "", line) - print line - exit - } - print line } ' | issue_references } diff --git a/changelog.d/184.md b/changelog.d/184.md new file mode 100644 index 0000000..0a7336c --- /dev/null +++ b/changelog.d/184.md @@ -0,0 +1,3 @@ +### Fixed + +- Issue-flow reconciliation unions every `Blocked by` clause, preventing a closed first dependency from hiding later open blockers (#184). diff --git a/test/issueflow-reconcile.test.sh b/test/issueflow-reconcile.test.sh index d1a8f15..d228f61 100644 --- a/test/issueflow-reconcile.test.sh +++ b/test/issueflow-reconcile.test.sh @@ -115,6 +115,12 @@ check "empty offsite timeline extracts nothing" 0 "" offsite_cross_referenced_pr # Invariant 3: blocked declarations parse and release only when all close. refs="$(blocked_references <<< $'Context #99. Blocked by #12 (first), #7 (second). Blocks #44.')" check "blocked declaration extracts only declared refs" 0 $'7\n12' printf '%s\n' "$refs" +body='Part of #151. Blocked by #152. Blocked by #153. Blocked by #148 — prose. Blocks #155.' +check "repeated blocker declarations retain every clause" 0 "" test \ + "$(blocked_references <<<"$body")" = $'148\n152\n153' +body=$'Note: restored because it is blocked by an operator act. See below.\nBlocked by #152, #153, #148.' +check "earlier blocker prose does not hide a later declaration" 0 "" test \ + "$(blocked_references <<<"$body")" = $'148\n152\n153' body=$'Blocked by #12 (first),\n#7 (soft-wrapped second). Blocks #44.' check "soft-wrapped blocker declaration retains continuation refs" 0 "" test \ "$(blocked_references <<<"$body")" = $'7\n12' @@ -143,6 +149,8 @@ check "slash-adjacent local references survive" 0 $'14\n15' \ check "comma-adjacent local references survive" 0 $'11\n12' \ blocked_references <<<"Blocked by #11, #12." check "open blocker keeps issue blocked" 0 "KEEP" blocked_decision "$refs" $'CLOSED\nOPEN' +check "an open blocker in a repeated declaration prevents promotion" 0 "KEEP" \ + blocked_decision $'12\n13' $'CLOSED\nOPEN' check "all closed blockers release issue" 0 "READY" blocked_decision "$refs" $'CLOSED\nCLOSED' check "missing blocked declaration is flagged" 0 "FLAG_UNPARSEABLE" blocked_decision "" "" check "unreadable blocker is flagged" 0 "FLAG_UNPARSEABLE" blocked_decision "12" "UNKNOWN" -- 2.45.2