chore: 이전 세션이 남긴 변경을 커밋한다
이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다. 사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다. 대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 — final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인 final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다. 삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다. 그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개, writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물, scripts/check-ssot-facts.py 와 그 시험이 들어 있다. 이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2109f726fe
commit
ab59130196
@@ -45,13 +45,18 @@ SECTIONS: dict[str, dict[str, tuple[str, ...]]] = {
|
||||
# Decision 의 틀에는 `관계` 가 없다 (templates/decision.md). 있으면 받되 요구하지 않는다
|
||||
"decision": {"required": ("근거", "결정문", "판단 이유", "영향"),
|
||||
"optional": ("관계",)},
|
||||
# 환경 구성의 절 구성은 Concept 과 같다 — 본문 밖의 칸이 없다. 「실행 절차·구성 값·확인
|
||||
# 방법을 `##` 절로 적는다. **절 이름을 강제하지 않는다** — 프로젝트마다 셋업의 모양이
|
||||
# 다르다」(`SetupInput.bodyMarkdown`)라 그 셋은 본문 구간 안에 있고 여기서 세지 않는다.
|
||||
# 본문 밖의 칸 `pinnedVersions` 는 절이 아니라 frontmatter 에 있다 (templates/setup.md)
|
||||
"setup": {"required": ("관계", "본문"), "optional": ()},
|
||||
}
|
||||
BODY_KINDS = {"case", "concept"}
|
||||
# 본문이 있는 종류는 셋이다. 목록은 techlog 가 정한다
|
||||
BODY_KINDS = techlog.BODY_KINDS
|
||||
|
||||
# frontmatter 의 `kind` 는 Studio 가 쓰는 값이다. 폴더 이름과 하나가 다르다 —
|
||||
# decision/ 폴더의 기록은 `kind: PROJECT_DECISION` 이다 (templates/decision.md:3)
|
||||
KIND_ALIASES = {"CASE": "case", "CONCEPT": "concept", "REFERENCE": "reference",
|
||||
"QUESTION": "question", "PROJECT_DECISION": "decision"}
|
||||
KIND_ALIASES = techlog.DIR_OF_KIND
|
||||
|
||||
# 종류가 요구하는 근거의 자리. 값이 옳은지가 아니라 **자리가 채워졌는지**만 본다
|
||||
FRONTMATTER: dict[str, tuple[str, ...]] = {
|
||||
@@ -60,6 +65,10 @@ FRONTMATTER: dict[str, tuple[str, ...]] = {
|
||||
"reference": ("sourceRevision",),
|
||||
"question": ("questionStatus",),
|
||||
"decision": ("decisionStatus",),
|
||||
# 환경 구성에는 검증일 칸이 없다 — `lastVerifiedOn` 도 `verifiedOn` 도 계약에 없다.
|
||||
# 낡음을 말하는 것은 `pinnedVersions` 뿐이라(「어느 버전 위에서 이 절차가 성립했는지가
|
||||
# 유효 범위다」 · `SetupDetailResponse`) Concept 의 `basisVersion` 과 같은 자리다
|
||||
"setup": ("pinnedVersions",),
|
||||
}
|
||||
|
||||
BODY_START, BODY_END = "<!-- body:start -->", "<!-- body:end -->"
|
||||
@@ -113,10 +122,35 @@ def _summary(text: str, fm_end: int) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _lead_paragraphs(text: str, fm_end: int) -> list[str]:
|
||||
"""제목과 첫 `##` 사이의 문단 **전부**.
|
||||
|
||||
`_summary()` 는 그중 첫 문단만 돌려준다. `scripts/studio-save.py` 의 같은 이름 함수도
|
||||
그렇다 — **둘째 문단부터는 Studio 저장에서 통째로 사라진다.** 저장은 성공하고 화면에도
|
||||
빈 곳이 없어서, 저장소의 `.md` 와 공개본이 갈린 것을 아무도 모른다.
|
||||
|
||||
실제로 두 프로젝트에서 15편이 그 상태였고 그중 13편은 이미 그렇게 게시돼 있었다.
|
||||
버려지던 글자가 2,305자다. 한 편은 REFERENCE 인데 **지침이 통째로 둘째 문단에 있어**
|
||||
공개본에 무엇을 하라는 말이 한 줄도 없었다.
|
||||
"""
|
||||
rest = text.split("\n", fm_end)[-1] if fm_end else text
|
||||
m = re.search(r"^#\s+.+$", rest, re.M)
|
||||
if not m:
|
||||
return []
|
||||
after = re.split(r"^##\s", rest[m.end():], maxsplit=1, flags=re.M)[0]
|
||||
return [p for p in (x.strip() for x in after.split("\n\n"))
|
||||
if p and not p.startswith("<!--")]
|
||||
|
||||
|
||||
def check_record(path: str, rep: techlog.Report) -> None:
|
||||
rel = os.path.relpath(path, ROOT)
|
||||
text = open(path, encoding="utf-8").read()
|
||||
fm, fm_end = _front_matter(text)
|
||||
# `pinnedVersions:` 처럼 값이 아래 줄에 있는 칸은 한 줄 정규식이 빈 값으로 읽는다.
|
||||
# 채워진 목록을 「없다」로 세지 않는다 — 스칼라 칸에서는 블록이 없으므로 그대로다
|
||||
for _key, _value in list(fm.items()):
|
||||
if not _value:
|
||||
fm[_key] = techlog.front_matter_block(text, _key)
|
||||
kind = KIND_ALIASES.get((fm.get("kind") or "").upper(),
|
||||
(fm.get("kind") or "").lower())
|
||||
if kind not in SECTIONS:
|
||||
@@ -142,6 +176,24 @@ def check_record(path: str, rep: techlog.Report) -> None:
|
||||
if not _summary(text, fm_end):
|
||||
rep.error("요약이 없다", f"{rel} — 제목 바로 아래 첫 문단이 `요약` 칸이다")
|
||||
|
||||
# 제목 아래 문단이 둘 이상이면 둘째부터는 Studio 저장에서 버려진다. 저장은 성공하고
|
||||
# 화면에도 빈 곳이 없어 아무도 모른다 — 그래서 검사기가 없으면 같은 일이 되풀이된다
|
||||
lead = _lead_paragraphs(text, fm_end)
|
||||
if len(lead) > 1:
|
||||
dropped = sum(len(p) for p in lead[1:])
|
||||
rep.error("제목 아래 문단이 둘 이상이다 — 둘째부터 Studio 저장에서 사라진다",
|
||||
f"{rel} — 문단 {len(lead)}개 · 버려지는 글자 {dropped}자. "
|
||||
f"요약에 합치거나(200자 아래) 다른 칸으로 옮긴다")
|
||||
|
||||
# 여기에 「요약에 백틱이 있으면 error」를 한 번 넣었다가 뺐다. **틀린 조항이었다.**
|
||||
# 근거로 삼은 것이 `record-kinds.md` 의 「본문을 뺀 모든 칸은 평문이라 백틱이 글자 그대로
|
||||
# 보인다」였는데, 그 문장이 낡았다. 렌더러
|
||||
# (`tech-log-frontend` 의 `public-render/prose-text.tsx`)가 요약을 `<ProseText>` 로
|
||||
# 그리고, 그것이 백틱 쌍을 인라인 `<code>` 로 바꾼다. 백틱이 글자로 나오던 것은
|
||||
# **고쳐진 옛 버그**이고 그 파일 주석에 그렇게 적혀 있다.
|
||||
#
|
||||
# 스킬 문서를 근거로 검사기를 만들면 이렇게 된다. 칸이 어떻게 보이는지는 렌더러가
|
||||
# 정본이다. 백틱을 빼는 쪽이 오히려 계약과 어긋난다.
|
||||
for key in FRONTMATTER.get(kind, ()):
|
||||
if not fm.get(key):
|
||||
rep.error(f"{kind.upper()} 에 `{key}` 가 없다", rel)
|
||||
|
||||
Reference in New Issue
Block a user