pipeline: make tech-log-tree.json the one decomposition contract and enforce it

리뷰 두 건을 반영했다.

계약
- tech-log-tree.json 하나가 분해 계약이자 색인이다. 사람이 읽는 트리·Node Specification·
  후보 대장은 없어졌고, 문서에 남아 있던 그 개념을 걷어냈다
- candidateScope — 후보를 찾는 SSOT 범위. 접어 넣은 제2부·제3부는 근거이지 후보가 아니다
- sourceRepository — 분석한 저장소의 경로·리비전·판단 근거. 리비전을 모르면 null 로 두고
  지어내지 않는다. 갈래가 여럿이면 revisions
- 검사기: 계약 미채택·PENDING·PROMOTE↔글감 양방향·candidateScope·sourceRepository 를
  error/warn 으로 센다. 옛 스키마도 검사를 피하지 못한다. 테스트 22 → 31

기록 쓰기
- 템플릿 5종에 source·sourceRevision·topicName, Question 에 닫는 조건, 본문 없는 종류에서
  assets 제거. 고정 절 개수 삭제
- check_evidence.mjs — 인용한 코드가 SSOT 에 있는지, 앵커가 SSOT 를 가리키는지, 제목이
  계약과 같은지, 리비전이 저장소에 있는지. 게시된 기록에서 SSOT 와 다른 URL 을 잡았다

문체
- 문체 규칙의 정본을 ai-tells.md 로. explaining.md 의 질문체 제목·절 끝 대조 반복·그림 예고
  규칙을 삭제해 충돌을 없앴다. 첫 절 「설명 뒤에 평가를 붙이지 않는다」에 지우는 사례 네 유형
