From b2048f63bd9957330d8f1109a72cb99a34998c90 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 24 Aug 2026 16:02:01 +0000 Subject: [PATCH 1/5] test: pin Refs token boundaries (#234) --- test/issueflow-reconcile.test.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/issueflow-reconcile.test.sh b/test/issueflow-reconcile.test.sh index 0d0d8ed..83131d9 100644 --- a/test/issueflow-reconcile.test.sh +++ b/test/issueflow-reconcile.test.sh @@ -101,6 +101,25 @@ check "empty labels do not exempt a claimed issue" 0 "SWEEP" claim_clock_exempt refs_body=$'Refs #12\nAlso refs: #8 and heavy-duty/rig#4.\nCloses #99\nNot refs-ish #7\nfix refs parsing from #200\nCloses #40; refs: none\nRefs #175 (split from #150)' check "Refs parser returns only references owned by a valid Refs marker" 0 "" \ test "$(refs_references <<<"$refs_body")" = $'8\n12\n175' +# A Refs declaration binds one token, never the prose that follows it; every +# later occurrence starts a fresh declaration (#234). +crew_round_line='- **Claude verified the safe `Refs #52`, TDD provenance, and inherited #65 CI failure.**' +check "Refs parser ignores issue prose after the bound token" 0 "" \ + test "$(refs_references <<<"$crew_round_line")" = 52 +check "Refs comma-list narrowing is deliberate" 0 "" \ + test "$(refs_references <<<'Refs #8, #9')" = 8 +check "a cross-repo Refs token stays non-local" 0 "" \ + test -z "$(refs_references <<<'Refs heavy-duty/rig#4')" +check "prose after a cross-repo Refs token stays non-local" 0 "" \ + test -z "$(refs_references <<<'Refs heavy-duty/rig#4, #12')" +check "parenthesized prose after a local Refs token is ignored" 0 "" \ + test "$(refs_references <<<'Refs #175 (split from #150)')" = 175 +check "every Refs occurrence on one line contributes its bound token" 0 "" \ + test "$(refs_references <<<'Refs #8. Refs #9.')" = $'8\n9' +check "lowercase refs in unrelated prose declares nothing" 0 "" \ + test -z "$(refs_references <<<'fix refs parsing from #200')" +check "Refs without a following token declares nothing" 0 "" \ + test -z "$(refs_references <<<'Closes #40; refs: none')" open_records=$'BODY\tRefs #5\nCLOSING\t9\nBODY\tRefs heavy-duty/rig#112\nBODY\tRefs #5\nCLOSING\t5' check "open PR linkage unions closing and local Refs body references" 0 $'5\n9' \ open_pr_issues <<<"$open_records" From b105939d9507e7e79598dcf0d9277f06cff704f6 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 24 Aug 2026 16:04:02 +0000 Subject: [PATCH 2/5] fix: bind Refs declarations to one token (#234) --- actions/issueflow-reconcile/issueflow-reconcile.sh | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/actions/issueflow-reconcile/issueflow-reconcile.sh b/actions/issueflow-reconcile/issueflow-reconcile.sh index faf6c8d..f6166a2 100644 --- a/actions/issueflow-reconcile/issueflow-reconcile.sh +++ b/actions/issueflow-reconcile/issueflow-reconcile.sh @@ -208,14 +208,12 @@ claim_reclaim_marker() { # $1 = last activity epoch refs_references() { # PR body on stdin -> local issue numbers named by Refs awk ' { - line = $0 - lower = tolower(line) - if (match(lower, /(^|[^[:alnum:]_-])refs[[:space:]:]+/)) { - line = substr(line, RSTART + RLENGTH) - if (line ~ /^(#|([[:alnum:]_.-]+\/)?[[:alnum:]_.-]+#)[0-9]+/) { - sub(/[.(;].*/, "", line) - print line - } + rest = tolower($0) + while (match(rest, /(^|[^[:alnum:]_-])refs[[:space:]:]+(#|([[:alnum:]_.-]+\/)?[[:alnum:]_.-]+#)[0-9]+/)) { + token = substr(rest, RSTART, RLENGTH) + sub(/^.*refs[[:space:]:]+/, "", token) + print token + rest = substr(rest, RSTART + RLENGTH) } } ' | issue_references \ From 6b2b467b7c35acf700f935383a08f451a14aab7d Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 24 Aug 2026 16:04:33 +0000 Subject: [PATCH 3/5] docs: record bounded Refs parsing (#234) --- changelog.d/234.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/234.md diff --git a/changelog.d/234.md b/changelog.d/234.md new file mode 100644 index 0000000..bbc2ed5 --- /dev/null +++ b/changelog.d/234.md @@ -0,0 +1,3 @@ +### Fixed + +- Refs-based issue-flow transitions now bind each declaration to its immediately following reference token, so later issue prose cannot release or preserve unrelated claims (#234). From 5232027361687d6b62d6a45002c0dd7545c866b4 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 24 Aug 2026 16:06:12 +0000 Subject: [PATCH 4/5] test: mark Refs fixture Markdown literal (#234) --- test/issueflow-reconcile.test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/issueflow-reconcile.test.sh b/test/issueflow-reconcile.test.sh index 83131d9..a2ae806 100644 --- a/test/issueflow-reconcile.test.sh +++ b/test/issueflow-reconcile.test.sh @@ -103,6 +103,7 @@ check "Refs parser returns only references owned by a valid Refs marker" 0 "" \ test "$(refs_references <<<"$refs_body")" = $'8\n12\n175' # A Refs declaration binds one token, never the prose that follows it; every # later occurrence starts a fresh declaration (#234). +# shellcheck disable=SC2016 # backticks are the fixture's literal Markdown crew_round_line='- **Claude verified the safe `Refs #52`, TDD provenance, and inherited #65 CI failure.**' check "Refs parser ignores issue prose after the bound token" 0 "" \ test "$(refs_references <<<"$crew_round_line")" = 52 From d712f0636f4a27be8b052024ea90a74208d2b52c Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 24 Aug 2026 16:13:19 +0000 Subject: [PATCH 5/5] fix: preserve Refs keyword boundaries (#234) --- actions/issueflow-reconcile/issueflow-reconcile.sh | 4 +++- test/issueflow-reconcile.test.sh | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/actions/issueflow-reconcile/issueflow-reconcile.sh b/actions/issueflow-reconcile/issueflow-reconcile.sh index f6166a2..1d00b4c 100644 --- a/actions/issueflow-reconcile/issueflow-reconcile.sh +++ b/actions/issueflow-reconcile/issueflow-reconcile.sh @@ -213,7 +213,9 @@ refs_references() { # PR body on stdin -> local issue numbers named by Refs token = substr(rest, RSTART, RLENGTH) sub(/^.*refs[[:space:]:]+/, "", token) print token - rest = substr(rest, RSTART + RLENGTH) + # Retain the token final byte so ^ cannot turn a concatenated + # alphanumeric suffix into a fresh keyword boundary (#234). + rest = substr(rest, RSTART + RLENGTH - 1) } } ' | issue_references \ diff --git a/test/issueflow-reconcile.test.sh b/test/issueflow-reconcile.test.sh index a2ae806..b09a8ff 100644 --- a/test/issueflow-reconcile.test.sh +++ b/test/issueflow-reconcile.test.sh @@ -117,6 +117,8 @@ check "parenthesized prose after a local Refs token is ignored" 0 "" \ test "$(refs_references <<<'Refs #175 (split from #150)')" = 175 check "every Refs occurrence on one line contributes its bound token" 0 "" \ test "$(refs_references <<<'Refs #8. Refs #9.')" = $'8\n9' +check "a concatenated Refs spelling is not a second keyword occurrence" 0 "" \ + test "$(refs_references <<<'Refs #8Refs #9')" = 8 check "lowercase refs in unrelated prose declares nothing" 0 "" \ test -z "$(refs_references <<<'fix refs parsing from #200')" check "Refs without a following token declares nothing" 0 "" \