fix(check-preservation): 절을 통째로 지우면 아무것도 안 움직이던 자리

「확인하지 못한 것」 절을 통째로 지운 편집이 경고를 비운 채 지나갔다. 배관 문제가 아니라
실을 것이 없었다 — 지운 절에 보호 구간이 없었고 그 절의 문장이 유보 목록에 없었다.
그래서 「경고가 비면 자동 통과」라는 읽기 계약으로도 그대로 통과한다.

유보 목록이 추정·가능성 쪽에만 몰려 있었다. 한계를 밝히는 말은 대개 「안 했다」 모양인데
그쪽이 얇았다. 두 갈래로 나누고 뒤쪽을 채웠다.

그리고 `##` 절이 통째로 사라지면 그 자체를 경고에 올린다. 무엇이 사라졌는지는 절 제목으로
충분하다.

error 를 늘리지 않았다. 판정하는 것이 아니라 보이게 하는 것이다. 절을 덜어 낸 것인지
한계를 지운 것인지는 근거를 읽어야 안다.

채택된 편집 100쌍을 다시 돌려 막은 쌍이 1 그대로인 것을 확인했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
This commit is contained in:
DongHyeonka
2026-09-10 11:27:49 +09:00
co-authored by Claude Opus 5
parent 4fae5b5398
commit f1637ee532
2 changed files with 57 additions and 4 deletions
+26 -4
View File
@@ -46,14 +46,28 @@ EXTRACTORS: dict[str, re.Pattern[str]] = {
r"(?![0-9A-Za-z_.\-])"),
}
# 유보 표현. 늘어난 것은 세지 않고 줄어든 것만 낸다
# 유보 표현. 늘어난 것은 세지 않고 줄어든 것만 낸다.
#
# 두 갈래다. 앞쪽은 **확신을 낮추는 말**이고, 뒤쪽은 **「안 했다」로 한계를 밝히는 말**이다.
# 처음에는 앞쪽에만 몰려 있었는데, 그러면 「확인하지 못한 것」 절을 통째로 지워도 계수가
# 안 움직인다 — 그 절의 문장이 대개 뒤쪽 모양이기 때문이다.
HEDGES = (
# 확신을 낮춘다
"추정", "가능성", "보인다", "보였다", "아마", "", "일 수 있다", "일지도",
"확인하지 못했", "확인하지 않았다", "미확인", "안 봤다", "못 봤다",
"모른다", "정하지 않았다", "재지 않았다", "돌리지 않았다", "열지 않았다",
"로컬", "이 환경에서", "이번에는", "한정", "범위 안",
"모른", "로컬", "이 환경에서", "이번에는", "한정", "범위 안",
# 「안 했다」로 한계를 밝힌다
"확인하지 못했다", "확인하지 않았다", "확인 안 했다", "미확인",
"세지 않았다", "세 보지 않았다", "재지 않았다", "재 보지 않았다",
"안 쟀다", "못 쟀다", "돌리지 않았다", "돌려 보지 않았다", "안 돌렸다", "못 돌렸다",
"열지 않았다", "열어 보지 않았다", "안 봤다", "못 봤다",
"대조하지 않았다", "시험하지 않았다", "정하지 않았다", "만들지 않았다",
"하지 않았다", "않은 것", "못 한 것",
)
# `## 이름` 절. 통째로 사라진 절은 그 자체를 낸다 — 안에 보호 구간이 없으면
# 다른 어떤 계수도 안 움직인다. 「확인하지 못한 것」 절을 지우는 편집이 그 모양이다
SECTION = re.compile(r"^##\s+(.+?)\s*$", re.M)
def _counts(text: str) -> dict[str, collections.Counter]:
out = {}
@@ -98,6 +112,14 @@ def compare(before: str, after: str) -> dict:
"sentence": _sentence_of(after, value),
"note": "편집 전에 없던 값이다"})
# 절이 통째로 사라졌나. 제목만으로 무엇이 사라졌는지 충분하다
gone = [t for t in SECTION.findall(before) if t not in set(SECTION.findall(after))]
for title in gone:
warnings.append({"kind": "", "change": "사라짐", "count": 1, "value": f"## {title}",
"sentence": _sentence_of(before, f"## {title}"),
"note": "절이 통째로 없어졌다. 덜어 낸 것인지 한계를 지운 것인지는 "
"근거를 읽어야 안다"})
hb, ha = _hedges(before), _hedges(after)
hedge = []
for w in sorted(hb - ha):
+31
View File
@@ -65,6 +65,37 @@ class PreservationTest(unittest.TestCase):
res = cp.compare(self.before, after)
self.assertEqual([], res["hedgesDropped"])
def test_a_whole_section_removed_is_surfaced(self):
"""절을 통째로 지우면 안에 보호 구간이 없어도 보여야 한다.
「확인하지 못한 것」 절을 지우는 편집이 그 모양이다. 낱말 계수만 보면
아무것도 안 움직여서 경고가 비고, 「경고가 비면 자동 통과」로 그대로 지나간다.
"""
before = ("# 제목\n\n요약이다.\n\n## 결론\n\n값은 14ms 였다.\n\n"
"## 확인하지 못한 것\n\n다른 환경에서는 세 보지 않았다.\n")
after = "# 제목\n\n요약이다.\n\n## 결론\n\n값은 14ms 였다.\n"
res = cp.compare(before, after)
self.assertEqual([], res["errors"], "절 삭제는 error 가 아니다")
gone = [w["value"] for w in res["warnings"] if w["kind"] == ""]
self.assertIn("## 확인하지 못한 것", gone)
def test_a_section_that_stays_is_not_reported(self):
"""대조군. 절이 그대로면 아무것도 안 나온다."""
text = "# 제목\n\n요약이다.\n\n## 결론\n\n값은 14ms 였다.\n"
self.assertEqual([], cp.compare(text, text)["warnings"])
def test_limitation_hedges_are_counted(self):
"""「안 했다」로 한계를 밝히는 말도 유보다.
목록이 추정·가능성 쪽에만 몰려 있으면 한계를 적은 문장을 지워도 계수가 안 움직인다.
"""
for word in ("세 보지 않았다", "돌려 보지 않았다", "재 보지 않았다", "확인 안 했다"):
with self.subTest(word=word):
before = f"값은 14ms 였다. 다른 환경에서는 {word}."
after = "값은 14ms 였다."
dropped = {h["word"] for h in cp.compare(before, after)["hedgesDropped"]}
self.assertIn(word, dropped)
def test_a_missing_file_is_not_reported_as_clean(self):
import subprocess
p = subprocess.run(