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
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: deriving-tech-log-root-tree
|
||||
description: Use when a completed or substantially completed docs project analysis must be decomposed into grounded Tech Log Topics and candidate Case, Concept, Reference, Open Question, and Decision records.
|
||||
description: Use when a completed or substantially completed docs project analysis must be decomposed into grounded Tech Log Topics and candidate Case, Concept, Setup, Reference, Open Question, and Decision records.
|
||||
metadata:
|
||||
version: "1.0.0"
|
||||
language: "ko-KR"
|
||||
@@ -77,7 +77,9 @@ They do not hold candidates, and in a folded project they are gone.
|
||||
4. Pick **Decisions** from the explicit-decision section (§10).
|
||||
5. Pick **Questions** from the unresolved section (§11).
|
||||
6. Only now add the **Concepts** those four need in order to be understood. Concept is
|
||||
derived backwards from the records that require it, never by sweeping headings.
|
||||
derived backwards from the records that require it, never by sweeping headings. Add the
|
||||
**Setups** the analysis actually supports in the same pass — the section below says which
|
||||
ones those are.
|
||||
7. Give every candidate a disposition — `references/candidate-disposition.md` — and set
|
||||
`dispositionReview` to `CONFIRMED` only for the ones a person actually re-read.
|
||||
8. Group `PROMOTE` candidates into Topics. Write one reader question per Topic.
|
||||
@@ -89,6 +91,20 @@ They do not hold candidates, and in a folded project they are gone.
|
||||
`python3 scripts/verify-tech-log-tree.py <project>` — errors must be 0. Build fills
|
||||
`ssotSha256`; verify errors when it is absent, so verifying before building always fails.
|
||||
|
||||
## When a Setup belongs in the tree
|
||||
|
||||
Add a **Setup** where the analysis records the commands and configuration values that stand
|
||||
an environment up and someone other than the author has to run them.
|
||||
|
||||
Its test is not the one the other five take. They ask whether a claim is worth publishing on
|
||||
its own; this one asks whether a reader would type these lines. A reproduction that only
|
||||
re-obtains one measurement stays inside that Case's `재현 조건` field, and a procedure nobody
|
||||
but the author would run is that Case's environment section.
|
||||
|
||||
Setup nodes need a project. A Topic is optional, and a Setup without one reads as that
|
||||
project's shared configuration. Instead of a verification date it carries `pinned-versions`
|
||||
— the versions the procedure was established on.
|
||||
|
||||
## Three fields the verifier requires and this procedure does not otherwise name
|
||||
|
||||
Write them by hand. `verify-tech-log-tree.py` counts each as an error when missing.
|
||||
@@ -141,12 +157,19 @@ Do not create one Topic per source file or module. A directory is not a Topic.
|
||||
- **Open Question** — no answer yet, the design turns on the answer, and there is a next
|
||||
verification and a closing criterion.
|
||||
- **Decision** — the project actually chose a direction, with grounds and an accepted cost.
|
||||
- **Setup** — a procedure the reader runs to stand the environment up. Carries
|
||||
`pinned-versions` instead of a verification date, and always belongs to a project.
|
||||
|
||||
The independence test decides all five:
|
||||
The independence test decides the first five:
|
||||
|
||||
> Delete this record and fold it into a related Case or Concept as one section. If
|
||||
> understanding, decisions, and reuse are unchanged, it is not an independent record.
|
||||
|
||||
Setup does not answer that question, because folding a procedure into a Case is exactly what
|
||||
this kind exists to stop — the commands end up in a plain-text `검증 환경` field where they
|
||||
cannot be copied. Ask instead who runs it. If only the author ever will, it is that Case's
|
||||
environment, not a record.
|
||||
|
||||
Branches may be empty. Symmetry is not a quality goal. Neither is volume — a large
|
||||
denominator justifies a long `final/document.md`, not a long tree.
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
| Reference | 다음 프로젝트에도 적용할 규칙이며 적용 조건과 예외가 있다 | Case 결론을 선언문으로 바꾼 것 |
|
||||
| Question | 답이 아직 없고, 답에 따라 설계가 달라지며, 다음 검증과 종료 기준이 있다 | 실행하지 않은 테스트 목록, 막연한 "다른 방법은?" |
|
||||
| Decision | 대안 중 프로젝트가 실제 방향을 정했고 근거와 감수한 비용이 있다 | 기술이 존재한다는 사실, 권장사항, 아직 정하지 않은 방향 |
|
||||
| Setup | 남이 자기 기계에서 실행할 명령과 구성 값이 있고 프로젝트가 정해져 있다 | 글쓴이만 다시 돌릴 재현 순서(그 Case 의 재현 조건이다), 명령 없는 구성 설명 |
|
||||
|
||||
## Case 를 언제 합치나
|
||||
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
- [ ] The title names a mechanism, not an absence, a count, or an analysis-scope fact.
|
||||
- [ ] No analysis section number or finding grade survives in the title.
|
||||
|
||||
## Setup
|
||||
|
||||
- [ ] Someone other than the author has to run it; it is not one Case's reproduction steps.
|
||||
- [ ] The commands and configuration values come from the analysis, not from memory.
|
||||
- [ ] `pinned-versions` names the versions the procedure was established on.
|
||||
- [ ] A project is set. No verification date is invented to stand in for the versions.
|
||||
|
||||
## Reference
|
||||
|
||||
- [ ] The rule is reusable beyond the originating incident.
|
||||
|
||||
@@ -102,7 +102,8 @@
|
||||
"classification": "대안을 두고 프로젝트가 실제로 고른 방향이다",
|
||||
"relations": ["case:eager-to-one-n-plus-one"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"setup": []
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -149,4 +150,5 @@
|
||||
- 후보 셋 중 하나만 글감이 됐다. `MERGE_INTO` 와 `KEEP_IN_SSOT` 이 없는 분해는 선별하지 않은 분해다.
|
||||
- Concept 은 Case 를 먼저 고른 뒤에 그것을 읽는 데 필요해서 더했다.
|
||||
- Question 에 `decision-criterion` 이 있다. 무엇이 나오면 닫는지를 적지 않으면 검증을 마쳐도 열려 있다.
|
||||
- 다섯 종류를 억지로 채우지 않아도 된다. 여기서 다섯이 다 있는 것은 실제로 다섯이 있었기 때문이다.
|
||||
- 여섯 종류를 억지로 채우지 않아도 된다. 여기서 다섯이 다 있는 까닭은 실제로 다섯이 있었기
|
||||
때문이고, 여섯 번째인 `setup` 은 비어 있다 — 이 프로젝트에는 남이 따라 할 절차가 없었다.
|
||||
|
||||
@@ -90,8 +90,14 @@ python3 scripts/studio-body.py <기록.md> --key <저장소 key>=<서버가 준
|
||||
줄이 모자란 채로 채우면 뒤엣것이 조용히 버려진다. 셀렉터와 배치 실행 방법은
|
||||
[references/playwright-recipes.md](references/playwright-recipes.md).
|
||||
|
||||
**본문이 없는 세 종류(Reference·Question·Decision)의 칸은 평문으로 렌더링된다.** 백틱과
|
||||
파이프가 글자 그대로 보이고, 줄바꿈은 `<br>` 로만 살아난다.
|
||||
**본문이 없는 세 종류(Reference·Question·Decision)의 칸은 마크다운 블록 파서를 안 거친다.**
|
||||
그래도 전부 글자로 나오지는 않는다 — 렌더러(`tech-log-frontend` 의
|
||||
`presentation/shared/public-render/prose-text.tsx`)가 **백틱 쌍은 인라인 `<code>` 로** 살리고,
|
||||
빈 줄은 문단으로, 한 줄 바꿈은 `<br>` 로 남긴다. **글자 그대로 나오는 것은 별표·파이프·`#`·
|
||||
코드펜스·인용 표지 `>` 다.** 백틱을 빼지 않는다 — 빼면 식별자가 민무늬로 나온다.
|
||||
|
||||
**환경 구성에도 되풀이 칸이 하나 있다.** `고정한 버전` 은 줄마다 `이름`·`버전` 입력 둘이고
|
||||
「버전 추가」 버튼으로 늘린다. 여기도 줄 수를 먼저 맞추고 값을 넣는다.
|
||||
|
||||
### 4. 저장한다
|
||||
|
||||
@@ -128,16 +134,26 @@ python3 scripts/verify-tech-log-tree.py <프로젝트>
|
||||
|
||||
## 시험용 초안을 남기지 않는다
|
||||
|
||||
확인하려고 만든 작업본은 지운다. **다섯 종류 전부 삭제 경로가 있다** —
|
||||
`tech-log-backend` @ `a000f87` 의 `ManagementDocumentController` 에서 센 것이다.
|
||||
확인하려고 만든 작업본은 지운다. **여섯 종류 전부 삭제 경로가 있다.** 앞의 다섯은
|
||||
`tech-log-backend` @ `a000f87` 의 `ManagementDocumentController` 에서 셌고, 환경 구성은
|
||||
`tech-log-frontend` @ `9e5642c` 의 `management-api.openapi.yaml:606-628`(`deleteSetupDraft`)
|
||||
에서 읽었다. 경로 앞머리가 줄마다 다른 것은 두 문서가 각자 적는 대로 옮겼기 때문이다.
|
||||
|
||||
| 종류 | 경로 |
|
||||
|---|---|
|
||||
| Case | `DELETE /v1/studio/cases/{id}` (`:72`) |
|
||||
| Reference | `DELETE /v1/studio/references/{id}` (`:81`) |
|
||||
| Concept | `DELETE /v1/studio/concepts/{id}` (`:95`) |
|
||||
| Question | `DELETE /v1/studio/questions/{id}` (`:114`) |
|
||||
| Decision | `DELETE /v1/studio/projects/{id}/decisions/{decisionId}` (`:123`) |
|
||||
| 종류 | 경로 | 본문 |
|
||||
|---|---|---|
|
||||
| Case | `DELETE /v1/studio/cases/{id}` (`:72`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Reference | `DELETE /v1/studio/references/{id}` (`:81`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Concept | `DELETE /v1/studio/concepts/{id}` (`:95`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Setup | `DELETE /api/v1/studio/setups/{id}` | `{"expectedVersion": <저장 버전>}` |
|
||||
| Question | `DELETE /v1/studio/questions/{id}` (`:114`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Decision | `DELETE /v1/studio/projects/{id}/decisions/{decisionId}` (`:123`) | `{"expectedVersion": <저장 버전>}` |
|
||||
|
||||
**본문 없이 부르면 204 가 아니라 422 다.** `ExpectedVersionRequest` 가 없으면
|
||||
`REQUEST_VALIDATION_FAILED` / `Request body is malformed` 로 거절된다. 헤더에는
|
||||
`X-CSRF-TOKEN` 이 있어야 한다. 여섯 줄 다 같고, 전에 이 표는 경로만 적고 본문을 적지 않았다.
|
||||
|
||||
환경 구성 줄은 2026-09-12 에 시험 작업본 하나를 만들고 이 경로로 지워 204 를 받아 확인했다
|
||||
(계약 `9e5642c`).
|
||||
|
||||
**전에 이 자리에 「Decision 은 계약에 삭제 경로가 없다」고 적혀 있었고 그것은 틀렸다.**
|
||||
확인 없이 적힌 문장이 옮겨 다녔다 — 이 배치에서 그 문장을 코드 주석과 보고서로 다시 옮긴
|
||||
|
||||
@@ -17,19 +17,23 @@
|
||||
|
||||
종류를 라디오로 고르고 `작업본 만들기` 를 누른다. 라디오의 이름은 화면에 보이는 그대로다.
|
||||
|
||||
| 종류 | 라디오 이름 | 화면이 적어 놓은 칸 |
|
||||
| 계약 `kind` | 라디오 이름 | 화면이 적어 놓은 칸 |
|
||||
|---|---|---|
|
||||
| Case | `Case` | 문제 · 결론 · 환경 · 재현 · 본문 |
|
||||
| Reference | `Reference` | 목적 · 규칙 · 적용 조건 · 예외 · 예시 |
|
||||
| Concept | **`개념`** | 기준 버전 · 본문 |
|
||||
| Question | `Question` | 상태 · 사실 · 가정 · 미지수 · 선택지 |
|
||||
| Decision | `Decision` | 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
|
||||
| `CASE` | `검증 기록` | 문제 · 결론 · 환경 · 재현 · 본문 |
|
||||
| `REFERENCE` | `적용 기준` | 목적 · 규칙 · 적용 조건 · 예외 · 예시 |
|
||||
| `CONCEPT` | `동작 원리` | 기준 버전 · 본문 |
|
||||
| `SETUP` | `환경 구성` | 버전 · 본문 |
|
||||
| `QUESTION` | `열린 질문` | 상태 · 사실 · 가정 · 미지수 · 선택지 |
|
||||
| `PROJECT_DECISION` | `설계 결정` | 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
|
||||
|
||||
**Concept 만 라디오 이름이 한글(`개념`)이다.** 나머지 넷은 영어다.
|
||||
**여섯 다 한글이다.** 이 표는 전에 「Concept 만 라디오 이름이 한글(`개념`)이다. 나머지 넷은
|
||||
영어다」라고 적고 있었고 그것은 낡았다 — 2026-09-12 에 `/studio/documents/new` 를 열어 여섯
|
||||
라디오의 이름을 그대로 읽었다. **화면 문구는 이렇게 조용히 바뀐다.** 표를 외워서 넣지 말고
|
||||
스냅샷으로 읽은 이름을 쓴다.
|
||||
|
||||
```js
|
||||
await page.goto('https://hyeonworks.com/studio/documents/new');
|
||||
await page.getByRole('radio', { name: /^Reference/ }).check();
|
||||
await page.getByRole('radio', { name: /^환경 구성/ }).check();
|
||||
await page.getByRole('button', { name: '작업본 만들기' }).click();
|
||||
// 주소가 /studio/documents/<uuid>/edit 로 바뀐다. 그 uuid 를 기록 frontmatter 에 적는다
|
||||
```
|
||||
|
||||
@@ -17,12 +17,32 @@
|
||||
|---|---|---|
|
||||
| **Case** | `관계` · `문제` · `결론` · `검증 환경` · `재현 조건` · `본문` | 있음 |
|
||||
| **Concept** | `관계` · `본문` | 있음 |
|
||||
| **Setup** | `관계` · `본문` — 본문 안의 `##` 는 칸이 아니다 | 있음 |
|
||||
| **Reference** | `관계` · `목적` · `규칙` · `적용 조건` · `예외` · `예시` | 없음 |
|
||||
| **Question** | `관계` · `사실` · `가정` · `미지수` · `제약` · `선택지` · `다음 검증` | 없음 |
|
||||
| **Decision** | **`근거`** · `결정문` · `판단 이유` · `영향` | 없음 |
|
||||
|
||||
**Decision 만 관계 절 이름이 `근거` 다.** 그리고 근거가 1개 이상 없으면 게시가 거절된다.
|
||||
|
||||
## 환경 구성은 `##` 를 칸으로 세지 않는다
|
||||
|
||||
다른 다섯은 `## <이름>` 하나가 칸 하나다. 환경 구성은 화면 칸이 `고정한 버전` 과
|
||||
`절차 Markdown` 둘뿐이고, **`## 실행 절차` · `## 구성 값` · `## 확인 방법` 은 그 `절차 Markdown`
|
||||
안의 소제목**이다. 계약이 `bodyMarkdown` 설명에 「절 이름을 강제하지 않는다 — 프로젝트마다
|
||||
셋업의 모양이 다르다」고 적는다.
|
||||
|
||||
그래서 넣는 법이 다르다. 기록의 `## 본문` 아래 전체가 `bodyMarkdown` 한 칸으로 들어가고, 화면
|
||||
칸에 따로 옮길 값은 `고정한 버전` 하나뿐이다.
|
||||
|
||||
| 기록 `.md` | 어디로 |
|
||||
|---|---|
|
||||
| frontmatter `pinnedVersions[]` | `고정한 버전` — 줄마다 `이름`·`버전` 입력 둘 |
|
||||
| `## 본문` 의 `<!-- body:start -->`~`<!-- body:end -->` | `절차 Markdown` 통째로 |
|
||||
| `## 관계` | `관계` |
|
||||
|
||||
작업본을 만들면 `절차 Markdown` 이 비어 있지 않다. Studio 가 위의 절 셋을 미리 넣어 두므로,
|
||||
본문을 넣기 전에 그 내용을 지운다.
|
||||
|
||||
## 화면의 라벨은 기록의 절 이름과 다르다
|
||||
|
||||
**이것이 이 문서에서 가장 자주 틀리는 자리다.** 위 표는 기록 `.md` 가 쓰는 이름이고,
|
||||
@@ -40,8 +60,9 @@ Reference 에서 실제로 확인한 대응이다.
|
||||
| `예시` | `예시` |
|
||||
| `관계` | `관계` |
|
||||
|
||||
종류 이름도 자리마다 다르다. 상태 레일은 Reference 를 **`적용 기준`** 이라고 부르고,
|
||||
`새 문서` 화면의 라디오는 `Reference` 다.
|
||||
종류 이름도 화면마다 달랐다. 상태 레일이 Reference 를 **`적용 기준`** 이라고 부르는 동안
|
||||
`새 문서` 화면의 라디오는 `Reference` 였다. 2026-09-12 에는 `새 문서` 쪽도 여섯 다 한글이다 —
|
||||
검증 기록 · 적용 기준 · 동작 원리 · 환경 구성 · 열린 질문 · 설계 결정.
|
||||
|
||||
**화면을 먼저 스냅샷으로 읽고 그 라벨을 쓴다.** 이 표를 외워서 넣지 않는다 — 화면이 바뀌면
|
||||
표가 먼저 낡는다.
|
||||
@@ -65,6 +86,7 @@ Reference 에서 실제로 확인한 대응이다.
|
||||
| `slug` · `title` | 화면 위쪽의 슬러그·제목 칸 |
|
||||
| `topic` · `topicName` · `project` | 주제·프로젝트 선택 |
|
||||
| `basisVersion` (Concept) | 기준 버전 칸 |
|
||||
| `pinnedVersions` (Setup) | 고정한 버전 칸. `name` · `version` 이 한 줄 |
|
||||
| `questionStatus` (Question) · `decisionStatus` (Decision) | 상태 선택 |
|
||||
| `assets[].file` | 올릴 Asset 파일 |
|
||||
| `assets[].key` | 본문 `:::evidence key` 의 저장소 쪽 이름. 올리면 서버 키로 바뀐다 |
|
||||
@@ -82,7 +104,7 @@ Decision 의 도메인 `ACCEPTED` 가 화면에서는 `ADOPTED` 로 보인다.
|
||||
- 줄바꿈은 `<br>` 로만 살아난다
|
||||
- 나열은 쉼표로 잇지 말고 `이름 : 값` 으로 줄을 나눈다
|
||||
|
||||
코드·표·그림이 필요하면 짝이 되는 Case 나 Concept 에 담고 `관계` 로 가리킨다.
|
||||
코드·표·그림이 필요하면 짝이 되는 Case·Concept·Setup 에 담고 `관계` 로 가리킨다.
|
||||
|
||||
## 본문을 넣기 전에
|
||||
|
||||
|
||||
@@ -103,8 +103,18 @@ const ACRONYM_OK = new Set([
|
||||
'CANCEL','GREEN','RED','TODO','NOTE','CODE','BLOCK','AND','OR','NOT','NULL','TRUE','FALSE',
|
||||
]);
|
||||
|
||||
// YAML frontmatter 는 글쓴이가 고르는 문장이 아니라 **계약에서 옮겨 온 메타데이터**다.
|
||||
// 여기에 문장 규칙을 걸면 고칠 수 없는 error 가 나고, 글쓴이는 통과하려고 **칸을 지운다** —
|
||||
// 실제로 `topicName: 측정이 거짓말하는 자리` 가 spatial-metaphor 로 잡혀 그 주제의 기록들이
|
||||
// 그 칸 없이 저장됐다. 줄 번호는 유지한 채 비운다.
|
||||
function stripFrontMatter(src) {
|
||||
const m = /^---\r?\n[\s\S]*?\r?\n---(\r?\n|$)/.exec(src);
|
||||
if (!m) return src;
|
||||
return m[0].replace(/[^\n]/g, ' ') + src.slice(m[0].length);
|
||||
}
|
||||
|
||||
function strip(src) {
|
||||
return src
|
||||
return stripFrontMatter(src)
|
||||
.replace(/```[\s\S]*?```/g, (m) => m.replace(/[^\n]/g, ' '))
|
||||
.replace(/`[^`\n]*`/g, (m) => ' '.repeat(m.length))
|
||||
// 표와 인용은 「쓰지 않는다」 예시가 사는 자리다. 규칙을 적은 문서가 그 규칙을 어긴 것으로
|
||||
|
||||
@@ -36,19 +36,50 @@ metadata:
|
||||
|
||||
## 일곱 단계
|
||||
|
||||
| # | 단계 | 스킬 | 산출물 |
|
||||
|---|---|---|---|
|
||||
| S1 | 코드베이스 → SSOT | `analyzing-codebase-for-tech-log` | `docs/<프로젝트>/final/document.md` |
|
||||
| S2 | SSOT → 분해 계약 | `deriving-tech-log-root-tree` | `docs/<프로젝트>/tech-log-studio/tech-log-tree.json` |
|
||||
| S3 | 글감 → 기록 | `writing-tech-log-records` | `.../<주제>/<종류>/<기록>.md` |
|
||||
| S4 | 기록 → 그림 | `technical-visualizer` | `final/assets/<이름>/` · `final/.techviz/<이름>/` |
|
||||
| S5 | AI 티 제거 | `rewriting-technical-prose-naturally` | 같은 기록 파일 (제자리 수정) |
|
||||
| S6 | 일한 사람의 목소리 | `writing-as-the-person-who-did-it` | 같은 기록 파일 (제자리 수정) |
|
||||
| S7 | Studio 저장 | `publishing-tech-log-to-studio` | Studio 작업본 + `studio:` URL. **게시하지 않는다** |
|
||||
| # | 단계 | 스킬 | 에이전트 | 산출물 |
|
||||
|---|---|---|---|---|
|
||||
| S1 | 코드베이스 → SSOT | `analyzing-codebase-for-tech-log` | `ssot-analyst` | `docs/<프로젝트>/final/document.md` |
|
||||
| S2 | SSOT → 분해 계약 | `deriving-tech-log-root-tree` | `tree-deriver` | `docs/<프로젝트>/tech-log-studio/tech-log-tree.json` |
|
||||
| S3 | 글감 → 기록 | `writing-tech-log-records` | `record-writer` | `.../<주제>/<종류>/<기록>.md` |
|
||||
| S4 | 기록 → 그림 | `technical-visualizer` | `diagram-maker` | `final/assets/<이름>/` · `final/.techviz/<이름>/` |
|
||||
| S5 | AI 티 제거 | `rewriting-technical-prose-naturally` | `prose-rewriter` | 같은 기록 파일 (제자리 수정) |
|
||||
| S6 | 일한 사람의 목소리 | `writing-as-the-person-who-did-it` | `voice-writer` | 같은 기록 파일 (제자리 수정) |
|
||||
| S7 | Studio 저장 | `publishing-tech-log-to-studio` | `studio-validator` | Studio 작업본 + `studio:` URL. **게시하지 않는다** |
|
||||
|
||||
단계마다의 입력·관문·원장 칸은 [references/stage-contracts.md](references/stage-contracts.md).
|
||||
서브에이전트에 그대로 넣는 프롬프트는 [references/subagent-prompts.md](references/subagent-prompts.md).
|
||||
|
||||
## 에이전트를 그때그때 만들지 않는다
|
||||
|
||||
단계마다 맡을 에이전트가 `.claude/agents/` 에 있다. `Agent` 도구의 `subagent_type` 에 위 표의
|
||||
이름을 준다.
|
||||
|
||||
```
|
||||
Agent(subagent_type="record-writer", prompt=<references/subagent-prompts.md 의 S3>)
|
||||
```
|
||||
|
||||
프롬프트만 새로 써서 일반 에이전트를 띄우면 **어떤 규칙으로 일했는지가 어디에도 안 남는다.**
|
||||
`skillEcho` 는 「스킬을 열었다」를 증명하지만 **누가 열었는지는 증명하지 않는다** — 매번 새로
|
||||
띄운 에이전트도 SKILL.md 를 읽고 한 줄을 옮겨 적을 수 있다. 그래서 원장의 `runBy` 가 이름을
|
||||
담고, 검사기가 계약과 대조한 뒤 그 `.md` 가 실재하는지까지 본다.
|
||||
|
||||
**배정의 정본은 `scripts/verify-pipeline-run.py` 의 `STAGES` 다.** 틀(`templates/run.json`)에도
|
||||
같은 값이 적혀 있고, 둘이 갈리면 시험이 잡는다.
|
||||
|
||||
에이전트 정의는 그 단계의 「하는 일 · **안 하는 일** · 관문 · 보고」를 적는다. 프롬프트가
|
||||
그것을 되풀이하지 않아도 되는 것이 이 방식의 값이다 — 프롬프트는 **이번 런의 입력 경로와
|
||||
글감**만 준다.
|
||||
|
||||
**단계가 아닌 에이전트가 넷 더 있다.** 기록 한 편을 놓고 역할을 가른 것이라 아무 단계에도
|
||||
붙지 않는다. 부를지는 사람이 정하고, 원장의 단계 칸에는 안 들어간다.
|
||||
|
||||
| 에이전트 | 언제 | 안 하는 일 |
|
||||
|---|---|---|
|
||||
| `source-auditor` | S3 앞. 원본의 주장을 `관측`·`추론`·`미검증`으로 가른 표를 만든다 | 기록을 안 쓴다 |
|
||||
| `fact-reviewer` | 쓴 뒤. 결과 문장을 원문과 한 글자씩 역대조한다 | 파일을 안 고친다 |
|
||||
| `reader-reviewer` | 쓴 뒤. 제목·요약·목차만 보고 30초 안에 읽히는지 본다 | 본문을 안 연다 |
|
||||
| `setup-runner` | Setup 을 쓴 뒤. 손으로 끝까지 칠 수 있는지 읽는다 | 실제로 치지는 않는다 |
|
||||
|
||||
## 순서가 고정된 곳
|
||||
|
||||
세 자리는 바꾸면 결과가 틀어진다.
|
||||
@@ -108,8 +139,15 @@ python3 scripts/verify-pipeline-run.py --init runs/<프로젝트>/<runId>/run.js
|
||||
`runId` 는 `YYYY-MM-DD-HHMM` 이다. 원장의 틀은
|
||||
[templates/run.json](templates/run.json) 이고, `--init` 이 그 틀을 채워 놓는다.
|
||||
|
||||
`--init` 은 단계마다 `skillRevision` 도 적는다 — 그 시점 그 스킬의 커밋이다. 스킬은
|
||||
나중에 고쳐지고, 그러면 이 런의 영수증(`skillEcho`)이 현재 SKILL.md 에서 사라진다.
|
||||
그 커밋이 적혀 있으면 검사기가 이력을 훑지 않고 그것 하나로 대조한다. 작업 트리가 그
|
||||
커밋과 다르면 `null` 이다 — 모르는 리비전을 지어내지 않는다. 칸이 없는 옛 원장은
|
||||
이력 훑기로 떨어지고, 그것도 정상이다.
|
||||
|
||||
### 1. 단계마다 서브에이전트를 띄운다
|
||||
|
||||
에이전트는 위 표의 것을 쓴다 — `Agent(subagent_type="<이름>", ...)`. 새로 만들지 않는다.
|
||||
프롬프트는 [references/subagent-prompts.md](references/subagent-prompts.md) 의 것을 쓴다.
|
||||
프롬프트에 **반드시** 들어가야 하는 넷이 있다.
|
||||
|
||||
@@ -135,6 +173,12 @@ error 0 이어야 런이 끝난 것이다. 이 검사기가 보는 것은 결과
|
||||
준수**다 — 단계가 빠졌는지, 스킬 영수증이 그 스킬의 실제 문장인지, 관문이 돌았고 종료 코드가
|
||||
0 이었는지, 적어 낸 산출물이 디스크에 있는지.
|
||||
|
||||
영수증은 셋이 아니라 **넷으로 갈린다.** 지금 SKILL.md 에 있으면 통과, 그 스킬의 과거
|
||||
커밋에만 있으면 warn(그 뒤에 스킬이 고쳐졌다 — 어느 커밋에 있었는지 함께 적는다), 어느
|
||||
판에도 없으면 error, 과거를 볼 수 없었으면(git 이 없다 · 이력 상한에 걸렸다) 또 다른
|
||||
warn 이다. **warn 은 통과가 아니다** — 요약 줄이 「대조 못 한 영수증」을 따로 센다.
|
||||
지난 런의 영수증이 warn 으로 바뀌었다고 원장을 고쳐 쓰지 않는다. 그것은 영수증이다.
|
||||
|
||||
### 3. 프로젝트 검사기를 돌린다
|
||||
|
||||
```bash
|
||||
@@ -161,3 +205,4 @@ python3 scripts/check-figure-text.py <프로젝트>
|
||||
| SSOT 에 없는 인용이 있다 | S3 이 앞 기록에서 코드를 옮겨 적었다 | `check_evidence` 종료 코드 ≠ 0 |
|
||||
| 기록이 색인에 없다 | S2 를 건너뛰고 S3 을 했다 | S2 가 `SKIPPED` 인데 사유가 없다 |
|
||||
| Studio 에서 그림이 안 보인다 | S7 이 Asset 을 올리기 전에 본문을 넣었다 | S7 관문에 미리보기 확인이 없다 |
|
||||
| 스킬은 열었는데 결과가 그 역할 같지 않다 | 일반 에이전트를 띄웠다 | `runBy` 가 계약 이름이 아니다 |
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
# 단계 계약
|
||||
|
||||
단계마다 넷을 정한다 — **입력 · 스킬 · 관문 · 산출물.** 원장에 적히는 것도 이 넷이다.
|
||||
단계마다 다섯을 정한다 — **입력 · 스킬 · 에이전트 · 관문 · 산출물.** 원장에 적히는 것도
|
||||
이 다섯이다.
|
||||
|
||||
**에이전트는 그때그때 만들지 않는다.** `.claude/agents/<이름>.md` 가 그 단계의 「하는 일 ·
|
||||
안 하는 일 · 관문 · 보고」를 적고 있고, `Agent` 도구의 `subagent_type` 에 그 이름을 준다.
|
||||
배정의 정본은 `scripts/verify-pipeline-run.py` 의 `STAGES` 이고, 원장의 `runBy` 가 그 이름을
|
||||
담는다 — 검사기가 계약과 대조한 뒤 그 `.md` 가 실재하는지까지 본다.
|
||||
|
||||
관문은 종료 코드가 0 이어야 지난 것이다. 0 이 아니면 그 단계는 `FAILED` 이고 다음 단계로
|
||||
넘어가지 않는다.
|
||||
@@ -12,6 +18,7 @@
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `analyzing-codebase-for-tech-log` |
|
||||
| 에이전트 | `ssot-analyst` — `.claude/agents/ssot-analyst.md` |
|
||||
| 입력 | 분석 대상 저장소 경로(사용자가 준다) · 그 저장소의 `AGENTS.md`(있으면) |
|
||||
| 산출물 | `docs/<프로젝트>/final/document.md` |
|
||||
| 관문 | `python3 scripts/verify-project-layout.py <프로젝트>` |
|
||||
@@ -62,6 +69,7 @@ SSOT 가 코드와 **어긋나는** 것을 찾으면 그것은 보강 후보가
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `deriving-tech-log-root-tree` |
|
||||
| 에이전트 | `tree-deriver` — `.claude/agents/tree-deriver.md` |
|
||||
| 입력 | `docs/<프로젝트>/final/document.md` **하나** |
|
||||
| 산출물 | `docs/<프로젝트>/tech-log-studio/tech-log-tree.json` |
|
||||
| 관문 | `python3 scripts/build-tech-log-tree.py <프로젝트>` → `python3 scripts/verify-tech-log-tree.py <프로젝트>` (error 0) |
|
||||
@@ -100,6 +108,7 @@ SSOT 가 코드와 **어긋나는** 것을 찾으면 그것은 보강 후보가
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `writing-tech-log-records` |
|
||||
| 에이전트 | `record-writer` — `.claude/agents/record-writer.md` |
|
||||
| 입력 | `tech-log-tree.json` 의 노드 하나 · 그 노드의 `source` 앵커가 가리키는 SSOT 절 |
|
||||
| 산출물 | `docs/<프로젝트>/tech-log-studio/<주제>/<종류>/<기록>.md` |
|
||||
| 관문 | 아래 셋 |
|
||||
@@ -127,6 +136,7 @@ node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs <프로
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `technical-visualizer` |
|
||||
| 에이전트 | `diagram-maker` — `.claude/agents/diagram-maker.md` |
|
||||
| 입력 | S3 이 쓴 기록 `.md`(무엇을 그릴지) · 그 기록의 `source` 앵커가 가리키는 SSOT 절(그림의 사실) |
|
||||
| 산출물 | `final/.techviz/<이름>/{context.json,prompt.md,spec.json}` · `final/assets/<이름>/` |
|
||||
| 관문 | `techviz lint` · `check-figure-text.py` · `check-figure-overlap.py` · `preview-figure.py` 로 눈 확인 |
|
||||
@@ -173,6 +183,7 @@ python3 scripts/check-figure-overlap.py --file 그림.svg
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `rewriting-technical-prose-naturally` |
|
||||
| 에이전트 | `prose-rewriter` — `.claude/agents/prose-rewriter.md` |
|
||||
| 입력 | S3·S4 를 지난 기록 `.md` (제자리 수정) |
|
||||
| 산출물 | 같은 파일 |
|
||||
| 관문 | `check_prose.mjs` error 0 · `style_profile.mjs` · S3 관문 재실행 |
|
||||
@@ -202,6 +213,7 @@ node $S/style_profile.mjs <기록.md>
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `writing-as-the-person-who-did-it` |
|
||||
| 에이전트 | `voice-writer` — `.claude/agents/voice-writer.md` |
|
||||
| 입력 | S5 를 지난 기록 `.md` · **그리고 그 기록의 상류 자료** (SSOT · 커밋 메시지 · 주석 · `확인하지 못한 것` 칸) |
|
||||
| 산출물 | 같은 파일 |
|
||||
| 관문 | `check_voice.mjs` · `check_prose.mjs` 재실행 · S3 관문 재실행 |
|
||||
@@ -228,6 +240,7 @@ node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `publishing-tech-log-to-studio` |
|
||||
| 에이전트 | `studio-validator` — `.claude/agents/studio-validator.md` |
|
||||
| 입력 | S6 을 지난 기록 `.md` · frontmatter `assets:` 가 가리키는 SVG |
|
||||
| 산출물 | Studio 작업본. 기록 frontmatter 의 `id`·`studio:` |
|
||||
| 관문 | 상태 레일이 `저장됨` · `python3 scripts/build-tech-log-tree.py` → `verify-tech-log-tree.py` |
|
||||
@@ -243,12 +256,12 @@ Asset 을 본문보다 먼저 올린다. 순서를 뒤집으면 미리보기가
|
||||
|
||||
## 관문 요약
|
||||
|
||||
| 단계 | 명령 |
|
||||
|---|---|
|
||||
| S1 | `verify-project-layout.py <프로젝트>` |
|
||||
| S2 | `build-tech-log-tree.py` → `verify-tech-log-tree.py` (error 0) |
|
||||
| S3 | `studio-body.py` → `check_body.mjs` · `check_prose.mjs` · `check_evidence.mjs --repo` |
|
||||
| S4 | `techviz lint` · `check-figure-text.py` · `check-figure-overlap.py` · `preview-figure.py` (눈 확인) |
|
||||
| S5 | `check_prose.mjs` (error 0) · `style_profile.mjs` · S3 관문 |
|
||||
| S6 | `check_voice.mjs` · `check_prose.mjs` · S3 관문 |
|
||||
| S7 | `저장됨` 확인 · `build-tech-log-tree.py` → `verify-tech-log-tree.py` |
|
||||
| 단계 | 에이전트 | 명령 |
|
||||
|---|---|---|
|
||||
| S1 | `ssot-analyst` | `verify-project-layout.py <프로젝트>` |
|
||||
| S2 | `tree-deriver` | `build-tech-log-tree.py` → `verify-tech-log-tree.py` (error 0) |
|
||||
| S3 | `record-writer` | `studio-body.py` → `check_body.mjs` · `check_prose.mjs` · `check_evidence.mjs --repo` |
|
||||
| S4 | `diagram-maker` | `techviz lint` · `check-figure-text.py` · `check-figure-overlap.py` · `preview-figure.py` (눈 확인) |
|
||||
| S5 | `prose-rewriter` | `check_prose.mjs` (error 0) · `style_profile.mjs` · S3 관문 |
|
||||
| S6 | `voice-writer` | `check_voice.mjs` · `check_prose.mjs` · S3 관문 |
|
||||
| S7 | `studio-validator` | `저장됨` 확인 · `build-tech-log-tree.py` → `verify-tech-log-tree.py` |
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
단계마다 에이전트를 하나 띄운다. 아래를 그대로 쓰고 `<...>` 만 바꾼다.
|
||||
|
||||
**에이전트를 새로 만들지 않는다.** 단계마다 맡을 에이전트가 `.claude/agents/` 에 있고,
|
||||
`Agent` 도구의 `subagent_type` 에 그 이름을 준다. 절마다 첫 줄에 적혀 있다.
|
||||
|
||||
```
|
||||
Agent(subagent_type="record-writer", prompt=<아래 S3 프롬프트>)
|
||||
```
|
||||
|
||||
에이전트 정의가 그 단계의 「하는 일 · 안 하는 일 · 관문 · 보고」를 이미 적고 있다. 그래서
|
||||
프롬프트는 **이번 런의 입력 경로와 글감**을 준다 — 아래 것을 그대로 쓰되, 정의와 어긋나는
|
||||
지시를 프롬프트로 덮어쓰지 않는다.
|
||||
|
||||
## 모든 프롬프트에 들어가는 넷
|
||||
|
||||
1. **스킬 이름과 「SKILL.md 를 끝까지 먼저 읽어라」.** 요약을 주지 않는다. 요약을 주면
|
||||
@@ -42,6 +53,8 @@ S1 처럼 산출물이 `.md` 인 단계는 그래서 한 줄을 더한다.
|
||||
|
||||
## S1 — 코드베이스 → SSOT
|
||||
|
||||
**에이전트:** `ssot-analyst` — `subagent_type="ssot-analyst"`
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 1단계를 맡는다.
|
||||
|
||||
@@ -72,6 +85,8 @@ skillEcho 는 방금 읽은 SKILL.md 에서 네 작업에 해당하는 규칙
|
||||
|
||||
## S2 — SSOT → 분해 계약
|
||||
|
||||
**에이전트:** `tree-deriver` — `subagent_type="tree-deriver"`
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 2단계를 맡는다.
|
||||
|
||||
@@ -104,6 +119,8 @@ error 0 까지 고쳐라.
|
||||
|
||||
## S3 — 글감 → 기록
|
||||
|
||||
**에이전트:** `record-writer` — `subagent_type="record-writer"`
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 3단계를 맡는다.
|
||||
|
||||
@@ -112,6 +129,11 @@ error 0 까지 고쳐라.
|
||||
record-kinds.md · writing-each-kind.md · body-syntax.md · code-tables-diagrams.md ·
|
||||
explaining.md · ai-tells.md · choosing-a-diagram.md.
|
||||
|
||||
**종류가 Setup 이면 본문을 쓰기 전에 `Skill` 도구로 `writing-practitioner-guides` 를 연다.**
|
||||
명령을 어떤 형태로 쓸지는 그 스킬이 정한다 — 사람이 직접 치는 실습 가이드이지 에이전트가
|
||||
실행하기 편한 명령이 아니다. **그 스킬을 못 열면 Setup 을 쓰지 말고 그 사실을 돌려줘라.**
|
||||
안 열고 쓴 Setup 은 검사기를 다 지나면서도 실행이 중간에 끊긴다 — 실제로 그렇게 나갔다.
|
||||
|
||||
글감: docs/<프로젝트>/tech-log-studio/tech-log-tree.json 의 <주제> / <종류> / "<제목>"
|
||||
그 노드가 PROMOTE 이고 dispositionReview 가 CONFIRMED 인지 먼저 확인해라. 아니면 쓰지 마라.
|
||||
|
||||
@@ -138,6 +160,8 @@ error 0 까지 고쳐라.
|
||||
|
||||
## S4 — 기록 → 그림
|
||||
|
||||
**에이전트:** `diagram-maker` — `subagent_type="diagram-maker"`
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 4단계를 맡는다.
|
||||
|
||||
@@ -178,6 +202,8 @@ error 0 까지 고쳐라.
|
||||
|
||||
## S5 — AI 티 제거
|
||||
|
||||
**에이전트:** `prose-rewriter` — `subagent_type="prose-rewriter"`
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 5단계를 맡는다.
|
||||
|
||||
@@ -212,6 +238,8 @@ document-skeleton.md · article-shape.md · korean-tech-blog-register.md.
|
||||
|
||||
## S6 — 일한 사람의 목소리
|
||||
|
||||
**에이전트:** `voice-writer` — `subagent_type="voice-writer"`
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 6단계를 맡는다.
|
||||
|
||||
@@ -246,6 +274,8 @@ check_voice.mjs 는 목소리가 모자란지 재지 않는다. 지어낸 목소
|
||||
|
||||
## S7 — Studio 저장
|
||||
|
||||
**에이전트:** `studio-validator` — `subagent_type="studio-validator"`
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 7단계를 맡는다.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"schemaVersion": 2,
|
||||
"runId": "<YYYY-MM-DD-HHMM>",
|
||||
"project": "<프로젝트>",
|
||||
"record": "<docs/<프로젝트>/tech-log-studio/<주제>/<종류>/<기록>.md — 이 런이 만드는 기록>",
|
||||
@@ -10,10 +10,11 @@
|
||||
"id": "S1",
|
||||
"name": "코드베이스 → SSOT",
|
||||
"skill": "analyzing-codebase-for-tech-log",
|
||||
"runBy": "subagent",
|
||||
"runBy": "ssot-analyst",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
@@ -23,10 +24,11 @@
|
||||
"id": "S2",
|
||||
"name": "SSOT → 분해 계약",
|
||||
"skill": "deriving-tech-log-root-tree",
|
||||
"runBy": "subagent",
|
||||
"runBy": "tree-deriver",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
@@ -36,10 +38,11 @@
|
||||
"id": "S3",
|
||||
"name": "글감 → 기록",
|
||||
"skill": "writing-tech-log-records",
|
||||
"runBy": "subagent",
|
||||
"runBy": "record-writer",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
@@ -49,10 +52,11 @@
|
||||
"id": "S4",
|
||||
"name": "기록 → 그림",
|
||||
"skill": "technical-visualizer",
|
||||
"runBy": "subagent",
|
||||
"runBy": "diagram-maker",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
@@ -62,10 +66,11 @@
|
||||
"id": "S5",
|
||||
"name": "AI 티 제거",
|
||||
"skill": "rewriting-technical-prose-naturally",
|
||||
"runBy": "subagent",
|
||||
"runBy": "prose-rewriter",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
@@ -75,10 +80,11 @@
|
||||
"id": "S6",
|
||||
"name": "일한 사람의 목소리",
|
||||
"skill": "writing-as-the-person-who-did-it",
|
||||
"runBy": "subagent",
|
||||
"runBy": "voice-writer",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
@@ -88,10 +94,11 @@
|
||||
"id": "S7",
|
||||
"name": "Studio 저장",
|
||||
"skill": "publishing-tech-log-to-studio",
|
||||
"runBy": "subagent",
|
||||
"runBy": "studio-validator",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"skillRevision": null,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
---
|
||||
name: writing-practitioner-guides
|
||||
description: Use when writing hands-on guides, runbooks, troubleshooting docs, lab walkthroughs, or validation procedures that a human will execute themselves in a terminal over SSH. Symptoms that this applies - the reader is expected to type the commands, the doc teaches how to read a system rather than reporting a result, or a draft contains python -c, embedded JSON parsing, one-liners nobody types by hand, or a config file or YAML authored with printf/echo >>/heredoc instead of an editor.
|
||||
---
|
||||
|
||||
# Writing Practitioner Guides
|
||||
|
||||
## Overview
|
||||
|
||||
**Optimize for human operation, not command compactness.**
|
||||
|
||||
A guide is not a script. The reader types each command, reads its raw output,
|
||||
and decides what to do next. Commands that are efficient for an agent to run
|
||||
once are often useless for a human learning to read a system.
|
||||
|
||||
The failure this prevents: an agent writes `curl … | python3 -c 'import json…'`
|
||||
because it produces a clean answer in one call. The reader gets the answer and
|
||||
learns nothing about the tool they will need at 3am.
|
||||
|
||||
## The boundary
|
||||
|
||||
Three tiers. Pick the lowest one that fits.
|
||||
|
||||
```
|
||||
interactive command → short pipeline → saved script file
|
||||
```
|
||||
|
||||
| Tier | When | Form |
|
||||
|---|---|---|
|
||||
| **interactive** | reading state, one question | `kubectl get pods`, `ss -lntp`, `journalctl -u nginx -n 50` |
|
||||
| **short pipeline** | filtering that a person would actually type | `ps aux \| grep java`, `… \| jq .status`, `for p in a b; do … done` |
|
||||
| **saved script** | it has become a program | write the file with an editor, then run it |
|
||||
|
||||
**Move to a saved script when any of these is true:**
|
||||
- multiple branches (`if`) or nested loops
|
||||
- combining several requests and computing across them
|
||||
- non-trivial JSON transformation
|
||||
- you cannot tell what it does by reading it once
|
||||
- it will be run again later
|
||||
|
||||
For a saved script, the guide says to open an editor, shows the code as a
|
||||
**separate file listing** (not a terminal command), then shows the run command.
|
||||
|
||||
```bash
|
||||
vim scripts/check_sessions.py
|
||||
```
|
||||
```python
|
||||
# file: scripts/check_sessions.py
|
||||
...
|
||||
```
|
||||
```bash
|
||||
python3 scripts/check_sessions.py
|
||||
```
|
||||
|
||||
Terminal command ≠ program source. Never blur them with a heredoc.
|
||||
|
||||
## Files the reader has to understand before changing them
|
||||
|
||||
The tiers above stop at the saved script. The same split reaches further: **any
|
||||
file whose content the reader must read and understand to change it is written
|
||||
in an editor, not assembled by a shell one-liner.** Config files and YAML are
|
||||
that kind of file.
|
||||
|
||||
> **조회·진단·실행은 CLI를 적극 사용하고, 사람이 내용을 이해하면서 작성해야 하는 설정 파일은
|
||||
> 에디터를 사용한다.**
|
||||
|
||||
| Reading or acting on the system → CLI | Authoring a file a human must understand → editor |
|
||||
|---|---|
|
||||
| CPU flags → `grep /proc/cpuinfo`, `lscpu` | cloud-init YAML → `nano kc-lab-1.yaml` |
|
||||
| service state → `systemctl status` | systemd unit → `sudo nano /etc/systemd/system/x.service` |
|
||||
| VM state → `virsh list --all` | `nginx.conf` → `sudo nano /etc/nginx/nginx.conf` |
|
||||
| network → `ip addr`, `virsh net-list --all` | `~/.bashrc` → `nano ~/.bashrc` |
|
||||
| logs → `journalctl -u x` | Kubernetes manifest → `nano deploy.yaml` |
|
||||
| fetch / copy → `curl`, `cp`, `scp` | a script → `nano x.sh` → `chmod +x x.sh` → `./x.sh` |
|
||||
| create a VM → `virt-install` | |
|
||||
|
||||
### This rule is not "replace sed with nano"
|
||||
|
||||
> 단순히 **「`sed`를 `nano`로 바꿔라」**라고 하면 안 됩니다. 그러면 모든 shell 명령을 기계적으로
|
||||
> 에디터 작업으로 바꿀 가능성이 큽니다.
|
||||
|
||||
The trigger is the *file-authoring step*, not the appearance of a shell tool.
|
||||
|
||||
| Kind | Examples | Verdict |
|
||||
|---|---|---|
|
||||
| what an operator types by hand | `virsh`, `systemctl`, `ssh`, `curl`, `virt-install` | keep |
|
||||
| reading / diagnosing | `grep`, `lsmod`, `cat`, `stat`, `groups` | keep |
|
||||
| shell tricks that author a file | `printf >`, `echo >>`, `cat <<EOF`, `python3 -c`, `ssh '… cat > …'` | rewrite as an editor step |
|
||||
|
||||
A pipe is not the problem. A 12-line `virt-install` is not the problem —
|
||||
creating the VM *is* that command's purpose, so the CLI is the right way to show
|
||||
it. `sed` is fine for a query or a throwaway substitution; it is wrong as the
|
||||
default interface for editing config, because what the reader will actually do
|
||||
during an incident is open the file and read what is in it.
|
||||
|
||||
### Rewrite: `~/.bashrc`
|
||||
|
||||
```bash
|
||||
# before
|
||||
echo 'export LIBVIRT_DEFAULT_URI=qemu:///system' >> ~/.bashrc
|
||||
virsh uri
|
||||
```
|
||||
|
||||
```bash
|
||||
# after
|
||||
nano ~/.bashrc
|
||||
```
|
||||
```text
|
||||
export LIBVIRT_DEFAULT_URI=qemu:///system
|
||||
```
|
||||
```bash
|
||||
source ~/.bashrc
|
||||
virsh uri
|
||||
```
|
||||
|
||||
Two reasons, and the second is the one that gets forgotten:
|
||||
|
||||
1. The reader sees the chain — file → variable → reload this shell → `virsh`
|
||||
now resolves that URI. `echo >>` produces only the end state.
|
||||
2. **`echo >>` is not idempotent.** Someone who walks the guide a second time
|
||||
appends the same line again. Opening the file shows what is already there.
|
||||
|
||||
### Rewrite: cloud-init meta-data
|
||||
|
||||
```bash
|
||||
# before
|
||||
printf 'instance-id: kc-lab-1-%s\nlocal-hostname: kc-lab-1\n' "$(date +%s)" > meta-kc-lab-1
|
||||
```
|
||||
|
||||
```bash
|
||||
# after
|
||||
nano meta-kc-lab-1
|
||||
```
|
||||
```yaml
|
||||
instance-id: kc-lab-1-20260912
|
||||
local-hostname: kc-lab-1
|
||||
```
|
||||
> `instance-id`는 이전 cloud-init 실행과 다른 인스턴스로 인식시키기 위해 이전 값과 겹치지 않게
|
||||
> 지정한다.
|
||||
|
||||
The `printf` form makes the reader decode `%s`, `\n`, `$(...)`, `date +%s` and
|
||||
`>` before reaching the two keys the page is about. A document teaching
|
||||
cloud-init should not be teaching shell `printf`. The editor form also gives the
|
||||
one line about `instance-id` a place to sit, right where it is typed.
|
||||
|
||||
### Rewrite: a file on a remote host
|
||||
|
||||
```bash
|
||||
# before
|
||||
ssh donghyeon@192.168.122.11 'umask 077; cat > ~/kc-lab-2.yaml' < kc-lab-2.yaml
|
||||
ssh donghyeon@192.168.122.11 'cloud-init schema -c ~/kc-lab-2.yaml; rm -f ~/kc-lab-2.yaml'
|
||||
```
|
||||
|
||||
```bash
|
||||
# after
|
||||
scp kc-lab-2.yaml donghyeon@192.168.122.11:~/
|
||||
ssh donghyeon@192.168.122.11
|
||||
```
|
||||
then, on the guest:
|
||||
```bash
|
||||
chmod 600 ~/kc-lab-2.yaml
|
||||
cloud-init schema -c ~/kc-lab-2.yaml
|
||||
rm ~/kc-lab-2.yaml
|
||||
```
|
||||
|
||||
The first line asked the reader to hold SSH, redirection, `umask`, file creation
|
||||
and local stdin at once. The rewrite uses more commands and puts fewer things in
|
||||
each: **one action, one command.** In a guide someone is learning from, that
|
||||
trade is the right way round.
|
||||
|
||||
One more rewrite belongs to this rule — replacing a `python3 -c` YAML check with
|
||||
the format's own validator. It sits in **Command priority** below, where the
|
||||
ranking it changes lives.
|
||||
|
||||
## Command priority
|
||||
|
||||
| Rank | Reach for | Examples |
|
||||
|---|---|---|
|
||||
| 1 | the system's own CLI | `kubectl`, `systemctl`, `psql`, `redis-cli`, `docker compose` |
|
||||
| 2 | standard OS tools | `ps`, `ss`, `lsof`, `free`, `top`, `dmesg` |
|
||||
| 3 | network / protocol tools | `curl`, `dig`, `openssl`, `nc`, `tcpdump` |
|
||||
| 4 | short Unix combinators | `grep`, `jq`, `awk`, `head`, `tail`, `less`, `watch` |
|
||||
| — | **avoid** | python/node heredocs, `python -c` that *processes data*, giant awk programs, pipelines built to produce one tidy answer |
|
||||
|
||||
**The line is doing-the-work vs checking-a-fact, not the language.**
|
||||
|
||||
```bash
|
||||
# not fine — this is data processing the native tool should do
|
||||
curl -s "$URL" | python3 -c '
|
||||
import json,sys
|
||||
for r in json.load(sys.stdin)["data"]["result"]:
|
||||
print(r["metric"]["pod"], r["value"][1])'
|
||||
```
|
||||
|
||||
That one iterates, reshapes, and formats. `jq`, or the tool's own
|
||||
output flag, does that — and when neither is installed, say so and show the
|
||||
raw output instead of writing a parser.
|
||||
|
||||
`grep`, `jq`, `awk`, and a one-line `for` are what practitioners type. Do not
|
||||
ban them. Ban the ones written for the *agent's* convenience.
|
||||
|
||||
### Validate with the format's own checker first
|
||||
|
||||
Checking a fact is allowed — but a hand-rolled syntax check is the *last*
|
||||
resort, not the default. If the domain ships a command that validates this file,
|
||||
that command is the step. Drop to a one-line syntax check only when nothing
|
||||
validates the format.
|
||||
|
||||
```bash
|
||||
# before — checks that it parses as YAML, and nothing else
|
||||
python3 -c 'import yaml,sys; yaml.safe_load(open("kc-lab-1.yaml")); print("YAML OK")'
|
||||
|
||||
# after — the domain's own validator: schema, keys, and deprecations too
|
||||
cloud-init schema -c ~/kc-lab-2.yaml
|
||||
```
|
||||
|
||||
Same rank elsewhere: `nginx -t`, `sshd -t`, `systemd-analyze verify x.service`,
|
||||
`kubectl apply --dry-run=server -f deploy.yaml`, `terraform validate`,
|
||||
`docker compose config`. Each one is also the command the reader will reach for
|
||||
when the service refuses to start, which a `python3 -c` line never becomes.
|
||||
|
||||
### Two shapes of the same tool
|
||||
|
||||
Most tools have a *reading* form and a *value-extracting* form. Pick by what
|
||||
the reader does next with the output.
|
||||
|
||||
| The reader will… | Form | Example |
|
||||
|---|---|---|
|
||||
| **look at the response** and judge | reading form | `curl -I <url>` · `curl -v <url>` |
|
||||
| **compare or count the value** across runs or hosts | extracting form | `curl -s -o /dev/null -w '%{http_code}\n' <url>` |
|
||||
|
||||
The extracting form hides everything except the field you chose. Use it only
|
||||
when that field *is* the answer — a status code you will compare before and
|
||||
after an injection, or a number you will repeat 900 times. When the reader is
|
||||
still figuring out what is wrong, they need the headers and the TLS handshake,
|
||||
not `200`.
|
||||
|
||||
Same split elsewhere: `kubectl get` (read) vs `-o jsonpath=` (extract),
|
||||
`systemctl status` (read) vs `systemctl show -p X --value` (extract),
|
||||
`psql` interactive (read) vs `psql -tAc` (extract).
|
||||
|
||||
**Show the reading form first at least once per tool.** A reader who has only
|
||||
ever seen `-w '%{http_code}'` cannot debug a TLS error.
|
||||
|
||||
## Progressive narrowing
|
||||
|
||||
Never jump to the precise command. Show the widening-to-narrowing path the
|
||||
reader will actually walk.
|
||||
|
||||
```
|
||||
list / status → detail / describe → logs → targeted inspection
|
||||
```
|
||||
|
||||
```bash
|
||||
kubectl get pods # what is there
|
||||
kubectl get pods -o wide # where, and on which node
|
||||
kubectl describe pod api-7c874-8m2js # why is this one unhappy
|
||||
kubectl logs api-7c874-8m2js # what did it say
|
||||
kubectl logs api-7c874-8m2js --previous # what did it say before it died
|
||||
```
|
||||
|
||||
**Observe before filtering.** Show the raw output at least once before piping
|
||||
it. A reader who has never seen `ss -lntp` output needs to see it whole.
|
||||
|
||||
## Mutation ordering
|
||||
|
||||
```
|
||||
observe → diagnose → reproduce → mutate
|
||||
```
|
||||
|
||||
`rm`, `kill`, `delete`, `UPDATE`, restart, config change do not appear in the
|
||||
diagnosis phase. If a step changes state, say what it changes and how to undo it.
|
||||
|
||||
## Shape of one step
|
||||
|
||||
Four elements, in this order. A command with no interpretation is not a step.
|
||||
|
||||
```
|
||||
무엇을 확인하는가 one line — the question this answers
|
||||
$ command the command, short
|
||||
어디를 봐야 하는가 which field/line in the output matters
|
||||
이 결과가 의미하는 것 what it tells you, and what to do next
|
||||
```
|
||||
|
||||
Show the real output. If it was measured, quote it verbatim; if it is
|
||||
illustrative, say so.
|
||||
|
||||
### When the step changes state
|
||||
|
||||
The four elements above are the shape of a step that **reads**. A step that
|
||||
**changes state** needs a different one — otherwise the why, the failure
|
||||
symptoms and the special cases all pile into the same paragraph:
|
||||
|
||||
> 정보 밀도는 높은데 **처음 따라 하는 사람의 시선 이동이 어렵습니다.**
|
||||
|
||||
```
|
||||
목적 → 행동(번호 매긴 명령) → 예상 결과 → 왜 필요한가 → 문제가 생기면
|
||||
```
|
||||
|
||||
```text
|
||||
### 4. libvirt 기본 연결을 system으로 설정한다
|
||||
|
||||
목적
|
||||
virsh가 사용자 세션이 아니라 시스템 libvirt에 연결되도록 한다.
|
||||
|
||||
1. 설정 파일을 연다.
|
||||
$ nano ~/.bashrc
|
||||
|
||||
2. 다음 줄을 추가한다.
|
||||
export LIBVIRT_DEFAULT_URI=qemu:///system
|
||||
|
||||
3. 저장한 설정을 현재 셸에 반영한다.
|
||||
$ source ~/.bashrc
|
||||
|
||||
4. 확인한다.
|
||||
$ virsh uri
|
||||
|
||||
예상 결과
|
||||
qemu:///system
|
||||
|
||||
왜 필요한가
|
||||
qemu:///session과 qemu:///system은 서로 다른 libvirt 연결이다.
|
||||
VM을 system 쪽에 만들고 virsh가 session 쪽을 보고 있으면
|
||||
VM을 만들었는데도 목록에서 찾지 못할 수 있다.
|
||||
|
||||
문제가 생기면
|
||||
$ virsh uri
|
||||
부터 확인한다.
|
||||
```
|
||||
|
||||
**A step that only reads takes the first shape; a step that changes state takes
|
||||
this one.** Same headings every time, so the reader's eye lands in the same
|
||||
place on step 11 as on step 1.
|
||||
|
||||
## Verify before publishing
|
||||
|
||||
Every command in a guide must have been run, or be marked as unverified.
|
||||
**Check that the tools you reach for are actually installed on the machine
|
||||
the reader will be on** — `jq` and `yamllint` are absent more often than you
|
||||
expect, and a guide that assumes them sends the reader to install things
|
||||
mid-diagnosis.
|
||||
Two failures this catches, both real:
|
||||
|
||||
- `kubectl get endpoints` — deprecated since v1.33, prints a warning
|
||||
- `kubectl exec keycloak-0 -- curl …` — the image has no curl, exit 127
|
||||
|
||||
A guide that teaches a stale or failing command makes the reader doubt their
|
||||
own environment.
|
||||
|
||||
## No placeholders
|
||||
|
||||
`<token>` puts the value outside the document. Give the command that produces
|
||||
it. For secrets, confirm existence or length — never print the value.
|
||||
|
||||
```bash
|
||||
TOKEN=$(ssh node1 'sudo cat /var/lib/rancher/k3s/server/node-token')
|
||||
echo "${#TOKEN} chars"
|
||||
```
|
||||
|
||||
One exception: a value an earlier step already printed on screen, which the
|
||||
reader carries into a later step in a different shell — a session id, a realm
|
||||
UUID. The producing command is already in the document, so the value is not
|
||||
outside it. Write that slot as `{{NAME}}` (uppercase, digits, underscore), and
|
||||
say in the prose above the block which step printed it.
|
||||
|
||||
Not `${NAME}` — guides use `$TOKEN`, `$SID` and friends as real shell
|
||||
variables, and a reader who reads a placeholder as one will paste it unchanged.
|
||||
`{{ }}` is not shell syntax, so pasting it fails where the reader can see it.
|
||||
|
||||
## Rationalization table
|
||||
|
||||
| Excuse | Reality |
|
||||
|---|---|
|
||||
| "The pipeline gives a clean answer" | The reader needs to read the raw output, not your summary of it |
|
||||
| "python -c is shorter than explaining" | It is shorter for you. The reader learns nothing and cannot adapt it |
|
||||
| "jq/awk are also programming" | They are what practitioners type. The line is *program vs command*, not *language* |
|
||||
| "I'll show the efficient way" | Efficient for one run. This doc is for someone learning to read the system |
|
||||
| "The reader can copy-paste it" | Copy-paste is not the goal. Knowing where to look is |
|
||||
| "I verified the logic mentally" | Run it. Two commands in a recent guide were wrong and both looked right |
|
||||
| "It's obvious what this output means" | Then write the one line. If it is obvious it costs nothing |
|
||||
| "`-w '%{http_code}'` is precise" | Precise about one field. The reader debugging TLS needs `-v`, not `200` |
|
||||
| "It's fewer commands" | Fewer for you to type. The reader cannot tell which action they are in the middle of |
|
||||
| "The file ends up the same either way" | Only on the first run. `echo >>` appends again every time someone repeats the guide |
|
||||
| "So I should use nano for everything" | No. The trigger is authoring a file the reader must understand. `grep`, `virsh`, a long `virt-install` stay as they are |
|
||||
| "`python3 -c` only checks syntax" | Then it misses the schema. If the format has a validator — `cloud-init schema`, `nginx -t`, `--dry-run` — that is the step |
|
||||
|
||||
## Red flags — stop and rewrite
|
||||
|
||||
- `python3 -c` or a `<<'PY'` heredoc inside a guide
|
||||
- JSON parsed with a language runtime instead of `jq` or the tool's own `-o`
|
||||
- a pipeline whose purpose you cannot state in one clause
|
||||
- a command with no "what to look at" line under it
|
||||
- `delete`/`kill`/`restart` before any observation step
|
||||
- `<placeholder>` with no command that produces it
|
||||
- output shown that you never actually ran
|
||||
- only the extracting form of a tool appears, never the reading form
|
||||
- a config file or YAML built with `printf >`, `echo >>`, or `cat <<EOF`
|
||||
- one line stacking connect + redirect + file creation (`ssh host 'cat > f' < f`)
|
||||
- `python3 -c` checking syntax when the format has its own validator
|
||||
- a step that appends, so walking the guide twice appends the line twice
|
||||
|
||||
## References
|
||||
|
||||
Per-technology command vocabulary — what practitioners reach for first, not
|
||||
an encyclopedia. Load only the one you need.
|
||||
|
||||
- [kubernetes.md](references/kubernetes.md)
|
||||
- [linux-systemd.md](references/linux-systemd.md)
|
||||
- [networking-tls.md](references/networking-tls.md)
|
||||
- [datastores.md](references/datastores.md)
|
||||
@@ -0,0 +1,52 @@
|
||||
# 데이터 저장소 — what to reach for first
|
||||
|
||||
## PostgreSQL
|
||||
```bash
|
||||
psql -U <user> -d <db>
|
||||
```
|
||||
```
|
||||
\conninfo 지금 어디에 붙어 있나
|
||||
\dt 테이블 목록
|
||||
\d <table> 구조 · 인덱스 · 제약
|
||||
\du 롤
|
||||
\l 데이터베이스 목록
|
||||
\x 세로 출력 토글 (넓은 행을 볼 때)
|
||||
```
|
||||
|
||||
```sql
|
||||
SHOW <setting>; -- 전역값
|
||||
SELECT * FROM pg_stat_activity; -- 지금 도는 쿼리
|
||||
EXPLAIN <query>; -- 계획만
|
||||
EXPLAIN (ANALYZE, BUFFERS) <query>; -- 실제 실행. 변경 쿼리에 쓰면 실제로 바뀐다
|
||||
```
|
||||
|
||||
한 줄로 값만 뽑을 때.
|
||||
```bash
|
||||
kubectl exec deploy/postgres -- psql -U <user> -d <db> -tAc 'select count(*) from <t>'
|
||||
```
|
||||
|
||||
문장 로깅 — 애플리케이션을 고치지 않고 「무엇이 DB 를 어떻게 쓰는지」 본다.
|
||||
```sql
|
||||
ALTER SYSTEM SET log_statement = 'all';
|
||||
SELECT pg_reload_conf();
|
||||
-- 끝나면 되돌린다
|
||||
ALTER SYSTEM RESET log_statement;
|
||||
```
|
||||
|
||||
## Redis
|
||||
```bash
|
||||
redis-cli ping
|
||||
redis-cli info server | head
|
||||
redis-cli dbsize
|
||||
redis-cli --scan --pattern '<prefix>*' # KEYS 대신. 블로킹하지 않는다
|
||||
redis-cli type <key>
|
||||
redis-cli ttl <key>
|
||||
redis-cli --no-raw get <key> # 바이너리를 이스케이프해 보여 준다
|
||||
redis-cli config get appendonly
|
||||
```
|
||||
`\xac\xed` 로 시작하면 Java 네이티브 직렬화라 사람이 읽을 수 없다.
|
||||
|
||||
`/data` 가 볼륨이 아니면 영속화 설정은 컨테이너와 함께 사라진다.
|
||||
```bash
|
||||
kubectl get pod -l app=redis -o jsonpath='{.items[0].spec.volumes}'
|
||||
```
|
||||
@@ -0,0 +1,72 @@
|
||||
# Kubernetes — what to reach for first
|
||||
|
||||
Order matters. Widen first, then narrow.
|
||||
|
||||
## 무엇이 있나
|
||||
```bash
|
||||
kubectl get pods # 이름 · 상태 · 재시작 횟수
|
||||
kubectl get pods -o wide # + 노드 · 파드 IP
|
||||
kubectl get all # 워크로드 계열만. Secret·PVC·Ingress 는 안 나온다
|
||||
kubectl get secret,configmap,pvc,ingress
|
||||
```
|
||||
|
||||
## 왜 이 파드가 이런가
|
||||
```bash
|
||||
kubectl describe pod <pod> # 이벤트가 여기 붙는다 — 로그보다 먼저 본다
|
||||
kubectl logs <pod>
|
||||
kubectl logs <pod> --previous # CrashLoop 이면 죽은 이유는 여기 있다
|
||||
kubectl logs <pod> -c <container> # 컨테이너가 여럿일 때
|
||||
kubectl events --for pod/<pod>
|
||||
kubectl get events --sort-by=.lastTimestamp | tail -20
|
||||
```
|
||||
|
||||
## 사슬을 따라간다
|
||||
```bash
|
||||
kubectl get deploy,rs,pod -l app=<label>
|
||||
```
|
||||
Deployment 는 파드를 직접 만들지 않는다. ReplicaSet 을 만들고 그것이 파드를
|
||||
만든다. RS 가 여러 개 남아 있는 것은 정상이며(배포 이력) 활성인 것만 0 이 아니다.
|
||||
|
||||
StatefulSet 은 RS 를 쓰지 않고 파드를 직접 만든다 — 이름이 고정이라
|
||||
`Terminating` 이 안 풀리면 대체 파드가 생기지 않는다.
|
||||
|
||||
## Service 가 파드를 잡고 있나
|
||||
```bash
|
||||
kubectl describe svc <svc> | grep -i endpoints
|
||||
kubectl get endpointslice -l kubernetes.io/service-name=<svc>
|
||||
```
|
||||
`kubectl get endpoints` 는 v1.33+ 에서 deprecated 다.
|
||||
|
||||
비어 있으면 셀렉터와 라벨이 안 맞거나 readiness 미통과다.
|
||||
```bash
|
||||
kubectl get svc <svc> -o jsonpath='{.spec.selector}'
|
||||
kubectl get pods --show-labels
|
||||
```
|
||||
|
||||
## Secret 이 실제로 들어갔나 — 값은 찍지 않는다
|
||||
```bash
|
||||
kubectl get secret <s> -o jsonpath='{.data}' | tr ',' '\n' | grep -o '"[A-Z_]*"' # 키 이름만
|
||||
kubectl get secret <s> -o jsonpath='{.data.<KEY>}' | base64 -d | wc -c # 길이만
|
||||
kubectl exec <pod> -- sh -c 'echo ${#MY_ENV}' # 파드 안 주입 확인
|
||||
```
|
||||
|
||||
## 적용과 대기
|
||||
```bash
|
||||
kubectl apply -f <file>
|
||||
kubectl rollout status deploy/<name> --timeout=180s # 끝날 때까지 블록한다
|
||||
kubectl rollout undo deploy/<name>
|
||||
```
|
||||
|
||||
## 안에서 볼 때
|
||||
```bash
|
||||
kubectl exec -it <pod> -- sh
|
||||
kubectl port-forward svc/<svc> 8080:80
|
||||
kubectl debug -it <pod> --image=busybox --target=<container> # 최소 이미지에 도구가 없을 때
|
||||
```
|
||||
|
||||
**최소 이미지에는 `curl` 도 `wget` 도 없다.** Keycloak 공식 이미지가 그렇다
|
||||
(`exit 127`). 밖에서 물어보거나 임시 파드를 띄운다.
|
||||
```bash
|
||||
kubectl run tmp --rm -it --restart=Never --image=curlimages/curl:8.11.1 -- \
|
||||
curl -s http://<podIP>:9000/metrics
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
# Linux · systemd — what to reach for first
|
||||
|
||||
## 서비스 상태
|
||||
```bash
|
||||
systemctl status <unit> # 상태 · Main PID · CGroup · 최근 로그
|
||||
systemctl is-active <unit> # 한 단어. 스크립트용
|
||||
systemctl cat <unit> # 유닛 파일에 적힌 것
|
||||
systemctl show <unit> # 기본값까지 합쳐 실제 적용되는 것
|
||||
```
|
||||
`cat` 과 `show` 는 다르다. `Restart=on-failure` 만 적혀 있어도 `show` 는
|
||||
`RestartUSec`·`StartLimitBurst` 같은 기본값을 함께 보여 준다.
|
||||
|
||||
## 로그
|
||||
```bash
|
||||
journalctl -u <unit> -n 50 # 최근 50줄
|
||||
journalctl -u <unit> -e # 끝으로 (페이저)
|
||||
journalctl -u <unit> -f # 실시간
|
||||
journalctl -u <unit> -p err # 에러만
|
||||
journalctl -u <unit> --since '1 hour ago'
|
||||
journalctl -u <unit> -o json # 메타데이터까지
|
||||
```
|
||||
긴 줄이 접히면 `less -S` 로 좌우 스크롤한다.
|
||||
|
||||
**nginx 에러 로그는 2048바이트에서 잘린다**(`NGX_MAX_ERROR_STR`). 저널
|
||||
포맷을 바꿔도 안 늘어난다 — 기록 자체가 잘렸기 때문이다. access 로그에는
|
||||
제한이 없으므로 그쪽을 본다.
|
||||
|
||||
## 프로세스 · 포트 · 자원
|
||||
```bash
|
||||
ps aux | grep <name>
|
||||
ps -eo pid,ppid,etimes,lstart,args | grep <name> # 얼마나 오래 떠 있나
|
||||
ss -lntp # 듣고 있는 TCP 포트 + 프로세스
|
||||
lsof -i :8080
|
||||
free -m
|
||||
top / htop
|
||||
```
|
||||
|
||||
## cgroup
|
||||
```bash
|
||||
systemd-cgls /system.slice/<unit>.service
|
||||
cat /sys/fs/cgroup/system.slice/<unit>.service/memory.current
|
||||
cat /sys/fs/cgroup/system.slice/<unit>.service/pids.current
|
||||
```
|
||||
`systemctl status` 의 `Memory:` `Tasks:` `CPU:` 가 여기서 읽은 값이다.
|
||||
|
||||
## nginx
|
||||
```bash
|
||||
nginx -t && systemctl reload nginx # -t 를 통과할 때만 reload
|
||||
ps -eo pid,lstart,args | grep 'nginx: worker' # reload 판정은 워커 PID 로
|
||||
```
|
||||
reload 하면 마스터는 유지되고 워커만 새로 뜬다. 로그 문구가 아니라 이걸 본다.
|
||||
@@ -0,0 +1,61 @@
|
||||
# 네트워크 · TLS — what to reach for first
|
||||
|
||||
## HTTP
|
||||
```bash
|
||||
curl -I <url> # 한 번 볼 때
|
||||
curl -v <url> # 헤더 · TLS 협상까지
|
||||
curl -s -o /dev/null -w '%{http_code}\n' <url> # 여러 번 재서 비교할 때만
|
||||
```
|
||||
`-w` 형태는 측정용이다. 눈으로 한 번 볼 때는 `-I` 나 `-v` 로 충분하다.
|
||||
|
||||
## 이름 해석
|
||||
```bash
|
||||
dig +short <name>
|
||||
getent hosts <name> # /etc/hosts 와 NSS 순서까지 반영된 결과
|
||||
```
|
||||
|
||||
## TLS
|
||||
```bash
|
||||
echo | openssl s_client -connect <host>:443 -servername <host> 2>/dev/null \
|
||||
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
|
||||
|
||||
echo | openssl s_client -connect <host>:443 -servername <host> 2>/dev/null \
|
||||
| grep -E '^ *[0-9]+ s:|^ *i:|Verify return code'
|
||||
```
|
||||
체인 단계가 1개면 `cert.pem` 을 쓴 것이다. `fullchain.pem` 이어야 한다.
|
||||
|
||||
발급 시각의 외부 기준이 필요하면 SCT 를 본다.
|
||||
```bash
|
||||
… | openssl x509 -noout -ext ct_precert_scts
|
||||
```
|
||||
|
||||
## 연결 추적
|
||||
```bash
|
||||
sudo conntrack -L | grep <port>
|
||||
sudo conntrack -C
|
||||
cat /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established # 보통 86400
|
||||
```
|
||||
`ESTABLISHED` 는 대부분의 방화벽 규칙 평가를 건너뛴다. 규칙을 넣었는데
|
||||
아무 일도 없으면 여기를 먼저 본다.
|
||||
|
||||
## 방화벽 규칙
|
||||
```bash
|
||||
sudo iptables -S FORWARD # 순서가 중요하다 — 내 규칙이 몇 번째인가
|
||||
sudo iptables -L FORWARD -v -n # 카운터가 0 이면 도달하지 않았다
|
||||
sudo iptables -t raw -S PREROUTING # conntrack 보다 먼저 잡는 자리
|
||||
```
|
||||
|
||||
## 패킷
|
||||
```bash
|
||||
sudo tcpdump -i <iface> -n port <p> -c 20
|
||||
```
|
||||
오버레이 네트워크(flannel VXLAN 등)에서는 물리 인터페이스에 안쪽 IP 가 안
|
||||
보인다. `flannel.1` 같은 터널 인터페이스에서 잡는다.
|
||||
|
||||
## 시계
|
||||
```bash
|
||||
timedatectl show -p NTP -p NTPSynchronized
|
||||
A=$(date -u +%s.%N); B=$(ssh <host> 'date -u +%s.%N'); C=$(date -u +%s.%N)
|
||||
curl -sI https://www.google.com | grep -i '^date:' # 어느 쪽이 맞는지 외부 기준
|
||||
```
|
||||
두 기계의 로그를 나란히 놓기 전에 확인한다.
|
||||
@@ -1,14 +1,14 @@
|
||||
# writing-tech-log-records
|
||||
|
||||
Tech Log Studio에 올릴 기록을 쓰는 스킬. Case · Concept · Reference · Question · Decision 다섯 종류의
|
||||
종류 선택, 칸 채우기, Case 본문 작성, 게시 전 대조를 다룬다.
|
||||
Tech Log Studio에 올릴 기록을 쓰는 스킬. Case · Concept · Setup · Reference · Question · Decision
|
||||
여섯 종류의 종류 선택, 칸 채우기, 본문 작성, 게시 전 대조를 다룬다.
|
||||
|
||||
## 파일
|
||||
|
||||
| 파일 | 무엇 |
|
||||
|---|---|
|
||||
| `SKILL.md` | 진입점. 종류 선택과 절차 |
|
||||
| `references/record-kinds.md` | 다섯 종류의 칸·상한·게시 조건 |
|
||||
| `references/record-kinds.md` | 여섯 종류의 칸·상한·게시 조건 |
|
||||
| `references/from-ssot-to-records.md` | 긴 글에서 글감을 뽑는 기준과 `tech-log-tree.json` |
|
||||
| `references/writing-each-kind.md` | 종류마다 무엇을 어떤 순서로 쓰나 |
|
||||
| `templates/*.md` | 종류별 빈 틀. 복사해서 채운다 |
|
||||
@@ -56,5 +56,5 @@ FAIL 초안.md:3:1 unsupported block syntax: html
|
||||
|
||||
## 알아둘 제약
|
||||
|
||||
코드·표·다이어그램·이미지는 **본문에만** 들어간다. 본문이 있는 종류는 Case 와 Concept 이다. Reference·Question·Decision의 모든
|
||||
칸은 평문으로 렌더링된다. 설계상 그렇다 — 본문을 가진 종류는 Case뿐이다.
|
||||
코드·표·다이어그램·이미지는 **본문에만** 들어간다. 본문이 있는 종류는 Case·Concept·Setup 셋이고,
|
||||
Reference·Question·Decision의 모든 칸은 평문으로 렌더링된다.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: writing-tech-log-records
|
||||
description: Use when writing or revising a Tech Log Studio record — Case, Concept, Reference, Question, or Decision — including choosing the right kind, filling each kind's fields, authoring body Markdown with code blocks, tables, callouts, diagrams and evidence images, and linking records so a published document renders correctly on the public site.
|
||||
description: Use when writing or revising a Tech Log Studio record — Case, Concept, Setup, Reference, Question, or Decision — including choosing the right kind, filling each kind's fields, authoring body Markdown with code blocks, tables, callouts, diagrams and evidence images, and linking records so a published document renders correctly on the public site.
|
||||
metadata:
|
||||
version: "1.1.0"
|
||||
language: "ko-KR"
|
||||
@@ -12,21 +12,26 @@ metadata:
|
||||
|
||||
## 개요
|
||||
|
||||
Studio는 다섯 종류를 구분한다. 종류를 잘못 고르면 채울 칸이 맞지 않는다.
|
||||
Studio는 여섯 종류를 구분한다. 종류를 잘못 고르면 채울 칸이 맞지 않는다.
|
||||
|
||||
| 종류 | 쓰는 때 | 본문 |
|
||||
|---|---|---|
|
||||
| **Case** | 내가 재현하고 검증해 결론을 냈다 | 있음 |
|
||||
| **Concept** | 남의 것이 어떻게 동작하는지 읽고 정리했다 | 있음 |
|
||||
| **Setup** | 남이 자기 손으로 따라 할 절차를 남긴다 (`SETUP`, 화면 이름 「환경 구성」) | 있음 |
|
||||
| **Reference** | 반복 적용할 기준을 굳혔다 | 없음 |
|
||||
| **Question** | 아직 판단이 안 끝났다 | 없음 |
|
||||
| **Decision** | 프로젝트가 방향을 정했다 (`PROJECT_DECISION`) | 없음 |
|
||||
|
||||
**Setup 만 끝난 일을 적지 않는다.** 나머지 다섯은 이미 일어난 일을 적고, Setup 은 읽는 사람이
|
||||
자기 기계에서 실행할 순서를 적는다. 그래서 본문에 명령이 들어가고, 버전만 본문 밖의
|
||||
`pinnedVersions` 에 남는다. 프로젝트가 필수이고 주제는 비워도 된다.
|
||||
|
||||
## 절대 규칙
|
||||
|
||||
**코드·표·다이어그램·이미지는 본문에만 들어간다. 본문이 있는 종류는 Case 와 Concept 둘뿐이다.**
|
||||
본문이 없는 세 종류의 칸은 평문으로 렌더링돼 백틱과 파이프가 글자 그대로 보인다. 그런 자료는 Case 나
|
||||
Concept 에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
**코드·표·다이어그램·이미지는 본문에만 들어간다. 본문이 있는 종류는 Case·Concept·Setup 셋이다.**
|
||||
본문이 없는 세 종류(Reference·Question·Decision)의 칸은 평문으로 렌더링돼 백틱과 파이프가 글자
|
||||
그대로 보인다. 그런 자료는 본문이 있는 종류에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
|
||||
## 필수 절차
|
||||
|
||||
@@ -37,17 +42,22 @@ Concept 에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
사람이 다시 읽지 않았다는 뜻이라, 그 위에 쓴 글은 과분류를 그대로 물려받는다. 계약 밖에서 쓴
|
||||
기록은 색인에 `unlisted` 로 남는다. 그 노드의 `ssot-assets`·`ssot-evidence` 도 함께 본다 —
|
||||
SSOT 가 이미 그린 그림과 이미 돌린 측정 가운데 이 글감에 배정된 것이 거기 적혀 있다.
|
||||
1. **종류 선택** — 위 표. 애매하면 "재현했나"를 묻는다.
|
||||
1. **종류 선택** — 위 표. 애매하면 「끝난 일을 적나, 남이 따라 할 절차를 적나」를 먼저 묻고,
|
||||
끝난 일이면 "재현했나"를 묻는다.
|
||||
2. **칸 채우기** — 칸과 게시 조건은 `references/record-kinds.md`.
|
||||
3. **본문 작성**(Case·Concept) — **종류마다 무엇을 어떤 순서로 쓰는지는
|
||||
`references/writing-each-kind.md`.** 문법은 `references/body-syntax.md`, 표·코드·그림은
|
||||
3. **본문 작성**(Case·Concept·Setup) — **종류마다 무엇을 어떤 순서로 쓰는지는
|
||||
`references/writing-each-kind.md`.**
|
||||
**Setup 은 본문을 쓰기 전에 `Skill` 도구로 `writing-practitioner-guides` 를 연다.** 명령을
|
||||
어떤 형태로 쓸지는 그 스킬이 정한다. 그다음이 `references/writing-each-kind.md` 의 Setup 절이다 —
|
||||
단계 하나의 모양과 이 저장소에서만 걸리는 셋이 거기 있다.
|
||||
문법은 `references/body-syntax.md`, 표·코드·그림은
|
||||
`references/code-tables-diagrams.md`. **문장은 `references/explaining.md`.**
|
||||
**문서군 전체의 리듬은 `references/ai-tells.md`.** 이 둘은 첫 초안부터 적용한다 — AI 티를
|
||||
남겨 두고 나중에 걷어내는 순서가 아니다. **문체를 손보기 전에 문장을 고른다** — 설명이 끝난
|
||||
뒤에 붙은 평가·예고·되풀이·독자 오해 가정을 먼저 뺀다(`ai-tells.md` 첫 절). 문체 규칙의
|
||||
정본은 `ai-tells.md` 다.
|
||||
**그림이 필요한지, 필요하면 무엇을 그릴지는 `references/choosing-a-diagram.md`.** 자리(Case·
|
||||
Concept 만) · 표가 아닌지 · 옆 문단이 이미 말하지 않았는지 세 관문을 지나야 그린다.
|
||||
**그림이 필요한지, 필요하면 무엇을 그릴지는 `references/choosing-a-diagram.md`.** 담을 곳
|
||||
(Case·Concept·Setup 만) · 표가 아닌지 · 옆 문단이 이미 말하지 않았는지 세 관문을 지나야 그린다.
|
||||
순서·경계·상태 전이처럼 문장만으로 따라가기 어려운 관계가 있으면 **`final/assets/` 에 그
|
||||
그림이 이미 있는지부터 본다.** SSOT 를 만들 때 그려 둔 것이 있고 계약의 `ssot-assets` 가 이
|
||||
글감에 배정해 두었으면 기록의 `assets` 가 **그 파일을 그대로** 가리킨다. 사본을 따로 만들지
|
||||
@@ -76,18 +86,20 @@ Concept 에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
|
||||
## 어느 스킬이 무엇을 하나
|
||||
|
||||
이 스킬이 첫 초안을 만든다. 나머지는 초안이 나온 뒤에 각각 다른 것을 고친다.
|
||||
이 스킬이 첫 초안을 만든다. `writing-practitioner-guides` 는 Setup 본문을 쓰는 동안 함께 열고,
|
||||
나머지는 초안이 나온 뒤에 각각 다른 것을 고친다.
|
||||
|
||||
| 스킬 | 하는 일 | 하지 않는 일 |
|
||||
|---|---|---|
|
||||
| `deriving-tech-log-root-tree` | 후보에 처분을 매기고 `PROMOTE` 를 글감으로 올린다 | 글을 쓰지 않는다 |
|
||||
| `writing-tech-log-records` | 종류를 고르고 칸과 본문을 쓴다. `explaining.md`·`ai-tells.md` 를 처음부터 적용한다 | — |
|
||||
| `writing-practitioner-guides` | 사람이 직접 치는 명령의 형태를 정한다 — 한 줄에 담는 계층, 도구 우선순위, 출력을 읽는 형태와 값 하나만 뽑는 형태, 넓은 명령에서 좁은 명령으로 | Tech Log 의 종류와 칸을 모른다. SSOT 대조도 색인 갱신도 하지 않는다 |
|
||||
| `technical-visualizer` | 문장으로 따라가기 어려운 관계를 그림으로 만든다 | 무엇을 그릴지 정하지 않는다 — `choosing-a-diagram.md` 가 정한다 |
|
||||
| `rewriting-technical-prose-naturally` | 사실과 구조가 이미 맞는 초안의 번역투·반복 문형·과한 대구를 고친다 | 분류가 틀렸거나 근거가 모자란 것은 못 고친다 |
|
||||
| `writing-as-the-person-who-did-it` | 자료에 남아 있는 선택·비교·어긋남·확인하지 못한 범위를 제자리에 놓는다 | 자료에 없는 「처음에는」·「고민 끝에」를 만들지 않는다 |
|
||||
|
||||
Reference·Question·Decision 은 그림을 렌더링할 자리가 없다. 그림이 필요한 내용은 짝이 되는
|
||||
Case 나 Concept 에 담고 `관계`로 가리킨다.
|
||||
Reference·Question·Decision 은 그림을 렌더링할 곳이 없다. 그림이 필요한 내용은 짝이 되는
|
||||
Case·Concept·Setup 에 담고 `관계`로 가리킨다.
|
||||
|
||||
## 보호 구간
|
||||
|
||||
@@ -113,7 +125,7 @@ SSOT 에 없는데 필요한 인용이라면 순서가 반대다 — `final/docu
|
||||
| 실패 | 대응 |
|
||||
|---|---|
|
||||
| Reference 규칙에 코드블록 | Case로 옮겨 관계 연결 |
|
||||
| 본문 밖 칸에 백틱 | 평문이다. 백틱을 뺀다 |
|
||||
| 본문 밖 칸에서 백틱을 뺌 | 이 칸도 백틱은 `<code>` 로 산다. 빼는 것은 별표·파이프·`#` 다 |
|
||||
| 평문 칸에 쉼표 나열 | `이름 : 값`으로 줄 나눔. 있음·없음은 `o`·`x` |
|
||||
| 표 머리글이 명사뿐 | 질문으로. 비교 축은 `전`·`후` 시점으로 |
|
||||
| 그림 안에 문장·숫자 | `<text>`는 이름만. 문장은 `<desc>`·옆 문단에 |
|
||||
@@ -130,6 +142,11 @@ SSOT 에 없는데 필요한 인용이라면 순서가 반대다 — `final/docu
|
||||
| SSOT 에 있는 그림을 두고 새로 그림 | `final/assets/` 를 먼저 본다. `ssot-assets` 가 배정한 것을 쓴다 |
|
||||
| SSOT 에 있는 측정을 두고 다시 돌림 | `final/evidence/` 의 원문을 가리킨다 |
|
||||
| Decision에 근거 없음 | 관계 1개 이상 연결 |
|
||||
| Setup에 끝난 일을 적음 | 따라 할 순서가 아니면 Case다 |
|
||||
| Setup 본문에 `printf >`·`echo >>`·`python3 -c` | 사람이 치는 형태로 바꾼다. 설정 파일은 에디터로 연다 |
|
||||
| Setup 본문의 명령만 고치고 SSOT 를 그대로 둠 | `final/document.md` 를 먼저 고친다. `check_evidence.mjs` 가 막는다 |
|
||||
| Setup 본문에 「2026-09-12 기준」 | 검증일 칸이 없다. 버전은 `pinnedVersions` 에 적는다 |
|
||||
| Setup에 프로젝트를 안 고름 | 「환경 구성은 프로젝트에 속합니다」로 막힌다 |
|
||||
| 측정 안 한 검증일 | 비워 둔다 |
|
||||
| 설명 직후에 「~증거다」「~가 아니다」로 평가 | 지운다. 앞 문장이 사실을 말했으면 거기서 끝낸다 |
|
||||
| 「~로 읽기 쉽다. 그렇지 않다」 | 오해를 지어내지 않는다. 관측부터 적는다 |
|
||||
|
||||
@@ -93,5 +93,24 @@ mailto:…
|
||||
| `list items must contain exactly one paragraph` | 목록을 중첩했다 |
|
||||
| `unsafe link URL: …` | 허용되지 않는 스킴이나 `//` 주소 |
|
||||
| `duplicate explicit ID: …` | 같은 id를 두 번 썼다 |
|
||||
| `unknown inline directive: 27` | **문단에 시각을 그냥 썼다** — 아래를 본다 |
|
||||
|
||||
거절 메시지에는 줄·칸 번호가 붙는다. 그 줄을 먼저 본다.
|
||||
|
||||
## 문단 안의 시각은 백틱으로 감싼다
|
||||
|
||||
`:` 뒤에 글자가 붙으면 파서가 인라인 directive 로 읽는다. 그래서 문단에 `08:20:27` 을
|
||||
그냥 쓰면 `:27` 에서 막힌다. `00/12:00:00` 같은 설정값도 같다.
|
||||
|
||||
```text
|
||||
새 인증서가 08:20:27 에 기록됐다. ← FAIL unknown inline directive: 27
|
||||
새 인증서가 `08:20:27` 에 기록됐다. ← PASS
|
||||
```
|
||||
|
||||
**이것이 조용한 결함이 되는 경로가 있다.** 막히면 시각을 빼고 넘어가게 되고, 그러면
|
||||
검사기는 통과하는데 **기록에서 수치가 사라진다.** 실제로 한 회차에서 두 편이 각각
|
||||
시각 둘과 타이머 설정값을 빼고 통과시켰다. 수치·날짜·시각은 보호 구간이다 —
|
||||
**빼지 말고 감싼다.**
|
||||
|
||||
표와 코드블록 안은 걸리지 않는다. 그래서 같은 프로젝트의 다른 기록이 통과하는 것이
|
||||
「이 문법이 괜찮다」는 뜻이 아니다 — 그쪽은 시각이 전부 표 안에 있었을 뿐이다.
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
### 1. 자리가 있는가
|
||||
|
||||
`assets` 는 본문이 있는 두 종류만 갖는다 — **Case 와 Concept**. Reference·Question·Decision 의
|
||||
칸은 평문으로 렌더링돼 그림이 들어갈 자리가 없다.
|
||||
`assets` 는 본문이 있는 세 종류만 갖는다 — **Case·Concept·Setup**. Reference·Question·Decision 의
|
||||
칸은 평문으로 렌더링돼 그림이 들어갈 곳이 없다.
|
||||
|
||||
파생 기록에 그림이 필요해 보이면 그 그림은 **짝이 되는 Case 나 Concept 의 것**이다. 거기 담고
|
||||
`관계`로 가리킨다. 담을 Case 나 Concept 이 없으면 그 그림은 아직 집이 없다 — 계약의
|
||||
파생 기록에 그림이 필요해 보이면 그 그림은 **짝이 되는 Case·Concept·Setup 의 것**이다. 거기 담고
|
||||
`관계`로 가리킨다. 담을 기록이 없으면 그 그림은 아직 집이 없다 — 계약의
|
||||
`assetLedger.unassigned` 에 그렇게 적고, 새 글감을 세울지는 따로 판단한다.
|
||||
|
||||
### 2. 표가 아닌가
|
||||
@@ -40,11 +40,17 @@
|
||||
| **Case** | 이 요청 한 번이 어떤 순서로 무엇을 지나갔나 | `sequence` · `component-flow` |
|
||||
| **Case** (경계가 논지일 때) | 무엇이 어느 경계 안에 있고 무엇이 밖에 있나 | `two-zone-pipeline` |
|
||||
| **Concept** | 남의 것이 어떤 순서·구조로 동작하나 | `sequence` · `component-flow` · `ports-adapters` |
|
||||
| **Setup** | (아직 못 적는다 — 아래) | — |
|
||||
|
||||
Case 는 **내가 돌려서 본 것**이라 대개 순서가 논지다. Concept 은 **남의 것이 어떻게 동작하는지**라
|
||||
구조나 변환 사슬이 논지다. 어느 쪽이든 「무엇이 무엇으로 바뀌는가」를 못 적으면 아직 그릴 것이
|
||||
없다는 뜻이다.
|
||||
|
||||
**Setup 은 담을 곳만 있고 본보기가 없다.** 본문 파서가 Case 와 같아서 그림이 렌더링되기는 한다.
|
||||
다만 2026-09-12 에 Studio 의 환경 구성 문서가 0건이라, 위 두 줄처럼 「반복된 물음」을 셀 자료가
|
||||
없다. 이 줄은 그림을 그리지 말라는 뜻이 아니라 **아직 아무도 안 그려 봤다**는 뜻이다. 그릴 때는
|
||||
세 관문만 그대로 지나고, 무엇을 그렸는지 이 표에 적어 둔다.
|
||||
|
||||
**한 절에 그림 하나.** 같은 절에 구조 그림과 흐름 그림을 둘 다 넣으면 독자가 어느 쪽을 먼저
|
||||
읽어야 하는지 알 수 없다. 둘 다 필요하면 절을 나눈다.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 코드·표·다이어그램·이미지
|
||||
|
||||
본문(`bodyMarkdown`)에만 해당한다. 본문이 있는 종류는 Case 와 Concept 이다.
|
||||
본문(`bodyMarkdown`)에만 해당한다. 본문이 있는 종류는 Case·Concept·Setup 셋이다.
|
||||
|
||||
## 코드블록
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ Reference의 칸도 반쯤만 맞는 기록이 나온다. 종류를 먼저 정
|
||||
|---|---|---|
|
||||
| 재현한 관측 하나 | 조건을 갖추고 돌려서 얻은 수치·실행계획·로그 | Case |
|
||||
| 남의 것의 동작 하나 | 문서와 코드를 읽어 정리한 규칙·흐름 | Concept |
|
||||
| 남이 따라 할 절차 하나 | 그 환경을 처음 세우는 명령과 구성 값 | Setup |
|
||||
| 반복 적용할 기준 하나 | 다음에도 같게 하기로 한 규칙 | Reference |
|
||||
| 프로젝트가 고른 방향 하나 | 대안을 두고 정한 것 | Decision |
|
||||
| 닫히지 않은 판단 하나 | 아직 모르는 것과 무엇을 하면 닫히는지 | Question |
|
||||
@@ -107,6 +108,8 @@ Reference의 칸도 반쯤만 맞는 기록이 나온다. 종류를 먼저 정
|
||||
|
||||
순서대로 묻는다. 먼저 걸리는 것이 그 글감의 종류다.
|
||||
|
||||
0. **남이 자기 기계에서 따라 할 절차인가** → Setup. 나머지 다섯은 끝난 일을 적고 이것만 실행할
|
||||
순서를 적는다. 명령과 구성 값이 SSOT에 있어야 하고, 프로젝트를 반드시 고른다
|
||||
1. **내가 돌려서 얻은 결과인가** → Case. 수치·실행계획·로그가 SSOT에 있어야 한다. 없으면 Case가 아니다
|
||||
2. **남의 것이 어떻게 동작하는지인가** → Concept. 무엇을 보고 썼는지(버전)가 있어야 한다
|
||||
3. **다음에도 같게 하기로 한 규칙인가** → Reference
|
||||
@@ -131,7 +134,7 @@ Reference 하나로 나누고 `관계`로 잇는다.
|
||||
미리 알아야 하는 구조가 있을 때만 Concept을 만든다. 메커니즘처럼 보이는 절을 훑어 채우면 어느
|
||||
기록도 필요로 하지 않는 개념이 쌓인다.
|
||||
|
||||
**주제 하나에 독자 질문 하나.** Studio는 주제 아래에 다섯 종류를 나눠 보여 준다. 그 주제의
|
||||
**주제 하나에 독자 질문 하나.** Studio는 주제 아래에 여섯 종류를 나눠 보여 준다. 그 주제의
|
||||
기록들이 함께 답하는 물음을 한 줄로 적고, 그 물음에 답하지 않는 글감은 다른 주제로 옮긴다.
|
||||
주제 slug는 Studio의 것을 그대로 쓴다.
|
||||
|
||||
@@ -157,7 +160,7 @@ Reference 하나로 나누고 `관계`로 잇는다.
|
||||
"status": "게시 전", "studioId": "32d0be7d-…", "assets": 1, "evidence": 3 },
|
||||
{ "title": "아직 쓰지 않은 글감" }
|
||||
],
|
||||
"concept": [], "reference": [], "question": [], "decision": []
|
||||
"concept": [], "setup": [], "reference": [], "question": [], "decision": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
# 다섯 종류의 칸과 게시 조건
|
||||
# 여섯 종류의 칸과 게시 조건
|
||||
|
||||
칸 이름은 Studio 편집 화면 그대로, 상한과 필수 여부는 `studio-api.openapi.yaml` 그대로다.
|
||||
계약 필드명을 괄호에 적는다. 계약은 `tech-log-frontend/src/features/tech-log/contracts/studio/`
|
||||
에 있고, 이 파일과 계약이 어긋나면 계약이 맞다.
|
||||
|
||||
`RecordKind` 는 다섯이다 — `CASE` · `REFERENCE` · `QUESTION` · `CONCEPT` · `PROJECT_DECISION`.
|
||||
`RecordKind` 는 여섯이다 — `CASE` · `REFERENCE` · `QUESTION` · `PROJECT_DECISION` · `CONCEPT` ·
|
||||
`SETUP` (`studio-api.openapi.yaml:838-840`).
|
||||
|
||||
**이 목록을 손으로 옮길 때마다 종류가 빠졌다.** 이 문서도 한동안 「다섯이다」라고 적고 `SETUP`
|
||||
을 뺐다. 프론트엔드에서 먼저 같은 일이 났고 소스에 적혀 있다
|
||||
(`application/ports/studio-gateway.ts:8-12`).
|
||||
|
||||
> 종류는 계약의 `RecordKind` 를 그대로 쓴다. 여기 손으로 적어 두었던 동안 개념과 환경 구성이
|
||||
> 빠져 있었고, 작업본 목록의 종류 필터는 그 둘을 아예 고를 수 없었다 — 손으로 나열한 목록에
|
||||
> 새 종류를 빠뜨리는 일이 이 저장소에서 반복됐다.
|
||||
|
||||
일곱 번째가 생기면 같은 일이 난다. 이 문서를 고칠 때는 기억으로 세지 말고
|
||||
`studio-api.openapi.yaml` 의 `RecordKind` 를 열어 몇 줄인지부터 센다.
|
||||
|
||||
## 종류별 칸 (`새 문서` 화면이 적어 주는 그대로)
|
||||
|
||||
| 화면 이름 | 계약 `kind` | 그 종류만의 칸 |
|
||||
|---|---|---|
|
||||
| Case | `CASE` | 문제 · 결론 · 환경 · 재현 · 본문 |
|
||||
| Reference | `REFERENCE` | 목적 · 규칙 · 적용 조건 · 예외 · 예시 |
|
||||
| **개념** | `CONCEPT` | 기준 버전 · 본문 |
|
||||
| Question | `QUESTION` | 상태 · 사실 · 가정 · 미지수 · 선택지 |
|
||||
| Decision | `PROJECT_DECISION` | 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
|
||||
| **검증 기록** (Case) | `CASE` | 문제 · 결론 · 환경 · 재현 · 본문 |
|
||||
| **적용 기준** (Reference) | `REFERENCE` | 목적 · 규칙 · 적용 조건 · 예외 · 예시 |
|
||||
| **동작 원리** (Concept) | `CONCEPT` | 기준 버전 · 본문 |
|
||||
| **환경 구성** (Setup) | `SETUP` | 버전 · 본문 |
|
||||
| **열린 질문** (Question) | `QUESTION` | 상태 · 사실 · 가정 · 미지수 · 선택지 |
|
||||
| **설계 결정** (Decision) | `PROJECT_DECISION` | 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
|
||||
|
||||
**화면 이름은 여섯 다 한글이다.** 2026-09-12 에 `/studio/documents/new` 에서 읽었고 작업본
|
||||
목록(`/studio/documents`)의 종류 필터도 같은 여섯 이름을 쓴다. 이 문서의 절 제목과 산문은
|
||||
괄호 안의 이름을 쓴다 — 폴더 이름과 frontmatter 의 `kind` 가 그쪽이기 때문이다.
|
||||
|
||||
여기에 아래 공통 칸이 더해진다.
|
||||
|
||||
## 공통 (다섯 종류 모두 — `WorkingCopyInputBase`)
|
||||
## 공통 (여섯 종류 모두 — `WorkingCopyInputBase`)
|
||||
|
||||
| 칸 | 필드 | 상한 | 게시 조건 |
|
||||
|---|---|---|---|
|
||||
@@ -27,7 +44,7 @@
|
||||
| 요약 | `summary` | **2,000자** | 경고. 목록 카드에는 약 90자까지 보인다 |
|
||||
| Topic | `topicId` | — | 경고 |
|
||||
| 축 | `variantIds` | 8개 | 선택. 고르지 않으면 그 주제의 **공통 기록**이 된다 |
|
||||
| Project | `projectId` | — | `PROJECT_DECISION`은 게시 시 필수 |
|
||||
| Project | `projectId` | — | `PROJECT_DECISION`과 `SETUP`은 필수 |
|
||||
| 관계 | `relations` | 20개 | `PROJECT_DECISION`은 **1개 이상 필수**. 그 종류에서는 절 이름이 「근거」다 |
|
||||
|
||||
편집 화면에서 축은 체크박스 묶음으로 나오고 묶음 이름이 「축 — 고르지 않으면 이 주제의 공통
|
||||
@@ -67,8 +84,8 @@ evidence:
|
||||
- ../../../final/evidence/explain/highlights-child-plan-A.txt
|
||||
```
|
||||
|
||||
**`assets` 는 본문이 있는 Case 와 Concept 에만 둔다.** 나머지 세 종류는 칸이 평문으로
|
||||
렌더링돼 그림을 표시할 자리가 없다. 그림이 필요한 내용은 Case 나 Concept 에 담고 `관계`로
|
||||
**`assets` 는 본문이 있는 Case·Concept·Setup 에만 둔다.** 나머지 세 종류는 칸이 평문으로
|
||||
렌더링돼 그림을 표시할 곳이 없다. 그림이 필요한 내용은 본문이 있는 종류에 담고 `관계`로
|
||||
가리킨다.
|
||||
|
||||
`assets` 는 Studio 에 올릴 파일이다. 아직 안 올렸으면 key 가 파일 이름과 같고, 올린 뒤에는
|
||||
@@ -80,13 +97,35 @@ evidence:
|
||||
|
||||
## 평문 칸 쓰는 법
|
||||
|
||||
본문(`bodyMarkdown`)을 뺀 모든 칸은 평문이다. 백틱·파이프·`#`는 글자 그대로 보이고 줄바꿈만
|
||||
살아난다. 그 줄바꿈이 유일한 서식이므로 아끼지 않는다.
|
||||
본문(`bodyMarkdown`)을 뺀 모든 칸은 **마크다운 블록 파서를 거치지 않는다.** 그렇다고 전부
|
||||
글자 그대로 나오는 것은 아니다. 렌더러가 이 칸들만 따로 그리고(`tech-log-frontend` 의
|
||||
`presentation/shared/public-render/prose-text.tsx`), 거기서 셋이 살아난다.
|
||||
|
||||
**무엇을 지우나.** 백틱·별표·코드펜스는 글자 그대로 보인다. 식별자는 남기고 표시 문자만 뺀다.
|
||||
`InboxCleanupJob:56` 은 InboxCleanupJob:56 으로, `**this is the parameter**` 는 그 문장만 남긴다.
|
||||
코드펜스 안이 `이름 : 값` 꼴이면 펜스 줄만 지우면 그대로 읽힌다. 줄바꿈이 유일한 서식이므로
|
||||
문단 사이 빈 줄은 지킨다.
|
||||
| 이 칸에서 | 어떻게 되나 |
|
||||
|---|---|
|
||||
| 백틱 쌍 | 인라인 `<code>` 로 **살아난다.** 빼지 않는다 |
|
||||
| 백틱이 홀수 개 | 짝이 안 맞으므로 원문 그대로 둔다 — 반쯤 해석하지 않는다 |
|
||||
| 빈 줄 | 문단이 갈린다 |
|
||||
| 한 줄 바꿈 | `<br>` 로 그 자리에 남는다 |
|
||||
| 별표·파이프·`#`·코드펜스·인용 표지 `>` | **글자 그대로 보인다.** 이것들만 뺀다 |
|
||||
|
||||
**옛 판을 기억하지 마라.** 이 칸들은 오래 진짜 평문으로 나갔고 백틱이 백틱째 화면에
|
||||
나왔다 — 어떤 Reference 는 한 문서에 백틱이 32개였고 그 원문이 카드와 검색 결과까지
|
||||
퍼졌다. 그건 **고쳐진 버그**다. 지금 백틱을 빼면 식별자가 본문과 같은 민무늬로 나온다.
|
||||
|
||||
**무엇을 지우나.** 별표·코드펜스·`>` 는 글자 그대로 보인다. 식별자는 남기고 표시 문자만 뺀다.
|
||||
`**this is the parameter**` 는 그 문장만 남긴다. 코드펜스 안이 `이름 : 값` 꼴이면 펜스 줄만
|
||||
지우면 그대로 읽힌다. 백틱은 그대로 두고, 문단 사이 빈 줄도 지킨다.
|
||||
|
||||
**SSOT 를 그대로 옮긴 인용도 `>` 를 못 쓴다.** 인용이라는 것을 표지로 나타낼 방법이 이 칸에는
|
||||
없다 — `>` 도, 들여쓰기도 안 산다. 표지를 빼고 한 문단으로 두거나, 인용이 꼭 인용으로 보여야
|
||||
하면 본문이 있는 종류로 옮긴다. 「」 를 새로 씌우지 않는다. 옮긴 글자는 보호 구간이라 그대로다.
|
||||
|
||||
**코드펜스를 뗄 때 언어 표시 줄을 같이 지운다.** ` ```text ` 에서 펜스만 지우면 `text` 한 줄이
|
||||
남고, 그 낱말이 화면에 그대로 나온다. 실제로 한 기록에서 그렇게 남아 있었다.
|
||||
|
||||
**칸이 어떻게 보이는지는 렌더러가 정본이다.** 이 파일이 아니다. 여기 적힌 것과 화면이
|
||||
다르면 `prose-text.tsx` 와 `public-record-renderer.tsx` 를 열어서 가른다.
|
||||
|
||||
관계(`근거`) 절은 평문 칸이 아니다. 기록을 거는 목록이라 `- **제목**` 표기를 그대로 둔다.
|
||||
|
||||
@@ -116,25 +155,26 @@ issuer · audience : 검증
|
||||
| 검증 환경 | `environment` | 런타임·버전·DB·도구 |
|
||||
| 재현 조건 | `reproduction` | 다른 사람이 같은 결과를 얻는 방법 |
|
||||
| 마지막 검증일 | `lastVerifiedOn` | 실제로 확인한 날. 30일 지나면 경고 |
|
||||
| 본문 Markdown | `bodyMarkdown` | 10만 자. 본문을 갖는 두 종류 중 하나 |
|
||||
| 본문 Markdown | `bodyMarkdown` | 10만 자. 본문을 갖는 세 종류 중 하나 |
|
||||
|
||||
공개 화면에서 `검증 환경`과 `재현 조건`은 `environmentSummary` 배열에 그 순서로 실린다.
|
||||
|
||||
## Concept — 남의 것이 어떻게 동작하는지
|
||||
|
||||
`새 문서` 화면에서 이 종류만 이름이 한글이다. **「개념」을 고른다.** 나머지 넷은 Case·Reference·
|
||||
Question·Decision 으로 적혀 있다. 화면 설명은 「개념의 내부 동작과 아키텍처를 처음부터 풀어 씁니다」다.
|
||||
`새 문서` 화면에서 **「동작 원리」를 고른다.** 화면 설명은 「개념의 내부 동작과 아키텍처를 처음부터
|
||||
풀어 씁니다.」다. 전에 이 절은 「이 종류만 이름이 한글이다」라고 적었는데 2026-09-12 에는 여섯 다
|
||||
한글이었다.
|
||||
|
||||
| 칸 | 필드 | 비고 |
|
||||
|---|---|---|
|
||||
| 본문 Markdown | `bodyMarkdown` | 10만 자. **Case 와 함께 본문을 갖는 두 종류 중 하나** |
|
||||
| 본문 Markdown | `bodyMarkdown` | 10만 자. **Case·Setup 과 함께 본문을 갖는 세 종류 중 하나** |
|
||||
| 기준 버전 | `basisVersion` | 120자. 무엇을 보고 쓴 글인지 한 줄 |
|
||||
|
||||
칸이 둘뿐이다. 문제도 결론도 재현 조건도 없다. 내가 재현한 결과가 아니라 **이미 그렇게 동작하는
|
||||
것**을 적기 때문이다. `Authorization Code Flow 가 code 를 한 번 더 교환하는 이유`, `Forward-Auth
|
||||
subrequest 가 실어 보내는 것` 같은 것이 여기 온다.
|
||||
|
||||
**`lastVerifiedOn` 이 없고 `basisVersion` 이 그 자리를 대신한다.** 개념은 날짜로 낡지 않고 버전으로
|
||||
**`lastVerifiedOn` 이 없고 `basisVersion` 이 낡음을 말한다.** 개념은 날짜로 낡지 않고 버전으로
|
||||
낡기 때문이다. `Keycloak 26.7.0 identity brokering`, `Kubernetes 1.31` 처럼 적는다. 비워도 게시된다.
|
||||
|
||||
공개 주소는 `/concepts/{slug}` 다.
|
||||
@@ -143,8 +183,7 @@ subrequest 가 실어 보내는 것` 같은 것이 여기 온다.
|
||||
검증도 막지 않는다(2026-09-04 에 임시 개념 기록을 만들어 비운 채로 게시해 확인했다). 그래도
|
||||
채우는 편이 낫다 — 개념이 언제 낡았는지 읽는 사람이 알 방법이 이 칸뿐이다.
|
||||
|
||||
편집 화면 오른쪽 `작업 상태` 는 이 종류를 「동작 원리」라고 부른다. `새 문서` 의 「개념」과 같은
|
||||
것이다.
|
||||
편집 화면 오른쪽 `작업 상태` 도 이 종류를 「동작 원리」라고 부른다.
|
||||
|
||||
Case 와 헷갈리면 **내가 무엇을 했는지**를 묻는다. 내가 돌려 보고 수치를 얻었으면 Case, 남의 문서와
|
||||
코드를 읽고 동작을 정리했으면 Concept 이다.
|
||||
@@ -169,6 +208,87 @@ Concept 이 되는 것은 이런 것들이다 — `Spring 조립의 세 경로`,
|
||||
시점`, `커밋 증거 상태 전이`, `fenced lease 와 CAS`, `gRPC flow control 과 backpressure`.
|
||||
여러 Case 가 같은 선수 지식을 요구할 때 그것을 한 번만 설명하려고 만드는 자리다.
|
||||
|
||||
## Setup — 남이 따라 할 절차 (`SETUP`)
|
||||
|
||||
화면 이름은 「환경 구성」이고 설명은 「이 기록을 자기 손으로 재현하는 절차를 남깁니다.」다.
|
||||
편집 화면은 구역 둘로 나뉜다 — 「기본 정보」와 「환경 구성」(eyebrow `SETUP`).
|
||||
|
||||
| 화면 이름 | 필드 | 상한·모양 |
|
||||
|---|---|---|
|
||||
| 고정한 버전 | `pinnedVersions` | 배열 30개. 줄마다 `이름`(1~60자) + `버전`(1~40자) 입력 둘. 「버전 추가」 버튼으로 늘린다 |
|
||||
| 절차 Markdown | `bodyMarkdown` | 10만 자 |
|
||||
|
||||
화면에 붙은 도움말을 그대로 옮기면 이렇다.
|
||||
|
||||
- 고정한 버전 : `“Keycloak” / “26.7.0” 처럼 적습니다. 비우면 화면에 표를 그리지 않습니다.`
|
||||
- 절차 Markdown : `“##” 소제목이 목차가 됩니다. 명령은 코드블록으로 적어야 그대로 복사됩니다.`
|
||||
|
||||
`SetupInput.required` 는 `[kind, bodyMarkdown, pinnedVersions]` 다.
|
||||
|
||||
**작업본을 만들면 본문이 비어 있지 않다.** Studio 가 절 뼈대를 미리 넣어 준다.
|
||||
|
||||
```text
|
||||
## 실행 절차
|
||||
|
||||
## 구성 값
|
||||
|
||||
## 확인 방법
|
||||
```
|
||||
|
||||
계약의 `bodyMarkdown` 설명은 「실행 절차·구성 값·확인 방법을 `##` 절로 적는다. **절 이름을
|
||||
강제하지 않는다 — 프로젝트마다 셋업의 모양이 다르다.**」다. 뼈대는 출발점이고, 절 이름은 그
|
||||
프로젝트가 쓰는 말로 바꿔도 저장과 게시가 막히지 않는다.
|
||||
|
||||
### 왜 이 종류가 따로 있나
|
||||
|
||||
다른 다섯은 끝난 일을 적고 환경 구성만 남이 따라 할 절차를 적는다. 편집 화면 주석
|
||||
(`presentation/studio/components/setup-fields.tsx:44-52`)이 그 차이를 적어 두었다.
|
||||
|
||||
> 다른 다섯 종류는 끝난 일을 적는다. 이 종류만 읽는 사람이 그대로 따라 하는 절차를 적으므로,
|
||||
> 본문에 명령과 표가 들어간다 — Case 의 「검증 환경」 같은 평문 한 칸으로는 담기지 않는다.
|
||||
> … 버전만 본문 밖에 둔다. 이 절차가 어느 버전 위에서 성립했는지는 그 기록의 유효 범위이고,
|
||||
> 목록과 머리말이 본문을 열지 않고 보여 줘야 하는 값이기 때문이다.
|
||||
|
||||
그래서 칸이 둘뿐인데도 Concept 과 다르게 쓴다. 명령·표·그림은 본문에 넣고 버전만 본문 밖에
|
||||
남긴다. 개념의 「기준 버전」도 같은 이유로 본문 밖에 있고, 다른 점은 셋업의 버전이 여럿이라는
|
||||
데 있다.
|
||||
|
||||
**검증일 칸이 없다.** Case 의 `lastVerifiedOn` 도 Reference 의 `verifiedOn` 도 이 종류에는 없다.
|
||||
공개 계약의 `SetupDetailResponse` 가 왜인지 적는다.
|
||||
|
||||
> 환경 구성은 끝난 일이 아니라 따라 하는 절차다. 낡음은 검증일이 아니라
|
||||
> `pinnedVersions` 가 말한다 — 어느 버전 위에서 이 절차가 성립했는지가 유효 범위다.
|
||||
> 주제는 없을 수 있다. 주제 없는 셋업은 그 프로젝트의 공통 구성이다.
|
||||
|
||||
### 프로젝트는 필수, 주제는 선택
|
||||
|
||||
`PROJECT_DECISION` 말고 프로젝트를 요구하는 종류가 하나 더 있다.
|
||||
|
||||
```text
|
||||
if (input.kind === "SETUP" && !project) fail("환경 구성은 프로젝트에 속합니다. 기본 정보에서 프로젝트를 골라 주세요.");
|
||||
```
|
||||
|
||||
`domain/content-format/project-public-render-model.ts:245` 다.
|
||||
|
||||
주제는 비워도 된다. 비우면 그 프로젝트의 공통 구성으로 읽힌다. 다만 2026-09-12 에 빈 초안의
|
||||
미리보기는 `1:1 TOPIC catalog entry is required` 로 막혔다 — 미리보기를 보려면 Topic 을 고른다.
|
||||
|
||||
### 본문 파서와 공개 주소
|
||||
|
||||
본문 파서는 Case 와 같다(`presentation/public/components/setup-document-page.tsx:11`).
|
||||
|
||||
> 환경 구성의 본문도 Case 와 같은 파서를 탄다 — `##` 소제목이 목차가 되고 `:::evidence` 가…
|
||||
|
||||
그래서 코드블록·표·다이어그램·이미지를 쓸 수 있다.
|
||||
|
||||
- 공개 상세 : `/setups/{slug}` (`contracts/tech-log-route-contract.ts:31`, 라우트 제목 「환경 구성」)
|
||||
- 공개 목록 : `/explore/setups` — 설명 「이 기록을 자기 손으로 재현하는 절차를 남깁니다.」
|
||||
(`presentation/public/pages/explore-kind-page.tsx:17`)
|
||||
|
||||
**Studio 에 환경 구성 문서는 아직 0건이다.** 2026-09-12 에 `/studio/documents?kind=SETUP` 이
|
||||
「0개 중 0개 표시 중」이었다. 종류는 있는데 한 번도 쓰이지 않았다. 위의 칸 설명은 계약과 편집
|
||||
화면에서 읽었고, 올라간 기록에서 확인하지 않았다.
|
||||
|
||||
## Reference — 반복 적용할 기준
|
||||
|
||||
| 칸 | 필드 | 비고 |
|
||||
@@ -217,6 +337,10 @@ Concept 이 되는 것은 이런 것들이다 — `Spring 조립의 세 경로`,
|
||||
## 종류 고르기
|
||||
|
||||
```text
|
||||
남이 그대로 따라 할 절차를 적나 ── 예 ──→ Setup
|
||||
│
|
||||
아니오 (끝난 일을 적는다)
|
||||
│
|
||||
내가 직접 돌려 보고 결과를 얻었나 ── 예 ──→ Case
|
||||
│
|
||||
아니오
|
||||
@@ -236,6 +360,11 @@ Concept 이 되는 것은 이런 것들이다 — `Spring 조립의 세 경로`,
|
||||
└──→ Reference
|
||||
```
|
||||
|
||||
첫 갈래가 「끝난 일을 적나, 남이 따라 할 절차를 적나」다. 나머지 다섯은 이미 끝난 일을 적고,
|
||||
Setup 만 읽는 사람이 자기 기계에서 실행할 순서를 적는다. 편집 화면 주석이 그 경계를 「Case 의
|
||||
「검증 환경」 같은 평문 한 칸으로는 담기지 않는다」로 적는다 — 명령이 여러 줄이고 그대로
|
||||
복사돼야 하면 Case 의 평문 칸이 아니라 Setup 의 본문에 들어간다.
|
||||
|
||||
Case 와 Concept 이 가장 자주 헷갈린다. **수치와 재현 조건이 있으면 Case**, 읽고 정리한 동작이면
|
||||
Concept 이다. Concept 과 Reference 는 「무엇이 그런가」와 「우리가 어떻게 할 것인가」로 갈린다 —
|
||||
`PKCE 가 code 가로채기를 막는 방식`은 Concept, `우리는 public client 에 PKCE 를 필수로 쓴다`는
|
||||
|
||||
@@ -27,6 +27,25 @@
|
||||
- [ ] Decision에 근거 기록이 1개 이상 연결됐다
|
||||
- [ ] Decision의 영향에 감수한 비용이 있다
|
||||
- [ ] Reference의 예시가 문장이다. 코드를 밀어 넣지 않았다
|
||||
- [ ] Setup이 끝난 일이 아니라 남이 따라 할 순서를 적었다
|
||||
- [ ] Setup의 명령이 코드블록에 있다. 산문에 섞지 않았다
|
||||
- [ ] Setup의 `pinnedVersions`가 채워졌다. 본문에 날짜를 적어 검증일을 대신하지 않았다
|
||||
|
||||
## 환경 구성의 명령 (`writing-practitioner-guides`)
|
||||
|
||||
명령을 어떤 형태로 쓸지는 그 스킬이 정한다. 여기서는 그 결과가 본문에 남았는지만 센다.
|
||||
|
||||
- [ ] 사람이 내용을 읽고 고쳐야 하는 설정 파일을 에디터로 열게 했다. `printf >`·`echo >>`·`cat <<EOF` 로 만들지 않았다
|
||||
- [ ] 한 명령에 여러 작업이 겹치지 않았다. 접속·리다이렉션·권한·파일 생성을 한 줄에 묶지 않았다
|
||||
- [ ] 그 도메인의 전용 검증 명령이 있는데 `python3 -c` 로 대신하지 않았다
|
||||
- [ ] 다시 따라 해도 같은 줄이 또 붙지 않는다. 두 번 실행하면 늘어나는 명령이 없다
|
||||
- [ ] 조회·진단·실행은 운영자가 쓰는 CLI 를 그대로 썼다. `grep`·`virsh`·`systemctl` 을 에디터로 바꾸지 않았다
|
||||
- [ ] 단계마다 목적·행동·예상 결과·왜 필요한가·문제가 생기면이 같은 순서로 있다
|
||||
- [ ] 셸이 여럿이면 코드블록마다 `label` 로 어디서 치는지 적었다
|
||||
- [ ] 자리표시자가 없다. 값을 찾는 명령이 함께 있다
|
||||
- [ ] 앞 단계가 찍은 값을 옮겨 넣는 자리만 예외다. 그 자리는 `{{NAME}}` 으로 적었다. 한글로 감싸지도 `${NAME}` 으로 적지도 않았다
|
||||
- [ ] 비밀은 길이나 존재 여부까지만 확인하고 값을 찍지 않았다
|
||||
- [ ] 명령의 형태를 고쳤으면 SSOT 를 먼저 고쳤다. `check_evidence.mjs` 가 통과한다
|
||||
|
||||
## 설명
|
||||
|
||||
@@ -80,7 +99,7 @@
|
||||
- [ ] 테스트를 말할 때 무엇을 단정하는지 적었다
|
||||
- [ ] 어미·절·안내 문장의 수치는 참고만 했다. 맞추려고 문장을 넣지 않았다
|
||||
|
||||
## 본문 (Case)
|
||||
## 본문 (Case · Concept · Setup)
|
||||
|
||||
- [ ] raw HTML·각주·체크박스·중첩 목록·취소선이 없다
|
||||
- [ ] 표를 파이프로 썼다. 감쌌다면 세 속성이 다 있다
|
||||
@@ -99,7 +118,8 @@
|
||||
|
||||
## 평문 칸
|
||||
|
||||
- [ ] 본문 밖 칸에 백틱·파이프가 없다
|
||||
- [ ] 본문 밖 칸에 별표·파이프·`#`·코드펜스·인용 표지 `>` 가 없다 (**백틱은 괜찮다** — 인라인 `<code>` 로 산다)
|
||||
- [ ] 코드펜스를 뗀 자리에 언어 표시(` ```text ` 의 `text`)가 남지 않았다
|
||||
- [ ] 나열을 쉼표로 잇지 않고 `이름 : 값`으로 끊었다
|
||||
- [ ] 있음·없음을 `o`·`x`로 적었다
|
||||
- [ ] 한 문장이 화면에서 두 줄을 넘지 않는다
|
||||
@@ -107,7 +127,7 @@
|
||||
## 연결
|
||||
|
||||
- [ ] Topic이 지정됐다
|
||||
- [ ] Project가 필요하면 지정됐고, 그 Project에 slug가 있다
|
||||
- [ ] Project가 필요하면 지정됐고, 그 Project에 slug가 있다 (Decision과 Setup은 필수다)
|
||||
- [ ] 관계의 대상이 실제로 있는 공개 기록이다
|
||||
- [ ] 코드·표·그림이 필요한 내용을 Reference나 Question에 밀어 넣지 않았다
|
||||
- [ ] 본문이 없는 세 종류에 `assets`를 선언하지 않았다
|
||||
|
||||
@@ -73,7 +73,7 @@ section it names exists, and the diagram stage cannot translate the anchor into
|
||||
|
||||
## Topics
|
||||
|
||||
Each Topic has `topic` (its key), `title`, **`readerQuestion`**, and `kinds` with the five
|
||||
Each Topic has `topic` (its key), `title`, **`readerQuestion`**, and `kinds` with the six
|
||||
record kinds.
|
||||
|
||||
```json
|
||||
@@ -81,13 +81,13 @@ record kinds.
|
||||
"topic": "oauth-oidc-auth-boundary",
|
||||
"title": "OAuth 자격증명과 세션의 보관 경계",
|
||||
"readerQuestion": "자격증명과 세션을 누가 보관하고, 누가 API 요청을 만들며, 보호 자원은 무엇을 신뢰하는가?",
|
||||
"kinds": { "case": [], "concept": [], "reference": [], "question": [], "decision": [] }
|
||||
"kinds": { "case": [], "concept": [], "setup": [], "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.
|
||||
to fill all six.
|
||||
|
||||
## Candidates
|
||||
|
||||
@@ -178,6 +178,26 @@ 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.
|
||||
|
||||
### Setup
|
||||
|
||||
`slug` · `readiness` · `source` · `classification` · `pinned-versions` · `relations`.
|
||||
|
||||
Setup is the one kind that does not report a finished result. It is a procedure a reader
|
||||
runs on their own machine, so `pinned-versions` states the versions the procedure was
|
||||
established on — the same job `basis-version` does for a Concept, except there is more than
|
||||
one of them. There is no verification date for this kind.
|
||||
|
||||
A Setup node also needs a project. `SETUP` and `PROJECT_DECISION` are the two kinds Studio
|
||||
refuses to save without one; a Topic is optional, and a Setup with no Topic reads as that
|
||||
project's shared configuration.
|
||||
|
||||
`verify-tech-log-tree.py` checks this kind like the others: `REQUIRED_FIELDS["setup"]` names
|
||||
the six fields above and `GENERATABLE["setup"]` is `READY`. The kind list those tables are
|
||||
keyed on lives in `techlog.KINDS`, which also decides which `<topic>/<kind>/` folders
|
||||
`build-tech-log-tree.py` scans. Add a kind in one place and the tables that key off it go
|
||||
quiet rather than failing — a kind missing from `REQUIRED_FIELDS` is not an error, it is a
|
||||
node nobody asks anything of.
|
||||
|
||||
### Reference
|
||||
|
||||
`slug` · `readiness` · `source` · `classification` · `scope` · `exceptions` · `relations`.
|
||||
|
||||
@@ -7,18 +7,21 @@
|
||||
Question 4·Decision 2), n+1liner 24건(Case 5·Reference 7·Question 5·Decision 7).
|
||||
「대개 이렇게 쓴다」는 말은 그 47건이 그렇게 돼 있다는 뜻이다.
|
||||
|
||||
**Setup 은 그 47건에 없다.** 2026-09-12 에 Studio 의 환경 구성 문서가 0건이었다. 그래서 Setup
|
||||
절은 계약과 편집 화면에서 읽어 썼다. 나머지 다섯처럼 올라간 기록을 세지 않았다.
|
||||
|
||||
`clean-architecture-backend-template` 의 949건은 다른 절 이름으로 쓰여 있었고, 지금 이 기준으로
|
||||
다시 판정하는 중이다. 아직 기준을 만족한 상태가 아니므로 그 949건을 본보기로 삼지 않는다. 올라간
|
||||
적 없는 초안이 아니라 **올라간 것**이 기준이다.
|
||||
|
||||
## 파일 뼈대 — 다섯 종류가 같다
|
||||
## 파일 뼈대 — 여섯 종류가 같다
|
||||
|
||||
```markdown
|
||||
---
|
||||
id · kind · slug · title · topic · topicName · project · status · studio
|
||||
source · sourceRevision
|
||||
(종류별) basisVersion · decisionStatus · questionStatus · lastVerifiedOn
|
||||
(있으면) evidence · assets — assets 는 Case 와 Concept 만
|
||||
(종류별) basisVersion · decisionStatus · questionStatus · lastVerifiedOn · pinnedVersions
|
||||
(있으면) evidence · assets — assets 는 Case · Concept · Setup 만
|
||||
---
|
||||
|
||||
# 제목
|
||||
@@ -27,7 +30,7 @@ source · sourceRevision
|
||||
|
||||
## 관계 ← Decision 만 「근거」다
|
||||
## <칸 이름> ← 종류마다 다르다
|
||||
## 본문 ← Case · Concept 만
|
||||
## 본문 ← Case · Concept · Setup 만
|
||||
<!-- body:start -->
|
||||
...
|
||||
<!-- body:end -->
|
||||
@@ -110,6 +113,159 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
「확인했다」는 Case 의 말이다. Concept 은 「~한다」로 적는다. 규격이 정한 것과 그 구현이 그렇게 한
|
||||
것을 구분한다.
|
||||
|
||||
## Setup — 0건
|
||||
|
||||
칸은 `관계` · `본문` 둘뿐이고, 고정한 버전은 frontmatter 의 `pinnedVersions` 에 있다. 계약의
|
||||
`PinnedVersion` 은 `name` 과 `version` 을 나눠 담는다 — 이름 1~60자, 버전 1~40자, 30개까지.
|
||||
|
||||
```yaml
|
||||
pinnedVersions:
|
||||
- name: Keycloak
|
||||
version: 26.7.0
|
||||
```
|
||||
|
||||
**절 이름을 강제하지 않는다.** 계약이 그렇게 적는다 — 「실행 절차·구성 값·확인 방법을 `##`
|
||||
절로 적는다. 절 이름을 강제하지 않는다 — 프로젝트마다 셋업의 모양이 다르다.」 그래도 빈 본문에서
|
||||
시작하지는 않는다. 작업본을 만들면 Studio 가 절 셋을 미리 넣어 주므로 거기서 출발한다.
|
||||
|
||||
```markdown
|
||||
## 실행 절차
|
||||
|
||||
## 구성 값
|
||||
|
||||
## 확인 방법
|
||||
```
|
||||
|
||||
**명령은 코드블록으로 적는다.** 절차 Markdown 칸의 도움말이 이유를 적는다 —
|
||||
`“##” 소제목이 목차가 됩니다. 명령은 코드블록으로 적어야 그대로 복사됩니다.` 읽는 사람이
|
||||
자기 기계에서 실행하므로, 산문에 섞어 적으면 복사할 때 프롬프트 기호와 설명이 함께 붙는다.
|
||||
|
||||
Case 와 무엇이 다른지는 **읽는 사람이 무엇을 하는가**로 갈린다. Case 의 「재현 조건」은 내가 잰
|
||||
값을 남이 다시 얻는 순서이고, Setup 의 본문은 그 환경을 처음 세우는 절차다. Case 는 평문 한 칸에
|
||||
그 순서를 담지만 Setup 은 본문을 쓰므로 명령·표·그림이 들어간다.
|
||||
|
||||
**검증일을 쓰지 않는다.** 이 종류에는 그 칸이 없다. 절차가 어느 버전 위에서 성립했는지는
|
||||
`pinnedVersions` 가 말하므로, 본문에 「2026-09-12 기준」 같은 날짜를 적어 대신하지 않는다.
|
||||
|
||||
`project` 는 비울 수 없다. `topic` 은 비워도 되고, 비우면 그 프로젝트의 공통 구성으로 읽힌다.
|
||||
|
||||
### 본문을 쓰기 전에 `writing-practitioner-guides` 를 연다
|
||||
|
||||
`Skill` 도구로 `writing-practitioner-guides` 를 부른다. 명령을 어떤 형태로 쓸지는 그 스킬이
|
||||
정한다 — 한 줄에 어느 계층까지 담는지, 어떤 도구를 먼저 잡는지, 출력을 읽는 형태와 값 하나만
|
||||
뽑는 형태를 어떻게 가르는지, 넓은 명령에서 좁은 명령으로 내려가는 순서, 무엇이 보이면 멈추고
|
||||
다시 쓰는지가 거기 적혀 있다. 그 규칙을 이 문서로 옮겨 적지 않는다. 같은 규칙이 두 곳에 있으면
|
||||
한쪽만 고쳐지고 둘이 갈린다.
|
||||
|
||||
다른 다섯 종류에는 이 절차가 없다. 나머지는 끝난 일을 적으므로 명령이 나와도 그때 무엇을 쳤는지
|
||||
보여 주는 인용이고, 읽는 사람이 자기 기계에서 그것을 치지 않는다. 환경 구성은 읽는 사람이 그대로
|
||||
따라 치므로 명령의 형태가 내용의 일부다. `echo 'export ...' >> ~/.bashrc` 는 결과를 만들지만
|
||||
읽는 사람이 `~/.bashrc` 를 한 번도 열어 보지 못하고, 같은 가이드를 다시 따라 하면 같은 줄이
|
||||
하나 더 붙는다.
|
||||
|
||||
기계적으로 바꾸는 방향도 틀린다. 조회·진단·실행은 운영자가 쓰는 CLI 를 그대로 쓴다 —
|
||||
`grep`·`lsmod`·`virsh`·`systemctl`·`journalctl`·`kubectl` 이 들어갔다는 것 자체는 문제가
|
||||
아니다. 사람이 내용을 읽고 고쳐야 하는 설정 파일을 만드는 대목에서만 에디터로 연다.
|
||||
|
||||
### 단계 하나의 모양
|
||||
|
||||
`writing-practitioner-guides` 의 「Shape of one step」은 상태를 읽는 단계의 모양이다. 환경 구성의
|
||||
본문은 상태를 바꾸는 단계가 대부분이고, 그쪽은 칸이 다섯이다.
|
||||
|
||||
```text
|
||||
### N. <이 단계가 무엇을 만드는가>
|
||||
|
||||
목적 한 줄. 이 단계가 끝나면 무엇이 달라지나
|
||||
행동 번호를 매긴 명령. 한 번호에 한 가지 일
|
||||
예상 결과 그때 화면에 나오는 것
|
||||
왜 필요한가 건너뛰거나 어긋나면 무엇이 깨지나
|
||||
문제가 생기면 어느 명령부터 다시 보나
|
||||
```
|
||||
|
||||
번호는 행동을 세려고 매긴다. 한 번호가 접속과 파일 생성과 권한 설정을 함께 하면 읽는 사람은
|
||||
어디까지 왔는지 셀 수 없고, 실패해도 그 줄의 어느 대목에서 실패했는지 모른다. 다섯 칸이 단계마다
|
||||
같은 순서로 오면 처음 따라 하는 사람이 단계마다 같은 곳에서 같은 것을 찾는다.
|
||||
|
||||
채운 예는 `writing-practitioner-guides` 에 있다 — libvirt 연결 URI 를 `qemu:///system` 으로
|
||||
고정하는 단계다.
|
||||
|
||||
### 자리표시자
|
||||
|
||||
**원칙은 자리표시자를 두지 않는 것이다.** `writing-practitioner-guides` 의 「No placeholders」가
|
||||
이유를 적는다 — `<토큰>` 이라고 적어 두면 그 값을 어디서 가져오는지가 문서 밖으로 나간다. 값을
|
||||
뽑는 명령을 먼저 주는 것이 먼저다.
|
||||
|
||||
**예외는 하나다 — 같은 기록의 앞 단계가 화면에 찍은 값을 뒤 단계에 옮겨 넣을 때.** 세션 `sid` 는
|
||||
로그인할 때마다 새로 생기고 클라이언트 UUID 는 렐름을 만들 때 정해져서 읽는 사람의 실험대에서
|
||||
다르다. 앞 단계가 탐침 파드 안에서 돌았으면 그 셸의 변수가 뒤 단계의 셸에 없어서 변수로 넘길
|
||||
수도 없다. 값을 만드는 명령은 이미 같은 문서 안에 있으므로 전역 스킬의 빨간 깃발
|
||||
(`<placeholder>` with no command that produces it)에는 걸리지 않는다.
|
||||
|
||||
**그때 쓰는 꼴은 `{{NAME}}` 하나다.** `NAME` 은 대문자로 시작하고 대문자·숫자·밑줄만 쓴다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 읽은 값을 그대로 넣어 행을 찾는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select user_session_id, created_on, last_session_refresh from offline_user_session
|
||||
where offline_flag='0' and user_session_id='{{SID}}'"
|
||||
```
|
||||
|
||||
**`${SID}` 로 쓰지 않는다.** 같은 가이드들이 `$SID`·`$K0`·`$TOK`·`$PW` 를 진짜 셸 변수로 쓴다.
|
||||
자리표시자를 셸 변수 꼴로 적으면 읽는 사람이 「이 변수는 이미 셸에 있다」로 읽고 그대로 붙여
|
||||
넣는다. `{{ }}` 는 셸 문법이 아니라서 붙여 넣으면 반드시 틀리고, 틀린 자리가 화면에 보인다.
|
||||
|
||||
**한글로 감싸지 않는다.** `'<① 이 찍은 sid>'` 는 사람에게는 읽히지만 검사기가 그 줄을 흐름도로
|
||||
오인하기 쉽고, 무엇보다 자리표시자 바깥의 SQL 이 SSOT 와 갈려도 드러나지 않는다. 어느 단계가 그
|
||||
값을 찍었는지는 코드블록 바로 위 산문에 적는다 — 「sid 는 ③ 이 `SID=` 로 화면에 찍은 값을 옮겨
|
||||
넣는다」처럼.
|
||||
|
||||
**검사기는 그 자리만 와일드카드로 본다.** `{{SID}}` 는 따옴표도 공백도 넘지 않는 값 하나로
|
||||
열리고 나머지는 한 글자씩 SSOT 와 대조된다. 그래서 `user_session_id` 를 `user_session_idx` 로
|
||||
잘못 적으면 그대로 걸린다.
|
||||
|
||||
### 이 저장소에서만 걸리는 것 셋
|
||||
|
||||
전역 스킬은 Tech Log 의 검사기도 SSOT 도 모른다.
|
||||
|
||||
**① 명령의 형태를 고치려면 SSOT 를 먼저 고친다.** `check_evidence.mjs` 가 본문 코드블록의 줄을
|
||||
`final/document.md` 와 대조한다. `bash`·`yaml`·`nginx` 처럼 언어를 적은 펜스는 줄 단위로 보고,
|
||||
`text`·`txt`·`console`·`diff` 펜스와 언어를 안 적은 펜스는 그 안의 경로·URL·점 있는 식별자만
|
||||
본다. 그래서 `sudo nano /etc/letsencrypt/cloudflare.ini` 를 SSOT 에 없는 채로 넣으면 「인용한
|
||||
코드가 SSOT 에 없다」로 막힌다.
|
||||
|
||||
대조에서 빠지는 줄도 있다. 20자 미만인 줄, `#`·`//`·`|`·`>` 로 시작하는 줄, 그리고 한글과 흐름
|
||||
글리프(`─`·`▶`·`→`·`←`)가 함께 있는 줄 — 필자가 그린 흐름도 — 을 건너뛴다. `nano ~/.bashrc` 는
|
||||
14자라 대조 없이 통과한다. 통과했다는 것과 SSOT 에 있다는 것은 다르므로 짧은 명령도 사람이
|
||||
SSOT 에서 찾아 대조한다.
|
||||
|
||||
**한글이 섞였다는 것만으로는 안 빠진다.** 예전에는 그랬고, 그래서 자리표시자를 한글로 감싼
|
||||
`psql -c "…"` 한 줄이 통째로 대조에서 빠졌다. 지금은 흐름 글리프까지 있어야 흐름도로 본다.
|
||||
|
||||
순서는 SSOT 가 먼저다. 구축 절차를 담은 부(virtualization 은 제6부 §184~§194)에 사람이 치는
|
||||
형태를 적고, 그 형태도 원 가이드나 저장소에서 확인한 뒤에 적는다. 기록을 먼저 고치고 검사기가
|
||||
막을 때 SSOT 를 맞추면 SSOT 가 근거이기를 그만두고 기록의 사본이 된다. CLAUDE.md 의 「보강은
|
||||
상류 원문으로 한다」가 같은 말이다.
|
||||
|
||||
형태를 바꾸는 것과 사실을 바꾸는 것은 다르다. 원 가이드가 `printf ... > meta-kc-lab-1` 로
|
||||
적었다면 그 파일에 무엇이 들어가는지는 원문이 이미 갖고 있으므로, SSOT 에는 그 내용을 파일
|
||||
목록으로 옮기고 파일을 여는 명령을 앞에 둔다. 원문이 만들지 않은 파일이나 재지 않은 출력을
|
||||
새로 만들지 않는다.
|
||||
|
||||
**② 셸이 여럿인 실험대에서는 코드블록마다 어디서 치는지 붙인다.** 산문에 한 번 적어 두면 따라
|
||||
하는 도중에는 안 보인다. 본문 파서가 코드블록의 `label` 을 받으므로 거기에 적는다.
|
||||
|
||||
```bash label="[lab host] 저장소 루트에서 친다"
|
||||
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml
|
||||
```
|
||||
|
||||
virtualization 의 SSOT §185 ③ 이 그 표시를 다섯으로 정해 두었다 — `[워크스테이션]` ·
|
||||
`[lab host]` · `[kc-lab-edge]` · `[kc-lab-1]` · `[kc-lab-2]`. 표시가 없으면 같은 명령이 다른
|
||||
기계에서 다른 결과를 낸다.
|
||||
|
||||
**③ 비밀은 길이와 존재 여부까지만 적는다.** 값을 찍는 명령을 본문에 두지 않는다. 토큰이 필요한
|
||||
단계는 값을 찾는 명령을 주고 `echo "${#TOKEN} 자"` 로 끝낸다. `final/evidence/` 에 올리는 원문에도
|
||||
값이 들어가지 않도록 명령을 짜는 것이 먼저다. 터미널 렌더러가 Bearer·Cookie·token·password 를
|
||||
`[REDACTED]` 로 바꾸지만 그 앞에서 막는다.
|
||||
|
||||
## Reference — 14건
|
||||
|
||||
칸은 `관계` · `목적` · `규칙` · `적용 조건` · `예외` · `예시`. 14건 모두 여섯 칸을 채웠다. 본문이 없다.
|
||||
@@ -124,7 +280,8 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
|
||||
규칙 제목만 읽어도 무엇을 금지하는지 알아야 한다. 「지표를 정확히 읽는다」보다
|
||||
「지표 이름이 뜻하는 것을 그대로 읽는다」가 낫다. 코드가 필요하면 그 코드가 있는 Case 를 만들고
|
||||
`관계`로 가리킨다 — Reference 칸은 평문이라 백틱이 글자로 보인다.
|
||||
`관계`로 가리킨다 — Reference 칸은 마크다운 블록 파서를 안 거쳐서 코드펜스가 글자로 보인다.
|
||||
낱말 하나짜리 식별자는 백틱으로 감싸면 인라인 `<code>` 로 살아난다. 여러 줄짜리 코드가 문제다.
|
||||
|
||||
## Question — 9건
|
||||
|
||||
@@ -151,7 +308,7 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
|
||||
## Decision — 9건
|
||||
|
||||
칸은 **`근거`** · `결정문` · `판단 이유` · `영향`. **다른 넷과 달리 관계 절 이름이 「근거」다.**
|
||||
칸은 **`근거`** · `결정문` · `판단 이유` · `영향`. **다른 다섯과 달리 관계 절 이름이 「근거」다.**
|
||||
`decisionStatus` 는 frontmatter 에 있다. 근거가 1개 이상 없으면 게시가 거절된다.
|
||||
|
||||
| 칸 | 무엇을 |
|
||||
@@ -163,14 +320,14 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
대안을 적고 왜 고르지 않았는지 적는다. 대안이 없으면 결정이 아니라 사실이다. 자료가 뒷받침하지
|
||||
않는 이유를 만들지 않는다.
|
||||
|
||||
## 본문이 있는 두 종류의 공통 규칙
|
||||
## 본문이 있는 세 종류의 공통 규칙
|
||||
|
||||
- **그림이 필요한지와 무엇을 그릴지는 `choosing-a-diagram.md` 가 정한다** — 종류마다 그림이
|
||||
답해야 할 물음이 다르다. Case 는 순서, Concept 은 구조나 변환 사슬이 대개 논지다
|
||||
- 그림은 `technical-visualizer` 로 만든다. 손으로 SVG 를 그리지 않는다
|
||||
- 그림 안에는 이름만 넣는다. 문장은 `<desc>` 와 옆 문단에 둔다
|
||||
- 그림과 증거는 frontmatter 의 `assets` · `evidence` 로 잇는다. 같은 파일을 기록 옆에 복사하지 않는다
|
||||
- `assets` 는 본문이 있는 두 종류만 갖는다. Reference·Question·Decision 은 그림을 렌더링할 자리가
|
||||
- `assets` 는 본문이 있는 세 종류만 갖는다. Reference·Question·Decision 은 그림을 렌더링할 곳이
|
||||
없어서 선언해도 화면에 나오지 않는다
|
||||
- 본문은 `<!-- body:start -->` 와 `<!-- body:end -->` 사이다. 그 밖은 Studio 로 가지 않는다
|
||||
|
||||
|
||||
@@ -60,6 +60,27 @@ const PROSE_FENCE = new Set(["text", "", "txt", "console", "diff"]);
|
||||
// 경로·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;
|
||||
|
||||
// Setup 의 자리표시자. 읽는 사람의 실험대에서 값이 달라지는 자리라 SSOT 의 실측값과 글자가
|
||||
// 다르다. 그 자리만 와일드카드로 두고 나머지는 한 글자씩 대조한다. 문법은
|
||||
// references/writing-each-kind.md 「Setup — 자리표시자」 가 정한다.
|
||||
// 셸 변수와 갈라야 해서 `${...}` 를 안 쓴다 — 같은 가이드가 `$SID`·`$TOK` 를 진짜 변수로 쓴다.
|
||||
const PLACEHOLDER = /\{\{[A-Z][A-Z0-9_]*\}\}/;
|
||||
const PLACEHOLDER_G = /\{\{[A-Z][A-Z0-9_]*\}\}/g;
|
||||
// 필자가 그린 흐름도의 글리프. `주입 ① ─▶ 검증 §1` 꼴은 코드가 아니라 그림이다
|
||||
const FLOW = /[─━│┃┌┐└┘├┤┬┴┼╭╮╯╰▶◀►◄→←↔⇒⇐↑↓]/;
|
||||
const RE_META = /[.*+?^${}()|[\]\\]/g;
|
||||
|
||||
// 자리표시자가 없으면 지금까지처럼 통째로 찾는다. 있으면 그 자리만 「값 하나」로 열어 두는데,
|
||||
// 따옴표와 공백은 못 넘게 해서 와일드카드가 엉뚱한 구간을 삼키지 않도록 한다.
|
||||
function inSsot(line) {
|
||||
const n = norm(line);
|
||||
if (!PLACEHOLDER.test(n)) return ssot.includes(n);
|
||||
const pattern = n.split(PLACEHOLDER_G)
|
||||
.map(part => part.replace(RE_META, "\\$&"))
|
||||
.join("[^'\"\\s]+");
|
||||
return new RegExp(pattern).test(ssot);
|
||||
}
|
||||
|
||||
const findings = [];
|
||||
const studio = join(base, "tech-log-studio");
|
||||
for (const topicDir of readdirSync(studio)) {
|
||||
@@ -89,8 +110,10 @@ for (const topicDir of readdirSync(studio)) {
|
||||
const t = raw.trim();
|
||||
if (t.length < 20) continue;
|
||||
if (/^(\/\/|\*|\/\*\*|#|--|>|\|)/.test(t)) continue;
|
||||
if (/[가-힣]/.test(t)) continue; // 한글이 섞인 줄은 코드가 아니다
|
||||
if (!ssot.includes(norm(t)))
|
||||
// 한글이 섞인 줄을 전부 건너뛰면 자리표시자를 한글로 감싼 명령이 통째로 빠진다.
|
||||
// 그래서 흐름 글리프가 함께 있는 줄 — 필자가 그린 흐름도 — 만 건너뛴다
|
||||
if (/[가-힣]/.test(t) && FLOW.test(t)) continue;
|
||||
if (!inSsot(t))
|
||||
findings.push([file, "인용한 코드가 SSOT 에 없다", t.slice(0, 90)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ evidence:
|
||||
|
||||
<!--
|
||||
본문이 없는 종류라 `assets` 를 두지 않는다. 칸이 평문으로 렌더링되므로 그림과 코드블록은
|
||||
표시되지 않는다. 그런 자료는 근거로 건 Case 나 Concept 에 담는다.
|
||||
표시되지 않는다. 그런 자료는 근거로 건 Case·Concept·Setup 에 담는다.
|
||||
-->
|
||||
|
||||
# <title>
|
||||
|
||||
@@ -18,7 +18,7 @@ evidence:
|
||||
|
||||
<!--
|
||||
본문이 없는 종류라 `assets` 를 두지 않는다. 칸이 평문으로 렌더링되므로 그림과 코드블록은
|
||||
표시되지 않는다. 그런 자료는 Case 나 Concept 에 담고 `관계`로 가리킨다.
|
||||
표시되지 않는다. 그런 자료는 본문이 있는 종류(Case·Concept·Setup)에 담고 `관계`로 가리킨다.
|
||||
-->
|
||||
|
||||
# <title>
|
||||
|
||||
@@ -17,7 +17,8 @@ evidence:
|
||||
|
||||
<!--
|
||||
본문이 없는 종류라 `assets` 를 두지 않는다. 이 기록의 칸은 평문으로 렌더링되므로 그림도
|
||||
코드블록도 표시되지 않는다. 그림이 필요한 내용은 Case 나 Concept 에 담고 `관계`로 가리킨다.
|
||||
코드블록도 표시되지 않는다. 그림이 필요한 내용은 본문이 있는 종류(Case·Concept·Setup)에 담고
|
||||
`관계`로 가리킨다.
|
||||
`evidence` 는 이 기록이 인용한 측정 자료의 출처이고 화면에는 나오지 않는다.
|
||||
-->
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
id: <Studio 가 준 uuid. 아직 없으면 빈 값>
|
||||
kind: SETUP
|
||||
slug: <slug>
|
||||
title: <제목>
|
||||
topic: <topic-slug — 폴더 이름과 같다. 비워도 된다>
|
||||
topicName: <화면에 보이는 주제 이름. 비워도 된다>
|
||||
project: <프로젝트 이름 — 이 종류는 프로젝트가 필수다>
|
||||
status: 게시 전
|
||||
studio: "<편집 화면 주소. 아직 없으면 빈 값>"
|
||||
pinnedVersions:
|
||||
- name: <이름 1~60자. 예 Keycloak>
|
||||
version: <버전 1~40자. 예 26.7.0>
|
||||
source:
|
||||
- final/document.md#<anchor>
|
||||
sourceRevision: <분석한 리비전>
|
||||
assets:
|
||||
- key: <본문의 :::evidence key 와 같은 값>
|
||||
file: <../../../final/assets/… 상대 경로>
|
||||
evidence:
|
||||
- <../../../final/evidence/raw/… 상대 경로>
|
||||
---
|
||||
|
||||
<!--
|
||||
검증일 칸이 없다. 이 절차가 어느 버전 위에서 성립했는지는 `pinnedVersions` 가 말한다.
|
||||
본문에 「2026-09-12 기준」 같은 날짜를 적어 대신하지 않는다.
|
||||
절 이름은 강제되지 않는다. 아래 셋은 Studio 가 작업본에 미리 넣어 주므로 거기서 시작해
|
||||
그 프로젝트가 쓰는 말로 바꾼다.
|
||||
|
||||
본문을 쓰기 전에 `Skill` 도구로 `writing-practitioner-guides` 를 연다. 명령을 어떤 형태로
|
||||
쓸지는 그 스킬이 정한다. 이 저장소에서만 걸리는 셋(SSOT 를 먼저 고치기·셸 표시·비밀 값)은
|
||||
`references/writing-each-kind.md` 의 Setup 절에 있다.
|
||||
-->
|
||||
|
||||
# <제목>
|
||||
|
||||
<요약. 이 절차를 따라 하면 무엇이 서는지 한 문단>
|
||||
|
||||
## 관계
|
||||
|
||||
- **<이어지는 기록>**
|
||||
<왜 이어지는지>
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 실행 절차
|
||||
|
||||
<!--
|
||||
단계 하나의 칸은 다섯이다 — 목적 · 행동 · 예상 결과 · 왜 필요한가 · 문제가 생기면.
|
||||
다섯이 단계마다 같은 순서로 와야 처음 따라 하는 사람이 같은 곳에서 같은 것을 찾는다.
|
||||
행동에는 번호를 매기고 한 번호에 한 가지 일만 둔다. 접속과 파일 생성과 권한 설정을
|
||||
한 줄에 묶지 않는다. 셸이 여럿이면 코드블록마다 `label` 로 어디서 치는지 적는다.
|
||||
-->
|
||||
|
||||
### 1. <이 단계가 무엇을 만드는가>
|
||||
|
||||
목적
|
||||
<한 줄. 이 단계가 끝나면 무엇이 달라지나>
|
||||
|
||||
1. <행동 한 줄. 사람이 내용을 읽고 고쳐야 하는 파일이면 에디터로 연다>
|
||||
|
||||
```bash label="[어느 셸] <이 블록이 무엇을 하나>"
|
||||
<명령 하나>
|
||||
```
|
||||
|
||||
2. <다음 행동>
|
||||
|
||||
```bash label="[어느 셸] <이 블록이 무엇을 하나>"
|
||||
<명령 하나>
|
||||
```
|
||||
|
||||
예상 결과
|
||||
<그때 화면에 나오는 것. 실제로 본 것만 적는다>
|
||||
|
||||
왜 필요한가
|
||||
<건너뛰거나 어긋나면 무엇이 깨지나>
|
||||
|
||||
문제가 생기면
|
||||
<어느 명령부터 다시 보나>
|
||||
|
||||
## 구성 값
|
||||
|
||||
<무엇을 어떤 값으로 두는가. 값이 여럿이면 표로>
|
||||
|
||||
## 확인 방법
|
||||
|
||||
<제대로 섰는지 확인하는 명령과 그때 보이는 출력>
|
||||
|
||||
<!-- body:end -->
|
||||
Reference in New Issue
Block a user