diff --git a/.jules/bolt.md b/.jules/bolt.md index 20bdfb8a..e7066a3a 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -37,3 +37,6 @@ ## 2024-07-25 - Pre-calculate and cache environmental file reads **Learning:** The `current_changed_files` function in `scripts/ci/opencode_review_normalize_output.py` was being called multiple times per item during JSON structure normalization, leading to redundant I/O reads of `OPENCODE_CHANGED_FILES_FILE`. **Action:** Use `@functools.lru_cache(maxsize=1)` and return an immutable `frozenset` when repeatedly reading static contextual files within a script's execution lifecycle. +## 2024-11-23 - Memoize File-Based Subprocess Queries in Embedded Python Scripts +**Learning:** Found an N+1 subprocess bottleneck in `scripts/ci/opencode_review_approve_gate.sh` where `changed_new_lines` invoked `git diff` for every finding, even when multiple findings pointed to the same file. Repeatedly shelling out inside loops is a severe performance anti-pattern. +**Action:** When validating multiple findings against the same file, decorate the inspection function with `@functools.cache` and ensure the return value is immutable (e.g., `frozenset` instead of `set`) to avoid redundant subprocess calls. diff --git a/scripts/ci/opencode_review_approve_gate.sh b/scripts/ci/opencode_review_approve_gate.sh index 03828be1..406c16bd 100755 --- a/scripts/ci/opencode_review_approve_gate.sh +++ b/scripts/ci/opencode_review_approve_gate.sh @@ -210,6 +210,7 @@ PR_HEAD_SHA_VAR="${PR_HEAD_SHA:-${HEAD_SHA:-}}" if ! python3 - "$SOURCE_ROOT" "$TMP_JSON" "$PR_BASE_SHA_VAR" "$PR_HEAD_SHA_VAR" <<'PY' from __future__ import annotations +import functools import json import re import subprocess @@ -231,9 +232,12 @@ def normalized_line(value: str) -> str: return " ".join(value.strip().split()) -def changed_new_lines(path_value: str) -> set[int]: +# ⚡ Bolt: Memoize changed_new_lines to prevent N+1 git diff subprocess calls +# Impact: Substantially reduces I/O wait overhead when multiple findings are on the same file path +@functools.cache +def changed_new_lines(path_value: str) -> frozenset[int]: if not pr_base_sha or not pr_head_sha: - return set() + return frozenset() try: completed = subprocess.run( [ @@ -255,9 +259,9 @@ def changed_new_lines(path_value: str) -> set[int]: shell=False, ) except OSError: - return set() + return frozenset() if completed.returncode not in {0, 1}: - return set() + return frozenset() line_numbers: set[int] = set() hunk_header = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@") @@ -270,7 +274,7 @@ def changed_new_lines(path_value: str) -> set[int]: if count <= 0: continue line_numbers.update(range(start, start + count)) - return line_numbers + return frozenset(line_numbers) _file_cache: dict[Path, list[str]] = {}