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:
DongHyeonka
2026-09-17 11:02:02 +09:00
co-authored by Claude Opus 5
parent 2109f726fe
commit ab59130196
1524 changed files with 3160026 additions and 8369 deletions
+55 -3
View File
@@ -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)