feat: 가상화 문서들 추가
This commit is contained in:
@@ -11,7 +11,8 @@ Produce a highly detailed, source-traceable engineering analysis. This stage dis
|
||||
|
||||
## Required sequence
|
||||
|
||||
1. **First read `<분석 대상 저장소>/analysis-queue.yaml`.** Before inspecting any project contents, apply `references/queue-contract.md`: reconcile newly discovered project directories, preserve queue order, and determine the single active project.
|
||||
0. **Fix the two roots before anything else.** `<분석 대상 저장소>` is the repository being analyzed — an absolute path outside this repository, given by the caller. `docs/<프로젝트>/` is inside *this* repository. Never write into the analyzed repository, and never read analysis state from it.
|
||||
1. **Read `<분석 대상 저장소>/analysis-queue.yaml` if it exists.** It exists only when several repositories are queued. Apply `references/queue-contract.md`: reconcile newly discovered project directories, preserve queue order, and determine the single active project. **If there is no queue, analyze the one repository the caller named and skip to step 3.** A missing queue is not a blocker — it means nothing is queued.
|
||||
2. If an `IN_PROGRESS` project exists, analyze only that project. If none exists, activate the first `PENDING` project in queue order. Never preempt an active project because a new project appeared.
|
||||
3. For the selected `<분석 대상 저장소>`, check the nearest `AGENTS.md` or equivalent repository instructions.
|
||||
4. Record Git revision and `git status` when Git is available. Never modify or reset user source as part of analysis.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Primary evidence first
|
||||
|
||||
Store command output, logs, generated query plans, benchmark results, browser observations, and other primary material under `docs/<프로젝트>/evidence/raw/` before creating presentation assets.
|
||||
Store command output, logs, generated query plans, benchmark results, browser observations, and other primary material under `docs/<프로젝트>/final/evidence/raw/` before creating presentation assets.
|
||||
|
||||
## Terminal evidence
|
||||
|
||||
@@ -15,7 +15,7 @@ For a command used as evidence, retain:
|
||||
- raw stdout/stderr;
|
||||
- source revision when relevant.
|
||||
|
||||
Render terminal UI with `./tools/terminal-evidence/render_terminal.py`.
|
||||
Render terminal UI with `scripts/terminal-evidence/render_terminal.py`.
|
||||
|
||||
The visual asset is explanatory. The raw evidence is the provenance.
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## 분석 기준 revision
|
||||
|
||||
- repository: `/shared/codebase/<project>`
|
||||
- repository: `<분석 대상 저장소의 절대 경로>`
|
||||
- revision: `<git revision or non-git snapshot note>`
|
||||
|
||||
## Build and module map
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"project": "<project>",
|
||||
"codebasePath": "/shared/codebase/<project>",
|
||||
"codebasePath": "<분석 대상 저장소의 절대 경로>",
|
||||
"gitRevision": null,
|
||||
"analysisStatus": "NOT_STARTED",
|
||||
"analysisCycle": 1,
|
||||
|
||||
@@ -82,7 +82,28 @@ They do not hold candidates, and in a folded project they are gone.
|
||||
with the fields its kind requires. There is no second tree to keep in step.
|
||||
10. Run `references/decomposition-checklist.md`.
|
||||
11. Record `candidateScope`, the source document hash, and the project revision.
|
||||
12. `python3 scripts/verify-tech-log-tree.py <project>` — errors must be 0.
|
||||
12. `python3 scripts/build-tech-log-tree.py <project>` first, then
|
||||
`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.
|
||||
|
||||
## 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.
|
||||
|
||||
| Field | What goes in it |
|
||||
|---|---|
|
||||
| `candidateScope` | the range candidates were found in — above |
|
||||
| `sourceRepository` | `path` of the analyzed repository, its `revision`, and how that was established. Leave `revision` `null` rather than inventing one; when the work is split across branches, pair names and commits under `revisions` |
|
||||
| `ssotSha256` | filled by `build-tech-log-tree.py`. Its job is to catch a tree whose SSOT changed after the candidates were chosen |
|
||||
|
||||
```json
|
||||
"sourceRepository": {
|
||||
"path": "/absolute/path/to/analyzed-repo",
|
||||
"revision": null,
|
||||
"revisions": {"AP1 develop-pattern1": "64175266"},
|
||||
"verified": "how the revision was established, or why it is null"
|
||||
}
|
||||
```
|
||||
|
||||
Use `.agents/skills/writing-tech-log-records/references/tech-log-tree-contract.md` as the
|
||||
output contract.
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
name: publishing-tech-log-to-studio
|
||||
description: Use when a finished Tech Log record .md must be put into Tech Log Studio through the browser with Playwright MCP — creating or opening the working copy, uploading assets, filling the per-kind fields, and saving. Save only; this skill never publishes.
|
||||
---
|
||||
|
||||
# Studio 반입 — 저장까지만
|
||||
|
||||
## 이 스킬의 경계
|
||||
|
||||
기록 `.md` 하나를 Studio 편집 화면에 넣고 **저장**한다. 거기서 끝난다.
|
||||
|
||||
**게시하지 않는다.** 게시는 공개 사이트에 올리는 일이고, 되돌리려면 `unpublish` 를 해야 하며
|
||||
그 사이에 누구나 본다. 더 중요한 것은 **한 번이라도 게시한 문서는 게시를 취소해도 지워지지
|
||||
않는다는 것이다** — 삭제가 409 로 거절되고 「공개된 기록은 삭제할 수 없습니다」가 뜬다. 그래서
|
||||
시험 삼아 게시하지 않는다. 게시는 사람이 미리보기를 읽고 판단한다.
|
||||
|
||||
편집 화면 오른쪽 `aside` 에 버튼이 `저장`·`게시` 둘뿐이다. **`게시` 를 누르면 저장·검증·
|
||||
미리보기·게시가 한 번에 돈다.** 이 스킬은 `저장` 만 누른다.
|
||||
|
||||
## 들어가기 전 조건
|
||||
|
||||
| 조건 | 확인 |
|
||||
|---|---|
|
||||
| 기록 `.md` 가 파서를 통과했다 | `studio-body.py` 로 바꾼 파일에 `check_body.mjs`, error 0 |
|
||||
| 문장 검사를 지났다 | `check_prose.mjs` error 0 |
|
||||
| 인용이 SSOT 에 실재한다 | `check_evidence.mjs <프로젝트> --repo` |
|
||||
| 브라우저가 이미 로그인돼 있다 | 아래 「인증」 |
|
||||
|
||||
**검사를 안 지난 초안을 넣지 않는다.** 저장은 빈 칸도 받아 주기 때문에(Studio 는 한 번에 다
|
||||
쓰지 않아도 저장되게 만들어져 있다) 넣는 것 자체는 성공한다. 그래서 파서·문장 검사를 여기서
|
||||
대신 잡아 주지 않는다.
|
||||
|
||||
## 인증
|
||||
|
||||
Studio 는 조회에도 권한을 요구한다. 읽는 것이 게시 전 초안이기 때문이다.
|
||||
|
||||
**이 저장소에 자격증명을 두지 않는다.** 이미 로그인된 브라우저 세션을 쓴다. 편집 화면을 열었을
|
||||
때 상태 `aside` 가 25초 안에 안 뜨면 **인증이 안 된 것으로 보고 멈춘다.** 로그인 화면을
|
||||
자동으로 통과하려 들지 않는다 — 사용자에게 로그인해 달라고 말하고 기다린다.
|
||||
|
||||
```
|
||||
aside[class*="studio-document-status"] ← 이것이 안 보이면 인증 실패
|
||||
```
|
||||
|
||||
## 주소
|
||||
|
||||
| 무엇 | 주소 |
|
||||
|---|---|
|
||||
| Studio | `https://hyeonworks.com/studio` |
|
||||
| 편집 화면 | `https://hyeonworks.com/studio/documents/<id>/edit` |
|
||||
| 새 문서 | Studio 에서 `새 문서` → 종류 선택 → `작업본 만들기` |
|
||||
|
||||
기록 frontmatter 의 `id` 와 `studio:` 가 이미 차 있으면 **새로 만들지 않는다.** 그 주소로 바로
|
||||
간다. 비어 있으면 새로 만들고, 받은 uuid 와 편집 주소를 기록 frontmatter 에 적는다.
|
||||
|
||||
## 절차
|
||||
|
||||
### 1. 기록을 읽고 무엇을 넣을지 정한다
|
||||
|
||||
`kind` 로 칸 목록이 정해진다. 어떤 `##` 제목이 Studio 의 어느 칸인지는
|
||||
[references/studio-form-map.md](references/studio-form-map.md).
|
||||
|
||||
**frontmatter 는 메타데이터고, 본문의 `##` 가 Studio 의 칸이며, 제목 아래 첫 문단이 `요약`
|
||||
이다.** 계약에 없는 `##` 는 화면에 자리가 없어 통째로 사라진다.
|
||||
|
||||
### 2. 그림이 있으면 Asset 을 먼저 올린다
|
||||
|
||||
frontmatter `assets:` 의 `file:` 이 올릴 파일이고 `key:` 는 저장소 쪽 이름이다. 올리면 서버가
|
||||
`<이름>-<해시8>` 형태의 키를 준다.
|
||||
|
||||
**본문을 넣기 전에 올린다.** 순서를 뒤집으면 미리보기가 본문 전체를 막는다 —
|
||||
`1:1 supported local evidence key not found: <asset-key>`. 다른 경로로 올렸으면 편집 화면을
|
||||
한 번 새로 고쳐야 목록을 다시 받는다. 본문 문법 문제로 오해하기 쉽다.
|
||||
|
||||
본문은 서버가 준 키로 바꿔서 넣는다.
|
||||
|
||||
```bash
|
||||
python3 scripts/studio-body.py <기록.md> --key <저장소 key>=<서버가 준 key> --body-only
|
||||
```
|
||||
|
||||
저장소의 `.md` 는 마크다운 이미지로 두고 고치지 않는다. `:::evidence` 는 Studio 렌더러의
|
||||
구문이라 저장소에 쓰면 편집기에서 그림이 안 보인다.
|
||||
|
||||
### 3. 칸을 채운다
|
||||
|
||||
되풀이되는 칸(`영향`·`선택지`·`사실`처럼 여러 줄인 것)은 **줄 수를 먼저 맞추고** 값을 넣는다.
|
||||
줄이 모자란 채로 채우면 뒤엣것이 조용히 버려진다. 셀렉터와 배치 실행 방법은
|
||||
[references/playwright-recipes.md](references/playwright-recipes.md).
|
||||
|
||||
**본문이 없는 세 종류(Reference·Question·Decision)의 칸은 평문으로 렌더링된다.** 백틱과
|
||||
파이프가 글자 그대로 보이고, 줄바꿈은 `<br>` 로만 살아난다.
|
||||
|
||||
### 4. 저장한다
|
||||
|
||||
```
|
||||
aside[class*="studio-document-status"] 안의 `저장` 버튼
|
||||
→ 같은 aside 의 글자가 `저장됨` 으로 바뀔 때까지 기다린다 (최대 30초)
|
||||
```
|
||||
|
||||
`저장됨` 을 못 보면 실패다. 그 `aside` 의 글자를 그대로 읽어서 보고한다. **버튼을 다시 누르지
|
||||
않는다** — 검증 오류로 막힌 것을 연타로 뚫으려다 `게시` 를 누르게 된다.
|
||||
|
||||
버전은 같은 `aside` 의 첫 `dd` 에 있다. 저장 전후로 읽어 두면 실제로 올라갔는지 보인다.
|
||||
|
||||
### 5. 미리보기로 읽는다
|
||||
|
||||
`즉시 미리보기` 탭은 저장한 값이 아니라 **화면에 입력한 값**을 렌더링한다. 그래서 게시 없이도
|
||||
공개 화면과 같은 블록 렌더러로 본문을 볼 수 있다.
|
||||
|
||||
읽을 항목은 `../writing-tech-log-records/references/studio-draft-review.md` 의 목록을 쓴다.
|
||||
고칠 것이 있으면 `편집` 탭으로 돌아가 고치고 다시 저장한다.
|
||||
|
||||
### 6. 기록에 되적는다
|
||||
|
||||
새로 만들었으면 frontmatter 의 `id` 와 `studio:` 를 채우고 색인을 다시 만든다.
|
||||
|
||||
```bash
|
||||
python3 scripts/build-tech-log-tree.py <프로젝트>
|
||||
python3 scripts/verify-tech-log-tree.py <프로젝트>
|
||||
```
|
||||
|
||||
`build-tech-log-tree.py` 는 frontmatter 의 `id` 가 있으면 `publication` 을 `게시됨` 으로
|
||||
적는다. **이 칸은 「Studio 에 있다」는 뜻이지 「공개돼 있다」가 아니다.** 공개 여부는 기록의
|
||||
`status` 와 `public:` 이 말한다.
|
||||
|
||||
## 시험용 초안을 남기지 않는다
|
||||
|
||||
확인하려고 만든 작업본은 지운다. 다만 **Decision 은 계약에 삭제 경로가 없다** — 확인용으로
|
||||
만들지 않는 편이 낫다. 관계로 참조된 문서도 지워지지 않는다(`DOCUMENT_IN_USE`). 참조하는 쪽의
|
||||
관계를 먼저 끊는다.
|
||||
|
||||
## 실패했을 때
|
||||
|
||||
| 증상 | 원인 |
|
||||
|---|---|
|
||||
| `aside` 가 안 뜬다 | 인증. 로그인 화면을 자동으로 넘기지 않는다 |
|
||||
| 미리보기가 본문 전체를 막는다 | Asset 을 올리기 전에 본문을 넣었다. 화면을 새로 고친다 |
|
||||
| 칸을 못 찾는다 (`label` 매치 0) | 그 종류에 없는 칸이다. `studio-form-map.md` 를 다시 본다 |
|
||||
| 값이 잘렸다 | 되풀이 칸의 줄 수를 안 맞추고 채웠다 |
|
||||
| `저장됨` 이 안 뜬다 | 검증이 막은 것이다. `aside` 글자를 읽어 보고한다 |
|
||||
@@ -0,0 +1,230 @@
|
||||
# Playwright MCP 로 편집 화면을 다루는 법
|
||||
|
||||
여기 적은 셀렉터는 지어낸 것이 아니라 이 저장소가 실제로 돌린 것이다. 원문은
|
||||
`.playwright-mcp/_p1.mjs`·`_p2.mjs`·`_p3.mjs` 에 남아 있다.
|
||||
|
||||
## 두 가지 방식
|
||||
|
||||
| 방식 | 언제 |
|
||||
|---|---|
|
||||
| MCP 도구를 하나씩 (`browser_navigate` · `browser_snapshot` · `browser_fill_form` · `browser_click`) | 기록 한 건. 화면을 보면서 한다 |
|
||||
| `browser_run_code_unsafe` 로 한 번에 | 여러 건을 같은 모양으로 채운다 |
|
||||
|
||||
**처음 넣는 기록은 하나씩 한다.** 배치는 이미 한 번 성공한 모양을 반복할 때만 쓴다. 화면을
|
||||
안 보고 배치를 돌리면 못 찾은 칸이 조용히 버려진다.
|
||||
|
||||
## 새로 만들 때 — `/studio/documents/new`
|
||||
|
||||
종류를 라디오로 고르고 `작업본 만들기` 를 누른다. 라디오의 이름은 화면에 보이는 그대로다.
|
||||
|
||||
| 종류 | 라디오 이름 | 화면이 적어 놓은 칸 |
|
||||
|---|---|---|
|
||||
| Case | `Case` | 문제 · 결론 · 환경 · 재현 · 본문 |
|
||||
| Reference | `Reference` | 목적 · 규칙 · 적용 조건 · 예외 · 예시 |
|
||||
| Concept | **`개념`** | 기준 버전 · 본문 |
|
||||
| Question | `Question` | 상태 · 사실 · 가정 · 미지수 · 선택지 |
|
||||
| Decision | `Decision` | 상태 · 결정일 · 결정문 · 판단 이유 · 영향 · 근거 |
|
||||
|
||||
**Concept 만 라디오 이름이 한글(`개념`)이다.** 나머지 넷은 영어다.
|
||||
|
||||
```js
|
||||
await page.goto('https://hyeonworks.com/studio/documents/new');
|
||||
await page.getByRole('radio', { name: /^Reference/ }).check();
|
||||
await page.getByRole('button', { name: '작업본 만들기' }).click();
|
||||
// 주소가 /studio/documents/<uuid>/edit 로 바뀐다. 그 uuid 를 기록 frontmatter 에 적는다
|
||||
```
|
||||
|
||||
화면에 이렇게 적혀 있다 — 「이 화면의 작업본은 현재 Studio 세션에서만 유지됩니다.」
|
||||
**만들었으면 그 자리에서 칸을 채우고 저장한다.** 만들어 두고 나중에 돌아오지 않는다.
|
||||
|
||||
## 화면의 기준점
|
||||
|
||||
```js
|
||||
const RAIL = 'aside[class*="studio-document-status"]';
|
||||
```
|
||||
|
||||
이 `aside` 가 상태 레일이다. 여기에 버전(`dd` 첫 번째)과 저장 상태(`저장됨`)와 버튼
|
||||
(`저장`·`게시`)이 있다.
|
||||
|
||||
**이것이 25초 안에 안 뜨면 인증이 안 된 것이다.** 로그인 화면을 자동으로 넘기려 들지 않는다.
|
||||
|
||||
```js
|
||||
await page.goto('https://hyeonworks.com/studio/documents/' + id + '/edit');
|
||||
try { await page.waitForSelector(RAIL, { timeout: 25000 }); }
|
||||
catch { return { error: 'AUTH? ' + page.url() }; }
|
||||
```
|
||||
|
||||
## 버전 읽기
|
||||
|
||||
```js
|
||||
const version = await page.locator(RAIL + ' dd').first().innerText();
|
||||
```
|
||||
|
||||
저장 전후로 읽는다. 안 올라갔으면 저장이 안 된 것이다.
|
||||
|
||||
## 칸 채우기
|
||||
|
||||
칸은 `label` 안의 `span` 이 이름을 들고 있다.
|
||||
|
||||
```js
|
||||
const loc = page.locator('xpath=//label[./span[normalize-space(.)="' + label + '"]]')
|
||||
.locator('textarea, input').first();
|
||||
if (await loc.count() !== 1) { /* 그 종류에 없는 칸이다 — 채우지 말고 기록한다 */ }
|
||||
if (await loc.inputValue() === value) { /* 이미 같다 — 건드리지 않는다 */ }
|
||||
await loc.fill(value);
|
||||
```
|
||||
|
||||
**매치가 1이 아니면 채우지 않고 못 찾았다고 적는다.** 비슷한 이름의 다른 칸에 넣는 것보다
|
||||
안 넣는 쪽이 낫다.
|
||||
|
||||
**이미 같은 값이면 건드리지 않는다.** 안 그러면 바뀐 것이 없는데 버전만 올라간다.
|
||||
|
||||
## 되풀이되는 칸은 줄 수를 먼저 맞춘다
|
||||
|
||||
`영향`·`선택지`·`사실`처럼 여러 줄인 칸은 `fieldset` 의 `legend` 가 이름이고 줄이
|
||||
`.studio-ordered-item` 이다.
|
||||
|
||||
```js
|
||||
const fs = page.locator('xpath=//fieldset[./legend[normalize-space(.)="' + legend + '"]]').first();
|
||||
let cur = await fs.locator('.studio-ordered-item').count();
|
||||
while (cur > want) { // 줄이 남으면 뒤에서 지운다
|
||||
await fs.locator('.studio-ordered-item').last().getByRole('button', { name: '삭제' }).click();
|
||||
await page.waitForTimeout(60); cur--;
|
||||
}
|
||||
while (cur < want) { // 모자라면 더한다
|
||||
await fs.getByRole('button', { name: legend + ' 추가' }).click();
|
||||
await page.waitForTimeout(60); cur++;
|
||||
}
|
||||
```
|
||||
|
||||
**값을 넣기 전에 한다.** 줄이 모자란 채로 채우면 뒤엣것이 조용히 버려진다.
|
||||
|
||||
**행 클래스가 칸마다 다르다.** `관계` 는 `.studio-relation-item` 이고 나머지가
|
||||
`.studio-ordered-item` 이다. 하나로 세면 관계는 늘 0으로 읽힌다.
|
||||
|
||||
**더한 뒤에는 카운트가 아니라 라벨 목록으로 검산한다.** 실제로 「판단 기준 추가」를 7번
|
||||
눌렀는데 행이 9개 생겼다. 카운트만 믿으면 못 잡는다.
|
||||
|
||||
```js
|
||||
// 채우기 전에 그 fieldset 안의 라벨을 전부 읽어 몇 줄인지 다시 센다
|
||||
const labels = await fs.locator('label span').allInnerTexts();
|
||||
```
|
||||
|
||||
## 칸 DOM 이 두 가지다
|
||||
|
||||
위 xpath 는 절반만 통한다. 실제 편집 화면에는 모양이 둘이다.
|
||||
|
||||
```html
|
||||
<label class="studio-field"><span>문제</span><textarea></textarea></label> <!-- A -->
|
||||
<div class="studio-field"><label for="studio-field-summary">요약</label>
|
||||
<textarea id="studio-field-summary"></textarea></div> <!-- B -->
|
||||
```
|
||||
|
||||
`요약` · `본문 Markdown` · `관계 N 이유` 가 B형이다. A형만 찾으면 이 칸들이 조용히 안 채워진다.
|
||||
**둘 다 본다.**
|
||||
|
||||
```js
|
||||
async function field(page, name) {
|
||||
const a = page.locator(`xpath=//label[./span[normalize-space(.)="${name}"]]`)
|
||||
.locator('textarea, input').first();
|
||||
if (await a.count()) return a;
|
||||
const id = await page.locator(`xpath=//label[normalize-space(.)="${name}"]`)
|
||||
.first().getAttribute('for');
|
||||
return id ? page.locator('#' + id) : null;
|
||||
}
|
||||
```
|
||||
|
||||
**본문 칸의 이름은 `본문` 이 아니라 `본문 Markdown` 이다.**
|
||||
|
||||
## Asset 을 올릴 때
|
||||
|
||||
`Asset 업로드` 는 모달을 띄우고 **`filechooser` 이벤트를 내지 않는다.** 기다리면 타임아웃이다.
|
||||
모달 안의 `input[type=file]` 에 직접 넣는다.
|
||||
|
||||
```js
|
||||
await page.getByRole('button', { name: 'Asset 업로드' }).click();
|
||||
await page.locator('input[type=file]').setInputFiles(svgPath);
|
||||
// 대체 텍스트 칸에 기록 본문의 alt 를 그대로 넣는다
|
||||
```
|
||||
|
||||
**올린 직후 목록이 갱신되지 않는다.** 화면의 `Asset 검색` 을 다시 눌러야 새 키가 나온다.
|
||||
새로 올리면 서버가 새 키를 준다(`<이름>-<해시8>`) — 옛 키는 목록에 남는다. 다른 문서가
|
||||
참조할 수 있으니 지우지 않는다.
|
||||
|
||||
## 값을 읽을 때는 `getByLabel` 을 쓰지 않는다
|
||||
|
||||
칸이 `<label><span>이름</span><textarea></label>` 모양이라 `getByLabel(...).inputValue()` 가
|
||||
30초 타임아웃으로 실패한다. `fill` 은 되는데 읽기만 안 된다. 읽기는 `evaluate` 로 한다.
|
||||
|
||||
```js
|
||||
const got = await page.evaluate((name) => {
|
||||
const el = [...document.querySelectorAll('label')]
|
||||
.find(l => l.querySelector('span')?.textContent.trim() === name);
|
||||
return el?.querySelector('textarea, input')?.value ?? null;
|
||||
}, '판단 기준');
|
||||
```
|
||||
|
||||
**넣은 값을 다시 읽어 원문과 대조한다.** 넣었다는 것과 들어갔다는 것은 다르다.
|
||||
|
||||
## 저장
|
||||
|
||||
```js
|
||||
await page.locator(RAIL).getByRole('button', { name: '저장' }).first().click();
|
||||
await page.waitForFunction(() => {
|
||||
const el = document.querySelector('aside[class*="studio-document-status"]');
|
||||
return el && /저장됨/.test(el.innerText);
|
||||
}, null, { timeout: 30000 });
|
||||
```
|
||||
|
||||
**버튼을 이름으로 고른다.** `aside button` 의 첫 번째로 고르면 화면이 바뀌었을 때 `게시` 를
|
||||
누르게 된다.
|
||||
|
||||
`저장됨` 을 못 보면 실패다. 다시 누르지 말고 레일의 글자를 읽어서 보고한다.
|
||||
|
||||
```js
|
||||
const why = (await page.locator(RAIL).innerText()).replace(/\s+/g, ' ').slice(0, 140);
|
||||
```
|
||||
|
||||
## 바뀐 것이 없으면 저장하지 않는다
|
||||
|
||||
```js
|
||||
if (filled.length === 0 && rowsChanged.length === 0) { /* CLEAN — 저장 버튼을 누르지 않는다 */ }
|
||||
```
|
||||
|
||||
누를 때마다 `version` 이 올라가고, 버전이 올라가면 앞서 만든 검증·미리보기 산출물이 무효가
|
||||
된다(`validatedVersion == document.version` 이 깨진다).
|
||||
|
||||
## 미리보기
|
||||
|
||||
**`즉시 미리보기` 는 탭이 아니다.** 편집 화면 오른쪽에 나란히 놓인 `region` 이라
|
||||
`getByRole('tab', ...)` 은 0개를 찾는다. 화면에 `[role="tab"]` 자체가 없다.
|
||||
|
||||
```js
|
||||
await page.getByRole('region', { name: /즉시 미리보기/ }).scrollIntoViewIfNeeded();
|
||||
```
|
||||
|
||||
저장한 값이 아니라 화면에 입력한 값을 렌더링한다. **Asset 을 방금 올렸으면 편집 화면을 한 번
|
||||
새로 고친다** — 안 그러면 편집 화면이 들고 있는 옛 asset 목록에서 키를 못 찾아 본문 전체가
|
||||
막힌다.
|
||||
|
||||
```text
|
||||
초안을 미리 볼 수 없습니다
|
||||
1:1 supported local evidence key not found: <asset-key>
|
||||
```
|
||||
|
||||
## 배치로 돌릴 때 돌려받을 것
|
||||
|
||||
한 건마다 이만큼은 남긴다. 무엇이 채워졌고 무엇을 못 찾았는지 없이 「성공」만 돌려받으면
|
||||
못 찾은 칸이 묻힌다.
|
||||
|
||||
```js
|
||||
{ name, version, saved: 'OK v13' | 'CLEAN' | 'FAIL <레일 글자>',
|
||||
filled: ['문제','결론'], skipped: 2, miss: ['적용 조건'], rows: ['영향 2→3'] }
|
||||
```
|
||||
|
||||
## 절대로 하지 않는 것
|
||||
|
||||
- `게시` 버튼을 누르지 않는다
|
||||
- 로그인 화면에 자격증명을 입력하지 않는다
|
||||
- `저장됨` 이 안 뜬다고 연타하지 않는다
|
||||
- 시험 삼아 문서를 만들지 않는다 — 한 번 게시한 문서는 취소해도 삭제가 409 로 거절된다
|
||||
@@ -0,0 +1,103 @@
|
||||
# 기록 `.md` 의 어디가 Studio 의 어느 칸인가
|
||||
|
||||
## 세 자리
|
||||
|
||||
| 기록 `.md` | Studio |
|
||||
|---|---|
|
||||
| frontmatter | 메타데이터. 화면 칸이 아니다 |
|
||||
| 제목 바로 아래 첫 문단 | **`요약` 칸** |
|
||||
| `## <이름>` | **같은 이름의 칸** |
|
||||
|
||||
`## 요약` 이라는 절을 만들지 않는다 — Studio 에 그런 칸이 없어 통째로 사라진다. `## 출처` 도
|
||||
칸이 아니다. 원본 경로는 frontmatter 의 `source` 에 있다.
|
||||
|
||||
## 종류마다의 칸
|
||||
|
||||
| 종류 | 기록 `.md` 의 `##` 이름 | 본문 |
|
||||
|---|---|---|
|
||||
| **Case** | `관계` · `문제` · `결론` · `검증 환경` · `재현 조건` · `본문` | 있음 |
|
||||
| **Concept** | `관계` · `본문` | 있음 |
|
||||
| **Reference** | `관계` · `목적` · `규칙` · `적용 조건` · `예외` · `예시` | 없음 |
|
||||
| **Question** | `관계` · `사실` · `가정` · `미지수` · `제약` · `선택지` · `다음 검증` | 없음 |
|
||||
| **Decision** | **`근거`** · `결정문` · `판단 이유` · `영향` | 없음 |
|
||||
|
||||
**Decision 만 관계 절 이름이 `근거` 다.** 그리고 근거가 1개 이상 없으면 게시가 거절된다.
|
||||
|
||||
## 화면의 라벨은 기록의 절 이름과 다르다
|
||||
|
||||
**이것이 이 문서에서 가장 자주 틀리는 자리다.** 위 표는 기록 `.md` 가 쓰는 이름이고,
|
||||
편집 화면의 라벨은 다른 말을 쓴다. 그리고 `/studio/documents/new` 의 종류 카드에 적힌 요약
|
||||
(`목적 · 규칙 · 적용 조건 · 예외 · 예시`)은 **카드 문구이지 편집 화면의 라벨이 아니다.**
|
||||
|
||||
Reference 에서 실제로 확인한 대응이다.
|
||||
|
||||
| 기록의 `##` | 편집 화면의 라벨 |
|
||||
|---|---|
|
||||
| `목적` | **`이 기준을 쓰는 이유`** |
|
||||
| `규칙` | **`판단 기준`** — 제목과 본문 두 칸이 한 줄이다 |
|
||||
| `적용 조건` | **`적용할 때`** |
|
||||
| `예외` | **`예외와 주의`** |
|
||||
| `예시` | `예시` |
|
||||
| `관계` | `관계` |
|
||||
|
||||
종류 이름도 자리마다 다르다. 상태 레일은 Reference 를 **`적용 기준`** 이라고 부르고,
|
||||
`새 문서` 화면의 라디오는 `Reference` 다.
|
||||
|
||||
**화면을 먼저 스냅샷으로 읽고 그 라벨을 쓴다.** 이 표를 외워서 넣지 않는다 — 화면이 바뀌면
|
||||
표가 먼저 낡는다.
|
||||
|
||||
## 기록에 없는데 화면에 있는 칸
|
||||
|
||||
| 칸 | 무엇 |
|
||||
|---|---|
|
||||
| `축` | 주제 안의 변이(SPA · Mediator · BFF · Forward-Auth). **주제를 고른 뒤에 나타난다.** 안 고르면 주제 공통 기록이 된다 |
|
||||
| `마지막 검증일` | 기록의 `verifiedOn` 이다. 없으면 비워 둔다 |
|
||||
|
||||
**근거가 없으면 비워 둔다.** `verifiedOn` 이 없는 채로 저장하면 미리보기에 「마지막 검증」 절이
|
||||
값 없이 뜬다. 그것은 날짜를 지어내는 것보다 낫다 — 게시 전에 사람이 채울지 정한다.
|
||||
|
||||
## frontmatter 에서 화면으로 가는 값
|
||||
|
||||
| frontmatter | 어디로 |
|
||||
|---|---|
|
||||
| `id` | 편집 주소 `/studio/documents/<id>/edit` |
|
||||
| `kind` | 새 문서를 만들 때 고르는 종류 |
|
||||
| `slug` · `title` | 화면 위쪽의 슬러그·제목 칸 |
|
||||
| `topic` · `topicName` · `project` | 주제·프로젝트 선택 |
|
||||
| `basisVersion` (Concept) | 기준 버전 칸 |
|
||||
| `questionStatus` (Question) · `decisionStatus` (Decision) | 상태 선택 |
|
||||
| `assets[].file` | 올릴 Asset 파일 |
|
||||
| `assets[].key` | 본문 `:::evidence key` 의 저장소 쪽 이름. 올리면 서버 키로 바뀐다 |
|
||||
|
||||
**계약이 화면에 주는 상태와 도메인이 들고 있는 상태가 다르다.** `QuestionStatus` 는 화면에서
|
||||
`OPEN`·`RESOLVED` 둘인데 도메인은 `OPEN`·`INVESTIGATING`·`PAUSED`·`RESOLVED` 넷이고,
|
||||
Decision 의 도메인 `ACCEPTED` 가 화면에서는 `ADOPTED` 로 보인다. 화면에서 고른 값이 도메인
|
||||
상태를 덮어쓰지는 않는다.
|
||||
|
||||
## 본문이 없는 세 종류
|
||||
|
||||
`Reference`·`Question`·`Decision` 의 칸은 **평문으로 렌더링된다.**
|
||||
|
||||
- 백틱과 파이프가 글자 그대로 보인다. 코드·표를 넣지 않는다
|
||||
- 줄바꿈은 `<br>` 로만 살아난다
|
||||
- 나열은 쉼표로 잇지 말고 `이름 : 값` 으로 줄을 나눈다
|
||||
|
||||
코드·표·그림이 필요하면 짝이 되는 Case 나 Concept 에 담고 `관계` 로 가리킨다.
|
||||
|
||||
## 본문을 넣기 전에
|
||||
|
||||
저장소의 `.md` 는 그림을 마크다운 이미지로 싣는다. Studio 는 `:::evidence` 를 쓴다. 바꾸는 것은
|
||||
스크립트가 한다.
|
||||
|
||||
```bash
|
||||
python3 scripts/studio-body.py <기록.md> --body-only # 저장소 key 그대로
|
||||
python3 scripts/studio-body.py <기록.md> --key a=a-1a2b3c4d --body-only # 서버가 준 key 로
|
||||
```
|
||||
|
||||
저장소 파일 자체는 고치지 않는다.
|
||||
|
||||
## 되돌아오는 값
|
||||
|
||||
저장이 끝나면 Studio 가 `id` 를 준다. 새로 만든 기록이면 frontmatter 의 `id` 와 `studio:` 를
|
||||
채운다. 그 두 칸이 차면 색인이 `publication` 을 `게시됨` 으로 적는데, **그것은 「Studio 에
|
||||
있다」는 뜻이고 공개됐다는 뜻이 아니다.** 공개 여부는 `status` 와 `public:` 이 말한다.
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
name: running-tech-log-pipeline
|
||||
description: Use when a codebase must go all the way to a saved Tech Log Studio draft — running the seven stages (SSOT, tree, record, diagram, prose, voice, Studio save) as separate subagents, one skill per stage, with a run ledger that records which skill each stage actually used and which gate it passed.
|
||||
---
|
||||
|
||||
# Tech Log 파이프라인 실행
|
||||
|
||||
## 이 스킬이 하는 일
|
||||
|
||||
스킬 일곱 개를 **한 줄기로 잇는다.** 각 스킬은 자기 단계만 알고 앞뒤를 모른다. 이 스킬이
|
||||
단계 사이의 인계물과 순서와 관문을 정하고, 단계마다 **서브에이전트를 하나씩 띄운다.**
|
||||
|
||||
한 실행이 남기는 것은 산출물과 **런 원장**(`runs/<프로젝트>/<runId>/run.json`) 둘이다.
|
||||
원장은 단계마다 어떤 스킬을 실제로 읽었고 어떤 관문을 어떤 종료 코드로 지났는지를 적는다.
|
||||
`python3 scripts/verify-pipeline-run.py <원장>` 이 그 원장을 검사한다.
|
||||
|
||||
**원장이 이 스킬의 존재 이유다.** 스킬을 안 읽고 쓴 글과 읽고 쓴 글은 결과물만 봐서는
|
||||
구분되지 않는다. 원장은 그것을 기계가 구분할 수 있게 만든다.
|
||||
|
||||
## 왜 단계마다 서브에이전트인가
|
||||
|
||||
한 세션이 일곱 단계를 이어서 하면 앞 단계의 문맥이 뒤 단계로 새어 든다. 그러면 세 가지가
|
||||
망가진다.
|
||||
|
||||
- **스킬을 안 읽고도 그럴듯하게 쓴다.** 3단계에서 본문 규칙을 읽었으니 5단계에서
|
||||
`rewriting-technical-prose-naturally` 를 열지 않아도 문장이 나온다. 스킬이 적용됐는지
|
||||
확인할 방법이 사라진다.
|
||||
- **관문이 형식이 된다.** 자기가 쓴 글을 자기가 검사하면 실패를 「이건 예외」로 넘긴다.
|
||||
- **어디서 어긋났는지 못 찾는다.** 결과가 이상할 때 일곱 단계 중 어디가 원인인지 가릴 수 없다.
|
||||
|
||||
서브에이전트는 **자기 단계의 입력 파일과 자기 스킬만** 받는다. 다른 단계가 무엇을 했는지
|
||||
모른다. 그래서 스킬을 안 읽으면 못 쓰고, 못 쓰면 원장에 남는다.
|
||||
|
||||
## 일곱 단계
|
||||
|
||||
| # | 단계 | 스킬 | 산출물 |
|
||||
|---|---|---|---|
|
||||
| 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. **게시하지 않는다** |
|
||||
|
||||
단계마다의 입력·관문·원장 칸은 [references/stage-contracts.md](references/stage-contracts.md).
|
||||
서브에이전트에 그대로 넣는 프롬프트는 [references/subagent-prompts.md](references/subagent-prompts.md).
|
||||
|
||||
## 순서가 고정된 곳
|
||||
|
||||
세 자리는 바꾸면 결과가 틀어진다.
|
||||
|
||||
**S3 → S4.** 그림은 기록을 쓴 다음에 만든다. 무엇을 그릴지는 기록 본문이 정하고
|
||||
(`writing-tech-log-records/references/choosing-a-diagram.md` 의 세 관문), 그림의 근거는 그
|
||||
기록의 `source` 앵커가 가리키는 SSOT 절이다. **기록 본문이 그림의 소재를 고르고, SSOT 절이
|
||||
그림의 사실을 댄다.** 기록 `.md` 를 `techviz prepare` 에 넣지 않는다 — 기록은 SSOT 의 인용이라
|
||||
줄 번호가 근거가 되지 못한다.
|
||||
|
||||
**S4 → S5 → S6.** 문장 손질은 본문이 다 찬 뒤에 한다. 그림을 붙이면 본문에 그림을 읽는 문단이
|
||||
한둘 늘고, 그 문단도 같은 손질을 받아야 한다. S5 가 먼저다 — 번역투와 반복 문형을 걷어낸 뒤라야
|
||||
S6 이 채울 자리(선택·비교·어긋남)가 보인다. 순서를 뒤집으면 S5 가 S6 이 넣은 목소리를
|
||||
「과한 대구」로 다시 깎는다.
|
||||
|
||||
**S6 → S7.** Studio 에는 문장 손질이 끝난 것만 넣는다. 저장한 뒤에 고치면 Studio 쪽 `version`
|
||||
이 올라가고 검증 산출물이 무효가 된다.
|
||||
|
||||
## S5·S6 뒤에는 S3 관문을 다시 돌린다
|
||||
|
||||
문장을 고치면 본문 문법과 인용이 함께 움직인다. `check_prose` 가 error 0 이어도 `check_body`
|
||||
가 깨지거나, 고쳐 쓴 코드블록 한 줄이 SSOT 와 달라져 `check_evidence` 가 걸린다.
|
||||
|
||||
```bash
|
||||
python3 scripts/studio-body.py <기록.md> -o /tmp/studio-body.md
|
||||
node --experimental-transform-types \
|
||||
.agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/studio-body.md
|
||||
node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs <프로젝트> --repo
|
||||
```
|
||||
|
||||
원장에서는 이것이 S5·S6 의 관문이지 S3 의 재실행이 아니다.
|
||||
|
||||
## 건너뛰어도 되는 단계
|
||||
|
||||
이미 있는 것을 다시 만들지 않는다. 건너뛴 단계도 **원장에 `SKIPPED` 와 사유를 적는다.**
|
||||
적지 않고 빠뜨린 것과 판단해서 건너뛴 것을 구분해야 한다.
|
||||
|
||||
| 단계 | 건너뛰는 조건 |
|
||||
|---|---|
|
||||
| S1 | `final/document.md` 가 있고 그 안에서 이 글감의 근거를 찾을 수 있다 |
|
||||
| S2 | `tech-log-tree.json` 에 이 글감이 `PROMOTE` · `CONFIRMED` 로 이미 있다 |
|
||||
| S4 | 그림이 필요 없다(세 관문에 걸린다) 또는 `ssot-assets` 가 배정한 그림이 이미 있다 |
|
||||
| S7 | 사용자가 Studio 반입을 요청하지 않았다 |
|
||||
|
||||
**S3·S5·S6 은 건너뛰지 않는다.** 기록을 쓰는 단계와 문장을 고치는 두 단계는 이 파이프라인의
|
||||
산출물 자체다.
|
||||
|
||||
## 실행 절차
|
||||
|
||||
### 0. 런을 연다
|
||||
|
||||
```bash
|
||||
python3 scripts/verify-pipeline-run.py --init runs/<프로젝트>/<runId>/run.json \
|
||||
--project <프로젝트> --record <기록 경로>
|
||||
```
|
||||
|
||||
`runId` 는 `YYYY-MM-DD-HHMM` 이다. 원장의 틀은
|
||||
[templates/run.json](templates/run.json) 이고, `--init` 이 그 틀을 채워 놓는다.
|
||||
|
||||
### 1. 단계마다 서브에이전트를 띄운다
|
||||
|
||||
프롬프트는 [references/subagent-prompts.md](references/subagent-prompts.md) 의 것을 쓴다.
|
||||
프롬프트에 **반드시** 들어가야 하는 넷이 있다.
|
||||
|
||||
1. **스킬 이름과 「먼저 그 SKILL.md 를 끝까지 읽어라」** — 요약을 주지 않는다. 요약을 주면
|
||||
에이전트가 스킬을 안 연다.
|
||||
2. **자기 단계의 입력 파일 경로만.** 앞 단계가 무엇을 했는지 설명하지 않는다.
|
||||
3. **관문 명령 원문과 「error 0 까지 고쳐라」.**
|
||||
4. **스킬 영수증** — 그 SKILL.md 에서 자기 단계에 해당하는 규칙 한 줄을 **원문 그대로**
|
||||
인용해 돌려보내게 한다. 이것이 스킬을 열었다는 기계 검증 가능한 증거다
|
||||
(`verify-pipeline-run.py` 가 그 문자열이 실제 SKILL.md 안에 있는지 대조한다).
|
||||
|
||||
한 단계가 끝나면 그 결과를 원장에 적고 다음 단계를 띄운다. **단계를 병렬로 띄우지 않는다** —
|
||||
S1~S7 은 앞 단계의 산출물이 뒤 단계의 입력이다. 병렬이 되는 것은 같은 단계 안에서
|
||||
**서로 다른 기록 여러 건**을 처리할 때뿐이다.
|
||||
|
||||
### 2. 원장을 검사한다
|
||||
|
||||
```bash
|
||||
python3 scripts/verify-pipeline-run.py runs/<프로젝트>/<runId>/run.json
|
||||
```
|
||||
|
||||
error 0 이어야 런이 끝난 것이다. 이 검사기가 보는 것은 결과물의 품질이 아니라 **절차의
|
||||
준수**다 — 단계가 빠졌는지, 스킬 영수증이 그 스킬의 실제 문장인지, 관문이 돌았고 종료 코드가
|
||||
0 이었는지, 적어 낸 산출물이 디스크에 있는지.
|
||||
|
||||
### 3. 프로젝트 검사기를 돌린다
|
||||
|
||||
```bash
|
||||
python3 scripts/verify-tech-log-tree.py <프로젝트>
|
||||
python3 scripts/verify-project-layout.py <프로젝트>
|
||||
python3 scripts/audit-records.py <프로젝트>
|
||||
python3 scripts/check-figure-text.py <프로젝트>
|
||||
```
|
||||
|
||||
## 이 스킬이 하지 않는 것
|
||||
|
||||
- **판단을 대신하지 않는다.** 어떤 후보를 `PROMOTE` 할지, 그림이 필요한지, 어떤 종류인지는
|
||||
각 단계의 스킬이 정한다. 이 스킬은 그 스킬이 실제로 불렸는지만 본다.
|
||||
- **관문을 완화하지 않는다.** 관문이 error 를 내면 그 단계는 끝나지 않은 것이다. 원장에
|
||||
`FAILED` 로 적고 멈춘다. 다음 단계로 넘기지 않는다.
|
||||
- **게시하지 않는다.** S7 은 저장까지다. 게시는 사람이 공개 화면을 보고 판단한다.
|
||||
|
||||
## 자주 어긋나는 자리
|
||||
|
||||
| 증상 | 원인 | 원장에 남는 모습 |
|
||||
|---|---|---|
|
||||
| 문장이 여전히 AI 같다 | S5 를 같은 세션이 겸했다 | `skillEcho` 가 비었거나 SKILL.md 에 없는 문장 |
|
||||
| 그림 안에 문장이 있다 | S4 가 `check-figure-text.py` 를 안 돌렸다 | 관문 목록에 그 명령이 없다 |
|
||||
| SSOT 에 없는 인용이 있다 | S3 이 앞 기록에서 코드를 옮겨 적었다 | `check_evidence` 종료 코드 ≠ 0 |
|
||||
| 기록이 색인에 없다 | S2 를 건너뛰고 S3 을 했다 | S2 가 `SKIPPED` 인데 사유가 없다 |
|
||||
| Studio 에서 그림이 안 보인다 | S7 이 Asset 을 올리기 전에 본문을 넣었다 | S7 관문에 미리보기 확인이 없다 |
|
||||
@@ -0,0 +1,254 @@
|
||||
# 단계 계약
|
||||
|
||||
단계마다 넷을 정한다 — **입력 · 스킬 · 관문 · 산출물.** 원장에 적히는 것도 이 넷이다.
|
||||
|
||||
관문은 종료 코드가 0 이어야 지난 것이다. 0 이 아니면 그 단계는 `FAILED` 이고 다음 단계로
|
||||
넘어가지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## S1 — 코드베이스 → SSOT
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `analyzing-codebase-for-tech-log` |
|
||||
| 입력 | 분석 대상 저장소 경로(사용자가 준다) · 그 저장소의 `AGENTS.md`(있으면) |
|
||||
| 산출물 | `docs/<프로젝트>/final/document.md` |
|
||||
| 관문 | `python3 scripts/verify-project-layout.py <프로젝트>` |
|
||||
|
||||
**분석 대상 저장소는 이 저장소 밖이다.** `docs/<프로젝트>` 는 이 저장소 기준이고, 분석 대상은
|
||||
사용자가 준 절대 경로다. 두 저장소를 섞지 않는다 — 대상 저장소를 고치지 않는다.
|
||||
|
||||
**`analysis-queue.yaml` 은 여러 프로젝트를 줄 세울 때만 쓴다.** 사용자가 저장소 하나를
|
||||
지목했으면 큐 없이 그 하나를 분석한다. 큐가 있으면 큐가 정한 활성 프로젝트를 따른다.
|
||||
|
||||
작업 재료(`analysis/` · `notes/` · `checkpoints/` · `state.json` · `source-index.md`)는 분석
|
||||
중에만 있고, 끝나면 `final/document.md` 로 합치고 지운다.
|
||||
|
||||
```bash
|
||||
python3 scripts/fold-analysis-into-final.py <프로젝트>
|
||||
```
|
||||
|
||||
**S2 가 무엇을 기대하는지 알고 쓴다.** S2 는 `final/document.md` 를 절 단위로 훑어 후보를
|
||||
찾는다. 절 제목이 무엇을 다루는지 말하지 않으면 후보가 안 잡힌다. 접어 넣은 문서라면
|
||||
제1부(통합 분석)가 후보 범위이고 제2·3부는 근거다.
|
||||
|
||||
산출물이 이미 있고 이 글감의 근거가 그 안에 있으면 `SKIPPED` 로 적고 사유를 남긴다.
|
||||
|
||||
### 이미 접어 넣은 프로젝트를 다시 볼 때 — 대조 모드
|
||||
|
||||
`final/document.md` 가 이미 있는 프로젝트에 S1 을 다시 돌리는 것은 **분석이 아니라 대조**다.
|
||||
스킬의 절차(작업 재료를 만들고 → 분석하고 → 접어 넣는다)를 그대로 밟으면 두 곳이 깨진다.
|
||||
|
||||
- `docs/<프로젝트>/` 에 `state.json`·`analysis/` 를 만들면 배치 검사기가 error 로 센다.
|
||||
완료된 프로젝트의 폴더는 `final/` 과 `tech-log-studio/` 뿐이다.
|
||||
- `final/document.md` 를 고치면 `ssotSha256` 이 어긋나 분해 계약이 통째로 무효가 된다.
|
||||
|
||||
그래서 대조 모드는 이렇게 돈다.
|
||||
|
||||
| | 분석 모드 | 대조 모드 |
|
||||
|---|---|---|
|
||||
| 작업 재료 | `docs/<프로젝트>/analysis/` | `runs/<프로젝트>/<runId>/stage/S1/` |
|
||||
| SSOT | 만들거나 접어 넣는다 | **고치지 않는다.** 보강 후보만 적는다 |
|
||||
| 끝 조건 | fold 하고 재료를 지운다 | 어긋난 것·빠진 것을 목록으로 남긴다 |
|
||||
|
||||
SSOT 가 코드와 **어긋나는** 것을 찾으면 그것은 보강 후보가 아니다. 크게 적고 사람에게
|
||||
올린다 — 이미 그 SSOT 를 근거로 쓴 기록이 있기 때문이다.
|
||||
|
||||
---
|
||||
|
||||
## S2 — SSOT → 분해 계약
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `deriving-tech-log-root-tree` |
|
||||
| 입력 | `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) |
|
||||
|
||||
`analysis/**` 를 후보를 찾으려고 열지 않는다. 분석에만 있는 자료를 발견하면 `final/document.md`
|
||||
를 먼저 보강한다.
|
||||
|
||||
**검사기나 계약이 요구하는데 스킬의 절차가 안 적는 칸이 있다.** 손으로 채운다.
|
||||
|
||||
| 칸 | 무엇 |
|
||||
|---|---|
|
||||
| `candidateScope` | 후보를 찾은 범위. 적지 않으면 모듈 분석 절 제목이 전부 글감이 된다 |
|
||||
| `sourceRepository` | 분석한 저장소의 경로·리비전·그렇게 판단한 근거. 모르면 `null`, 지어내지 않는다 |
|
||||
| `ssotSha256` | `build-tech-log-tree.py` 가 채운다. 그래서 build 를 먼저 돌리고 verify 를 돌린다 |
|
||||
| `ssot-assets` · `ssot-evidence` | SSOT 가 이미 그린 그림과 이미 돌린 측정을 글감에 배정한다. 계약 문서에만 있고 절차에는 이 단계가 없다 |
|
||||
| `candidateScope.excludedAnchorPattern` | 「범위 밖 글감」 검사가 이 칸으로 판정한다. 없으면 그 검사가 통째로 꺼진다 |
|
||||
|
||||
`assetLedger` 는 계약이 요구하지만 스크립트 어느 것도 읽지 않는다. 사람이 보는 칸이다.
|
||||
|
||||
**`source` 앵커의 형식이 어디에도 적혀 있지 않다.** keycloak 은 「h2 슬러그 + `-ap1`」 같은
|
||||
합성 앵커를 쓰고, 제목 슬러그를 쓴 프로젝트도 있다. 검사기는 SSOT 경로를 포함하는지만 보고
|
||||
실재하는 heading 으로 풀지 않는다. **한 프로젝트 안에서는 한 형식으로 통일한다** — S4 가
|
||||
`--heading` 값을 이 앵커에서 옮기기 때문이다.
|
||||
|
||||
**검사기는 「후보 ↔ 글감」만 본다. 「SSOT ↔ 후보」는 안 본다.** 그래서 SSOT 에 있는 재료를
|
||||
후보 대장에 올리지도 않고 지나쳐도 error 가 0 이다. 범위의 절을 끝까지 읽는 것은 사람의 일이다.
|
||||
|
||||
제외가 0 건인 분해는 선별하지 않은 분해다. 후보마다 처분(`PROMOTE`·`MERGE_INTO`·
|
||||
`KEEP_IN_SSOT`·`NEEDS_EVIDENCE`·`NEEDS_DECISION`)을 적고, 사람이 다시 읽은 것만
|
||||
`dispositionReview: CONFIRMED` 로 둔다.
|
||||
|
||||
---
|
||||
|
||||
## S3 — 글감 → 기록
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `writing-tech-log-records` |
|
||||
| 입력 | `tech-log-tree.json` 의 노드 하나 · 그 노드의 `source` 앵커가 가리키는 SSOT 절 |
|
||||
| 산출물 | `docs/<프로젝트>/tech-log-studio/<주제>/<종류>/<기록>.md` |
|
||||
| 관문 | 아래 셋 |
|
||||
|
||||
```bash
|
||||
python3 scripts/studio-body.py <기록.md> -o /tmp/studio-body.md
|
||||
node --experimental-transform-types \
|
||||
.agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/studio-body.md
|
||||
node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn <기록.md>
|
||||
node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs <프로젝트> --repo
|
||||
```
|
||||
|
||||
**노드가 `PROMOTE` 이고 `CONFIRMED` 인지 먼저 본다.** 아니면 쓰지 않는다.
|
||||
|
||||
**인용한 줄은 SSOT 에서 찾아 대조한다.** 앞선 기록에서 옮겨 적은 것은 확인한 것이 아니다 —
|
||||
그렇게 게시된 기록에 SSOT 와 다른 redirect URI 가 네 곳 있었다.
|
||||
|
||||
그림이 필요해 보이면 **`final/assets/` 에 이미 있는지부터 본다.** 계약의 `ssot-assets` 가 이
|
||||
글감에 배정한 그림이 있으면 그 파일을 그대로 가리킨다. 없을 때만 S4 로 넘긴다.
|
||||
|
||||
---
|
||||
|
||||
## S4 — 기록 → 그림
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `technical-visualizer` |
|
||||
| 입력 | 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` 로 눈 확인 |
|
||||
|
||||
**입력이 둘이라는 것이 이 단계의 전부다.**
|
||||
|
||||
- **무엇을 그릴지는 기록 본문이 정한다.** 세 관문(자리가 Case·Concept 인가 · 표가 아닌가 ·
|
||||
옆 문단이 이미 말하지 않았는가)을 지나야 그린다. 그리고 **그림이 주장하는 것을 기록 본문이
|
||||
말해야 한다.** 본문이 안 적은 단계를 그림만 넣으면 설명 없는 주장이 남는다. 순서는
|
||||
본문을 먼저 보강하고 그다음 그림을 붙인다.
|
||||
- **그림의 사실은 SSOT 절이 댄다.** 기록은 SSOT 의 인용이라 줄 번호가 근거가 되지 못한다.
|
||||
`techviz prepare` 에는 `final/document.md` 를 넣고, 절은 기록의 `source` 앵커로 지목한다.
|
||||
|
||||
```bash
|
||||
./scripts/techviz prepare docs/<프로젝트>/final/document.md \
|
||||
--heading "<기록의 source 앵커가 가리키는 절 제목>" \
|
||||
-o docs/<프로젝트>/final/.techviz/<이름>/context.json
|
||||
```
|
||||
|
||||
`--line` 은 쓰지 않는다. context 는 관리 블록을 접은 좌표를 쓰므로 파일 줄 번호와 어긋난다.
|
||||
|
||||
**그림 안에는 이름만 넣는다.** 문장은 `<desc>` 와 옆 문단에 둔다. lint 는 이것을 못 잡는다 —
|
||||
`check-figure-text.py` 가 잡는다.
|
||||
|
||||
**lint 는 좌표를 안 본다.** 관계가 이어져 있는지만 본다. 그래서 구역 둘이 겹쳐 그려지거나
|
||||
라벨이 상자에 먹혀도 통과한다. `check-figure-overlap.py` 가 그것을 본다.
|
||||
|
||||
```bash
|
||||
python3 scripts/check-figure-overlap.py <프로젝트>
|
||||
python3 scripts/check-figure-overlap.py --file 그림.svg
|
||||
```
|
||||
|
||||
**그래도 마지막에는 눈으로 본다.** 검사기가 보는 것은 배경 사각형의 좌표라, 글자가 상자
|
||||
밖으로 조금 나가거나 화살표가 라벨을 지나는 것은 못 잡는다.
|
||||
|
||||
기록에 되적는다 — frontmatter `assets:` 에 `key` 와 `file`(=`final/assets/<이름>/<이름>.svg`)
|
||||
을 적고, 본문에는 마크다운 이미지로 넣는다. `:::evidence` 는 저장소에 쓰지 않는다. 사본을
|
||||
`tech-log-studio/` 쪽에 두지 않는다 — 사본에는 `.techviz/<이름>/` 이 없어 다시 못 만든다.
|
||||
|
||||
---
|
||||
|
||||
## S5 — AI 티 제거
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `rewriting-technical-prose-naturally` |
|
||||
| 입력 | S3·S4 를 지난 기록 `.md` (제자리 수정) |
|
||||
| 산출물 | 같은 파일 |
|
||||
| 관문 | `check_prose.mjs` error 0 · `style_profile.mjs` · S3 관문 재실행 |
|
||||
|
||||
```bash
|
||||
S=.agents/skills/rewriting-technical-prose-naturally/scripts
|
||||
node $S/check_prose.mjs --warn <기록.md> # error 0 까지 고친다
|
||||
node $S/style_profile.mjs <기록.md>
|
||||
```
|
||||
|
||||
문서 전체를 다시 쓴 것이 아니면 `--doc` 을 빼고 부른다.
|
||||
|
||||
**`density.mjs` 는 본문이 있는 종류에만 건다.** 그 기준은 글 한 편(낱말 1277+ · 코드블록 1+ ·
|
||||
수치 13+)을 잰 값이고, Reference·Question·Decision 은 칸이 평문이라 코드블록을 넣는 것 자체가
|
||||
규칙 위반이다. 본문 없는 종류에 걸면 구조적으로 통과할 수 없는 관문이 되고, 통과시키려면
|
||||
없는 측정값을 지어내야 한다.
|
||||
|
||||
**문체 수치를 맞추려고 문장을 넣지 않는다.** 검사기는 표면 패턴만 보고 뜻은 못 본다.
|
||||
|
||||
**보호 구간을 건드리지 않는다** — 수치·날짜·버전·단위·코드·명령어·URL·직접 인용·공식 명칭은
|
||||
원문과 한 글자도 달라지면 안 된다. 그래서 문장을 고친 뒤 `check_evidence.mjs` 를 다시 돌린다.
|
||||
|
||||
---
|
||||
|
||||
## S6 — 일한 사람의 목소리
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `writing-as-the-person-who-did-it` |
|
||||
| 입력 | S5 를 지난 기록 `.md` · **그리고 그 기록의 상류 자료** (SSOT · 커밋 메시지 · 주석 · `확인하지 못한 것` 칸) |
|
||||
| 산출물 | 같은 파일 |
|
||||
| 관문 | `check_voice.mjs` · `check_prose.mjs` 재실행 · S3 관문 재실행 |
|
||||
|
||||
```bash
|
||||
node .agents/skills/writing-as-the-person-who-did-it/scripts/check_voice.mjs <기록.md>
|
||||
node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn <기록.md>
|
||||
```
|
||||
|
||||
**S5 다음이다.** 번역투와 반복 문형을 걷어낸 뒤라야 채울 자리가 보인다. 그리고 이 스킬이
|
||||
넣은 문장을 `check_prose` 가 다시 본다 — 그래서 재실행이 관문이다.
|
||||
|
||||
**자료에 흔적이 없으면 이 단계는 여기서 끝난다.** 없는 사람을 만들지 않는다. 「처음에는」·
|
||||
「고민 끝에」·「놀랍게도」를 자료 없이 쓰면 지어낸 것이다. 원장에는 `DONE` 에 「흔적 없음」을
|
||||
적는다 — `SKIPPED` 가 아니다. 찾아봤다는 것이 이 단계의 일이다.
|
||||
|
||||
**`check_voice.mjs` 는 목소리가 모자란지 재지 않는다.** 지어낸 목소리를 잡는다. 조용하다고
|
||||
목소리가 생긴 것은 아니다.
|
||||
|
||||
---
|
||||
|
||||
## S7 — Studio 저장
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 스킬 | `publishing-tech-log-to-studio` |
|
||||
| 입력 | S6 을 지난 기록 `.md` · frontmatter `assets:` 가 가리키는 SVG |
|
||||
| 산출물 | Studio 작업본. 기록 frontmatter 의 `id`·`studio:` |
|
||||
| 관문 | 상태 레일이 `저장됨` · `python3 scripts/build-tech-log-tree.py` → `verify-tech-log-tree.py` |
|
||||
|
||||
**저장까지다. 게시하지 않는다.**
|
||||
|
||||
Asset 을 본문보다 먼저 올린다. 순서를 뒤집으면 미리보기가 본문 전체를 막는다.
|
||||
|
||||
바뀐 것이 없으면 저장 버튼을 누르지 않는다 — 누를 때마다 `version` 이 올라가고 앞서 만든
|
||||
검증·미리보기 산출물이 무효가 된다.
|
||||
|
||||
---
|
||||
|
||||
## 관문 요약
|
||||
|
||||
| 단계 | 명령 |
|
||||
|---|---|
|
||||
| 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` |
|
||||
@@ -0,0 +1,275 @@
|
||||
# 서브에이전트 프롬프트
|
||||
|
||||
단계마다 에이전트를 하나 띄운다. 아래를 그대로 쓰고 `<...>` 만 바꾼다.
|
||||
|
||||
## 모든 프롬프트에 들어가는 넷
|
||||
|
||||
1. **스킬 이름과 「SKILL.md 를 끝까지 먼저 읽어라」.** 요약을 주지 않는다. 요약을 주면
|
||||
스킬을 안 연다.
|
||||
2. **자기 단계의 입력 경로만.** 앞 단계가 무엇을 했는지 설명하지 않는다.
|
||||
3. **관문 명령 원문.** 「검사해라」가 아니라 붙여 넣을 수 있는 명령을 준다.
|
||||
4. **스킬 영수증** — SKILL.md 에서 한 줄을 **원문 그대로** 인용해 돌려보내게 한다.
|
||||
`verify-pipeline-run.py` 가 그 문자열이 실제 파일 안에 있는지 대조한다.
|
||||
|
||||
## 돌려받는 형식 (모든 단계 공통)
|
||||
|
||||
```json
|
||||
{
|
||||
"stage": "S3",
|
||||
"skill": "writing-tech-log-records",
|
||||
"skillEcho": "<SKILL.md 에서 그대로 옮긴 한 줄>",
|
||||
"status": "DONE",
|
||||
"outputs": ["docs/keycloak/tech-log-studio/.../case-x.md"],
|
||||
"gates": [{"cmd": "node ... check_body.mjs /tmp/studio-body.md", "exit": 0}],
|
||||
"notes": "<판단한 것과 못 한 것>"
|
||||
}
|
||||
```
|
||||
|
||||
`skillEcho` 를 지어내지 말라고 프롬프트에 적는다. 파일에 없는 문장이면 검사기가 잡는다.
|
||||
|
||||
## 파일을 쓰라고 할 때는 방법을 함께 준다
|
||||
|
||||
서브에이전트의 `Write` 는 「보고는 파일이 아니라 글로 돌려라」는 기본 정책에 막힐 수 있다.
|
||||
S1 처럼 산출물이 `.md` 인 단계는 그래서 한 줄을 더한다.
|
||||
|
||||
```
|
||||
파일을 쓸 때 Write 툴이 막히면 Bash heredoc (`cat > 경로 <<'EOF'`) 을 써라.
|
||||
```
|
||||
|
||||
이것을 안 적으면 에이전트가 산출물을 만들지 못하고 본문에 통째로 붙여 돌려준다.
|
||||
|
||||
---
|
||||
|
||||
## S1 — 코드베이스 → SSOT
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 1단계를 맡는다.
|
||||
|
||||
먼저 .agents/skills/analyzing-codebase-for-tech-log/SKILL.md 를 끝까지 읽어라.
|
||||
references/ 아래 문서도 그 스킬이 읽으라는 것을 읽어라. 요약본은 주지 않는다.
|
||||
|
||||
대상 저장소: <절대 경로>
|
||||
쓸 곳: docs/<프로젝트>/
|
||||
|
||||
analysis-queue.yaml 이 없으면 이 저장소 하나만 분석한다. 큐가 없다는 이유로 멈추지 마라.
|
||||
대상 저장소를 고치지 마라. 읽기만 한다.
|
||||
|
||||
끝나면 분석 재료를 SSOT 로 합쳐라:
|
||||
python3 scripts/fold-analysis-into-final.py <프로젝트>
|
||||
합친 뒤 analysis/ · notes/ · checkpoints/ · state.json · source-index.md 를 지운다.
|
||||
|
||||
관문:
|
||||
python3 scripts/verify-project-layout.py <프로젝트>
|
||||
error 0 이 될 때까지 고쳐라.
|
||||
|
||||
돌려줄 것 (JSON):
|
||||
stage, skill, skillEcho, status, outputs, gates, notes
|
||||
skillEcho 는 방금 읽은 SKILL.md 에서 네 작업에 해당하는 규칙 한 줄을 원문 그대로 옮긴 것이다.
|
||||
지어내지 마라 — 파일에 그 문자열이 있는지 기계가 대조한다.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## S2 — SSOT → 분해 계약
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 2단계를 맡는다.
|
||||
|
||||
먼저 .agents/skills/deriving-tech-log-root-tree/SKILL.md 를 끝까지 읽어라.
|
||||
references/candidate-disposition.md 와 references/decomposition-checklist.md 도 읽어라.
|
||||
출력 계약은 .agents/skills/writing-tech-log-records/references/tech-log-tree-contract.md 다.
|
||||
|
||||
입력: docs/<프로젝트>/final/document.md — 이것 하나다.
|
||||
analysis/** 를 후보를 찾으려고 열지 마라.
|
||||
|
||||
출력: docs/<프로젝트>/tech-log-studio/tech-log-tree.json
|
||||
|
||||
검사기가 error 로 요구하는데 스킬 본문이 안 적는 칸 셋을 손으로 채워라:
|
||||
candidateScope — 후보를 찾은 범위
|
||||
sourceRepository — 분석한 저장소의 경로·리비전·판단 근거. 모르면 null 로 두고 지어내지 마라
|
||||
ssotSha256 — build 가 채운다. 그래서 build 를 먼저 돌린다
|
||||
|
||||
제외가 0 건인 분해는 선별하지 않은 분해다. 후보마다 처분을 적고, 다시 읽은 것만
|
||||
dispositionReview: CONFIRMED 로 둬라.
|
||||
|
||||
관문:
|
||||
python3 scripts/build-tech-log-tree.py <프로젝트>
|
||||
python3 scripts/verify-tech-log-tree.py <프로젝트>
|
||||
error 0 까지 고쳐라.
|
||||
|
||||
돌려줄 것: 위 JSON 형식. skillEcho 포함.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## S3 — 글감 → 기록
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 3단계를 맡는다.
|
||||
|
||||
먼저 .agents/skills/writing-tech-log-records/SKILL.md 를 끝까지 읽어라.
|
||||
그 스킬이 가리키는 references/ 중 네 종류에 해당하는 것을 읽어라 —
|
||||
record-kinds.md · writing-each-kind.md · body-syntax.md · code-tables-diagrams.md ·
|
||||
explaining.md · ai-tells.md · choosing-a-diagram.md.
|
||||
|
||||
글감: docs/<프로젝트>/tech-log-studio/tech-log-tree.json 의 <주제> / <종류> / "<제목>"
|
||||
그 노드가 PROMOTE 이고 dispositionReview 가 CONFIRMED 인지 먼저 확인해라. 아니면 쓰지 마라.
|
||||
|
||||
근거: 그 노드의 source 앵커가 가리키는 docs/<프로젝트>/final/document.md 의 절.
|
||||
인용하는 줄은 SSOT 에서 찾아 대조해라. 기억이나 다른 기록에서 옮겨 적지 마라.
|
||||
|
||||
쓸 곳: docs/<프로젝트>/tech-log-studio/<주제>/<종류>/<파일>.md
|
||||
|
||||
그림이 필요해 보이면 docs/<프로젝트>/final/assets/ 에 이미 있는지부터 봐라.
|
||||
없으면 이 단계에서 만들지 말고 notes 에 "그림 필요: <무엇을>" 이라고 적어라. 4단계가 만든다.
|
||||
|
||||
관문:
|
||||
python3 scripts/studio-body.py <기록.md> -o /tmp/studio-body.md
|
||||
node --experimental-transform-types \
|
||||
.agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/studio-body.md
|
||||
node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn <기록.md>
|
||||
node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs <프로젝트> --repo
|
||||
error 0 까지 고쳐라.
|
||||
|
||||
돌려줄 것: 위 JSON 형식. skillEcho 포함.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## S4 — 기록 → 그림
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 4단계를 맡는다.
|
||||
|
||||
먼저 두 개를 읽어라:
|
||||
.agents/skills/technical-visualizer/SKILL.md — 끝까지
|
||||
.agents/skills/writing-tech-log-records/references/choosing-a-diagram.md
|
||||
|
||||
기록: <기록.md>
|
||||
이 기록을 읽고 무엇을 그릴지 정해라. 세 관문을 지나야 그린다 —
|
||||
자리가 Case·Concept 인가 / 표로 될 것이 아닌가 / 옆 문단이 이미 말하지 않았는가.
|
||||
그리고 그림이 주장하는 것을 이 기록 본문이 말하고 있어야 한다. 본문이 안 적은 단계를
|
||||
그림만으로 넣지 마라.
|
||||
|
||||
그림의 근거는 기록이 아니라 기록의 source 앵커가 가리키는 SSOT 절이다:
|
||||
./scripts/techviz prepare docs/<프로젝트>/final/document.md \
|
||||
--heading "<그 절 제목>" \
|
||||
-o docs/<프로젝트>/final/.techviz/<이름>/context.json
|
||||
--line 은 쓰지 마라.
|
||||
|
||||
그림 안의 <text> 는 전부 이름이어야 한다. 문장은 <desc> 와 옆 문단에 둬라.
|
||||
|
||||
관문:
|
||||
./scripts/techviz lint docs/<프로젝트>/final/.techviz/<이름>/spec.json \
|
||||
--context docs/<프로젝트>/final/.techviz/<이름>/context.json
|
||||
python3 scripts/check-figure-text.py <프로젝트>
|
||||
python3 scripts/check-figure-overlap.py <프로젝트>
|
||||
python3 scripts/preview-figure.py <프로젝트> -o /tmp/figs
|
||||
마지막 것은 PNG 로 떠서 라벨이 상자를 덮지 않는지 눈으로 봐라. lint 는 그것을 못 잡는다.
|
||||
|
||||
기록에 되적어라 — frontmatter assets: 에 key 와 file, 본문에는 마크다운 이미지.
|
||||
:::evidence 를 저장소 .md 에 쓰지 마라.
|
||||
|
||||
돌려줄 것: 위 JSON 형식. skillEcho 포함. 그리지 않기로 했으면 status 를 SKIPPED 로 하고
|
||||
어느 관문에 걸렸는지 적어라.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## S5 — AI 티 제거
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 5단계를 맡는다.
|
||||
|
||||
먼저 .agents/skills/rewriting-technical-prose-naturally/SKILL.md 를 끝까지 읽어라.
|
||||
그 스킬이 "첫 rewrite 전에 읽으라"고 지정한 references 세 개도 읽어라 —
|
||||
document-skeleton.md · article-shape.md · korean-tech-blog-register.md.
|
||||
|
||||
고칠 파일: <기록.md> (제자리에서 고친다)
|
||||
|
||||
너의 일은 문체다. 사실을 만들지 마라. 분류가 틀렸거나 근거가 모자란 것은 네 일이 아니다 —
|
||||
발견하면 고치지 말고 notes 에 적어라.
|
||||
|
||||
보호 구간을 건드리지 마라: 수치 · 날짜 · 버전 · 단위 · 코드 · 명령어 · URL · 직접 인용 ·
|
||||
공식 명칭. 한 글자도 달라지면 안 된다.
|
||||
|
||||
관문:
|
||||
S=.agents/skills/rewriting-technical-prose-naturally/scripts
|
||||
node $S/check_prose.mjs --warn <기록.md> # error 0 까지
|
||||
node $S/style_profile.mjs <기록.md>
|
||||
그리고 문장을 고쳤으니 본문 문법과 인용을 다시 본다:
|
||||
python3 scripts/studio-body.py <기록.md> -o /tmp/studio-body.md
|
||||
node --experimental-transform-types \
|
||||
.agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/studio-body.md
|
||||
node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs <프로젝트> --repo
|
||||
|
||||
수치를 맞추려고 문장을 넣지 마라. 검사기는 표면 패턴만 본다.
|
||||
|
||||
돌려줄 것: 위 JSON 형식. skillEcho 포함.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## S6 — 일한 사람의 목소리
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 6단계를 맡는다.
|
||||
|
||||
먼저 .agents/skills/writing-as-the-person-who-did-it/SKILL.md 를 끝까지 읽어라.
|
||||
references/voice-moves.md 도 읽어라. 그 스킬이 "이것보다 먼저 본다"고 한
|
||||
../rewriting-technical-prose-naturally/references/article-shape.md 를 먼저 읽어라.
|
||||
|
||||
고칠 파일: <기록.md> (제자리에서 고친다)
|
||||
상류 자료: docs/<프로젝트>/final/document.md · <대상 저장소의 커밋 메시지·주석·README>
|
||||
|
||||
찾을 것은 자료에 남아 있는 사람의 흔적이다 — 무엇을 골랐고 무엇과 견주었나,
|
||||
확인하지 못한 것이 무엇이고 그것이 어느 주장에 걸리나, 처음 생각과 어긋난 자리가 있나.
|
||||
|
||||
없는 사람을 만들지 마라. 「처음에는」·「고민 끝에」·「놀랍게도」를 자료 없이 쓰면 지어낸 것이다.
|
||||
넣은 문장마다 그것이 어느 파일 어느 줄에서 왔는지 댈 수 있어야 한다.
|
||||
자료에 흔적이 없으면 아무것도 넣지 말고 그렇게 보고해라. 그것도 이 단계를 한 것이다.
|
||||
|
||||
관문:
|
||||
node .agents/skills/writing-as-the-person-who-did-it/scripts/check_voice.mjs <기록.md>
|
||||
node .agents/skills/rewriting-technical-prose-naturally/scripts/check_prose.mjs --warn <기록.md>
|
||||
python3 scripts/studio-body.py <기록.md> -o /tmp/studio-body.md
|
||||
node --experimental-transform-types \
|
||||
.agents/skills/writing-tech-log-records/scripts/check_body.mjs /tmp/studio-body.md
|
||||
node .agents/skills/writing-tech-log-records/scripts/check_evidence.mjs <프로젝트> --repo
|
||||
|
||||
check_voice.mjs 는 목소리가 모자란지 재지 않는다. 지어낸 목소리를 잡는다.
|
||||
|
||||
돌려줄 것: 위 JSON 형식. skillEcho 포함. 흔적이 없었으면 status DONE 에 notes 로 적어라.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## S7 — Studio 저장
|
||||
|
||||
```
|
||||
너는 Tech Log 파이프라인의 7단계를 맡는다.
|
||||
|
||||
먼저 .agents/skills/publishing-tech-log-to-studio/SKILL.md 를 끝까지 읽어라.
|
||||
references/studio-form-map.md 와 references/playwright-recipes.md 도 읽어라.
|
||||
|
||||
넣을 기록: <기록.md>
|
||||
도구: Playwright MCP (mcp__playwright__browser_*)
|
||||
|
||||
저장까지만 한다. 게시 버튼을 누르지 마라. 한 번 게시한 문서는 취소해도 삭제가 409 로 거절된다.
|
||||
|
||||
상태 레일 aside[class*="studio-document-status"] 가 25초 안에 안 뜨면 인증이 안 된 것이다.
|
||||
로그인 화면에 자격증명을 입력하지 말고 거기서 멈추고 사용자에게 알려라.
|
||||
|
||||
frontmatter 의 id 와 studio: 가 이미 있으면 그 주소로 가라. 새로 만들지 마라.
|
||||
|
||||
그림이 있으면 Asset 을 본문보다 먼저 올려라. 순서를 뒤집으면 미리보기가 본문을 막는다.
|
||||
|
||||
관문:
|
||||
상태 레일의 글자가 저장됨 으로 바뀌는 것을 확인 (최대 30초)
|
||||
python3 scripts/build-tech-log-tree.py <프로젝트>
|
||||
python3 scripts/verify-tech-log-tree.py <프로젝트>
|
||||
|
||||
저장됨 이 안 뜨면 버튼을 다시 누르지 말고 레일의 글자를 그대로 보고해라.
|
||||
|
||||
돌려줄 것: 위 JSON 형식. skillEcho 포함. gates 에 저장 전후 version 을 적어라.
|
||||
```
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"runId": "<YYYY-MM-DD-HHMM>",
|
||||
"project": "<프로젝트>",
|
||||
"record": "<docs/<프로젝트>/tech-log-studio/<주제>/<종류>/<기록>.md — 이 런이 만드는 기록>",
|
||||
"startedAt": "<ISO-8601>",
|
||||
"finishedAt": null,
|
||||
"stages": [
|
||||
{
|
||||
"id": "S1",
|
||||
"name": "코드베이스 → SSOT",
|
||||
"skill": "analyzing-codebase-for-tech-log",
|
||||
"runBy": "subagent",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "S2",
|
||||
"name": "SSOT → 분해 계약",
|
||||
"skill": "deriving-tech-log-root-tree",
|
||||
"runBy": "subagent",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "S3",
|
||||
"name": "글감 → 기록",
|
||||
"skill": "writing-tech-log-records",
|
||||
"runBy": "subagent",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "S4",
|
||||
"name": "기록 → 그림",
|
||||
"skill": "technical-visualizer",
|
||||
"runBy": "subagent",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "S5",
|
||||
"name": "AI 티 제거",
|
||||
"skill": "rewriting-technical-prose-naturally",
|
||||
"runBy": "subagent",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "S6",
|
||||
"name": "일한 사람의 목소리",
|
||||
"skill": "writing-as-the-person-who-did-it",
|
||||
"runBy": "subagent",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": "S7",
|
||||
"name": "Studio 저장",
|
||||
"skill": "publishing-tech-log-to-studio",
|
||||
"runBy": "subagent",
|
||||
"status": "PENDING",
|
||||
"skipReason": "",
|
||||
"skillEcho": "",
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"gates": [],
|
||||
"notes": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -192,6 +192,16 @@ Load supporting guidance only as needed:
|
||||
|
||||
`techviz build ... --document docs/<프로젝트>/final/document.md` 가 그 블록을 갱신한다.
|
||||
|
||||
### Tech Log 파이프라인에서 불릴 때
|
||||
|
||||
`running-tech-log-pipeline` 의 4단계가 이 스킬이다. 입력이 둘이라는 것만 다르다.
|
||||
|
||||
- **무엇을 그릴지는 방금 쓴 기록 본문이 정한다.** 세 관문은
|
||||
`../writing-tech-log-records/references/choosing-a-diagram.md` 에 있고, 그림이 주장하는
|
||||
것을 기록 본문이 말하고 있어야 한다.
|
||||
- **그림의 사실은 SSOT 절이 댄다.** 기록은 SSOT 의 인용이라 줄 번호가 근거가 되지 못한다.
|
||||
`prepare` 에는 `final/document.md` 를 넣고 절은 기록의 `source` 앵커로 지목한다.
|
||||
|
||||
### Tech Log 기록으로 옮길 때
|
||||
|
||||
런의 `document.md` 는 마크다운 이미지로 그림을 싣지만, Studio 기록은 다르다. SVG 를 Studio 에 Asset 으로
|
||||
@@ -203,10 +213,45 @@ Load supporting guidance only as needed:
|
||||
```
|
||||
|
||||
`references/code-tables-diagrams.md`(writing-tech-log-records)의 규칙이 함께 적용된다. 특히
|
||||
**`<text>` 는 이름만 담고 문장은 `<desc>` 와 옆 문단에 둔다.** 이 저장소의 기존 손그림 SVG 는 이 규칙을
|
||||
**`<text>` 는 이름만 담고 문장은 `<desc>` 와 옆 문단에 둔다.** `render` 뒤에 반드시 돌린다 —
|
||||
`spec.json` 의 `label`·`details`·edge `label` 이 그대로 `<text>` 가 되므로 스펙을 쓸 때부터 이름으로 쓴다.
|
||||
|
||||
```bash
|
||||
python3 scripts/check-figure-text.py <프로젝트> # <text> 가 전부 이름인가
|
||||
python3 scripts/check-figure-overlap.py <프로젝트> # 상자와 라벨이 서로를 덮지 않는가
|
||||
```
|
||||
|
||||
### 컴파일한 뒤 반드시 눈으로 본다
|
||||
|
||||
**lint 는 라벨이 상자를 덮는 것을 못 잡는다.** 엣지가 노드를 지나가는 것(`edge-through-node`)은
|
||||
보지만 라벨은 앵커 점만 보고 폭을 재지 않는다. 그래서 `PASS` 인 그림에도 라벨이 상자에 먹히거나
|
||||
경계선 위에 얹히는 일이 생긴다. SVG 를 PNG 로 떠서 본다.
|
||||
|
||||
```bash
|
||||
python3 scripts/preview-figure.py <프로젝트> -o /tmp/figs
|
||||
python3 scripts/preview-figure.py --file 그림.svg
|
||||
```
|
||||
|
||||
지금까지 확인한 것:
|
||||
|
||||
| 증상 | 원인 | 대응 |
|
||||
|---|---|---|
|
||||
| 엣지 라벨이 옆 상자에 먹힌다 | 라벨이 길다 | 라벨을 짧은 이름으로. 자세한 것은 노드 `details` 로 |
|
||||
| 라벨이 group 점선 위에 얹힌다 | `two-zone-pipeline` 은 지역 **안쪽** 엣지 라벨을 캔버스 top 에 고정한다 | 지역 안 엣지를 없애거나 `component-flow` + `groups` 로 바꾼다 |
|
||||
| 원기둥이 제목·항목을 덮는다 | `shape: cylinder` 에 `details` 가 많다 | `details` 를 줄이거나 `shape: box` | 이 저장소의 기존 손그림 SVG 는 이 규칙을
|
||||
어기고 문단과 각주 번호·커밋 해시를 캔버스 안에 넣어 두었다. 다시 만들 때 그 문장들은 본문으로 내린다.
|
||||
|
||||
### 그림을 만들기 전에
|
||||
|
||||
`rewriting-technical-prose-naturally` 의 `## Figures` 를 먼저 읽는다. 옆 문단이 이미 말한 것을 상자와
|
||||
화살표로 다시 그린 그림은 만들지 않는다. 그림이 담아야 할 것은 순서·구조·측정값·실제 산출물이다.
|
||||
|
||||
**표로 되는 것을 그림으로 그리지 않는다.** `comparison` 프로필은 연결성 검사에서 빠지기 때문에
|
||||
항목을 나란히 늘어놓기만 해도 lint 를 통과한다. 그것이 표다 — 표는 값을 비교하고 그림은
|
||||
포함·순서·경계처럼 자리로만 보이는 것을 맡는다. 스펙을 쓰기 전에 묻는다.
|
||||
|
||||
> **관계선을 다 지워도 뜻이 남는가.** 남으면 표다. 마크다운 표로 쓴다.
|
||||
|
||||
`verify-project-layout.py` 의 「표로 되는 그림」이 관계선 없이 항목마다 같은 수의 `details` 를
|
||||
늘어놓은 spec 을 센다. `comparison` 이 맞는 자리는 비교 자체가 자리로 드러나는 때다 — 겹치는
|
||||
범위, 갈라지는 경계처럼.
|
||||
|
||||
@@ -46,16 +46,21 @@ Concept 에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
남겨 두고 나중에 걷어내는 순서가 아니다. **문체를 손보기 전에 문장을 고른다** — 설명이 끝난
|
||||
뒤에 붙은 평가·예고·되풀이·독자 오해 가정을 먼저 뺀다(`ai-tells.md` 첫 절). 문체 규칙의
|
||||
정본은 `ai-tells.md` 다.
|
||||
**그림이 필요한지, 필요하면 무엇을 그릴지는 `references/choosing-a-diagram.md`.** 자리(Case·
|
||||
Concept 만) · 표가 아닌지 · 옆 문단이 이미 말하지 않았는지 세 관문을 지나야 그린다.
|
||||
순서·경계·상태 전이처럼 문장만으로 따라가기 어려운 관계가 있으면 **`final/assets/` 에 그
|
||||
그림이 이미 있는지부터 본다.** SSOT 를 만들 때 그려 둔 것이 있고 계약의 `ssot-assets` 가 이
|
||||
글감에 배정해 두었으면 그것을 쓴다 — `final/assets/tech-log-studio/` 로 복사하고 기록의
|
||||
`assets` 가 그 사본을 가리킨다. **없을 때만** `technical-visualizer` 로 새로 만든다. 손으로
|
||||
글감에 배정해 두었으면 기록의 `assets` 가 **그 파일을 그대로** 가리킨다. 사본을 따로 만들지
|
||||
않는다 — 사본에는 `.techviz/<이름>/` 이 없어 다시 만들 수 없다. **없을 때만**
|
||||
`technical-visualizer` 로 새로 만든다. 손으로
|
||||
SVG 를 그리지 않고, 모든 글에 그림을 만들지도 않는다.
|
||||
인용할 측정도 같다. `final/evidence/` 에 있는 원문을 가리키고 같은 것을 다시 돌리지 않는다.
|
||||
**그림과 측정을 그 자리에서 만들어 내기 전에 SSOT 가 이미 가진 것을 먼저 찾는다** — keycloak
|
||||
에서는 그러지 않아 정본까지 갖춘 그림 13 장이 남고 이름이 다른 그림 5 장이 새로 만들어졌다.
|
||||
4. **검사** — 셋 다 돌린다. 파서와 문장과 증빙은 각각 다른 것을 본다.
|
||||
- `scripts/check_body.mjs` — Case 본문이 Studio 파서를 통과하는지. 같은 파서를 그대로 부른다.
|
||||
- `scripts/check_body.mjs` — 본문이 Studio 파서를 통과하는지. 같은 파서를 그대로 부른다.
|
||||
저장소의 그림은 마크다운 이미지이므로 `python3 scripts/studio-body.py <기록> -o /tmp/x.md`
|
||||
로 바꾼 파일에 돌린다. 저장소 파일에 그대로 돌리면 `unsafe image URL` 로 실패한다.
|
||||
- `rewriting-technical-prose-naturally/scripts/check_prose.mjs` — 문장 규범. **error 0 이 될 때까지 고친다.**
|
||||
칸 하나나 한 절만 고쳤으면 `--doc` 없이 부른다. 이어서 `style_profile.mjs` 로 문체 수치를 본다.
|
||||
- `scripts/check_evidence.mjs <프로젝트> --repo` — **인용한 것이 실재하는지.** 본문 코드블록의
|
||||
@@ -77,7 +82,7 @@ Concept 에 담고 `관계`로 가리킨다. `references/record-kinds.md`
|
||||
|---|---|---|
|
||||
| `deriving-tech-log-root-tree` | 후보에 처분을 매기고 `PROMOTE` 를 글감으로 올린다 | 글을 쓰지 않는다 |
|
||||
| `writing-tech-log-records` | 종류를 고르고 칸과 본문을 쓴다. `explaining.md`·`ai-tells.md` 를 처음부터 적용한다 | — |
|
||||
| `technical-visualizer` | 문장으로 따라가기 어려운 관계를 그림으로 만든다 | 모든 글에 그림을 붙이지 않는다 |
|
||||
| `technical-visualizer` | 문장으로 따라가기 어려운 관계를 그림으로 만든다 | 무엇을 그릴지 정하지 않는다 — `choosing-a-diagram.md` 가 정한다 |
|
||||
| `rewriting-technical-prose-naturally` | 사실과 구조가 이미 맞는 초안의 번역투·반복 문형·과한 대구를 고친다 | 분류가 틀렸거나 근거가 모자란 것은 못 고친다 |
|
||||
| `writing-as-the-person-who-did-it` | 자료에 남아 있는 선택·비교·어긋남·확인하지 못한 범위를 제자리에 놓는다 | 자료에 없는 「처음에는」·「고민 끝에」를 만들지 않는다 |
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# 기록에 어떤 그림이 필요한지 정하는 기준
|
||||
|
||||
`code-tables-diagrams.md` 는 그림을 **어떻게** 그리는지를 말한다. 이 문서는 그 앞 단계 —
|
||||
**이 기록에 그림이 필요한가, 필요하다면 무엇을 그리는가** 를 정한다.
|
||||
|
||||
## 세 관문
|
||||
|
||||
순서대로 통과해야 그림을 만든다. 하나라도 걸리면 그리지 않는다.
|
||||
|
||||
### 1. 자리가 있는가
|
||||
|
||||
`assets` 는 본문이 있는 두 종류만 갖는다 — **Case 와 Concept**. Reference·Question·Decision 의
|
||||
칸은 평문으로 렌더링돼 그림이 들어갈 자리가 없다.
|
||||
|
||||
파생 기록에 그림이 필요해 보이면 그 그림은 **짝이 되는 Case 나 Concept 의 것**이다. 거기 담고
|
||||
`관계`로 가리킨다. 담을 Case 나 Concept 이 없으면 그 그림은 아직 집이 없다 — 계약의
|
||||
`assetLedger.unassigned` 에 그렇게 적고, 새 글감을 세울지는 따로 판단한다.
|
||||
|
||||
### 2. 표가 아닌가
|
||||
|
||||
> **관계선을 다 지워도 뜻이 남는가.** 남으면 표다.
|
||||
|
||||
표는 값을 비교하고, 그림은 **포함·순서·경계**처럼 자리로만 보이는 것을 맡는다. 항목을 같은
|
||||
속성으로 늘어놓은 것은 마크다운 표로 쓴다. `verify-project-layout.py` 의 「표로 되는 그림」이
|
||||
관계선 없이 항목마다 같은 수의 `details` 를 늘어놓은 spec 을 센다.
|
||||
|
||||
### 3. 옆 문단이 이미 말하지 않았는가
|
||||
|
||||
> **이 그림이 없으면 독자가 무엇을 못 보나.** 한 문장으로 답할 수 없으면 그리지 않는다.
|
||||
|
||||
기록에 이미 그 비교표가 있으면 그림은 중복이다. 실제로 그렇게 만든 그림 셋을 지웠다 —
|
||||
`keyset-vs-offset` 을 넣은 기록에는 훑은 행·buffers·exec 까지 있는 플랜 비교표가 이미 있었다.
|
||||
|
||||
## 종류마다 무엇을 그리나
|
||||
|
||||
세 관문을 통과했을 때, 그 기록이 요구하는 그림은 종류마다 다르다.
|
||||
|
||||
| 종류 | 그림이 답하는 물음 | 흔한 profile |
|
||||
|---|---|---|
|
||||
| **Case** | 이 요청 한 번이 어떤 순서로 무엇을 지나갔나 | `sequence` · `component-flow` |
|
||||
| **Case** (경계가 논지일 때) | 무엇이 어느 경계 안에 있고 무엇이 밖에 있나 | `two-zone-pipeline` |
|
||||
| **Concept** | 남의 것이 어떤 순서·구조로 동작하나 | `sequence` · `component-flow` · `ports-adapters` |
|
||||
|
||||
Case 는 **내가 돌려서 본 것**이라 대개 순서가 논지다. Concept 은 **남의 것이 어떻게 동작하는지**라
|
||||
구조나 변환 사슬이 논지다. 어느 쪽이든 「무엇이 무엇으로 바뀌는가」를 못 적으면 아직 그릴 것이
|
||||
없다는 뜻이다.
|
||||
|
||||
**한 절에 그림 하나.** 같은 절에 구조 그림과 흐름 그림을 둘 다 넣으면 독자가 어느 쪽을 먼저
|
||||
읽어야 하는지 알 수 없다. 둘 다 필요하면 절을 나눈다.
|
||||
|
||||
## 어디를 근거로 삼나 — 기록의 `source` 가 앵커다
|
||||
|
||||
`techviz prepare` 는 `final/document.md` 를 받는다. 기록은 `tech-log-studio/` 에 있지만 **그림의
|
||||
근거는 기록이 아니라 기록이 가리키는 SSOT 절**이다. 기록의 `source` 앵커를 그대로 쓴다.
|
||||
|
||||
```bash
|
||||
# 기록의 source: final/document.md#선택의-이유와-지킨-경계-ap1 이면
|
||||
./scripts/techviz prepare docs/<프로젝트>/final/document.md \
|
||||
--heading "AP1: OAuth와 JWT 계약을 가장 가까이서 관찰한다" \
|
||||
-o docs/<프로젝트>/final/.techviz/<id>/context.json
|
||||
```
|
||||
|
||||
`--line` 은 쓰지 않는다. context 는 관리 블록을 접은 좌표를 쓰므로 파일의 줄 번호와 어긋난다.
|
||||
`--heading` 이나 `--marker` 로 절을 지목한다.
|
||||
|
||||
**그림이 주장하는 것을 기록 본문이 말해야 한다.** SSOT 에 근거가 있어도 기록이 그 단계를 적지
|
||||
않았으면 그림만 넣지 않는다 — 설명 없는 주장이 남는다. 순서는 하나다.
|
||||
|
||||
> 본문을 먼저 보강한다 → 그다음 그림을 붙인다.
|
||||
|
||||
`ap1-browser-bearer-flow` 가 그랬다. 마지막 단계인 `/api/me` 응답 4필드를 기록이 말하지 않아
|
||||
붙이지 못하고 있다가, SSOT §474 를 근거로 본문에 한 줄을 더한 뒤에 붙였다.
|
||||
|
||||
## 만든 뒤
|
||||
|
||||
`code-tables-diagrams.md` 의 규범과 아래 둘을 함께 돌린다. lint 는 라벨이 상자를 덮는 것을
|
||||
못 잡는다.
|
||||
|
||||
```bash
|
||||
python3 scripts/check-figure-text.py <프로젝트> # <text> 가 전부 이름인가
|
||||
python3 scripts/preview-figure.py <프로젝트> -o /tmp/figs # PNG 로 떠서 눈으로 본다
|
||||
```
|
||||
@@ -160,6 +160,28 @@ SVG는 `image/svg+xml`로 올라가고 다른 이미지와 같게 다뤄진다.
|
||||
|
||||
`READY`가 아닌 Asset은 게시 시 거절된다.
|
||||
|
||||
### 저장소의 `.md` 에는 `:::evidence` 를 쓰지 않는다
|
||||
|
||||
`:::evidence`는 Studio 렌더러의 구문이다. 저장소의 `.md`를 그 형태로 쓰면 편집기에서 그림이
|
||||
보이지 않고 구문이 글자로 남는다. **저장소는 읽는 형태로 쓴다.**
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
`alt`는 그림의 `alt`를 그대로 쓰고, 경로는 frontmatter `assets`의 `file`과 같아야 한다.
|
||||
|
||||
Studio 로 보낼 때만 `:::evidence` 로 바꾼다. 손으로 고치지 않는다.
|
||||
|
||||
```bash
|
||||
python3 scripts/studio-body.py <기록.md> -o /tmp/x.md # check_body 는 이 파일에 돌린다
|
||||
python3 scripts/studio-body.py <기록.md> --body-only # Studio 에 붙여넣을 본문
|
||||
python3 scripts/studio-body.py <기록.md> --key a=a-1a2b3c4d # 서버가 준 키로
|
||||
```
|
||||
|
||||
Studio 파서는 상대 경로 이미지를 `unsafe image URL`로 거절하므로 `check_body.mjs`는 **바꾼
|
||||
파일**에 돌린다. 저장소 파일에 그대로 돌리면 그림 자리에서 실패한다.
|
||||
|
||||
## 일반 이미지
|
||||
|
||||
Asset이 아닌 그림은 Markdown으로 쓴다.
|
||||
|
||||
@@ -62,7 +62,7 @@ topicName: OAuth/OIDC 인증 경계
|
||||
```yaml
|
||||
assets:
|
||||
- key: eager-lazy-query-sequence # 본문의 :::evidence key 와 같은 값
|
||||
file: ../../../final/assets/tech-log-studio/eager-lazy-query-sequence.svg
|
||||
file: ../../../final/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/explain/highlights-child-plan-A.txt
|
||||
```
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
- [ ] 그림이 있는 문단에 글을 섞지 않았다
|
||||
- [ ] `alt`가 무엇이 보이는지 말한다
|
||||
- [ ] 그림 안 `<text>`가 전부 이름이다. 문장도 숫자도 없다 (`<title>`·`<desc>`는 예외)
|
||||
- [ ] 눈으로만 보지 않고 `python3 scripts/check-figure-text.py <프로젝트>` 를 돌렸다
|
||||
- [ ] 관계선을 다 지워도 뜻이 남는 그림이 아니다 (남으면 표다 — 마크다운 표로 쓴다)
|
||||
- [ ] `python3 scripts/preview-figure.py`로 PNG 를 떠서 눈으로 봤다 (lint 는 라벨이 상자를 덮는 것을 못 잡는다)
|
||||
- [ ] 본문의 그림이 마크다운 이미지다 (`:::evidence` 는 Studio 로 보낼 때 `studio-body.py` 가 만든다)
|
||||
- [ ] 코드에 자격증명·토큰·내부 호스트가 없다. 지운 자리가 보인다
|
||||
- [ ] 제목 id와 표 id가 겹치지 않는다
|
||||
|
||||
|
||||
@@ -46,6 +46,31 @@ analysis material in one file. Only the first is candidate material.
|
||||
`excluded` names the parts that are evidence rather than candidates. A node may cite an
|
||||
anchor from an excluded part in `source`; it may not exist because of one.
|
||||
|
||||
`excludedAnchorPattern` is optional and is the only field the verifier can act on. Without it
|
||||
the rule above is a sentence nobody enforces — the check that a node did not come *only* from
|
||||
outside the scope is switched off entirely.
|
||||
|
||||
```json
|
||||
"candidateScope": {
|
||||
"document": "final/document.md",
|
||||
"sections": ["§3", "§4"],
|
||||
"excluded": ["제2부 — 모듈 분석 전문"],
|
||||
"excludedAnchorPattern": "#a[0-9]+$"
|
||||
}
|
||||
```
|
||||
|
||||
It is a regular expression matched against each `source` anchor. A node whose anchors *all*
|
||||
match it is an error: it was promoted from evidence, not from the candidate scope.
|
||||
|
||||
## Anchors resolve to real sections
|
||||
|
||||
`source` and `sourceRefs` anchors are checked against the SSOT's own headings when the project
|
||||
writes them as heading slugs (`#검토한-선택지와-막힌-지점-ap1` = the h2 slug plus a
|
||||
discriminator). **Keep one anchor style per project.** A project that numbers its anchors
|
||||
(`#§1.1`, `#10-2`, `#a18`) gets a warning instead — the verifier cannot tell whether the
|
||||
section it names exists, and the diagram stage cannot translate the anchor into a
|
||||
`techviz prepare --heading` value without a person reading it.
|
||||
|
||||
## Topics
|
||||
|
||||
Each Topic has `topic` (its key), `title`, **`readerQuestion`**, and `kinds` with the five
|
||||
|
||||
@@ -165,6 +165,8 @@ basisVersion: oauth2-proxy 7.15.2 · Nginx auth_request
|
||||
|
||||
## 본문이 있는 두 종류의 공통 규칙
|
||||
|
||||
- **그림이 필요한지와 무엇을 그릴지는 `choosing-a-diagram.md` 가 정한다** — 종류마다 그림이
|
||||
답해야 할 물음이 다르다. Case 는 순서, Concept 은 구조나 변환 사슬이 대개 논지다
|
||||
- 그림은 `technical-visualizer` 로 만든다. 손으로 SVG 를 그리지 않는다
|
||||
- 그림 안에는 이름만 넣는다. 문장은 `<desc>` 와 옆 문단에 둔다
|
||||
- 그림과 증거는 frontmatter 의 `assets` · `evidence` 로 잇는다. 같은 파일을 기록 옆에 복사하지 않는다
|
||||
|
||||
Reference in New Issue
Block a user