chore: 문서 수정
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user