Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 23 additions & 11 deletions scripts/ci/opencode_review_normalize_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,21 +315,26 @@ def mentions_changed_file_evidence(reason: str, summary: str) -> bool:
return bool(CHANGED_FILE_EVIDENCE_PATTERN.search(f"{reason}\n{summary}"))


def current_changed_files() -> set[str]:
"""Return the exact current-head changed files when the workflow provides them."""
def current_changed_files() -> set[str] | None:
"""Return the exact current-head changed files when the workflow provides them, or None if unavailable."""
changed_files_path = os.environ.get("OPENCODE_CHANGED_FILES_FILE")
if not changed_files_path:
return set()
return None

path = Path(changed_files_path)
if not path.is_file():
return None

try:
return {
line.strip()
for line in Path(changed_files_path)
for line in path
.read_text(encoding="utf-8")
.splitlines()
if line.strip()
}
except OSError:
return set()
return None


def changed_file_is_source_like(path: str) -> bool:
Expand Down Expand Up @@ -404,8 +409,10 @@ def contradicts_material_changed_file_scope(reason: str, summary: str) -> bool:
def mentions_actual_changed_file(reason: str, summary: str) -> bool:
"""Return whether an approval names an exact current-head changed file."""
changed_files = current_changed_files()
if not changed_files:
if changed_files is None:
return mentions_changed_file_evidence(reason, summary)
if not changed_files:
return True
combined = f"{reason}\n{summary}"
return any(changed_file in combined for changed_file in changed_files)

Expand Down Expand Up @@ -572,13 +579,18 @@ def build_approval_repair_summary(summary: str, evidence_text: str) -> str | Non
"""Append missing approval labels from bounded current-head evidence."""
changed_files = changed_files_from_evidence(evidence_text)
coverage_mode = evidence_coverage_mode(evidence_text)
if not changed_files or coverage_mode is None:
if coverage_mode is None:
return None

first_file = changed_files[0]
file_list = ", ".join(changed_files[:5])
if len(changed_files) > 5:
file_list += f", and {len(changed_files) - 5} more"
if not changed_files:
first_file = "no-files-changed"
file_list = "no files changed"
else:
first_file = changed_files[0]
file_list = ", ".join(changed_files[:5])
if len(changed_files) > 5:
file_list += f", and {len(changed_files) - 5} more"

if coverage_mode == "not_applicable":
coverage_line = (
"Coverage: coverage execution evidence reports test coverage as not applicable "
Expand Down
45 changes: 43 additions & 2 deletions tests/test_opencode_review_normalize_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def test_changed_file_and_verification_posture_detection():

def test_actual_changed_file_detection_prefers_current_head_file_list(tmp_path, monkeypatch):
monkeypatch.delenv("OPENCODE_CHANGED_FILES_FILE", raising=False)
assert norm.current_changed_files() == set()
assert norm.current_changed_files() is None
assert norm.mentions_actual_changed_file("scripts/ci/example.py", "")

changed_files = tmp_path / "changed-files.txt"
Expand Down Expand Up @@ -126,9 +126,27 @@ def test_actual_changed_file_detection_prefers_current_head_file_list(tmp_path,
)

monkeypatch.setenv("OPENCODE_CHANGED_FILES_FILE", str(tmp_path / "missing.txt"))
assert norm.current_changed_files() == set()
assert norm.current_changed_files() is None
assert norm.mentions_actual_changed_file("scripts/ci/example.py", "")

empty_files = tmp_path / "empty-files.txt"
empty_files.write_text("", encoding="utf-8")
monkeypatch.setenv("OPENCODE_CHANGED_FILES_FILE", str(empty_files))
assert norm.current_changed_files() == set()
assert norm.mentions_actual_changed_file("No files changed", "Empty PR")

dir_path = tmp_path / "a_directory"
dir_path.mkdir()
monkeypatch.setenv("OPENCODE_CHANGED_FILES_FILE", str(dir_path))
assert norm.current_changed_files() is None

def raise_os_error(*args, **kwargs):
raise OSError("Permission denied")

monkeypatch.setattr(norm.Path, "read_text", raise_os_error)
monkeypatch.setenv("OPENCODE_CHANGED_FILES_FILE", str(changed_files))
assert norm.current_changed_files() is None


def test_preferred_review_language_handles_unreadable_and_unknown_evidence(tmp_path, monkeypatch):
evidence = tmp_path / "evidence.md"
Expand Down Expand Up @@ -485,6 +503,29 @@ def test_valid_control_filters_shape_head_and_review_contract():


def test_valid_control_repairs_approval_summary_from_bounded_evidence(tmp_path, monkeypatch):
evidence = tmp_path / "bounded-review-evidence-empty.md"
evidence.write_text(
"""\
# OpenCode bounded PR review evidence

## Coverage execution evidence

# Coverage Evidence

## Coverage Decision

- Result: PASS
- Test coverage: not applicable because no supported changed source files or package manifests were found.
- Docstring coverage: not applicable

## Changed files
""",
encoding="utf-8",
)
monkeypatch.setenv("OPENCODE_EVIDENCE_FILE", str(evidence))
monkeypatch.delenv("OPENCODE_CHANGED_FILES_FILE", raising=False)
assert norm.build_approval_repair_summary("", evidence.read_text(encoding="utf-8")) is not None

evidence = tmp_path / "bounded-review-evidence.md"
evidence.write_text(
"""\
Expand Down
Loading