- voice 스킬의 「독자 쪽을 본다」를 자료에 오독 기록이 있을 때로 좁히고, 평가만 더한 예시를 교체
- check_prose: 안내 문장을 요구하던 경고 제거, 문장이 끝나지 않은 채 문단이 끝나는 조각 검사 추가

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 12:39:20 +09:00
co-authored by Claude Fable 5.1
parent 73026cada6
commit 9d2a3725c5
54 changed files with 3583 additions and 871 deletions
+135 -26
View File
@@ -9,6 +9,7 @@
| SSOT에서 글감을 뽑아 트리를 만든다 | `deriving-tech-log-root-tree` |
| 글감 하나를 기록으로 쓴다 | `writing-tech-log-records` |
| 문장이 AI가 쓴 것처럼 읽히면 다시 쓴다 | `rewriting-technical-prose-naturally` |
| 맞는 말인데 아무도 쓰지 않은 보고서처럼 읽히면 | `writing-as-the-person-who-did-it` |
| 그림을 만든다 | `technical-visualizer` |
| 분석에서 리팩터링 작업 항목을 뽑는다 | `refactoring-from-analysis` |
@@ -18,6 +19,10 @@
```text
원자료
→ SSOT final/document.md 하나. 여기서만 글감을 찾는다
→ 후보 처분 PROMOTE · MERGE_INTO · KEEP_IN_SSOT · NEEDS_EVIDENCE · NEEDS_DECISION
→ 분해 계약 tech-log-tree.json — PROMOTE 이고 CONFIRMED 인 것만 올린다. 주제마다 독자 질문 한 줄
→ 색인 생성 build-tech-log-tree.py · verify-tech-log-tree.py (error 0)
→ 종류 선택 Case · Concept · Reference · Question · Decision
→ 칸 채우기 종류마다 칸이 다르다
→ 본문 작성 Case · Concept. 코드·표·다이어그램·이미지
@@ -66,35 +71,99 @@ SVG로 컴파일한다. 손으로 SVG를 그리지 않는다. **그림 안에는
프로젝트 하나가 폴더 하나다. 프로젝트 문서는 그 폴더 밖에 두지 않는다.
**끝난 프로젝트의 폴더는 둘이고, 각각 정본 파일이 하나다.**
```text
docs/<프로젝트>/
├── source/ 밖에서 가져온 원본. 고치지 않는다
├── state.json 분석 상태 — 어디까지 봤나, 어느 리비전을 봤나
├── source-index.md 분석한 코드의 목록
├── analysis/ 모듈·서브시스템 단위 분석. 큰 저장소는 여기서 누적한다
│ 한 모듈을 끝까지 읽은 결과를 한 편으로 둔다
├── notes/ · checkpoints/ 분석 중에 남긴 메모와 중간 저장
├── final/ SSOT — 이 프로젝트에 대해 아는 것 전부
│ ├── document.md 상세한 글
│ ├── assets/ svg, drawio, 그림
── .techviz/ 그림의 정본 (context, spec, prompt)
│ ├── assets/ 그림. 그림 하나가 폴더 하나다 — <이름>/<이름>.svg 와 편집 형식들
│ └── tech-log-studio/ Studio 에 올릴 표현물. 기록의 assets: file: 이 가리키는 자리
│ ├── .techviz/ 그림의 정본 (context, spec, prompt). <이름>/ 이 assets 와 짝이다
│ └── evidence/ 증거. 원문이 정본이고 그림은 표현물이다
│ ├── raw/ 명령 출력·csv·덤프 원문 — 정본
│ ├── meta/ 그 실행의 command·cwd·executedAt·exitCode·revision
│ ├── rendered/ raw 에서 만든 터미널 SVG (표현물)
│ └── browser/ 브라우저 캡처 (Playwright MCP)
└── tech-log-studio/ Studio 에 올릴 글만
├── root-tree.md 사람이 쓴 분해 계약 — SSOT 의 sha256 을 물고 있
├── candidate-ledger.json · root-tree-source-manifest.json
├── tech-log-tree.json 위에서 파생한 색인. 기록을 고치면 다시 만든다
├── _meta/ 편집·검증 이력
├── tech-log-tree.json 분해 계약이자 색인. 이 프로젝트의 글감 전부
└── <주제 slug>/
├── case/ concept/ reference/ question/ decision/
```
**분해 계약이 정본이고 `tech-log-tree.json` 은 색인이다.** `root-tree.md` 는 글감마다 readiness·
source anchor·classification·missing-verification 을 사람이 적는 자리이고, json 은 기록 파일을
읽어 지금 상태를 비추는 것이다. 둘이 어긋나면 `root-tree.md` 가 맞다.
**분석하거나 반입하는 동안에만 있는 것이 따로 있다.**
```text
├── source/ 밖에서 가져온 원본. 대조가 끝나면 지운다
├── state.json 분석 상태 — 어디까지 봤나, 어느 리비전을 봤나
├── source-index.md 분석한 코드의 목록
├── analysis/ 모듈·서브시스템 단위 분석. 큰 저장소는 여기서 누적한다
└── notes/ · checkpoints/ 분석 중에 남긴 메모와 중간 저장
```
**이것들은 작업 재료다.** 틀도 프로젝트 폴더가 아니라
`.agents/skills/analyzing-codebase-for-tech-log/templates/` 에 있다. 끝나면 두 번 합친다.
```bash
python3 scripts/fold-analysis-into-final.py <프로젝트> # 분석 재료 → final/document.md
python3 scripts/fold-studio-contract-into-index.py <프로젝트> # 분해 재료 → tech-log-tree.json
```
모듈 분석은 `final/document.md` 제2부로, `source-index.md` 와 스코프 커버리지와 분석 과정
노트는 제3부로 옮기고, `analysis/NN` 을 가리키던 앵커를 `final/document.md#aNN` 으로 고친
뒤 폴더를 지운다. 분해 쪽도 같다 — 사람이 쓴 트리와 Node Specification, 후보 대장, 편집
이력이 `tech-log-tree.json` 하나로 들어간다. **옮기는 것이지 요약하는 것이 아니다** — 요약만 하고 근거를 원래 자리에
둔 채로 `COMPLETE` 를 찍으면 기록의 `source``analysis/` 를 가리켜 SSOT 가 둘이 된다.
`verify-project-layout.py` 의 「작업 재료가 남아 있다」와 `verify-tech-log-tree.py`
「근거가 SSOT 밖에만 있다」가 그 상태를 센다.
**배치의 정본은 `docs/_templates/` 다.** 폴더마다 `README.txt` 가 무엇을 담는지 한 줄로 적혀
있고, `python3 scripts/verify-project-layout.py` 가 프로젝트들이 그 모양인지 대조한다.
**`tech-log-tree.json` 이 분해 계약이자 색인이고 정본이다.** 글감마다 readiness·source
anchor·classification·missing-verification·relations 를 사람이 적고, 스크립트는 기록 파일에서
읽을 수 있는 것(`file`·`publication`·`status`)만 다시 채운다. 디렉터리를 훑어 주제를 만들지
않는다 — 폴더가 정본이면 계약에서 뺀 주제가 파일이 남아 있다는 이유만으로 되살아난다.
계약에 없는 기록은 `unlisted` 에 적힌다.
정본은 층으로 나뉜다.
| 층 | 하는 일 |
|---|---|
| 코드·설정·실행 증거 | 사실의 근거 |
| `final/document.md` | **글감 범위의 SSOT** — 후보를 발견하는 유일한 입력 |
| `analysis/**/*.md` | final 이 이미 채택한 주장을 상세히 확인하는 보조 근거. 분석 중에만 있다 |
| `tech-log-tree.json` | 사람이 고른 글감. 분해 계약이자 색인이고 이 파일이 정본이다 |
`analysis/**` 를 글감을 찾으려고 열지 않는다. 분석에만 있는 자료를 발견하면 트리에 바로 넣지 말고
`final/document.md` 를 먼저 보강한다. 그러지 않으면 모듈 문서마다 정본 노릇을 하고 트리는 그 절
수의 합만큼 자란다.
**접어 넣은 final 이 전부 후보 자리는 아니다.** 제1부(통합 분석)가 후보를 찾는 범위이고 제2부와
제3부는 근거다. 범위는 `tech-log-tree.json``candidateScope` 에 적는다 — 적지 않으면 모듈 분석
65편의 절 제목이 다시 글감이 된다.
```json
"candidateScope": {
"document": "final/document.md",
"sections": ["§3", "§4", "§5", "§6", "§7", "§8", "§9", "§10", "§11"],
"excluded": ["제2부 — 모듈 분석 전문", "제3부 — 분석 재료"]
}
```
**분석 후보 전부는 같은 파일의 `candidates` 에 처분과 함께 남고 `PROMOTE` 만 글감이 된다.**
`KEEP_IN_SSOT` 은 버린 것이 아니라 분석에 남기고 독립 기록으로 만들지 않기로 한 것이고, 그것도
정상적인 결과다. 제외가 0 건인 분해는 선별하지 않은 분해다.
계약을 아직 채택하지 않은 프로젝트도 error 다. 칸마다 error 를 내지는 않고 미채택 자체를 한 번
센다 — 경고로 두면 옛 스키마로 남아 있는 한 검사를 피한다.
처분과 글감은 양쪽으로 맞아야 한다 — `PROMOTE` 인데 글감이 없는 것도, 글감인데 그것을 낳은
`PROMOTE` 후보가 없는 것도 error 다. 사람이 다시 읽지 않은 후보(`dispositionReview: PENDING`)도
error 다. 경고로 두면 재판정하지 않은 트리로 글을 쓰기 시작하게 된다.
**readiness 와 게시 여부는 다른 것이다.** readiness 는 증거가 갖춰진 정도이고, 글을 썼는지·Studio 에
올렸는지는 색인의 `file``publication` 이 따로 말한다.
`final/` 이 정본이고 `tech-log-studio/` 는 거기서 뽑아낸 글이다. 증거는 `final/evidence/` 에만
두고 기록에서는 그 파일을 가리킨다. 같은 파일을 양쪽에 두지 않는다.
@@ -104,7 +173,8 @@ source anchor·classification·missing-verification 을 사람이 적는 자리
`README.txt` 에 적는다 — 파일 이름만으로는 6개월 뒤에 못 읽는다.
**SVG 는 정본이 아니다.** 실행한 명령의 원문과 메타데이터가 정본이고 터미널 SVG 는 문서에 넣기
위한 표현물이다. 원문 없이 SVG 만 남기지 않는다.
위한 표현물이다. 원문 없이 SVG 만 남기지 않는다. 그림도 같다 — `.techviz/<이름>/` 없이 남은
SVG 는 다시 만들 수 없고, `verify-project-layout.py` 가 그런 그림을 센다.
```bash
python3 scripts/terminal-evidence/render_terminal.py \
@@ -138,18 +208,35 @@ Redis 20편은 한 글을 나눠 쓴 것이라 `docs/clean-architecture-backend-
다른 프로젝트에서 쓴 문서를 이 저장소로 옮길 때 따르는 순서다.
1. 원본을 `docs/<프로젝트>/source/` 에 그대로 복사한다. 손대지 않는다 — 대조할 것이 필요하다
1. 원본을 `docs/<프로젝트>/source/` 에 그대로 복사한다. 손대지 않는다 — 대조할 것이 필요하다.
**`source/` 도 작업 재료다** — 대조가 끝나 `final/` 이 그 내용을 담으면 지운다
2. 원본과 증거를 `final/` 로 옮긴다. 글은 `final/document.md`, 그림은 `assets/`,
터미널 기록·스크린샷·실행계획은 `evidence/`. **여기까지가 SSOT 다**
3. SSOT 를 읽고 글감을 뽑아 `tech-log-tree.json` 에 적는다. 종류(case·concept·reference·
question·decision)와 주제를 먼저 정하고 제목만 적는다. 아직 글은 쓰지 않는다
4. 트리의 글감 하나를 골라 `<주제>/<종류>/` 아래에 기록을 쓴다. 증거는 `final/evidence/`
3. SSOT 를 읽고 후보마다 처분을 적는다. §3~§8 에서 Case, §9 에서 Reference, §10 에서 Decision,
§11 에서 Question 을 고르고, 그 넷을 이해하는 데 필요한 Concept 만 거꾸로 더한다.
사람이 다시 읽은 후보만 `dispositionReview: CONFIRMED` 로 둔다
4. `PROMOTE` 를 주제로 묶어 `tech-log-tree.json` 에 적는다. 주제마다 독자 질문을 한 줄 적고,
글감마다 종류가 요구하는 칸을 채운다. 아직 글은 쓰지 않는다
5. 트리의 글감 하나를 골라 `<주제>/<종류>/` 아래에 기록을 쓴다. 증거는 `final/evidence/`
파일을 가리킨다
5. 기록을 쓰거나 지웠으면 트리를 다시 만든다 — `python3 scripts/build-tech-log-tree.py`
6. Studio 에 넣고 저장한다
6. 색인을 다시 만들고 검사한다 — `build-tech-log-tree.py` · `verify-tech-log-tree.py`
7. Studio 에 넣고 저장한다
`tech-log-tree.json` 은 손으로 고쳐도 되고 스크립트로 다시 만들어도 된다. 스크립트는 기록
파일에서 제목·slug·상태를 읽어 채우고, 파일이 아직 없는 글감은 지우지 않고 남긴다.
글감의 칸은 손으로 적고, 스크립트는 기록 파일에서 읽는 칸만 다시 채운다. 계약에 없는 기록
디스크에 있으면 `unlisted` 에 적히고 검사기가 error 로 센다.
**분석한 저장소는 `tech-log-tree.json` 의 `sourceRepository` 가 가리킨다.** 경로·리비전·그렇게
판단한 근거를 함께 적는다. 리비전을 모르면 `null` 로 두고 지어내지 않는다 — 검사기가 warn 으로 센다. 작업이 한 줄기가 아니라
브랜치로 갈라져 있으면 `revisions` 에 이름과 커밋을 짝지어 적는다.
| 프로젝트 | 저장소 |
|---|---|
| `clean-architecture-backend-template` | `desktop-server-git/clean-architecture-backend-template` @ `21234e38` |
| `n+1liner` | `github-project/ca-tmpl` @ `761384d` — 저장소 HEAD 는 다른 브랜치다 |
| `ca-tmpl` | `github-project/ca-tmpl` — 같은 저장소의 아키텍처 경계 쪽 |
| `keycloak` | `keycloak-pattern` — 패턴 넷이 `develop-keycloak-pattern1`~`4` 브랜치에 나뉘어 있어 `revisions` 로 tip 넷을 적는다 |
| `keycloak-session-store` | `keycloak-pattern` @ `cdac9b8` — 같은 저장소의 실험 26건 |
| `TechLog` | `desktop-server-git/tech-log-frontend` · `tech-log-backend` |
| 런 또는 파일 | 문서 |
|---|---|
@@ -158,11 +245,25 @@ Redis 20편은 한 글을 나눠 쓴 것이라 `docs/clean-architecture-backend-
| `keycloak-session-store` | 세션은 어디에 있는가 — Keycloak 다중 노드 실험 26건의 기록. `keycloak` 이 남긴 열린 질문 네 개에 측정으로 답한다 |
| `n+1liner` | 하이라이트 피드 조회 성능 — N+1 진단과 조회 전략의 진화 |
| `TechLog` | 계약이 먼저인 시스템에서 값이 사라지는 자리들 — TechLog를 만들며 만난 결함의 전수 기록 |
| `clean-architecture-backend-template` | 62개 leaf를 23편으로 읽은 통합 분석 · 기록 949건 |
| `clean-architecture-backend-template` | 62개 leaf를 23편으로 읽은 통합 분석 · 모듈 분석 65편을 제2부에 합쳐 46,437줄 · 기록 949건 (선별 재판정 대기) |
## 검사
게시 전에 둘 다 돌린다. 하나는 파서를, 하나는 문장을 본다.
**분해 계약이 스스로 맞는지.** `tech-log-tree.json` 의 주제·글감·후보와 디스크의 기록이 같은
것을 말하는지 본다. `verify-pipeline.py` 는 스킬과 틀이 제자리에 있는지를 보고,
프로젝트 트리 정합성은 이 검사기가 본다(`--skip-projects` 로 끌 수 있다).
```bash
python3 scripts/verify-tech-log-tree.py <프로젝트> # 글감 계약. error 0 까지 고친다
python3 scripts/verify-project-layout.py <프로젝트> # 폴더 배치. 그림의 정본이 있는지도 본다
python3 scripts/fold-analysis-into-final.py <프로젝트> # 분석 재료 → final/document.md
python3 scripts/fold-studio-contract-into-index.py <프로젝트> # 분해 재료 → tech-log-tree.json
python3 scripts/build-tech-log-tree.py <프로젝트> # 파생 칸(file·publication·status)만 다시 채운다
python3 scripts/verify-pipeline.py # 위 셋을 모든 프로젝트에 돌린다
python3 -m unittest discover -s scripts/tests # 파서·검사기·생성기
```
게시 전에는 아래 둘을 돌린다. 하나는 파서를, 하나는 문장을 본다.
**본문이 Studio 파서를 통과하는지.** Studio가 쓰는 파서를 그대로 부르므로 통과하면 저장도 통과한다.
@@ -171,6 +272,14 @@ node --experimental-transform-types \
.agents/skills/writing-tech-log-records/scripts/check_body.mjs 초안.md
```
**인용한 것이 실재하는지.** 본문 코드블록의 각 줄이 SSOT 안에 있는지, `source` 앵커가 SSOT 를
가리키는지, 제목이 계약과 같은지, `sourceRepository` 의 리비전이 그 저장소에 있는지를 본다.
옮겨 적은 것은 확인한 것이 아니다 — 게시된 기록에 SSOT 와 다른 redirect URI 가 네 곳 있었다.
```bash
node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs <프로젝트> --repo
```
`tech-log-frontend` 체크아웃 경로가 다르면 `--frontend` 또는 `TECH_LOG_FRONTEND`로 알려 준다.
**문장이 규범을 지키는지.** `error` 가 남아 있으면 덜 된 글이다. 칸 하나나 한 절만 고쳤으면