From aaebe4816e66c191af39d015023668a4e1e414d6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 09:02:37 +0900 Subject: [PATCH 1/5] perf(normalize): scan verification labels once --- CHANGELOG.md | 3 ++ .../ci/opencode_review_normalize_output.py | 38 ++++++++----------- .../test_opencode_review_normalize_output.py | 13 +++++++ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cef0acda6..244fe9641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] +- Scan approval-verification labels once when extracting sections, matching + longer labels first so `docstring coverage:` remains distinct from + `coverage:` as the label catalogue evolves. - Route Strix cross-provider fallbacks to explicit direct-OpenAI models (`openai-direct/...`) through the OpenAI inference endpoint instead of inheriting a provider-specific primary base: the workflow now provisions diff --git a/scripts/ci/opencode_review_normalize_output.py b/scripts/ci/opencode_review_normalize_output.py index 6e07c29d0..38cfa30ae 100755 --- a/scripts/ci/opencode_review_normalize_output.py +++ b/scripts/ci/opencode_review_normalize_output.py @@ -136,6 +136,14 @@ APPROVAL_VERIFICATION_PATTERNS = { label: re.compile(re.escape(label)) for label in APPROVAL_VERIFICATION_LABELS } +# Match the longest label first so a future label cannot shadow a more specific +# label that starts at the same position (for example, docstring coverage:). +ANY_LABEL_PATTERN = re.compile( + "|".join( + re.escape(label) + for label in sorted(APPROVAL_VERIFICATION_LABELS, key=len, reverse=True) + ) +) SOURCE_LIKE_CHANGED_FILE_EXTENSIONS = frozenset( { @@ -959,32 +967,18 @@ def mentions_verification_posture(reason: str, summary: str) -> bool: def label_section(text: str, label: str) -> str: """Return text after a verification label until the next known label.""" - def label_starts(candidate: str) -> list[int]: - """Return exact verification-label starts without suffix collisions.""" - starts = [] - pattern = APPROVAL_VERIFICATION_PATTERNS.get(candidate) - if pattern is None: - pattern = re.compile(re.escape(candidate)) - for match in pattern.finditer(text): - index = match.start() - if ( - candidate == "coverage:" - and text[max(0, index - 10) : index] == "docstring " - ): - continue - starts.append(index) - return starts - - starts = label_starts(label) + actual_matches = [ + (match.start(), match.group(0)) for match in ANY_LABEL_PATTERN.finditer(text) + ] + starts = [index for index, candidate in actual_matches if candidate == label] if not starts: return "" + start = starts[-1] + len(label) next_starts = [ - candidate_start - for candidate in APPROVAL_VERIFICATION_LABELS - if candidate != label - for candidate_start in label_starts(candidate) - if candidate_start >= start + index + for index, candidate in actual_matches + if candidate != label and index >= start ] end = min(next_starts) if next_starts else len(text) return text[start:end] diff --git a/tests/test_opencode_review_normalize_output.py b/tests/test_opencode_review_normalize_output.py index a24c54174..fc17a0a56 100644 --- a/tests/test_opencode_review_normalize_output.py +++ b/tests/test_opencode_review_normalize_output.py @@ -1386,6 +1386,7 @@ def test_label_and_full_coverage_detection(tmp_path, monkeypatch): assert not norm.mentions_full_coverage("", no_source_summary) assert norm.contradicts_changed_file_kinds("", no_source_summary) + changed_files = tmp_path / "opencode-changed-files.txt" changed_files.write_text("README.md\n", encoding="utf-8") monkeypatch.setenv("OPENCODE_CHANGED_FILES_FILE", str(changed_files)) @@ -1434,6 +1435,18 @@ def test_label_and_full_coverage_detection(tmp_path, monkeypatch): ) +def test_label_section_scans_labels_once_and_prefers_longest_label(): + """Parse adjacent labels without rescanning the input for every label.""" + text = "docstring coverage: 100% coverage: 98% performance: measured" + + assert norm.label_section(text, "docstring coverage:") == " 100% " + assert norm.label_section(text, "coverage:") == " 98% " + assert norm.label_section(text, "performance:") == " measured" + assert norm.ANY_LABEL_PATTERN.pattern.index("docstring\\ coverage:") < ( + norm.ANY_LABEL_PATTERN.pattern.index("coverage:") + ) + + def test_check_structural_approval_rejects_invalid_or_unsafe_approvals( tmp_path, monkeypatch ): From 37d8fa8bcb77a78daaee8e441a0337fe2fa98dd9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 09:09:37 +0900 Subject: [PATCH 2/5] fix(normalize): remove dead label matcher --- scripts/ci/opencode_review_normalize_output.py | 3 --- tests/test_opencode_review_normalize_output.py | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/ci/opencode_review_normalize_output.py b/scripts/ci/opencode_review_normalize_output.py index 38cfa30ae..952dcca12 100755 --- a/scripts/ci/opencode_review_normalize_output.py +++ b/scripts/ci/opencode_review_normalize_output.py @@ -133,9 +133,6 @@ "security/privacy:", ) -APPROVAL_VERIFICATION_PATTERNS = { - label: re.compile(re.escape(label)) for label in APPROVAL_VERIFICATION_LABELS -} # Match the longest label first so a future label cannot shadow a more specific # label that starts at the same position (for example, docstring coverage:). ANY_LABEL_PATTERN = re.compile( diff --git a/tests/test_opencode_review_normalize_output.py b/tests/test_opencode_review_normalize_output.py index fc17a0a56..01cc34768 100644 --- a/tests/test_opencode_review_normalize_output.py +++ b/tests/test_opencode_review_normalize_output.py @@ -1442,8 +1442,9 @@ def test_label_section_scans_labels_once_and_prefers_longest_label(): assert norm.label_section(text, "docstring coverage:") == " 100% " assert norm.label_section(text, "coverage:") == " 98% " assert norm.label_section(text, "performance:") == " measured" - assert norm.ANY_LABEL_PATTERN.pattern.index("docstring\\ coverage:") < ( - norm.ANY_LABEL_PATTERN.pattern.index("coverage:") + alternatives = norm.ANY_LABEL_PATTERN.pattern.split("|") + assert alternatives.index(re.escape("docstring coverage:")) < alternatives.index( + re.escape("coverage:") ) From 8334c192b86b98024e55ed2c40a59048e0e4777d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 09:43:17 +0900 Subject: [PATCH 3/5] fix(normalize): preserve custom label sections --- scripts/ci/opencode_review_normalize_output.py | 6 +++++- tests/test_opencode_review_normalize_output.py | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/ci/opencode_review_normalize_output.py b/scripts/ci/opencode_review_normalize_output.py index 952dcca12..615239a3e 100755 --- a/scripts/ci/opencode_review_normalize_output.py +++ b/scripts/ci/opencode_review_normalize_output.py @@ -967,7 +967,11 @@ def label_section(text: str, label: str) -> str: actual_matches = [ (match.start(), match.group(0)) for match in ANY_LABEL_PATTERN.finditer(text) ] - starts = [index for index, candidate in actual_matches if candidate == label] + if label in APPROVAL_VERIFICATION_LABELS: + starts = [index for index, candidate in actual_matches if candidate == label] + else: + # Preserve repository-specific labels without rescanning catalogue labels. + starts = [match.start() for match in re.finditer(re.escape(label), text)] if not starts: return "" diff --git a/tests/test_opencode_review_normalize_output.py b/tests/test_opencode_review_normalize_output.py index 01cc34768..6b528198e 100644 --- a/tests/test_opencode_review_normalize_output.py +++ b/tests/test_opencode_review_normalize_output.py @@ -1448,6 +1448,13 @@ def test_label_section_scans_labels_once_and_prefers_longest_label(): ) +def test_label_section_preserves_custom_label_lookup(): + """Repository-specific labels retain the helper's backwards-compatible path.""" + text = "custom evidence: measured coverage: 98%" + + assert norm.label_section(text, "custom evidence:") == " measured " + + def test_check_structural_approval_rejects_invalid_or_unsafe_approvals( tmp_path, monkeypatch ): From 29b4f1977a4b7c9eb559cb4e349249770e490c70 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 10:00:10 +0900 Subject: [PATCH 4/5] docs: clarify coverage summary changelog --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244fe9641..a360693fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,8 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] -- Scan approval-verification labels once when extracting sections, matching - longer labels first so `docstring coverage:` remains distinct from - `coverage:` as the label catalogue evolves. +- Review summaries now report test and documentation coverage separately and + consistently. No action is required. - Route Strix cross-provider fallbacks to explicit direct-OpenAI models (`openai-direct/...`) through the OpenAI inference endpoint instead of inheriting a provider-specific primary base: the workflow now provisions From db50914fc274dc78e33e7882ca81c18ede6be2eb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 10:24:46 +0900 Subject: [PATCH 5/5] docs: make coverage note actionable --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a360693fe..e56f422a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Semantic Versioning where the repository publishes a release. ## [Unreleased] - Review summaries now report test and documentation coverage separately and - consistently. No action is required. + consistently. Open the summary to confirm both values before merging. - Route Strix cross-provider fallbacks to explicit direct-OpenAI models (`openai-direct/...`) through the OpenAI inference endpoint instead of inheriting a provider-specific primary base: the workflow now provisions