From f7688184ab846beea19b33d926e4d417ea083a6e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:53:37 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Python=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1=20=EC=84=B1=EB=8A=A5=20=ED=96=A5=EC=83=81=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=8A=AC?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=8A=A4=20=EC=B5=9C=EC=A0=81=ED=99=94\n\n-?= =?UTF-8?q?=20`strip=5Fjsonc=5Fcomments`=20=EB=82=B4=20=EA=B0=9C=EB=B3=84?= =?UTF-8?q?=20=EB=AC=B8=EC=9E=90=20=EB=8B=A8=EC=9C=84=20`append`=EB=A5=BC?= =?UTF-8?q?=20`slice`=20=EC=9D=BC=EA=B4=84=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=B4=20O(N^2)=20=ED=95=A0=EB=8B=B9=20=EC=98=A4=EB=B2=84?= =?UTF-8?q?=ED=97=A4=EB=93=9C=EB=A5=BC=20=EA=B0=90=EC=86=8C=EC=8B=9C?= =?UTF-8?q?=EC=BC=B0=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 + rewrite.py | 63 +++++++++++++++++++ .../ci/assert_opencode_reasoning_effort.py | 10 +-- 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 rewrite.py diff --git a/.jules/bolt.md b/.jules/bolt.md index 4f20b36047..7334b8d90c 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -54,3 +54,6 @@ ## 2026-09-01 - 대용량 문자열 서브스트링 스캐닝 루프 최적화 **Learning:** 긴 텍스트에서 여러 기준 문자열(`candidate`)을 탐색하여 다음 구역의 시작점을 찾을 때, 텍스트 전체에 대해 반복적으로 `text.find(candidate)`를 호출하면 O(N)의 비효율적인 중복 스캐닝 오버헤드가 발생합니다. 특히 가장 가까운 시작점을 찾기 위해 모든 후보를 스캔할 때 이 문제가 심화됩니다. **Action:** 기준점(`start`)을 잡은 후, `idx = text.find(candidate, start, end)`를 사용하여 검색 범위를 동적으로 축소(`end = min(end, idx)`)하십시오. 이렇게 하면 불필요한 스캐닝 오버헤드를 막고 검색 범위를 안전하게 줄여 매우 큰 성능 향상을 얻을 수 있습니다. +## 2024-11-23 - Python 파싱 로직에서 개별 문자 루프 추가(O(N^2)) 회피 +**Learning:** Python 문자열 파싱 시, 선형 루프 안에서 문자 하나씩 `output.append(text[i])`를 호출하면 함수 호출 오버헤드와 함께 큰 병목이 발생할 수 있습니다. 특히 입력 텍스트가 매우 길어지면 메모리 할당 및 리스트 확장 비용이 O(N^2)에 가까운 영향을 줄 수 있습니다. +**Action:** 긴 문자열을 파싱할 때 `while` 루프 등에서 매치되지 않은 텍스트를 처리하려면, 인덱스를 이용해 탐색(scan ahead)한 뒤 스트링 슬라이싱(`output.append(text[start:cursor])`)을 통해 일괄적으로 매치되지 않은 부분 전체를 잘라내어 리스트에 추가하십시오. diff --git a/rewrite.py b/rewrite.py new file mode 100644 index 0000000000..85c38f4442 --- /dev/null +++ b/rewrite.py @@ -0,0 +1,63 @@ +with open('scripts/ci/assert_opencode_reasoning_effort.py', 'r') as f: + text = f.read() + +new_text = """def strip_jsonc_comments(text: str) -> str: + \"\"\"Return ``text`` with ``//`` and ``/* */`` comments removed outside strings. + + ``opencode.jsonc`` is genuinely JSONC (it carries explanatory ``//`` notes, + e.g. above the ``contextual-orchestrator`` provider block), so a plain + :func:`json.loads` rejects it. Comment markers are only recognized outside + JSON string literals, so a string value that itself contains ``//`` (the + ``"$schema": "https://opencode.ai/config.json"`` line) is preserved + unchanged. Newlines inside removed content are kept so any remaining + ``json.JSONDecodeError`` still reports an accurate line number. + \"\"\" + result: list[str] = [] + in_string = False + index = 0 + length = len(text) + last_append = 0 + while index < length: + char = text[index] + if in_string: + if char == "\\\\" and index + 1 < length: + index += 2 + continue + if char == '"': + in_string = False + index += 1 + continue + if char == '"': + in_string = True + index += 1 + continue + if char == "/" and index + 1 < length and text[index + 1] == "/": + result.append(text[last_append:index]) + index += 2 + while index < length and text[index] not in "\\r\\n": + index += 1 + last_append = index + continue + if char == "/" and index + 1 < length and text[index + 1] == "*": + result.append(text[last_append:index]) + index += 2 + while index + 1 < length and not ( + text[index] == "*" and text[index + 1] == "/" + ): + if text[index] in "\\r\\n": + result.append(text[index]) + index += 1 + index += 2 + last_append = index + continue + index += 1 + result.append(text[last_append:]) + return "".join(result)""" + +old_func = text.split("def strip_jsonc_comments")[1].split("def load_config")[0] +old_func = "def strip_jsonc_comments" + old_func + +text = text.replace(old_func, new_text + "\n\n\n") + +with open('scripts/ci/assert_opencode_reasoning_effort.py', 'w') as f: + f.write(text) diff --git a/scripts/ci/assert_opencode_reasoning_effort.py b/scripts/ci/assert_opencode_reasoning_effort.py index 82079d5112..400af66856 100644 --- a/scripts/ci/assert_opencode_reasoning_effort.py +++ b/scripts/ci/assert_opencode_reasoning_effort.py @@ -35,12 +35,11 @@ def strip_jsonc_comments(text: str) -> str: in_string = False index = 0 length = len(text) + last_append = 0 while index < length: char = text[index] if in_string: - result.append(char) if char == "\\" and index + 1 < length: - result.append(text[index + 1]) index += 2 continue if char == '"': @@ -49,15 +48,17 @@ def strip_jsonc_comments(text: str) -> str: continue if char == '"': in_string = True - result.append(char) index += 1 continue if char == "/" and index + 1 < length and text[index + 1] == "/": + result.append(text[last_append:index]) index += 2 while index < length and text[index] not in "\r\n": index += 1 + last_append = index continue if char == "/" and index + 1 < length and text[index + 1] == "*": + result.append(text[last_append:index]) index += 2 while index + 1 < length and not ( text[index] == "*" and text[index + 1] == "/" @@ -66,9 +67,10 @@ def strip_jsonc_comments(text: str) -> str: result.append(text[index]) index += 1 index += 2 + last_append = index continue - result.append(char) index += 1 + result.append(text[last_append:]) return "".join(result) From 4b43180d071da3551fb5d15e3aca7e643eefec79 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:59:53 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Python=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1=20=EC=84=B1=EB=8A=A5=20=ED=96=A5=EC=83=81=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=8A=AC?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EC=8A=A4=20=EC=B5=9C=EC=A0=81=ED=99=94\n\n-?= =?UTF-8?q?=20`strip=5Fjsonc=5Fcomments`=20=EB=82=B4=20=EA=B0=9C=EB=B3=84?= =?UTF-8?q?=20=EB=AC=B8=EC=9E=90=20=EB=8B=A8=EC=9C=84=20`append`=EB=A5=BC?= =?UTF-8?q?=20`slice`=20=EC=9D=BC=EA=B4=84=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=B4=20O(N^2)=20=ED=95=A0=EB=8B=B9=20=EC=98=A4=EB=B2=84?= =?UTF-8?q?=ED=97=A4=EB=93=9C=EB=A5=BC=20=EA=B0=90=EC=86=8C=EC=8B=9C?= =?UTF-8?q?=EC=BC=B0=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit