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:
co-authored by
Claude Opus 5
parent
4fae5b5398
commit
f1637ee532
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user