diff --git a/.jules/bolt.md b/.jules/bolt.md index a86b7aaf..39c6cf14 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -43,3 +43,7 @@ ## 2026-07-09 - Avoid N+1 API blocking in SBOM aggregator **Learning:** The `collect_inventories` function in `scripts/ci/sbom_inventory_aggregator.py` was fetching SBOMs from the GitHub dependency graph synchronously for every repository in the organization. For large organizations (up to 500 repos), this N+1 network/CLI bottleneck significantly stalled the aggregation workflow. **Action:** Use `concurrent.futures.ThreadPoolExecutor` to fetch SBOMs concurrently when multiple repositories are provided, bounded by a `max_workers` limit (e.g., 10) to avoid overwhelming the CLI/API, while preserving the fast serial path for single-item inputs. + +## 2024-05-18 - [Fix O(N) Set Intersection in javascript_coverage_gate] +**Learning:** Found an O(N) performance bottleneck in `javascript_coverage_gate.py` where checking if a small line range intersects with a potentially large set of `changed_lines` was done by iterating over the entire set (`any(start <= line <= end for line in changed_lines)`). Because execution unit ranges are typically very small (often 1 line), iterating over all changed lines is highly inefficient for large PRs. +**Action:** Use `not set.isdisjoint(range(start, end + 1))` when checking for intersection between a set and a small continuous range to change the complexity from O(N) (size of set) to O(R) (size of range), drastically speeding up the inner loop. diff --git a/scripts/ci/javascript_coverage_gate.py b/scripts/ci/javascript_coverage_gate.py index 3cac2bfb..e69eb664 100644 --- a/scripts/ci/javascript_coverage_gate.py +++ b/scripts/ci/javascript_coverage_gate.py @@ -10,7 +10,6 @@ from pathlib import Path, PurePosixPath from typing import Any, Sequence - METRICS = ("statements", "branches", "functions", "lines") SOURCE_SUFFIXES = {".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"} EXCLUDED_PARTS = { @@ -129,15 +128,11 @@ def summarize_final(data: dict[str, Any]) -> dict[str, float]: for file_data in data.values(): statements = file_data.get("s") or {} totals["statements"][1] += len(statements) - totals["statements"][0] += sum( - 1 for count in statements.values() if count > 0 - ) + totals["statements"][0] += sum(1 for count in statements.values() if count > 0) functions = file_data.get("f") or {} totals["functions"][1] += len(functions) - totals["functions"][0] += sum( - 1 for count in functions.values() if count > 0 - ) + totals["functions"][0] += sum(1 for count in functions.values() if count > 0) branches = file_data.get("b") or {} for counts in branches.values(): @@ -155,8 +150,7 @@ def summarize_final(data: dict[str, Any]) -> dict[str, float]: totals["lines"][0] += sum(1 for count in line_counts.values() if count > 0) return { - metric: percentage(values[0], values[1]) - for metric, values in totals.items() + metric: percentage(values[0], values[1]) for metric, values in totals.items() } @@ -177,16 +171,14 @@ def intersects(location: dict[str, Any] | None, changed_lines: set[int]) -> bool if line_range is None: return False start, end = line_range - return any(start <= line <= end for line in changed_lines) + return not changed_lines.isdisjoint(range(start, end + 1)) def changed_metric_counts( records: Sequence[dict[str, Any]], changed_lines: set[int] ) -> dict[str, tuple[int, int]]: """Return covered/total counts for changed Istanbul execution units.""" - units: dict[str, dict[tuple[Any, ...], int]] = { - metric: {} for metric in METRICS - } + units: dict[str, dict[tuple[Any, ...], int]] = {metric: {} for metric in METRICS} for file_data in records: statements = file_data.get("s") or {} statement_map = file_data.get("statementMap") or {} @@ -194,9 +186,7 @@ def changed_metric_counts( line_range = location_range(location) if line_range is None: continue - if not any( - line_range[0] <= line <= line_range[1] for line in changed_lines - ): + if changed_lines.isdisjoint(range(line_range[0], line_range[1] + 1)): continue count = int(statements.get(statement_id, 0)) units["statements"][line_range] = max( @@ -213,9 +203,7 @@ def changed_metric_counts( line_range = location_range(location) if line_range is None: continue - if not any( - line_range[0] <= line <= line_range[1] for line in changed_lines - ): + if changed_lines.isdisjoint(range(line_range[0], line_range[1] + 1)): continue key = (*line_range, str(function_data.get("name") or "")) units["functions"][key] = max( @@ -227,19 +215,18 @@ def changed_metric_counts( counts = branches.get(branch_id) or [] locations = branch_data.get("locations") or [] for index, count in enumerate(counts): - location = locations[index] if index < len(locations) else branch_data.get("loc") + location = ( + locations[index] + if index < len(locations) + else branch_data.get("loc") + ) line_range = location_range(location) if line_range is None: continue - if not any( - line_range[0] <= line <= line_range[1] - for line in changed_lines - ): + if changed_lines.isdisjoint(range(line_range[0], line_range[1] + 1)): continue key = (*line_range, str(branch_data.get("type") or ""), index) - units["branches"][key] = max( - units["branches"].get(key, 0), int(count) - ) + units["branches"][key] = max(units["branches"].get(key, 0), int(count)) return { metric: (sum(1 for count in values.values() if count > 0), len(values)) @@ -266,17 +253,17 @@ def normalize_coverage_path( return normalized slash_path = raw_path.replace("\\", "/").rstrip("/") - suffix_matches = [ - path for path in changed_paths if slash_path.endswith(f"/{path}") - ] + suffix_matches = [path for path in changed_paths if slash_path.endswith(f"/{path}")] return suffix_matches[0] if len(suffix_matches) == 1 else None -def likely_runtime_lines(repo_root: Path, path: str, changed_lines: set[int]) -> list[int]: +def likely_runtime_lines( + repo_root: Path, path: str, changed_lines: set[int] +) -> list[int]: """Return changed lines that look executable when Istanbul maps no units.""" - source_lines = (repo_root / path).read_text( - encoding="utf-8", errors="replace" - ).splitlines() + source_lines = ( + (repo_root / path).read_text(encoding="utf-8", errors="replace").splitlines() + ) runtime_lines: list[int] = [] in_block_comment = False for line_number, raw_line in enumerate(source_lines, start=1): @@ -288,7 +275,9 @@ def likely_runtime_lines(repo_root: Path, path: str, changed_lines: set[int]) -> or in_block_comment or stripped.startswith("//") or stripped in {"{", "}", "};", ");", "]", "],"} - or stripped.startswith(("interface ", "type ", "export type ", "import type ")) + or stripped.startswith( + ("interface ", "type ", "export type ", "import type ") + ) ) if line_number in changed_lines and not non_runtime: runtime_lines.append(line_number) @@ -354,17 +343,23 @@ def main(argv: Sequence[str] | None = None) -> int: print(f"- {path.relative_to(repo_root)} (derived)") for metric in METRICS: print(f" {metric}: {metrics[metric]}%") - print("- Decision: advisory only; pre-existing global debt is visible but does not mask changed-code evidence.") + print( + "- Decision: advisory only; pre-existing global debt is visible but does not mask changed-code evidence." + ) if not changed: print("\n## Changed-source coverage") - print("- No changed JavaScript/TypeScript runtime source files; coverage is not applicable.") + print( + "- No changed JavaScript/TypeScript runtime source files; coverage is not applicable." + ) print("- Result: PASS") return 0 if not finals: print("\n## Changed-source coverage") print("- Result: FAIL") - print("- Reason: coverage-final.json is required for changed-line evidence but was not produced.") + print( + "- Reason: coverage-final.json is required for changed-line evidence but was not produced." + ) return 1 changed_paths = set(changed) @@ -384,8 +379,7 @@ def main(argv: Sequence[str] | None = None) -> int: continue counts = changed_metric_counts(records[path], changed_lines) metric_text = ", ".join( - f"{metric} {covered}/{total}" - for metric, (covered, total) in counts.items() + f"{metric} {covered}/{total}" for metric, (covered, total) in counts.items() ) print(f"- {path}: {metric_text}") total_units = sum(total for _covered, total in counts.values()) @@ -396,7 +390,9 @@ def main(argv: Sequence[str] | None = None) -> int: f"{path} changed runtime-looking lines {runtime_lines} but Istanbul mapped no execution units" ) else: - print(" changed lines are comments, delimiters, or type-only declarations; no executable units apply") + print( + " changed lines are comments, delimiters, or type-only declarations; no executable units apply" + ) continue for metric, (covered, total) in counts.items(): if total and covered != total: @@ -412,7 +408,9 @@ def main(argv: Sequence[str] | None = None) -> int: return 1 print("\n- Result: PASS") - print("- Reason: every instrumented execution unit intersecting changed runtime lines is covered.") + print( + "- Reason: every instrumented execution unit intersecting changed runtime lines is covered." + ) return 0