chore: 문서 수정

This commit is contained in:
DongHyeonka
2026-07-29 18:29:25 +09:00
parent 3ac0a367dc
commit 4b7f1a90d2
29 changed files with 1594 additions and 747 deletions
+87
View File
@@ -1,5 +1,8 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from claridoc.models import Brief, DocumentType
@@ -35,6 +38,29 @@ Write Korean reader-facing prose as a supported first-person experience, not as
- Use conversational but disciplined Korean. A Korean developer should be able to say the sentence naturally to a colleague without turning it into forced colloquial speech.
"""
_PLAIN_FORM_ENDING = re.compile(r"(?<!니)다(?=[.!?](?:\s|$))")
_FENCE = re.compile(r"^\s*(?:```|~~~)")
_HEADING = re.compile(r"^\s{0,3}(#{1,6})\s+(.+?)\s*$")
_IMAGE_ONLY = re.compile(r"^\s*!\[[^\]]*\]\([^)]*\)\s*$")
_TABLE_DIVIDER = re.compile(
r"^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$"
)
_QUOTED_SPANS = (
re.compile(r'"[^"\n]*"'),
re.compile(r"'[^'\n]*'"),
re.compile(r"“[^”\n]*”"),
re.compile(r"[^\n]*"),
re.compile(r"「[^」\n]*」"),
re.compile(r"『[^』\n]*』"),
)
@dataclass(frozen=True, slots=True)
class ReaderProseSegment:
text: str
line: int
h2_title: str | None
def korean_experience_contract_applies(brief: Brief) -> bool:
if not brief.is_korean:
@@ -72,3 +98,64 @@ def revision_style_protocol(brief: Brief) -> str:
After resolving individual findings, recheck 문서 전체(the complete document) against `korean_first_person_experience_v1`.
Do not stop after adding one `저는` sentence. Confirm the opening and major transitions still form supported experience threads, all reader prose still uses `합니다/했습니다`, unfamiliar terms remain explained at first need, and no compliant section regressed during the whole-document rewrite.
"""
def reader_prose_segments(markdown: str) -> list[ReaderProseSegment]:
segments: list[ReaderProseSegment] = []
in_fence = False
current_h2: str | None = None
lines = markdown.splitlines()
table_lines: set[int] = set()
for index, line in enumerate(lines):
if not _TABLE_DIVIDER.match(line):
continue
if index > 0 and "|" in lines[index - 1]:
table_lines.add(index - 1)
table_lines.add(index)
cursor = index + 1
while cursor < len(lines) and lines[cursor].strip() and "|" in lines[cursor]:
table_lines.add(cursor)
cursor += 1
for line_number, raw_line in enumerate(lines, start=1):
if _FENCE.match(raw_line):
in_fence = not in_fence
continue
if in_fence:
continue
heading = _HEADING.match(raw_line)
if heading:
if len(heading.group(1)) == 2:
current_h2 = heading.group(2).strip()
continue
stripped = raw_line.strip()
if (
not stripped
or line_number - 1 in table_lines
or stripped.startswith(">")
or raw_line.startswith((" ", "\t"))
or _IMAGE_ONLY.match(raw_line)
or _TABLE_DIVIDER.match(raw_line)
or (stripped.startswith("|") and stripped.endswith("|"))
):
continue
prose = re.sub(r"!\[[^\]]*\]\([^)]*\)", "", stripped)
prose = re.sub(r"\[([^\]]+)\]\([^)]*\)", r"\1", prose)
prose = re.sub(r"`[^`\n]*`", "", prose)
for quoted_span in _QUOTED_SPANS:
prose = quoted_span.sub("", prose)
prose = re.sub(r"^\s*(?:[-*+]|\d+[.)])\s+", "", prose).strip()
if prose:
segments.append(ReaderProseSegment(prose, line_number, current_h2))
return segments
def plain_form_ending_locations(markdown: str) -> list[int]:
locations: list[int] = []
for segment in reader_prose_segments(markdown):
locations.extend(segment.line for _ in _PLAIN_FORM_ENDING.finditer(segment.text))
return locations