diff --git a/.agents/skills/analyzing-codebase-for-tech-log/SKILL.md b/.agents/skills/analyzing-codebase-for-tech-log/SKILL.md index 822616d..8acd15e 100644 --- a/.agents/skills/analyzing-codebase-for-tech-log/SKILL.md +++ b/.agents/skills/analyzing-codebase-for-tech-log/SKILL.md @@ -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. diff --git a/.agents/skills/analyzing-codebase-for-tech-log/references/evidence-contract.md b/.agents/skills/analyzing-codebase-for-tech-log/references/evidence-contract.md index 915b767..c52fd4b 100644 --- a/.agents/skills/analyzing-codebase-for-tech-log/references/evidence-contract.md +++ b/.agents/skills/analyzing-codebase-for-tech-log/references/evidence-contract.md @@ -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. diff --git a/.agents/skills/analyzing-codebase-for-tech-log/templates/analysis/00-project-overview.md b/.agents/skills/analyzing-codebase-for-tech-log/templates/analysis/00-project-overview.md index a3ee3ec..45b5c75 100644 --- a/.agents/skills/analyzing-codebase-for-tech-log/templates/analysis/00-project-overview.md +++ b/.agents/skills/analyzing-codebase-for-tech-log/templates/analysis/00-project-overview.md @@ -2,7 +2,7 @@ ## 분석 기준 revision -- repository: `/shared/codebase/` +- repository: `<분석 대상 저장소의 절대 경로>` - revision: `` ## Build and module map diff --git a/.agents/skills/analyzing-codebase-for-tech-log/templates/state.json b/.agents/skills/analyzing-codebase-for-tech-log/templates/state.json index 5244a94..8e09c77 100644 --- a/.agents/skills/analyzing-codebase-for-tech-log/templates/state.json +++ b/.agents/skills/analyzing-codebase-for-tech-log/templates/state.json @@ -1,7 +1,7 @@ { "schemaVersion": 2, "project": "", - "codebasePath": "/shared/codebase/", + "codebasePath": "<분석 대상 저장소의 절대 경로>", "gitRevision": null, "analysisStatus": "NOT_STARTED", "analysisCycle": 1, diff --git a/.agents/skills/deriving-tech-log-root-tree/SKILL.md b/.agents/skills/deriving-tech-log-root-tree/SKILL.md index 5172c28..e7ba951 100644 --- a/.agents/skills/deriving-tech-log-root-tree/SKILL.md +++ b/.agents/skills/deriving-tech-log-root-tree/SKILL.md @@ -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 ` — errors must be 0. +12. `python3 scripts/build-tech-log-tree.py ` first, then + `python3 scripts/verify-tech-log-tree.py ` — 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. diff --git a/.agents/skills/publishing-tech-log-to-studio/SKILL.md b/.agents/skills/publishing-tech-log-to-studio/SKILL.md new file mode 100644 index 0000000..4acdbd6 --- /dev/null +++ b/.agents/skills/publishing-tech-log-to-studio/SKILL.md @@ -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//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: `. 다른 경로로 올렸으면 편집 화면을 +한 번 새로 고쳐야 목록을 다시 받는다. 본문 문법 문제로 오해하기 쉽다. + +본문은 서버가 준 키로 바꿔서 넣는다. + +```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)의 칸은 평문으로 렌더링된다.** 백틱과 +파이프가 글자 그대로 보이고, 줄바꿈은 `
` 로만 살아난다. + +### 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` 글자를 읽어 보고한다 | diff --git a/.agents/skills/publishing-tech-log-to-studio/references/playwright-recipes.md b/.agents/skills/publishing-tech-log-to-studio/references/playwright-recipes.md new file mode 100644 index 0000000..aa62efe --- /dev/null +++ b/.agents/skills/publishing-tech-log-to-studio/references/playwright-recipes.md @@ -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//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 + +
+
+``` + +`요약` · `본문 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` 을 쓰지 않는다 + +칸이 `