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:
co-authored by
Claude Fable 5.1
parent
73026cada6
commit
9d2a3725c5
@@ -30,30 +30,67 @@ Concept 에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
|
||||
## 필수 절차
|
||||
|
||||
0. **글감 나누기** — 긴 글(SSOT)에서 옮겨 오는 것이면 먼저 나눈다. 기준과 `tech-log-tree.json`
|
||||
형식은 `references/from-ssot-to-records.md`. 나눈 뒤 글을 쓴다.
|
||||
0. **글감 나누기** — 긴 글(SSOT)에서 옮겨 오는 것이면 먼저 나눈다. 기준은
|
||||
`references/from-ssot-to-records.md`, 계약은 `references/tech-log-tree-contract.md`.
|
||||
**`tech-log-tree.json` 에 노드가 없는 글은 쓰지 않는다** — 트리에 먼저 올리고, 그 노드의
|
||||
후보가 `PROMOTE` 이면서 `dispositionReview: CONFIRMED` 인지 확인한 뒤에 쓴다. `PENDING` 은
|
||||
사람이 다시 읽지 않았다는 뜻이라, 그 위에 쓴 글은 과분류를 그대로 물려받는다. 계약 밖에서 쓴
|
||||
기록은 색인에 `unlisted` 로 남는다.
|
||||
1. **종류 선택** — 위 표. 애매하면 "재현했나"를 묻는다.
|
||||
2. **칸 채우기** — 칸과 게시 조건은 `references/record-kinds.md`.
|
||||
3. **본문 작성**(Case·Concept) — **종류마다 무엇을 어떤 순서로 쓰는지는
|
||||
`references/writing-each-kind.md`.** 문법은 `references/body-syntax.md`, 표·코드·그림은
|
||||
`references/code-tables-diagrams.md`. **문장은 `references/explaining.md`.**
|
||||
**문서군 전체의 리듬은 `references/ai-tells.md`.**
|
||||
그림이 필요하면 손으로 그리지 말고 `technical-visualizer` 스킬로 만든다.
|
||||
이미 쓴 문장이 AI가 쓴 것처럼 읽히면 `rewriting-technical-prose-naturally` 로 다시 쓴다.
|
||||
4. **검사** — 둘 다 돌린다. 파서와 문장은 다른 것을 본다.
|
||||
**문서군 전체의 리듬은 `references/ai-tells.md`.** 이 둘은 첫 초안부터 적용한다 — AI 티를
|
||||
남겨 두고 나중에 걷어내는 순서가 아니다. **문체를 손보기 전에 문장을 고른다** — 설명이 끝난
|
||||
뒤에 붙은 평가·예고·되풀이·독자 오해 가정을 먼저 뺀다(`ai-tells.md` 첫 절). 문체 규칙의
|
||||
정본은 `ai-tells.md` 다.
|
||||
순서·경계·상태 전이처럼 문장만으로 따라가기 어려운 관계가 있으면 그때
|
||||
`technical-visualizer` 로 그림을 만든다. 손으로 SVG 를 그리지 않고, 모든 글에 그림을 만들지도
|
||||
않는다.
|
||||
4. **검사** — 셋 다 돌린다. 파서와 문장과 증빙은 각각 다른 것을 본다.
|
||||
- `scripts/check_body.mjs` — Case 본문이 Studio 파서를 통과하는지. 같은 파서를 그대로 부른다.
|
||||
- `rewriting-technical-prose-naturally/scripts/check_prose.mjs` — 문장 규범. **error 0 이 될 때까지 고친다.**
|
||||
칸 하나나 한 절만 고쳤으면 `--doc` 없이 부른다. 이어서 `style_profile.mjs` 로 문체 수치를 본다.
|
||||
- `scripts/check_evidence.mjs <프로젝트> --repo` — **인용한 것이 실재하는지.** 본문 코드블록의
|
||||
각 줄이 SSOT 안에 있는지, `source` 앵커가 SSOT 를 가리키는지, 제목이 계약과 같은지,
|
||||
`sourceRepository` 의 리비전이 그 저장소에 있는지를 본다.
|
||||
5. **관계 연결** — Decision은 근거가 **1개 이상** 없으면 게시가 거절된다.
|
||||
6. **Studio에서 확인** — 넣고 **저장까지만** 한 뒤 미리보기로 읽는다.
|
||||
절차는 `references/studio-draft-review.md`. **게시하지 않는다.**
|
||||
7. **게시** — 고칠 것이 없을 때만. 못 채운 칸은 그 칸 아래에 표시된다.
|
||||
7. **색인 갱신** — 기록을 쓰거나 지웠으면 다시 만들고 검사한다.
|
||||
`python3 scripts/build-tech-log-tree.py <프로젝트>` ·
|
||||
`python3 scripts/verify-tech-log-tree.py <프로젝트>` — error 0 이어야 한다.
|
||||
8. **게시** — 고칠 것이 없을 때만. 못 채운 칸은 그 칸 아래에 표시된다.
|
||||
|
||||
## 어느 스킬이 무엇을 하나
|
||||
|
||||
이 스킬이 첫 초안을 만든다. 나머지는 초안이 나온 뒤에 각각 다른 것을 고친다.
|
||||
|
||||
| 스킬 | 하는 일 | 하지 않는 일 |
|
||||
|---|---|---|
|
||||
| `deriving-tech-log-root-tree` | 후보에 처분을 매기고 `PROMOTE` 를 글감으로 올린다 | 글을 쓰지 않는다 |
|
||||
| `writing-tech-log-records` | 종류를 고르고 칸과 본문을 쓴다. `explaining.md`·`ai-tells.md` 를 처음부터 적용한다 | — |
|
||||
| `technical-visualizer` | 문장으로 따라가기 어려운 관계를 그림으로 만든다 | 모든 글에 그림을 붙이지 않는다 |
|
||||
| `rewriting-technical-prose-naturally` | 사실과 구조가 이미 맞는 초안의 번역투·반복 문형·과한 대구를 고친다 | 분류가 틀렸거나 근거가 모자란 것은 못 고친다 |
|
||||
| `writing-as-the-person-who-did-it` | 자료에 남아 있는 선택·비교·어긋남·확인하지 못한 범위를 제자리에 놓는다 | 자료에 없는 「처음에는」·「고민 끝에」를 만들지 않는다 |
|
||||
|
||||
Reference·Question·Decision 은 그림을 렌더링할 자리가 없다. 그림이 필요한 내용은 짝이 되는
|
||||
Case 나 Concept 에 담고 `관계`로 가리킨다.
|
||||
|
||||
## 보호 구간
|
||||
|
||||
수치, 날짜, 버전, 단위, 코드, 명령어, URL, 인용, 공식 명칭은 원문과 한 글자도 달라지면 안 된다.
|
||||
측정하지 않은 값을 채우지 않는다 — 검증일은 실제로 확인한 날이다.
|
||||
|
||||
**옮겨 적었다고 확인한 것이 아니다.** 앞선 기록에서 코드를 가져오거나 기억으로 경로를 쓰면
|
||||
SSOT 에 없는 인용이 생긴다. 실제로 그렇게 게시된 기록에 잘못된 redirect URI 가 네 곳 남아 있었고,
|
||||
realm 설정이 와일드카드라 실행해도 드러나지 않았다. **인용한 줄은 SSOT 에서 찾아 대조한다.**
|
||||
`check_evidence.mjs` 가 그 대조를 기계로 한 번 더 한다.
|
||||
|
||||
SSOT 에 없는데 필요한 인용이라면 순서가 반대다 — `final/document.md` 를 먼저 보강하고,
|
||||
그것도 저장소에서 확인한 뒤에 한다. `sourceRepository.path` 가 그 저장소를 가리킨다.
|
||||
|
||||
## 쓰지 않는 것
|
||||
|
||||
- 자료가 뒷받침하지 않는 선택 이유. 썼다는 사실을 왜 골랐는지로 바꾸지 않는다.
|
||||
@@ -81,5 +118,8 @@ Concept 에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
| `` | Asset으로 올려 `/api/v1/public/media/…` |
|
||||
| Decision에 근거 없음 | 관계 1개 이상 연결 |
|
||||
| 측정 안 한 검증일 | 비워 둔다 |
|
||||
| 설명 직후에 「~증거다」「~가 아니다」로 평가 | 지운다. 앞 문장이 사실을 말했으면 거기서 끝낸다 |
|
||||
| 「~로 읽기 쉽다. 그렇지 않다」 | 오해를 지어내지 않는다. 관측부터 적는다 |
|
||||
| 「먼저 ~를 보고 …」 차례 예고 · 「~를 함께 적는다」 | 지운다. 다음 절이 바로 시작한다 |
|
||||
|
||||
작성 후 `references/review-checklist.md`로 대조한다.
|
||||
|
||||
@@ -3,6 +3,95 @@
|
||||
문장 하나하나는 멀쩡한데 문서군 전체가 같은 리듬으로 굴러갈 때 티가 난다. 개별 문장을 일부러
|
||||
어눌하게 만드는 방향은 역효과다. 아래는 실제 리뷰에서 지적된 것들이다.
|
||||
|
||||
**이 문서가 문체 규칙의 정본이다.** `explaining.md` 는 무엇을 더 설명할지를 다루고, 둘이 어긋나면
|
||||
이쪽을 따른다.
|
||||
|
||||
## 설명 뒤에 평가를 붙이지 않는다
|
||||
|
||||
가장 많이 지적된 형태다. 기술 설명은 이미 끝났는데 그 뒤에 **그 설명을 평가하거나, 다음을 예고하거나,
|
||||
독자가 오해할 것이라고 가정하거나, 어떻게 읽고 써야 하는지 지시하는 문장**이 하나 더 붙는다.
|
||||
|
||||
문장마다 무엇을 주는지 본다 — 동작, 정의, 근거, 판단에 영향을 주는 조건 가운데 하나여야 한다.
|
||||
그 넷 중 어느 것도 주지 않고 판정만 하는 문장은 지운다. 특정 단어를 금지하는 방식은 쓰지 않는다.
|
||||
「중요하다」가 나쁜 것이 아니라 그 문장이 아무것도 더하지 않는 것이 문제다.
|
||||
|
||||
**고치는 순서는 내용이 먼저다.** 필요 없는 문장을 남긴 채 표현만 자연스럽게 바꾸면 어색한 문장의
|
||||
표현만 달라진다. 평가·예고·되풀이를 걷어낸 뒤에 문장을 다듬는다.
|
||||
|
||||
네 가지가 반복된다. 전부 「다른 표현으로 고치는 사례」가 아니라 **「통째로 지우는 사례」**다.
|
||||
|
||||
### 설명한 것의 중요성을 다시 평가한다
|
||||
|
||||
```text
|
||||
✗ 다른 하나는 앞 단계가 어긋났는데도 나머지를 계속 돌려 마지막에 전체 통과를 남기는 일이다.
|
||||
그렇게 만든 기록은 경계가 지켜졌다는 근거가 되지 않는다.
|
||||
○ 다른 하나는 앞 단계가 어긋났는데도 나머지를 계속 돌려 마지막에 전체 통과를 남기는 일이다.
|
||||
|
||||
✗ 발행된 SQL에 Limit 노드가 없다는 것 자체가 DB가 페이징을 하지 않았다는 증거다.
|
||||
○ 발행된 SQL에는 Limit 노드가 없었다.
|
||||
```
|
||||
|
||||
「증거다」「핵심이다」「너무 넓다」「서로를 대신하지 않는다」로 끝나는 꼬리 문장이 이 형태다.
|
||||
앞 문장이 사실을 말했으면 거기서 끝낸다.
|
||||
|
||||
### 독자가 오해할 것이라고 먼저 가정한다
|
||||
|
||||
```text
|
||||
✗ 훑은 행만 보면 keyset이 결과까지 줄인 것으로 짐작하기 쉽다. 커서로 넘긴 두 번째 페이지는
|
||||
OFFSET의 두 번째 페이지와 같은 20개 식별자를 같은 순서로 반환했다.
|
||||
○ 커서로 넘긴 두 번째 페이지는 OFFSET의 두 번째 페이지와 같은 20개 식별자를 같은 순서로 반환했다.
|
||||
|
||||
✗ Web Storage에 토큰을 쓰지 않으니 JavaScript에서도 토큰이 사라진다고 읽기 쉽다. 그렇지 않다.
|
||||
액세스·리프레시·ID 토큰은 실행 중 메모리에 있다.
|
||||
○ 액세스·리프레시·ID 토큰은 실행 중 메모리에 있다.
|
||||
```
|
||||
|
||||
「~로 읽기 쉽다」「~라고 생각하면 안 된다」「~로 보기 쉽다」. 자료에 누군가 실제로 그렇게 읽었다는
|
||||
기록(버그·정정·문의)이 없으면 독자를 지어낸 것이다. 관측부터 적으면 오해는 생기지 않는다.
|
||||
|
||||
### 어떻게 읽고 어떻게 써야 하는지 지시한다
|
||||
|
||||
```text
|
||||
✗ AP2와 AP3의 쿠키는 서버 쪽 상태를 찾는 열쇠이고, AP4의 쿠키는 최소 상태를 담은 값이다.
|
||||
두 쿠키를 같은 문장으로 설명하면 서버 저장소가 있는 쪽과 없는 쪽이 구분되지 않는다.
|
||||
○ AP2와 AP3의 쿠키는 서버 쪽 상태를 찾는 열쇠이고, AP4의 쿠키는 최소 상태를 담은 값이다.
|
||||
|
||||
✗ 이 두 낱말만 알면 따라올 수 있고, 나머지는 처음 나오는 곳에서 푼다.
|
||||
✗ 먼저 브라우저가 들고 있는 값부터 보고, 그 값이 Bearer 요청이 되기까지를 따라간다.
|
||||
✗ native로 내려갔다는 것과 그 범위를 함께 적는다.
|
||||
```
|
||||
|
||||
「~라고 설명하면 ~가 구분되지 않는다」「~를 함께 적는다」「먼저 ~를 보고 다음에 ~를 본다」. 독자에게
|
||||
필요한 것은 각 쿠키가 무엇을 보관하는지이지 그것을 어떻게 설명해야 하는지가 아니다. 이런 문장은
|
||||
작성자의 검토 메모다. 차례 예고도 같다 — 다음 절이 바로 시작하면 된다.
|
||||
|
||||
### 이미 설명한 것을 다시 말한다
|
||||
|
||||
```text
|
||||
✗ 배치는 SQL 왕복 횟수를 줄이고 프로젝션은 적재할 대상을 줄인다. 두 효과는 서로를 대신하지 않는다.
|
||||
○ 배치는 SQL 왕복 횟수를 줄이고 프로젝션은 적재할 대상을 줄인다.
|
||||
|
||||
✗ 반복이 사라진 것이 아니라 스트림 뒤로 숨었다.
|
||||
○ 현재 매핑에서는 각 아이템의 getHighlights()에 접근하면서 지연 로딩이 실행된다.
|
||||
```
|
||||
|
||||
같은 대조를 추상어로 한 번 더 하거나, 동작 설명 뒤에 인상적인 문장으로 닫는 것. 동작을 그대로
|
||||
적으면 독자가 비유를 코드 동작으로 다시 번역하지 않아도 된다.
|
||||
|
||||
### SSOT 에 같은 문장이 있어도 옮기지 않는다
|
||||
|
||||
`final/document.md` 는 사실과 근거의 기준이지 문장의 기준이 아니다. 원문에 「너무 넓은 성공 기준입니다」가
|
||||
있어도 기록에 옮길 이유는 없다. 옮기는 것은 수치·조건·동작·판단이고, 평가는 옮기지 않는다.
|
||||
|
||||
### 무엇을 남기나
|
||||
|
||||
- 코드가 그렇게 동작하는 이유, 측정 조건, 결과를 읽는 데 필요한 예외
|
||||
- 실제 선택을 바꾼 판단 — 「200 이어도 정상인 이유는 세션이 유효하기 때문이다」처럼 판정에 영향을 주는 것
|
||||
- 요약·결론·본문 사이의 반복 — Studio 칸 구조상 필요하다. 걷어낼 것은 **한 칸 안에서** 설명 직후에 붙은 문장이다
|
||||
|
||||
어미 수·절 수·안내 문장 수 같은 수치는 참고 정보다. `style_profile.mjs` 가 「벗어남」을 내도 그것을
|
||||
맞추려고 문장을 넣지 않는다. 위 첫 예시는 문장 검사를 error 0 으로 통과한 채로 지적됐다.
|
||||
|
||||
## 억지 구어체를 만들지 않는다
|
||||
|
||||
AI 티를 지우려고 넣은 질문체와 청유형이 오히려 「AI 문장을 억지로 인간화한 것」으로 읽힌다.
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
가장 자주 나오는 지적은 **설명이 짧다**는 것이다. 사실은 맞는데 독자가 따라오지 못한다.
|
||||
|
||||
그 반대도 같은 무게로 지적된다 — 설명이 끝난 뒤에 그 설명을 평가하거나, 다음 절을 예고하거나,
|
||||
독자가 오해할 것이라고 가정하는 문장이 붙는 것. 이 문서는 **무엇을 더 설명하는가**를 다루고,
|
||||
무엇을 빼는가는 `ai-tells.md` 첫 절이 다룬다. **문체 규칙의 정본은 `ai-tells.md` 다.** 두 문서가
|
||||
어긋나면 그쪽을 따른다. 여기 규칙은 전부 「그 설명이 없으면 독자가 막히는 자리」에서만 쓴다.
|
||||
이미 설명된 문단에 더하지 않는다.
|
||||
|
||||
## 이름을 댔으면 왜 있는지도 댄다
|
||||
|
||||
낯선 클래스·기법·설정 이름을 적고 다음 문장으로 넘어가지 않는다. **왜 그것이 존재하는지**를 한
|
||||
@@ -13,13 +19,13 @@ XorCsrfTokenRequestAttributeHandler가 token을 XOR와 Base64로 가린다.
|
||||
|
||||
쓴다
|
||||
XorCsrfTokenRequestAttributeHandler가 token을 XOR와 Base64로 가린다.
|
||||
BREACH 공격을 줄이기 위해서다. 여기서 깊게 다루지는 않는다 — HTTP 응답 압축 크기의
|
||||
차이로 응답 안의 비밀값을 조금씩 추측하는 공격이고, 그래서 응답에 실리는 값을 매번
|
||||
다르게 만든다.
|
||||
BREACH 공격을 줄이기 위해서다. HTTP 응답 압축 크기의 차이로 응답 안의 비밀값을 조금씩
|
||||
추측하는 공격이고, 그래서 응답에 실리는 값을 매번 다르게 만든다.
|
||||
```
|
||||
|
||||
깊게 안 갈 것이면 **안 간다고 밝히고 한 문장 요약을 준다.** 이름만 던지고 넘어가면 독자는 그
|
||||
자리에서 검색하러 나간다.
|
||||
깊게 안 갈 것이면 **한 문장 요약만 준다.** 「여기서 깊게 다루지는 않는다」 같은 예고는 붙이지 않는다 —
|
||||
요약이 있으면 그것으로 충분하고, 없으면 독자는 그 자리에서 검색하러 나간다. 이 규칙은 **처음
|
||||
나오는 낯선 이름**에만 걸린다. 이미 설명한 이름이나 문맥에서 분명한 이름에는 붙이지 않는다.
|
||||
|
||||
## 「역할이 다르다」로 끝내지 않는다
|
||||
|
||||
@@ -87,27 +93,6 @@ sessionStorage에도 accessToken과 refreshToken이 없었다.
|
||||
쓴다 identity-header-trust
|
||||
```
|
||||
|
||||
## 제목은 묻고 본문은 답한다
|
||||
|
||||
절 제목에 `~해보자` `~하지?` `~일까?`를 쓴다. 그리고 **첫 문장에서 그 질문을 다시 던지고**
|
||||
답한다.
|
||||
|
||||
| 쓰지 않는다 | 쓴다 |
|
||||
|---|---|
|
||||
| 브라우저에 남은 것 | 브라우저에 관리 대상 |
|
||||
| 위조 요청은 어떻게 생겼나 | 위조 요청은 어떻게 생겼을까? |
|
||||
| 세 겹으로 나눠서 막는다 | 세 겹으로 나눠서 막아보자 |
|
||||
| 무엇이 서버로 넘어왔나 | 무엇이 서버로 책임이 넘어왔지? |
|
||||
| upstream이 JWT를 받지 않는다는 뜻 | upstream이 JWT를 받지 않는다? |
|
||||
|
||||
```text
|
||||
## 브라우저에 관리 대상
|
||||
|
||||
브라우저에 관리 대상은 그럼 어떤 게 될까?
|
||||
|
||||
| 무엇 | 브라우저에 있나..? | JavaScript가 읽나..? |
|
||||
```
|
||||
|
||||
## 굵게를 걷어낸다
|
||||
|
||||
`**굵게**`는 거의 쓰지 않는다. 한 절에 하나를 넘기면 강조가 아니라 얼룩이 된다. 강조는
|
||||
@@ -155,9 +140,6 @@ authorization code와 함께 redirect되고, 그 code를 mediator가 token으로
|
||||
이 구조에서는 token 관리와 교환의 위치가 브라우저에서 Spring backend로 옮겨지게 된다.
|
||||
```
|
||||
|
||||
같은 대조를 절 끝에서 한 번 더 쓴다 — 「앞선 구조에서는 브라우저가 OIDC client였다면 이
|
||||
구조에서는 mediator가 OIDC client가 된다」처럼.
|
||||
|
||||
## 결론은 문장 끝에 붙인다
|
||||
|
||||
한 줄짜리 단정문을 따로 떼어 강조하지 않는다. 앞 문장에서 `그래서` · `그렇기 때문에`로
|
||||
@@ -205,11 +187,6 @@ access token이 필요하고, 그것을 응답 본문으로 받게 된다. 그
|
||||
|
||||
Reference의 규칙 제목과 Decision의 결정문도 `~한다`로 끊는다. 그 자리는 기준이다.
|
||||
|
||||
## 그림은 한 줄로 예고한다
|
||||
|
||||
`전체적인 구조를 보면 다음과 같다` 같은 한 줄을 두고 그림을 넣는다. 문단 사이에 말없이
|
||||
끼우지 않는다.
|
||||
|
||||
## 번역투를 걷어낸다
|
||||
|
||||
가장 자주 나오는 지적 두 번째다. 어미는 한국어인데 **문장 구조가 영어**여서 읽기 힘들다.
|
||||
@@ -409,5 +386,4 @@ Reference의 규칙 제목과 Decision의 결정문도 `~한다`로 끊는다.
|
||||
- **`~하면 된다`를 쓰지 않는다.** 조언하는 말투이지 기록하는 말투가 아니다.
|
||||
`정하면 된다` → `정한다`, `적으면 된다` → `적는다`, `두면 된다` → `둔다`
|
||||
- `A는 B다`보다 `A는 B라는 점이 문제가 된다` — 무엇이 걸리는지까지 말한다
|
||||
- 무엇을 하자고 이끌 때는 `~해 보자`를 쓴다. 절 제목과 여는 문장에만 쓰고 규칙에는 쓰지 않는다
|
||||
- 명령조를 줄인다. 검사 항목이 아니라 같이 읽는 사람의 말로 쓴다
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
# SSOT에서 글감을 뽑는 기준
|
||||
|
||||
`final/`의 긴 글 하나가 정본(SSOT)이다. 거기서 Studio에 올릴 기록을 뽑아낸다. 이 문서는 **무엇을
|
||||
몇 건으로 나눌지**를 정하는 기준이다. 나눈 결과는 `tech-log-tree.json`에 제목만 먼저 적고, 글은
|
||||
그다음에 쓴다.
|
||||
`final/document.md` 하나가 정본(SSOT)이다. 거기서 Studio에 올릴 기록을 뽑아낸다. 이 문서는
|
||||
**무엇을 몇 건으로 나눌지**를 정하는 기준이다. 나눈 결과는 `tech-log-tree.json` 하나에 적는다 —
|
||||
분해 계약과 색인이 같은 파일이라 둘이 어긋날 자리가 없다.
|
||||
|
||||
## 어느 파일에서 뽑나
|
||||
|
||||
| 층 | 하는 일 |
|
||||
|---|---|
|
||||
| 코드·설정·실행 증거 | 사실의 근거 |
|
||||
| `final/document.md` | **글감 범위의 SSOT** — 후보를 발견하는 유일한 입력 |
|
||||
| `analysis/**/*.md` | final이 이미 채택한 주장을 상세히 확인하는 보조 근거 |
|
||||
| `tech-log-tree.json` | 사람이 고른 글감. 분해 계약이자 색인이고 이 파일이 정본이다 |
|
||||
|
||||
**`analysis/**`를 글감을 찾으려고 열지 않는다.** 이미 final에 있는 주장의 세부를 확인할 때만
|
||||
연다. 분석에만 있고 final에는 없는 자료를 발견하면 트리에 바로 넣지 말고 `final/document.md`를
|
||||
먼저 보강한다. 그러지 않으면 모듈 문서 61편이 각각 정본 노릇을 하고, 트리는 그 절 수의 합만큼
|
||||
자란다.
|
||||
|
||||
## 후보를 찾는 범위
|
||||
|
||||
접어 넣은 `final/document.md`가 전부 후보 자리는 아니다. **제1부(통합 분석)가 후보를 찾는
|
||||
범위**이고, 제2부(모듈 분석 전문)와 제3부(분석 재료)는 근거다. 제2부의 절 제목을 후보로 읽으면
|
||||
모듈 분석 편수만큼 글감이 늘어난다 — 접어 넣기 전에 있던 문제가 그대로 돌아온다.
|
||||
|
||||
범위는 기억하지 말고 계약에 적는다.
|
||||
|
||||
```json
|
||||
"candidateScope": {
|
||||
"document": "final/document.md",
|
||||
"sections": ["§3", "§4", "§5", "§6", "§7", "§8", "§9", "§10", "§11"],
|
||||
"excluded": ["제2부 — 모듈 분석 전문", "제3부 — 분석 재료"]
|
||||
}
|
||||
```
|
||||
|
||||
범위 밖의 앵커는 후보가 아니라 근거다. 제1부에서 나온 글감의 `source`로 건다.
|
||||
|
||||
## 왜 먼저 나누는가
|
||||
|
||||
@@ -10,6 +42,22 @@
|
||||
Reference의 칸도 반쯤만 맞는 기록이 나온다. 종류를 먼저 정하고 그 종류가 요구하는 것이 SSOT에
|
||||
있는지 확인해야 한다.
|
||||
|
||||
## 고르는 것이지 남김없이 내는 것이 아니다
|
||||
|
||||
분석에 빠진 것이 없는지 볼 때는 recall 100%가 맞다. 공개할 글을 정할 때는 아니다. 「분석에서
|
||||
보존할 가치」와 「독립된 글로 읽을 가치」는 다른 물음이고, 둘을 한 축으로 재면 분석 부산물이
|
||||
전부 글이 된다.
|
||||
|
||||
물음은 하나다.
|
||||
|
||||
> **이 기록을 없애고 관련 Case나 Concept의 한 절로 넣어도 이해·결정·재사용성이 그대로라면
|
||||
> 독립 기록으로 만들지 않는다.**
|
||||
|
||||
후보마다 처분을 적는다 — `PROMOTE` · `MERGE_INTO` · `KEEP_IN_SSOT` · `NEEDS_EVIDENCE` ·
|
||||
`NEEDS_DECISION` · `BLOCKED`. `KEEP_IN_SSOT`은 버린 것이 아니라 분석에 남기고 글로 만들지
|
||||
않기로 한 것이고, 그것도 정상적인 결과다. 자세한 것은
|
||||
`.agents/skills/deriving-tech-log-root-tree/references/candidate-disposition.md`.
|
||||
|
||||
## 한 건으로 자르는 단위
|
||||
|
||||
**절이 아니라 주장이다.** SSOT의 `##` 하나가 기록 하나가 아니다. 다음 넷 중 하나가 한 건이다.
|
||||
@@ -46,12 +94,20 @@ Reference 하나로 나누고 `관계`로 잇는다.
|
||||
**같은 관측을 두 건으로 쪼개지 않는다.** 「N+1이 났다」와 「그래서 몇 개가 나갔다」는 한 건이다.
|
||||
쪼개면 둘 다 반쪽이 된다.
|
||||
|
||||
**주제를 먼저 정한다.** Studio는 주제 아래에 다섯 종류를 나눠 보여 준다. 주제가 다르면 같은
|
||||
프로젝트여도 폴더가 갈린다. 주제 slug는 Studio의 것을 그대로 쓴다.
|
||||
**Concept은 거꾸로 뽑는다.** Case·Reference·Decision·Question을 먼저 고르고, 그것을 읽는 사람이
|
||||
미리 알아야 하는 구조가 있을 때만 Concept을 만든다. 메커니즘처럼 보이는 절을 훑어 채우면 어느
|
||||
기록도 필요로 하지 않는 개념이 쌓인다.
|
||||
|
||||
**주제 하나에 독자 질문 하나.** Studio는 주제 아래에 다섯 종류를 나눠 보여 준다. 그 주제의
|
||||
기록들이 함께 답하는 물음을 한 줄로 적고, 그 물음에 답하지 않는 글감은 다른 주제로 옮긴다.
|
||||
주제 slug는 Studio의 것을 그대로 쓴다.
|
||||
|
||||
## `tech-log-tree.json`
|
||||
|
||||
주제 → 종류 → 글감 순서로 담는다. 아직 쓰지 않은 글감은 `file` 없이 제목만 둔다.
|
||||
**주제·글감·`readiness`·`source`·`classification`·`relations`는 사람이 적는다.** 스크립트가
|
||||
채우는 것은 기록 파일에서 읽을 수 있는 넷뿐이다 — `file`·`publication`·`status`·`studioId`.
|
||||
주제 → 종류 → 글감 순서로 담고, 아직 쓰지 않은 글감은 `file` 없이 남는다. `readiness`는 증거가
|
||||
갖춰진 정도이고 `publication`은 Studio에 올렸는지다 — 둘은 다른 것이라 섞지 않는다.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -75,18 +131,23 @@ Reference 하나로 나누고 `관계`로 잇는다.
|
||||
}
|
||||
```
|
||||
|
||||
기록을 쓰거나 지운 뒤에는 다시 만든다. 스크립트는 기록 파일에서 값을 읽어 채우고, `file`이 없는
|
||||
글감은 지우지 않는다.
|
||||
기록을 쓰거나 지운 뒤에는 파생 칸을 다시 채운다. 사람이 적은 칸은 그대로 남는다. 디렉터리를
|
||||
훑어 주제를 만들지 않으므로, 계약에서 뺀 주제의 폴더가 남아 있어도 되살아나지 않고 `unlisted`에
|
||||
적힌다.
|
||||
|
||||
```bash
|
||||
python3 scripts/build-tech-log-tree.py [프로젝트]
|
||||
python3 scripts/verify-tech-log-tree.py [프로젝트] # error 0 이어야 한다
|
||||
```
|
||||
|
||||
## 순서
|
||||
|
||||
1. SSOT를 끝까지 읽는다. 절 제목만 훑지 않는다 — 수치와 근거가 어디 있는지 알아야 종류를 정한다
|
||||
2. 위 물음으로 글감을 나누고 주제를 정한다
|
||||
3. `tech-log-tree.json`에 제목만 적는다. 이때 글은 쓰지 않는다
|
||||
4. 글감 하나를 골라 `<주제>/<종류>/`에 기록을 쓴다. 형식은 `writing-each-kind.md`
|
||||
5. 트리를 다시 만든다
|
||||
6. Studio에 넣고 저장한다
|
||||
2. §3~§8에서 Case, §9에서 Reference, §10에서 Decision, §11에서 Question을 고른다
|
||||
3. 그 넷을 이해하는 데 필요한 Concept만 거꾸로 더한다
|
||||
4. 후보마다 처분을 적고, 사람이 다시 읽은 것만 `dispositionReview: CONFIRMED`로 둔다.
|
||||
`PROMOTE`이면서 `CONFIRMED`인 것만 글감이 된다
|
||||
5. 주제를 묶고 주제마다 독자 질문을 한 줄 적는다
|
||||
6. 글감 하나를 골라 `<주제>/<종류>/`에 기록을 쓴다. 형식은 `writing-each-kind.md`
|
||||
7. 색인을 다시 만들고 검사기를 돌린다
|
||||
8. Studio에 넣고 저장한다
|
||||
|
||||
@@ -67,6 +67,10 @@ evidence:
|
||||
- ../../../final/evidence/explain/highlights-child-plan-A.txt
|
||||
```
|
||||
|
||||
**`assets` 는 본문이 있는 Case 와 Concept 에만 둔다.** 나머지 세 종류는 칸이 평문으로
|
||||
렌더링돼 그림을 표시할 자리가 없다. 그림이 필요한 내용은 Case 나 Concept 에 담고 `관계`로
|
||||
가리킨다.
|
||||
|
||||
`assets` 는 Studio 에 올릴 파일이다. 아직 안 올렸으면 key 가 파일 이름과 같고, 올린 뒤에는
|
||||
서버가 준 `<이름>-<해시8>` 로 바뀐다. **Studio 에 넣을 때 이 목록을 보고 Asset 을 올리고,
|
||||
본문의 `:::evidence key` 를 서버가 준 키로 바꾼다.**
|
||||
@@ -145,6 +149,26 @@ subrequest 가 실어 보내는 것` 같은 것이 여기 온다.
|
||||
Case 와 헷갈리면 **내가 무엇을 했는지**를 묻는다. 내가 돌려 보고 수치를 얻었으면 Case, 남의 문서와
|
||||
코드를 읽고 동작을 정리했으면 Concept 이다.
|
||||
|
||||
### Concept 이 아닌 것
|
||||
|
||||
Concept 은 **어떤 Case 를 이해하려면 먼저 알아야 하는 구조**다. 그 Case 가 없으면 Concept 도
|
||||
없다. 분석하면서 알게 된 사실을 종류가 마땅치 않아 여기 넣지 않는다.
|
||||
|
||||
| 이런 제목 | 실제로는 |
|
||||
|---|---|
|
||||
| 호출자가 없다 · 프로덕션에서 실행되지 않는다 | 부재는 Case 의 관측이다 |
|
||||
| 구현 클래스 51개를 전부 읽었다 · 재현에 쓴 레인 | 분석 범위·방법. SSOT 의 coverage 원장에 남는다 |
|
||||
| 보류한 항목과 보류한 이유 | 분석 진행 기록. 같은 곳에 남는다 |
|
||||
| `grep refs=0` 은 시작점이지 결론이 아니다 | 분석 방법론. Reference 로 쓸 수 있으면 Reference 다 |
|
||||
| (8.4) 문서/구현 드리프트 — … · Confirmed — … | 분석 문서의 절 제목을 그대로 옮긴 것 |
|
||||
|
||||
한두 문장으로 Case 안에서 설명되는 것도 Concept 이 아니다. **없애고 Case 의 한 절로 넣어도
|
||||
이해가 그대로면 독립 기록으로 만들지 않는다.**
|
||||
|
||||
Concept 이 되는 것은 이런 것들이다 — `Spring 조립의 세 경로`, `@ConditionalOnBean 의 평가
|
||||
시점`, `커밋 증거 상태 전이`, `fenced lease 와 CAS`, `gRPC flow control 과 backpressure`.
|
||||
여러 Case 가 같은 선수 지식을 요구할 때 그것을 한 번만 설명하려고 만드는 자리다.
|
||||
|
||||
## Reference — 반복 적용할 기준
|
||||
|
||||
| 칸 | 필드 | 비고 |
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
- [ ] 종류가 내용과 맞는다 (`record-kinds.md`의 판단 흐름)
|
||||
- [ ] Question의 사실과 가정이 섞이지 않았다
|
||||
- [ ] Question이 `OPEN`이면 미지수가 있다
|
||||
- [ ] Question에 닫는 조건이 있다. 「더 알아본다」로 끝나지 않았다
|
||||
- [ ] Decision에 근거 기록이 1개 이상 연결됐다
|
||||
- [ ] Decision의 영향에 감수한 비용이 있다
|
||||
- [ ] Reference의 예시가 문장이다. 코드를 밀어 넣지 않았다
|
||||
@@ -53,6 +54,9 @@
|
||||
- [ ] `여지가 생긴다`·`소지가 있다` 대신 무엇이 어디로 가는지 썼다
|
||||
- [ ] 읽는 법을 지시하는 문장이 없다. `봐야 한다`·`여기까지다`·`먼저 본다`·`읽으면 안 된다`
|
||||
- [ ] 빼도 남은 뜻이 그대로인 문장이 없다
|
||||
- [ ] 설명 직후에 그 설명의 중요성을 평가하는 문장이 없다 (「~증거다」「~핵심이다」「서로를 대신하지 않는다」)
|
||||
- [ ] 독자가 오해할 것이라고 가정하는 문장이 없다 (「~로 읽기 쉽다」「~라고 생각하면 안 된다」)
|
||||
- [ ] 다음 절을 예고하거나 「~를 함께 적는다」처럼 작성 방법을 말하는 문장이 없다
|
||||
- [ ] 규칙 제목이 말한 것을 본문 끝에서 다시 지시하지 않았다
|
||||
- [ ] `싣는다`·`낸다`·`친다`·`짠다`를 실제 동작으로 풀어 썼다
|
||||
- [ ] 동사마다 목적어가 있다. `교환이 끝난다`처럼 무엇인지 빠지지 않았다
|
||||
@@ -74,6 +78,7 @@
|
||||
- [ ] Reference가 Case를 문장만 바꿔 옮기지 않았다
|
||||
- [ ] 현재 확인한 것과 운영에서 추가로 필요한 것을 나눴다
|
||||
- [ ] 테스트를 말할 때 무엇을 단정하는지 적었다
|
||||
- [ ] 어미·절·안내 문장의 수치는 참고만 했다. 맞추려고 문장을 넣지 않았다
|
||||
|
||||
## 본문 (Case)
|
||||
|
||||
@@ -101,6 +106,8 @@
|
||||
- [ ] Project가 필요하면 지정됐고, 그 Project에 slug가 있다
|
||||
- [ ] 관계의 대상이 실제로 있는 공개 기록이다
|
||||
- [ ] 코드·표·그림이 필요한 내용을 Reference나 Question에 밀어 넣지 않았다
|
||||
- [ ] 본문이 없는 세 종류에 `assets`를 선언하지 않았다
|
||||
- [ ] 이 글감의 후보가 `PROMOTE`이고 `dispositionReview`가 `CONFIRMED`다
|
||||
|
||||
## 마지막
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# Root Tree Contract
|
||||
|
||||
The root tree is the explicit boundary between deep project analysis and Tech Log record generation.
|
||||
|
||||
## Required document header
|
||||
|
||||
A root tree records:
|
||||
|
||||
- `schemaVersion`
|
||||
- `project`
|
||||
- `sourceDocument`
|
||||
- `sourceDocumentSha256`
|
||||
- `sourceRevision`
|
||||
- `generatedAt`
|
||||
|
||||
The hash/revision prevents a scheduled generator from treating a tree derived from old code as current.
|
||||
|
||||
## Required human-readable tree
|
||||
|
||||
Each Topic has a title, slug, and four branches:
|
||||
|
||||
```text
|
||||
PROJECT
|
||||
<project>
|
||||
|
||||
TOPIC
|
||||
<Topic title>
|
||||
<topic-slug>
|
||||
|
||||
├── CASE
|
||||
├── REFERENCE
|
||||
├── OPEN QUESTION
|
||||
└── DECISION
|
||||
```
|
||||
|
||||
Empty branches are allowed. Do not manufacture nodes to fill all four kinds.
|
||||
|
||||
## Node source contract
|
||||
|
||||
Every candidate includes a specification after the human-readable tree.
|
||||
|
||||
### Case
|
||||
|
||||
Required:
|
||||
|
||||
- `slug`
|
||||
- `readiness`
|
||||
- one or more `source` anchors
|
||||
- `classification` explaining the concrete incident/experiment/diagnosis
|
||||
- relevant code/evidence when the conclusion depends on them
|
||||
- `missing-verification`
|
||||
- `relations`
|
||||
|
||||
A Case with `NEEDS_EVIDENCE`, `BLOCKED`, or `REJECTED` is not generated.
|
||||
|
||||
### Reference
|
||||
|
||||
Required:
|
||||
|
||||
- `slug`
|
||||
- `readiness`
|
||||
- `source`
|
||||
- `classification` explaining the reusable criterion
|
||||
- `scope`
|
||||
- `exceptions`
|
||||
- `relations`
|
||||
|
||||
A Reference must be useful beyond retelling one Case. If removing the originating project's names leaves no rule, it is probably still a Case.
|
||||
|
||||
### Open Question
|
||||
|
||||
Required:
|
||||
|
||||
- `slug`
|
||||
- `readiness: OPEN`
|
||||
- `source`
|
||||
- `known`
|
||||
- `unknown`
|
||||
- `next-verification`
|
||||
- `decision-criterion`
|
||||
- `relations`
|
||||
|
||||
Do not generate a Question when the detailed analysis already contains a verified answer. Move the material to Case/Reference/Decision as appropriate and update the tree first.
|
||||
|
||||
### Decision
|
||||
|
||||
Required:
|
||||
|
||||
- `slug`
|
||||
- `readiness`
|
||||
- `decision-status`
|
||||
- `source`
|
||||
- `decision-evidence`
|
||||
- `grounds`
|
||||
- `classification`
|
||||
- `relations`
|
||||
|
||||
`decision-status` is one of `PROPOSED`, `ADOPTED`, `SUPERSEDED`, `NOT_DECIDED`. A `NOT_DECIDED` candidate uses `NEEDS_DECISION` and is not generated as a Decision.
|
||||
|
||||
## Readiness semantics
|
||||
|
||||
| readiness | meaning | generation |
|
||||
|---|---|---|
|
||||
| `READY` | grounded enough for the kind | allowed |
|
||||
| `NEEDS_EVIDENCE` | material assertion still lacks verification | blocked |
|
||||
| `NEEDS_DECISION` | direction sounds plausible but project has not decided | blocked |
|
||||
| `OPEN` | legitimate unresolved Question | allowed as Open Question |
|
||||
| `BLOCKED` | sources are incomplete or contradictory | blocked |
|
||||
| `REJECTED` | should not become a record | blocked |
|
||||
|
||||
## Derivation rules
|
||||
|
||||
1. Start from sections and evidence already present in detailed analysis; do not begin by brainstorming titles.
|
||||
2. Prefer several narrowly grounded Cases over one broad Case that combines unrelated incidents.
|
||||
3. Extract References only after identifying the invariant/selection criterion that survives outside the incident.
|
||||
4. Extract Questions from explicit uncertainty, missing verification, operational unknowns, or conflicting constraints.
|
||||
5. Extract Decisions only from explicit project choice evidence: ADR, commit/history, configuration plus recorded rationale, issue/PR decision, or user-supplied decision record.
|
||||
6. A node may relate to several siblings, but each record has one primary purpose.
|
||||
7. If new runtime evidence changes the answer, update detailed analysis and regenerate/review the tree before editing downstream records.
|
||||
@@ -0,0 +1,172 @@
|
||||
# Tech Log Tree Contract
|
||||
|
||||
`tech-log-tree.json` is the explicit boundary between deep project analysis and Tech Log
|
||||
record generation. **It is the decomposition contract and the index at once, and it is the
|
||||
source of truth.** There is one file, so nothing can disagree with it.
|
||||
|
||||
A finished `tech-log-studio/` holds `tech-log-tree.json` and the record folders. Nothing
|
||||
else.
|
||||
|
||||
A project whose index predates this contract fails verification with one error until it is
|
||||
migrated. The per-field checks stay off for such a project — "not written yet" must not read
|
||||
as "written wrong" — but non-adoption itself is counted, because a warning lets an old index
|
||||
avoid every check indefinitely.
|
||||
|
||||
## Required top level
|
||||
|
||||
- `schemaVersion`
|
||||
- `project`
|
||||
- `ssot` and `ssotSha256` — the hash prevents treating a tree derived from old material
|
||||
as current
|
||||
- `sourceRevision`
|
||||
- `generatedAt`
|
||||
- `sourceRepository` — `path`, `revision`, and `verified`: which checkout the analysis read,
|
||||
which commit the document describes, and how that was confirmed. Leave `revision` null rather
|
||||
than inventing one; the verifier warns instead of accepting a made-up label. When the work
|
||||
is spread over branches rather than one line of commits, use `revisions` — a label to commit
|
||||
map — and pin every tip the document describes
|
||||
- `candidateScope` — which part of the SSOT candidates may come from
|
||||
- `contract` — the decomposition rules, `readinessValues`, `dispositionValues`
|
||||
- `topics`, `candidates`, `counts`, `unlisted`
|
||||
|
||||
## Candidate scope
|
||||
|
||||
A folded `final/document.md` carries the integrated analysis, the module analyses, and the
|
||||
analysis material in one file. Only the first is candidate material.
|
||||
|
||||
```json
|
||||
"candidateScope": {
|
||||
"document": "final/document.md",
|
||||
"sections": ["§3", "§4", "§5", "§6", "§7", "§8", "§9", "§10", "§11"],
|
||||
"excluded": ["제2부 — 모듈 분석 전문", "제3부 — 분석 재료"]
|
||||
}
|
||||
```
|
||||
|
||||
`document` names the SSOT and must match `ssot`. `sections` names the candidate scope, and
|
||||
`excluded` names the parts that are evidence rather than candidates. A node may cite an
|
||||
anchor from an excluded part in `source`; it may not exist because of one.
|
||||
|
||||
## Topics
|
||||
|
||||
Each Topic has `topic` (its key), `title`, **`readerQuestion`**, and `kinds` with the five
|
||||
record kinds.
|
||||
|
||||
```json
|
||||
"oauth-oidc-auth-boundary": {
|
||||
"topic": "oauth-oidc-auth-boundary",
|
||||
"title": "OAuth 자격증명과 세션의 보관 경계",
|
||||
"readerQuestion": "자격증명과 세션을 누가 보관하고, 누가 API 요청을 만들며, 보호 자원은 무엇을 신뢰하는가?",
|
||||
"kinds": { "case": [], "concept": [], "reference": [], "question": [], "decision": [] }
|
||||
}
|
||||
```
|
||||
|
||||
Every node in the Topic must help answer the reader question. Two Topics do not share a
|
||||
question; one Topic does not need two. Empty kinds are allowed — do not manufacture nodes
|
||||
to fill all five.
|
||||
|
||||
## Candidates
|
||||
|
||||
Everything the analysis found lives in `candidates` with its disposition —
|
||||
`.agents/skills/deriving-tech-log-root-tree/references/candidate-disposition.md`. Only
|
||||
`PROMOTE` candidates become nodes under `topics`, and the relation runs both ways: a
|
||||
`PROMOTE` candidate whose target is not a node, and a node no `PROMOTE` candidate points
|
||||
at, are both contract errors.
|
||||
|
||||
`dispositionReview` records whether a person re-read the candidate under the independence
|
||||
test. `PENDING` means it reached the tree by recall alone, and a `PENDING` candidate is an
|
||||
error, not a warning — a record written over an unreviewed tree inherits the
|
||||
over-classification the disposition step exists to catch. Write records only for nodes
|
||||
whose candidate is `PROMOTE` and `CONFIRMED`.
|
||||
|
||||
## Written by hand, refreshed by script
|
||||
|
||||
`readiness` · `source` · `code` · `evidence` · `classification` · `relations` and the rest
|
||||
of each kind's fields are written by a person. `build-tech-log-tree.py` never touches them.
|
||||
It refreshes only what it can read from the record files — `file`, `publication`, `status`,
|
||||
`studioId`, `assets`, `evidenceFiles` — and lists records that have no node in `unlisted`.
|
||||
|
||||
### Case
|
||||
|
||||
`slug` · `readiness` · `source` · `classification` · `missing-verification` · `relations`,
|
||||
plus `code`/`evidence` when the conclusion depends on them.
|
||||
|
||||
One problem, an observation or reproduction, a diagnosis, a conclusion that closes. Several
|
||||
observations that answer the same question with the same conclusion are one Case with a
|
||||
table or sub-sections, not several partial Cases.
|
||||
|
||||
### Concept
|
||||
|
||||
`slug` · `readiness` · `source` · `basis-version` · `classification` · `relations`.
|
||||
|
||||
`basis-version` names what the explanation was written against — `Keycloak 26.7.0 identity
|
||||
brokering`, `Spring Boot 3.3 auto-configuration`. A Concept without it cannot be known to
|
||||
be stale.
|
||||
|
||||
A Concept exists because a Case, Decision, or Question needs it to be understood. Absence,
|
||||
call-counts, unwired subsystems, analysis scope, and coverage ledgers are not Concepts.
|
||||
|
||||
### Reference
|
||||
|
||||
`slug` · `readiness` · `source` · `classification` · `scope` · `exceptions` · `relations`.
|
||||
|
||||
A Reference must be useful beyond retelling one Case. If removing the originating
|
||||
project's names leaves no rule, it is still a Case.
|
||||
|
||||
### Open Question
|
||||
|
||||
`slug` · `readiness: OPEN` · `source` · `known` · `unknown` · `next-verification` ·
|
||||
`decision-criterion` · `relations`.
|
||||
|
||||
Do not create a Question when the analysis already contains a verified answer. Move the
|
||||
material to Case/Reference/Decision and update the tree first.
|
||||
|
||||
### Decision
|
||||
|
||||
`slug` · `readiness` · `decision-status` · `source` · `decision-evidence` · `grounds` ·
|
||||
`classification` · `relations`.
|
||||
|
||||
`decision-status` is `PROPOSED`, `ADOPTED`, `SUPERSEDED`, or `NOT_DECIDED`. A
|
||||
`NOT_DECIDED` candidate uses `NEEDS_DECISION` and is not written as a Decision.
|
||||
|
||||
## Readiness semantics
|
||||
|
||||
**`readiness` is about evidence, not about publication.** Whether a record has been
|
||||
written, and whether it has been saved into Studio, are separate facts that the generated
|
||||
index carries as `file` and `publication`. A published record with thin evidence is still
|
||||
`NEEDS_EVIDENCE`.
|
||||
|
||||
| readiness | meaning | generation |
|
||||
|---|---|---|
|
||||
| `READY` | grounded enough for the kind | allowed |
|
||||
| `OPEN` | legitimate unresolved Question | allowed as Open Question |
|
||||
| `NEEDS_EVIDENCE` | material assertion still lacks verification | blocked |
|
||||
| `NEEDS_DECISION` | direction sounds plausible but the project has not decided | blocked |
|
||||
| `BLOCKED` | sources are incomplete or contradictory | blocked |
|
||||
|
||||
`REJECTED` is not a readiness. Whether a candidate becomes a record at all is a
|
||||
disposition, and it lives in `candidates`, not on the node.
|
||||
|
||||
## Derivation rules
|
||||
|
||||
1. Discover candidates from `final/document.md` only. It is the whole analysis, folded in —
|
||||
there is no `analysis/` folder to search in a finished project.
|
||||
2. Give every candidate a disposition before writing any node. `KEEP_IN_SSOT` is a normal
|
||||
outcome, and a decomposition that excludes nothing has not selected anything.
|
||||
3. Apply the independence test: if folding the record into a related Case or Concept as
|
||||
one section changes nothing, it is not an independent record.
|
||||
4. Take Cases, References, Decisions, and Questions first; add Concepts backwards from
|
||||
what those four require.
|
||||
5. Prefer several narrowly grounded Cases over one broad Case combining unrelated
|
||||
incidents — but merge observations that share a question and a conclusion.
|
||||
6. Extract Decisions only from explicit choice evidence: ADR, commit/history, configuration
|
||||
plus recorded rationale, issue/PR decision, or a user-supplied decision record.
|
||||
7. A node may relate to several siblings, but each record has one primary purpose.
|
||||
8. If new runtime evidence changes the answer, update the analysis and revise the tree
|
||||
before editing downstream records.
|
||||
|
||||
## Generation and verification
|
||||
|
||||
```bash
|
||||
python3 scripts/build-tech-log-tree.py <project> # 파생 칸을 다시 채운다
|
||||
python3 scripts/verify-tech-log-tree.py <project> # error 0 이어야 한다
|
||||
```
|
||||
@@ -7,16 +7,18 @@
|
||||
Question 4·Decision 2), n+1liner 24건(Case 5·Reference 7·Question 5·Decision 7).
|
||||
「대개 이렇게 쓴다」는 말은 그 47건이 그렇게 돼 있다는 뜻이다.
|
||||
|
||||
`clean-architecture-backend-template` 의 949건은 다른 절 이름으로 쓰여 있었고 이 기준에 맞춰
|
||||
고쳤다. 올라간 적 없는 초안이 아니라 **올라간 것**이 기준이다.
|
||||
`clean-architecture-backend-template` 의 949건은 다른 절 이름으로 쓰여 있었고, 지금 이 기준으로
|
||||
다시 판정하는 중이다. 아직 기준을 만족한 상태가 아니므로 그 949건을 본보기로 삼지 않는다. 올라간
|
||||
적 없는 초안이 아니라 **올라간 것**이 기준이다.
|
||||
|
||||
## 파일 뼈대 — 다섯 종류가 같다
|
||||
|
||||
```markdown
|
||||
---
|
||||
id · kind · slug · title · topic · project · status · studio
|
||||
id · kind · slug · title · topic · topicName · project · status · studio
|
||||
source · sourceRevision
|
||||
(종류별) basisVersion · decisionStatus · questionStatus · lastVerifiedOn
|
||||
(있으면) assets · evidence
|
||||
(있으면) evidence · assets — assets 는 Case 와 Concept 만
|
||||
---
|
||||
|
||||
# 제목
|
||||
@@ -41,10 +43,13 @@ frontmatter 는 메타데이터, `##` 는 Studio 의 칸, 제목 아래 첫 문
|
||||
|
||||
```yaml
|
||||
source:
|
||||
- analysis/05-adapter-outbound-persistence-jpa.md#L354
|
||||
module: adapter-inbound-graphql
|
||||
- final/document.md#a05-adapter-outbound-persistence-jpa#L354
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
```
|
||||
|
||||
`source` 는 SSOT 의 앵커다. 분석을 접어 넣은 프로젝트에는 `analysis/` 가 없으므로 그 경로를 적으면
|
||||
가리키는 파일이 없다.
|
||||
|
||||
**관계 항목은 굵은 제목 한 줄 + 이유 한 줄**이다.
|
||||
|
||||
```markdown
|
||||
@@ -63,7 +68,9 @@ module: adapter-inbound-graphql
|
||||
| 검증 환경 | 런타임·버전·DB·측정 도구. `이름 : 값`으로 줄을 나눈다 |
|
||||
| 재현 조건 | 다른 사람이 같은 값을 얻는 순서. 번호를 매긴다 |
|
||||
|
||||
**본문은 5~12절, 대개 6절이다.**
|
||||
본문의 절은 하나의 주장과 그 근거가 이어지는 단위로 나눈다. 47건에서는 대개 여섯 절 안팎이었지만
|
||||
**절 수는 작성 조건이 아니다.** 숫자를 맞추려고 절을 쪼개거나 붙이면 문서마다 같은 모양이 된다.
|
||||
아래는 그 47건에서 실제로 반복된 순서다.
|
||||
|
||||
- **첫 절은 무대를 세운다.** 잰 코드나 구조를 먼저 보여 준다 — 「측정한 loadFeed 구현」,
|
||||
「무대 — 페이징 한 줄만 추가」, 「가시성을 얹기 전 — 인덱스로 커서 이후만」,
|
||||
@@ -75,7 +82,8 @@ module: adapter-inbound-graphql
|
||||
|
||||
그 마지막 절은 **본문 안**이다. 칸으로 빼면 Studio 에 그런 칸이 없어 사라진다.
|
||||
|
||||
코드블록에는 무엇을 보라는 한 줄을 붙인다. 표 앞이나 뒤에 그 표를 어떻게 읽는지 적는다. 예시는
|
||||
코드블록에는 라벨로 무엇인지 적는다. 표는 머리글이 무엇을 묻는지 말하게 하고, 그 표를 어떻게 읽는지
|
||||
설명하는 문장(「이렇게 갈린다」「함께 읽어야 한다」)은 두지 않는다. 예시는
|
||||
한 규모로 고정한다 — 표가 전체 계열을 이미 보여 준다.
|
||||
|
||||
## Concept — 6건
|
||||
@@ -90,7 +98,8 @@ basisVersion: Keycloak 26.7.0 · oidc-client-ts 3.3.0
|
||||
basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
```
|
||||
|
||||
**본문은 5~8절, 대개 6절이다.**
|
||||
절의 개수를 정해 두지 않는다. 설명해야 할 참여자와 단계가 몇 개인지가 정한다. 47건에서 반복된
|
||||
순서는 이렇다.
|
||||
|
||||
- 첫 절은 무엇이 무엇을 주고받는지다 — 「두 개의 OAuth 왕복이 이어진다」,
|
||||
「Resource Server가 받는 입력」, 「요청 하나가 두 번 평가된다」
|
||||
@@ -128,6 +137,18 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
`다음 검증`은 실행할 수 있는 문장으로 적는다. 「더 알아본다」로는 닫히지 않는다 —
|
||||
「seed(1000) 뒤 `ANALYZE highlights` 를 돌리고 Plan B 를 다시 잰다」처럼 적는다.
|
||||
|
||||
**그 아래 닫는 조건을 한 줄 붙인다.** 어떤 결과가 나오면 이 질문을 닫거나 Decision 으로 넘기는지
|
||||
적지 않으면 검증을 마쳐도 질문이 그대로 열려 있다. 계약의 `decision-criterion` 이 이 줄이다.
|
||||
|
||||
```markdown
|
||||
## 다음 검증
|
||||
|
||||
1. seed(1000) 뒤 `ANALYZE highlights` 를 돌리고 Plan B 를 다시 잰다
|
||||
|
||||
닫는 조건 : Plan B 의 실제 행 수가 추정치의 2배 안에 들어오면 닫고, 벗어나면 통계 갱신 주기를
|
||||
정하는 Decision 으로 넘긴다
|
||||
```
|
||||
|
||||
## Decision — 9건
|
||||
|
||||
칸은 **`근거`** · `결정문` · `판단 이유` · `영향`. **다른 넷과 달리 관계 절 이름이 「근거」다.**
|
||||
@@ -147,11 +168,13 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
- 그림은 `technical-visualizer` 로 만든다. 손으로 SVG 를 그리지 않는다
|
||||
- 그림 안에는 이름만 넣는다. 문장은 `<desc>` 와 옆 문단에 둔다
|
||||
- 그림과 증거는 frontmatter 의 `assets` · `evidence` 로 잇는다. 같은 파일을 기록 옆에 복사하지 않는다
|
||||
- `assets` 는 본문이 있는 두 종류만 갖는다. Reference·Question·Decision 은 그림을 렌더링할 자리가
|
||||
없어서 선언해도 화면에 나오지 않는다
|
||||
- 본문은 `<!-- body:start -->` 와 `<!-- body:end -->` 사이다. 그 밖은 Studio 로 가지 않는다
|
||||
|
||||
## 관계를 어디서 가져오나
|
||||
|
||||
관계는 **다른 기록을 가리키는 링크**다. 지어내지 않는다. 분해 계약(`root-tree.md`)이 노드마다
|
||||
관계는 **다른 기록을 가리키는 링크**다. 지어내지 않는다. 분해 계약(`tech-log-tree.json`)이 노드마다
|
||||
`relations` 를 적어 두면 그것을 그대로 옮긴다.
|
||||
|
||||
계약이 관계를 적지 않은 노드는 한 가지 규칙만 쓸 수 있다 — **Reference 의 근거 사건은 같은
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env node
|
||||
// 기록이 인용한 것이 정말 SSOT 에 있는지 본다.
|
||||
//
|
||||
// node check_evidence.mjs <프로젝트>
|
||||
// node check_evidence.mjs <프로젝트> --repo # 저장소까지 대조 (sourceRepository.path 필요)
|
||||
//
|
||||
// 세 가지를 본다.
|
||||
// 1. 본문 코드블록의 각 줄이 SSOT 안에 있는가
|
||||
// 2. frontmatter 의 source 앵커가 SSOT 를 가리키는가
|
||||
// 3. 기록의 title 이 계약(tech-log-tree.json)의 title 과 같은가
|
||||
//
|
||||
// 검사기가 못 보던 자리다. `verify-tech-log-tree.py` 는 slug 와 칸의 존재만 보고,
|
||||
// 인용한 코드가 실재하는지도 제목이 계약과 같은지도 보지 않는다.
|
||||
import { readFileSync, readdirSync, statSync, existsSync } from "node:fs";
|
||||
import { join, basename } from "node:path";
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
const [project, ...flags] = process.argv.slice(2);
|
||||
if (!project) { console.error("usage: check_evidence.mjs <프로젝트> [--repo]"); process.exit(2); }
|
||||
const withRepo = flags.includes("--repo");
|
||||
|
||||
const root = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
|
||||
const base = join(root, "docs", project);
|
||||
const treePath = join(base, "tech-log-studio", "tech-log-tree.json");
|
||||
if (!existsSync(treePath)) { console.error(`${project}: tech-log-tree.json 이 없다`); process.exit(2); }
|
||||
const tree = JSON.parse(readFileSync(treePath, "utf8"));
|
||||
const ssotRel = tree.ssot || "final/document.md";
|
||||
const norm = s => s.replace(/\s+/g, " ").trim();
|
||||
const ssot = norm(readFileSync(join(base, ssotRel), "utf8"));
|
||||
|
||||
// 계약이 말하는 제목
|
||||
const contractTitle = new Map();
|
||||
for (const topic of Object.values(tree.topics || {}))
|
||||
for (const [kind, items] of Object.entries(topic.kinds || {}))
|
||||
for (const n of items) if (n.slug) contractTitle.set(`${kind}:${n.slug}`, n.title || "");
|
||||
|
||||
// ``` 로 열고 닫는 펜스를 짝짓는다. ```java label="…" 도 여는 표시다
|
||||
function codeBlocks(text) {
|
||||
const out = []; let inside = false, lang = "", buf = [];
|
||||
for (const line of text.split("\n")) {
|
||||
const t = line.trimStart();
|
||||
if (t.startsWith("```")) {
|
||||
if (inside) { out.push([lang, buf.join("\n")]); buf = []; inside = false; lang = ""; }
|
||||
else { inside = true; lang = (t.slice(3).trim().split(/\s+/)[0] || "").toLowerCase(); }
|
||||
continue;
|
||||
}
|
||||
if (inside) buf.push(line);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ```text 는 필자가 짠 요약표·흐름도에 쓰인다. 정렬 공백이 열 구분자라 줄 단위로 대조하면
|
||||
// 전부 오탐이 된다. 그래서 text 펜스는 줄이 아니라 그 안의 식별자·URL·수치만 본다.
|
||||
const PROSE_FENCE = new Set(["text", "", "txt", "console", "diff"]);
|
||||
// 맨몸 영단어(observation, self-report …)는 필자가 붙인 열 이름이라 제외하고,
|
||||
// 경로·URL·점 있는 식별자처럼 저장소에서 온 것만 본다.
|
||||
const TOKEN = /(?:https?:\/\/[^\s"'`,)]+|\/[A-Za-z0-9_][A-Za-z0-9_./-]{4,}|[A-Za-z_][A-Za-z0-9_]*(?:[.][A-Za-z0-9_]+)+)/g;
|
||||
|
||||
const findings = [];
|
||||
const studio = join(base, "tech-log-studio");
|
||||
for (const topicDir of readdirSync(studio)) {
|
||||
const tp = join(studio, topicDir);
|
||||
if (!statSync(tp).isDirectory() || topicDir.startsWith("_")) continue;
|
||||
for (const kind of readdirSync(tp)) {
|
||||
const kp = join(tp, kind);
|
||||
if (!statSync(kp).isDirectory()) continue;
|
||||
for (const file of readdirSync(kp).filter(f => f.endsWith(".md"))) {
|
||||
const p = join(kp, file);
|
||||
const text = readFileSync(p, "utf8");
|
||||
const fm = text.startsWith("---") ? text.slice(4, text.indexOf("\n---", 3)) : "";
|
||||
const get = k => (fm.match(new RegExp(`^${k}: (.*)$`, "m")) || [, ""])[1].trim();
|
||||
const slug = get("slug"), title = get("title");
|
||||
|
||||
// 1. 인용한 코드가 SSOT 에 있는가
|
||||
const bodyStart = text.indexOf("<!-- body:start -->");
|
||||
const body = bodyStart === -1 ? text : text.slice(bodyStart);
|
||||
for (const [lang, block] of codeBlocks(body)) {
|
||||
if (PROSE_FENCE.has(lang)) {
|
||||
for (const tok of block.match(TOKEN) || [])
|
||||
if (tok.length >= 8 && !ssot.includes(tok))
|
||||
findings.push([file, "인용한 식별자가 SSOT 에 없다", tok.slice(0, 90)]);
|
||||
continue;
|
||||
}
|
||||
for (const raw of block.split("\n")) {
|
||||
const t = raw.trim();
|
||||
if (t.length < 20) continue;
|
||||
if (/^(\/\/|\*|\/\*\*|#|--|>|\|)/.test(t)) continue;
|
||||
if (/[가-힣]/.test(t)) continue; // 한글이 섞인 줄은 코드가 아니다
|
||||
if (!ssot.includes(norm(t)))
|
||||
findings.push([file, "인용한 코드가 SSOT 에 없다", t.slice(0, 90)]);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. source 앵커가 SSOT 를 가리키는가
|
||||
const src = (fm.match(/^source:\n((?:\s+-\s.*\n)+)/m) || [, ""])[1];
|
||||
const anchors = src.split("\n").map(l => l.replace(/^\s*-\s*/, "").trim()).filter(Boolean);
|
||||
if (anchors.length && !anchors.some(a => a.includes(ssotRel)))
|
||||
findings.push([file, "source 가 SSOT 를 가리키지 않는다", anchors.join(" · ").slice(0, 90)]);
|
||||
|
||||
// 3. 제목이 계약과 같은가
|
||||
const key = `${kind}:${slug}`;
|
||||
if (contractTitle.has(key) && contractTitle.get(key) !== title)
|
||||
findings.push([file, "제목이 계약과 다르다", `계약 "${contractTitle.get(key)}" ≠ 기록 "${title}"`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. (--repo) 저장소가 실재하고 리비전이 맞는가
|
||||
if (withRepo) {
|
||||
const repo = tree.sourceRepository || {};
|
||||
if (!repo.path) findings.push(["tech-log-tree.json", "sourceRepository.path 가 없다", ""]);
|
||||
else if (!existsSync(repo.path)) findings.push(["tech-log-tree.json", "저장소 경로가 없다", repo.path]);
|
||||
else {
|
||||
// 갈래가 여럿이면 revisions 로 적는다. 둘 다 없으면 verify-tech-log-tree.py 가 warn 을 낸다
|
||||
const revs = repo.revision ? { revision: repo.revision } : (repo.revisions || {});
|
||||
for (const [label, rev] of Object.entries(revs)) {
|
||||
try {
|
||||
execSync(`git -C ${JSON.stringify(repo.path)} cat-file -e ${rev}^{commit}`, { stdio: "ignore" });
|
||||
} catch {
|
||||
findings.push(["tech-log-tree.json", "그 리비전이 저장소에 없다", `${label} = ${rev}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const grouped = new Map();
|
||||
for (const [f, rule, detail] of findings) {
|
||||
if (!grouped.has(rule)) grouped.set(rule, []);
|
||||
grouped.get(rule).push(`${f} — ${detail}`);
|
||||
}
|
||||
console.log(`\n[${project}] 증빙 대조${withRepo ? " (저장소 포함)" : ""}`);
|
||||
if (!findings.length) { console.log(" 문제 없음"); process.exit(0); }
|
||||
for (const [rule, items] of [...grouped].sort((a, b) => b[1].length - a[1].length)) {
|
||||
console.log(` ✗ ${String(items.length).padStart(4)} ${rule}`);
|
||||
for (const it of items.slice(0, 3)) console.log(` · ${it}`);
|
||||
if (items.length > 3) console.log(` … 외 ${items.length - 3}건`);
|
||||
}
|
||||
console.log(`\n합계 ${findings.length}건`);
|
||||
process.exit(1);
|
||||
@@ -3,11 +3,15 @@ id: <Studio 가 준 uuid. 아직 없으면 빈 값>
|
||||
kind: CASE
|
||||
slug: <slug>
|
||||
title: <제목>
|
||||
topic: <주제 이름>
|
||||
topic: <topic-slug — 폴더 이름과 같다>
|
||||
topicName: <화면에 보이는 주제 이름>
|
||||
project: <프로젝트 이름>
|
||||
status: 게시 전
|
||||
studio: "<편집 화면 주소. 아직 없으면 빈 값>"
|
||||
lastVerifiedOn: <실제로 확인한 날 또는 빈 값>
|
||||
source:
|
||||
- final/document.md#<anchor>
|
||||
sourceRevision: <분석한 리비전>
|
||||
assets:
|
||||
- key: <본문의 :::evidence key 와 같은 값>
|
||||
file: <../../../final/assets/… 상대 경로>
|
||||
@@ -44,6 +48,6 @@ evidence:
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
<rich Case body>
|
||||
<rich Case body — 절은 주장 하나와 그 근거가 이어지는 단위로 나눈다. 정해진 개수는 없다>
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
@@ -3,11 +3,15 @@ id: <Studio 가 준 uuid. 아직 없으면 빈 값>
|
||||
kind: CONCEPT
|
||||
slug: <slug>
|
||||
title: <제목>
|
||||
topic: <주제 이름>
|
||||
topic: <topic-slug — 폴더 이름과 같다>
|
||||
topicName: <화면에 보이는 주제 이름>
|
||||
project: <프로젝트 이름>
|
||||
status: 게시 전
|
||||
studio: "<편집 화면 주소. 아직 없으면 빈 값>"
|
||||
basisVersion: <무엇을 보고 썼는지. 예 Keycloak 26.7.0 · oidc-client-ts 3.3.0>
|
||||
source:
|
||||
- final/document.md#<anchor>
|
||||
sourceRevision: <분석한 리비전>
|
||||
assets:
|
||||
- key: <본문의 :::evidence key 와 같은 값>
|
||||
file: <../../../final/assets/… 상대 경로>
|
||||
@@ -34,6 +38,8 @@ evidence:
|
||||
|
||||
## <단계마다 실제로 일어나는 일>
|
||||
|
||||
<설명할 단계가 몇 개인지가 절의 개수를 정한다. 미리 정해 둔 수에 맞추지 않는다>
|
||||
|
||||
## <그 설계가 막지 않는 것>
|
||||
|
||||
## <지금 확인한 범위>
|
||||
|
||||
@@ -3,18 +3,24 @@ id: <Studio 가 준 uuid. 아직 없으면 빈 값>
|
||||
kind: PROJECT_DECISION
|
||||
slug: <slug>
|
||||
title: <제목>
|
||||
topic: <주제 이름>
|
||||
topic: <topic-slug — 폴더 이름과 같다>
|
||||
topicName: <화면에 보이는 주제 이름>
|
||||
project: <프로젝트 이름>
|
||||
status: 게시 전
|
||||
studio: "<편집 화면 주소. 아직 없으면 빈 값>"
|
||||
decisionStatus: PROPOSED
|
||||
assets:
|
||||
- key: <본문의 :::evidence key 와 같은 값>
|
||||
file: <../../../final/assets/… 상대 경로>
|
||||
source:
|
||||
- final/document.md#<anchor>
|
||||
sourceRevision: <분석한 리비전>
|
||||
evidence:
|
||||
- <../../../final/evidence/raw/… 상대 경로>
|
||||
---
|
||||
|
||||
<!--
|
||||
본문이 없는 종류라 `assets` 를 두지 않는다. 칸이 평문으로 렌더링되므로 그림과 코드블록은
|
||||
표시되지 않는다. 그런 자료는 근거로 건 Case 나 Concept 에 담는다.
|
||||
-->
|
||||
|
||||
# <title>
|
||||
|
||||
<summary>
|
||||
|
||||
@@ -3,18 +3,24 @@ id: <Studio 가 준 uuid. 아직 없으면 빈 값>
|
||||
kind: QUESTION
|
||||
slug: <slug>
|
||||
title: <제목>
|
||||
topic: <주제 이름>
|
||||
topic: <topic-slug — 폴더 이름과 같다>
|
||||
topicName: <화면에 보이는 주제 이름>
|
||||
project: <프로젝트 이름>
|
||||
status: 게시 전
|
||||
studio: "<편집 화면 주소. 아직 없으면 빈 값>"
|
||||
questionStatus: OPEN
|
||||
assets:
|
||||
- key: <본문의 :::evidence key 와 같은 값>
|
||||
file: <../../../final/assets/… 상대 경로>
|
||||
source:
|
||||
- final/document.md#<anchor>
|
||||
sourceRevision: <분석한 리비전>
|
||||
evidence:
|
||||
- <../../../final/evidence/raw/… 상대 경로>
|
||||
---
|
||||
|
||||
<!--
|
||||
본문이 없는 종류라 `assets` 를 두지 않는다. 칸이 평문으로 렌더링되므로 그림과 코드블록은
|
||||
표시되지 않는다. 그런 자료는 Case 나 Concept 에 담고 `관계`로 가리킨다.
|
||||
-->
|
||||
|
||||
# <title>
|
||||
|
||||
<summary of unresolved issue>
|
||||
@@ -49,3 +55,5 @@ evidence:
|
||||
## 다음 검증
|
||||
|
||||
1. <next concrete verification>
|
||||
|
||||
닫는 조건 : <어떤 결과가 나오면 이 질문을 닫거나 Decision 으로 넘기는가>
|
||||
|
||||
@@ -3,17 +3,24 @@ id: <Studio 가 준 uuid. 아직 없으면 빈 값>
|
||||
kind: REFERENCE
|
||||
slug: <slug>
|
||||
title: <제목>
|
||||
topic: <주제 이름>
|
||||
topic: <topic-slug — 폴더 이름과 같다>
|
||||
topicName: <화면에 보이는 주제 이름>
|
||||
project: <프로젝트 이름>
|
||||
status: 게시 전
|
||||
studio: "<편집 화면 주소. 아직 없으면 빈 값>"
|
||||
assets:
|
||||
- key: <본문의 :::evidence key 와 같은 값>
|
||||
file: <../../../final/assets/… 상대 경로>
|
||||
source:
|
||||
- final/document.md#<anchor>
|
||||
sourceRevision: <분석한 리비전>
|
||||
evidence:
|
||||
- <../../../final/evidence/raw/… 상대 경로>
|
||||
---
|
||||
|
||||
<!--
|
||||
본문이 없는 종류라 `assets` 를 두지 않는다. 이 기록의 칸은 평문으로 렌더링되므로 그림도
|
||||
코드블록도 표시되지 않는다. 그림이 필요한 내용은 Case 나 Concept 에 담고 `관계`로 가리킨다.
|
||||
`evidence` 는 이 기록이 인용한 측정 자료의 출처이고 화면에는 나오지 않는다.
|
||||
-->
|
||||
|
||||
# <title>
|
||||
|
||||
<summary>
|
||||
|
||||
Reference in New Issue
Block a user