Initial commit: ABAP indexing pipeline (ingest, parser, summarize, index, query, wiki)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
byeongwook.choi
2026-09-21 13:23:37 +09:00
co-authored by Claude Fable 5.1
commit 11ae3629b2
453 changed files with 259183 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
"""사람 교정 보존 병합 (계획서 v4 §5.7).
대상은 git 에 커밋되는 program/package/concept 문서. units/ 는 재생성 산출물이라
merge 없이 항상 덮어쓴다(호출부에서 결정).
사람 교정 여부는 OKF v0.2 표준 필드로 판별한다 (docs/okf-version.md):
verified: { by: "human:<id>", at: <ISO-8601> } ← 사람이 검토·수정 후 저장할 때 추가
(구 계획서의 x-human-edited: true 규약도 하위호환으로 인식한다.)
규칙:
- verified(human) 없는 문서 → 새 내용으로 덮어쓴다.
- verified(human) 있는 문서 → 본문을 보존하고 관리 대상 사실 필드만 갱신한다.
코드 해시가 바뀐 경우에는 새 요약을 "## (자동 생성) 변경 후 요약" 섹션으로 덧붙이고
status: draft 로 내린 뒤 wiki/_review.md 에 재검토 항목을 올린다.
"""
from __future__ import annotations
import re
from datetime import date
from pathlib import Path
FM_RE = re.compile(r"\A---\r?\n(.*?)\r?\n---\r?\n?", re.S)
AUTO_SECTION = "## (자동 생성) 변경 후 요약"
# 배치가 항상 갱신하는 사실 필드 (모두 한 줄로 emit 됨 — okf_writer 참조)
MANAGED_KEYS = (
"resource", "tags", "generated",
"x-lines", "x-tables-read", "x-tables-write", "x-calls", "x-outputs",
"x-code-hash", "x-prompt-version", "x-unit-count", "x-chunk-count",
)
def split_frontmatter(text: str) -> tuple[str | None, str]:
"""(frontmatter 내용, 본문) — frontmatter 가 없으면 (None, 전체)."""
m = FM_RE.match(text)
if not m:
return None, text
return m.group(1), text[m.end():]
def is_human_verified(frontmatter: str | None) -> bool:
if not frontmatter:
return False
if re.search(r"""by:\s*['"]?human:""", frontmatter):
return True
return bool(re.search(r"^x-human-edited:\s*true\s*$", frontmatter, re.M)) # 하위호환
def get_scalar(frontmatter: str, key: str) -> str | None:
m = re.search(rf"""^{re.escape(key)}:\s*['"]?(.*?)['"]?\s*$""", frontmatter, re.M)
return m.group(1) if m else None
def _replace_or_append_line(fm: str, key: str, line: str) -> str:
pattern = re.compile(rf"^{re.escape(key)}:.*$", re.M)
if pattern.search(fm):
return pattern.sub(line.replace("\\", "\\\\"), fm, count=1)
return fm.rstrip("\n") + "\n" + line
def _strip_auto_section(body: str) -> str:
idx = body.find(AUTO_SECTION)
return body if idx < 0 else body[:idx].rstrip("\n") + "\n"
def add_review_entry(wiki_dir: Path, doc_rel: str, reason: str) -> None:
review = wiki_dir / "_review.md"
lines = review.read_text(encoding="utf-8").splitlines() if review.exists() else [
"# 재검토 필요 문서", "",
"코드가 바뀌어 사람 교정본과 어긋날 수 있는 문서 목록 (배치가 자동 추가).", "",
]
entry = f"- [ ] {doc_rel}{reason} ({date.today().isoformat()})"
if not any(doc_rel in ln for ln in lines):
lines.append(entry)
review.write_text("\n".join(lines) + "\n", encoding="utf-8")
def merge_write(path: Path, new_text: str, *, code_hash: str | None,
wiki_dir: Path, doc_rel: str) -> str:
"""새로 생성한 문서(new_text)를 사람 교정 보존 규칙에 따라 path 에 쓴다.
반환: created | overwritten | facts_updated | draft_appended
"""
path.parent.mkdir(parents=True, exist_ok=True)
if not path.exists():
path.write_text(new_text, encoding="utf-8")
return "created"
old = path.read_text(encoding="utf-8")
fm_old, body_old = split_frontmatter(old)
if not is_human_verified(fm_old):
path.write_text(new_text, encoding="utf-8")
return "overwritten"
# --- 사람 검토 문서: 본문 보존, 사실 필드만 갱신 ---
fm_new, body_new = split_frontmatter(new_text)
assert fm_new is not None
fm = fm_old or ""
for key in MANAGED_KEYS:
m = re.search(rf"^{re.escape(key)}:.*$", fm_new, re.M)
if m:
fm = _replace_or_append_line(fm, key, m.group(0))
old_hash = get_scalar(fm_old or "", "x-code-hash")
changed = bool(code_hash and old_hash and old_hash != code_hash)
if changed:
fm = _replace_or_append_line(fm, "status", "status: draft")
body = (_strip_auto_section(body_old).rstrip("\n")
+ f"\n\n{AUTO_SECTION}\n\n"
+ "> 코드가 변경되어 아래 요약을 새로 생성했습니다. 위 본문(사람 검토됨)과 대조해 반영해 주세요.\n\n"
+ body_new.lstrip("\n"))
result = "draft_appended"
add_review_entry(wiki_dir, doc_rel, "코드 변경 — 사람 교정본과 대조 필요")
else:
body = body_old
result = "facts_updated"
path.write_text(f"---\n{fm.strip()}\n---\n{body}", encoding="utf-8")
return result