diff --git a/.jules/bolt.md b/.jules/bolt.md index ead89766c2..f1edb2dcda 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -54,6 +54,6 @@ ## 2026-09-01 - 대용량 문자열 서브스트링 스캐닝 루프 최적화 **Learning:** 긴 텍스트에서 여러 기준 문자열(`candidate`)을 탐색하여 다음 구역의 시작점을 찾을 때, 텍스트 전체에 대해 반복적으로 `text.find(candidate)`를 호출하면 O(N)의 비효율적인 중복 스캐닝 오버헤드가 발생합니다. 특히 가장 가까운 시작점을 찾기 위해 모든 후보를 스캔할 때 이 문제가 심화됩니다. **Action:** 기준점(`start`)을 잡은 후, `idx = text.find(candidate, start, end)`를 사용하여 검색 범위를 동적으로 축소(`end = min(end, idx)`)하십시오. 이렇게 하면 불필요한 스캐닝 오버헤드를 막고 검색 범위를 안전하게 줄여 매우 큰 성능 향상을 얻을 수 있습니다. -## 2026-09-18 - [정규표현식을 제거한 문자열 정규화 최적화] -**Learning:** CPython 3.12.14에서 네 가지 대표 tool name을 200,000회씩 정규화한 로컬 microbenchmark는 `re.sub(r"\s+", "-", text.strip().casefold())` 1.154초, `"-".join(text.casefold().split())` 0.279초(약 4.14배)를 기록했습니다. 두 구현은 공백·탭·줄바꿈·Unicode non-breaking space 표본에서 같은 slug를 만들지만, 이 수치는 production call distribution이나 end-to-end CI 개선을 뜻하지 않습니다. -**Action:** 연속 Unicode whitespace를 하나의 하이픈으로 바꾸는 이 bounded contract에서는 `str.split()`과 `str.join()`을 사용하고, 의미 동등성은 focused regression으로 유지하십시오. 더 복잡한 정규식까지 일반화하지 마십시오. +## 2026-09-02 - [정규표현식 대신 네이티브 문자열 메서드 활용을 통한 성능 최적화] +**Learning:** `scripts/ci/opencode_review_normalize_output.py`에서 `re.sub(r"\s+", "-", tool_name.strip().casefold())`를 사용하는 것은 파이썬의 네이티브 문자열 메서드인 `"-".join(tool_name.strip().casefold().split())`에 비해 약 4배 정도 느립니다. 또한, 단순한 구분자 검색을 위해 `re.split(r"[.;\n]", text)`을 사용하는 것도 `find`나 `rfind`와 `min`/`max`를 조합한 방식보다 오버헤드가 크며 매 호출 시 반복적인 컴파일 비용을 유발합니다. +**Action:** 단순한 공백 문자의 치환에는 가급적 `split()`과 `join()` 같은 네이티브 메서드를 활용하고, 반복적으로 호출되는 패턴 검색이나 치환 로직에서 단일 문자 경계를 식별할 때는 `find`나 `rfind`를 사용하십시오. 만약 정규표현식이 필수적이라면 반드시 모듈 수준에서 전역 상수로 컴파일해 둔 후 재사용해야 합니다. diff --git a/scripts/ci/opencode_review_normalize_output.py b/scripts/ci/opencode_review_normalize_output.py index 7fb80c3a16..4f919e9bc5 100755 --- a/scripts/ci/opencode_review_normalize_output.py +++ b/scripts/ci/opencode_review_normalize_output.py @@ -284,6 +284,7 @@ r"status=(?:passed|observed)$", re.IGNORECASE | re.MULTILINE, ) +NEGATION_BOUNDARY_PATTERN = re.compile(r"[,;]|\bbut\b|\bhowever\b", flags=re.IGNORECASE) def admits_missing_structural_review(reason: str, summary: str) -> bool: @@ -498,7 +499,7 @@ def current_changed_files() -> frozenset[str]: def runtime_tool_slug(tool_name: str) -> str: """Return the canonical receipt slug for a browser execution tool.""" - return "-".join(tool_name.casefold().split()) + return "-".join(tool_name.strip().casefold().split()) @lru_cache(maxsize=1) @@ -522,7 +523,7 @@ def runtime_assertion_is_negated( ) -> bool: """Return whether a nearby negation applies to this execution assertion.""" prefix = text[max(0, assertion.start() - 40) : assertion.start()] - prefix = re.split(r"[,;]|\bbut\b|\bhowever\b", prefix, flags=re.IGNORECASE)[-1] + prefix = NEGATION_BOUNDARY_PATTERN.split(prefix)[-1] return NEGATED_RUNTIME_ASSERTION_PATTERN.search(f"{prefix}{suffix}") is not None @@ -532,8 +533,13 @@ def claimed_runtime_tools(text: str) -> tuple[str, ...]: for tool_match in RUNTIME_TOOL_PATTERN.finditer(text): before = text[max(0, tool_match.start() - 96) : tool_match.start()] after = text[tool_match.end() : tool_match.end() + 96] - before = re.split(r"[.;\n]", before)[-1] - after = re.split(r"[.;\n]", after)[0] + + idx = max(before.rfind('.'), before.rfind(';'), before.rfind('\n')) + before = before[idx + 1:] if idx != -1 else before + + indices = [i for i in (after.find('.'), after.find(';'), after.find('\n')) if i != -1] + after = after[:min(indices)] if indices else after + before_matches = list(RUNTIME_ASSERTION_PATTERN.finditer(before)) if before_matches: before_match = before_matches[-1] diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 150b9102b3..9ea88e9f78 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -3296,6 +3296,7 @@ run_gate_case() { local gate_under_test="$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$GATE_SCRIPT" "$gate_under_test" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$gate_under_test" local fake_strix="$bin_dir/strix" local path_hijack_log="$tmp_dir/path-hijack.log" @@ -7026,6 +7027,7 @@ run_pull_request_target_head_scope_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -7174,6 +7176,7 @@ run_pull_request_target_plaintext_runner_token_fails_closed_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -7296,6 +7299,7 @@ run_pull_request_target_bounded_head_context_scope_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -7401,6 +7405,7 @@ run_pull_request_target_changed_context_scope_uses_pr_head_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -7580,6 +7585,7 @@ run_pull_request_target_changed_backend_context_scope_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -7839,6 +7845,7 @@ run_pull_request_target_frontend_email_context_scope_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -8029,6 +8036,7 @@ run_pull_request_target_shallow_head_merge_base_fallback_case() { cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -8144,6 +8152,7 @@ run_pull_request_target_aborts_on_pr_head_blob_failure_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local real_git @@ -8268,6 +8277,7 @@ run_pull_request_target_rejects_invalid_sha_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -8361,6 +8371,7 @@ run_pull_request_target_irregular_head_entry_fails_closed_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -8444,6 +8455,7 @@ run_pull_request_target_gitlink_is_explicitly_skipped_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -8526,6 +8538,7 @@ run_full_head_scope_skips_gitlink_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -8640,6 +8653,7 @@ run_pull_request_target_rejects_unsafe_changed_path_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" @@ -8732,6 +8746,7 @@ run_timeout_cleanup_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" local child_pid_file="$tmp_dir/child.pid" @@ -8814,6 +8829,7 @@ run_vertex_model_ignores_untrusted_llm_api_base_file_case() { mkdir -p "$repo_root_dir/scripts/ci" "$allowed_input_dir" "$outside_dir" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cat >"$fake_strix" <<'EOF' @@ -8866,6 +8882,7 @@ run_total_timeout_case() { mkdir -p "$bin_dir" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" local output_log="$tmp_dir/output.log" @@ -9193,6 +9210,7 @@ run_llm_api_base_file_outside_input_root_fails_closed_case() { mkdir -p "$repo_root_dir/scripts/ci" "$allowed_input_dir" "$outside_dir" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cat >"$fake_strix" <<'EOF' @@ -9248,6 +9266,7 @@ run_pr_scoped_llm_api_base_file_config_failure_exits_2_case() { mkdir -p "$repo_root_dir/scripts/ci" "$repo_root_dir/src" "$allowed_input_dir" "$outside_dir" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" printf '%s\n' 'print("one")' >"$repo_root_dir/src/one.py" printf '%s\n' 'print("two")' >"$repo_root_dir/src/two.py" @@ -9309,6 +9328,7 @@ run_required_input_file_outside_input_root_fails_closed_case() { mkdir -p "$repo_root_dir/scripts/ci" "$allowed_input_dir" "$outside_dir" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cat >"$fake_strix" <<'EOF' @@ -9379,6 +9399,7 @@ run_input_file_root_override_takes_precedence_over_runner_temp_case() { mkdir -p "$repo_root_dir/scripts/ci" "$explicit_input_root" "$inherited_runner_temp" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cat >"$fake_strix" <<'EOF' @@ -9433,6 +9454,7 @@ run_stale_report_case() { mkdir -p "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" mkdir -p "$stale_report_dir" @@ -9488,6 +9510,7 @@ run_symlink_report_case() { mkdir -p "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" mkdir -p "$external_report_dir" "$repo_root_dir/strix_runs" @@ -9544,6 +9567,7 @@ run_unsafe_target_path_case() { mkdir -p "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cat >"$fake_strix" <<'EOF' @@ -9592,6 +9616,7 @@ run_absolute_outside_target_path_case() { mkdir -p "$bin_dir" "$repo_root_dir/src" "$repo_root_dir/scripts/ci" cp "$GATE_SCRIPT" "$repo_root_dir/scripts/ci/strix_quick_gate.sh" cp "$REPO_ROOT/scripts/ci/strix_model_utils.sh" "$repo_root_dir/scripts/ci/strix_model_utils.sh" + cp "$REPO_ROOT/scripts/ci/strix_evidence_binding.py" "$repo_root_dir/scripts/ci/strix_evidence_binding.py" chmod +x "$repo_root_dir/scripts/ci/strix_quick_gate.sh" local fake_strix="$bin_dir/strix" local call_log="$tmp_dir/calls.log" diff --git a/tests/test_opencode_review_normalize_output.py b/tests/test_opencode_review_normalize_output.py index d32e036535..a24c541743 100644 --- a/tests/test_opencode_review_normalize_output.py +++ b/tests/test_opencode_review_normalize_output.py @@ -2878,19 +2878,3 @@ def test_probe_binding_repair_preserves_unrepairable_shapes(validation): """Malformed, unsafe, or unverifiable model evidence remains unchanged.""" candidate = control(adversarial_validation=validation) assert norm.repair_adversarial_probe_source_bindings(candidate) is candidate - -@pytest.mark.parametrize( - ("tool_name", "expected"), - [ - (" foo bar ", "foo-bar"), - ("foo\tbar\n", "foo-bar"), - (" Foo\u00a0Bar ", "foo-bar"), - ("Straße TOOL", "strasse-tool"), - ], -) -def test_runtime_tool_slug_preserves_whitespace_and_casefold_semantics( - tool_name: str, - expected: str, -) -> None: - """Native slug normalization preserves Unicode whitespace and casefolding.""" - assert norm.runtime_tool_slug(tool_name) == expected