Work Doc Publishing — 저장소 HTML 작업 문서 규약
PR 작업내역과 이슈 상세 기획을 대상 저장소의 자기완결 HTML 로 남기는 방법 — 경로·커밋·링크·민감정보·폴백 — 을 정한다.
파일 이름(
artifact-publishing)은 과거 방식(claude.ai 아티팩트)에서 유래했다 — 지금은 대상 저장소에 커밋하는 자기완결 HTML 이고, 여러 플러그인과 생성 문서가 이 경로를 참조해 이름만 그대로 둔다.작업 문서(PR 작업내역·이슈 상세 기획)를 남기는 모든 지점이 공유하는 단일 SoT. 무엇을 싣는가는 표면별 문서가 정하고, 어떻게 남기는가(아래)는 복제하지 않고 이 문서를 참조한다. 이 규약을 다른 파일에 옮겨 적으면 다음 변경 때 사본들이 서로 어긋난다.
PR 작업내역과 이슈 상세 기획을 대상 저장소의 자기완결 HTML 로 남기는 방법 — 경로·커밋·링크·민감정보·폴백 — 을 정한다.
0. 표면 목록 (consumers)#
| 표면 | 파일 | 표면 기록 | 무엇을 싣는가 (표면별 SoT) |
|---|---|---|---|
| PR 작업내역 | .claude/docs/{scope}/pr-work-{issue_number}.html |
PR 본문 ## 📄 작업내역 — PR 을 만들 때 이미 들어 있다 |
plugins/cc-dev/skills/pr-work-artifact/SKILL.md |
| 이슈 상세 기획 | .claude/docs/{scope}/issue-{number}.html |
이슈 본문 ## 📄 상세 기획 — 작업 브랜치가 생긴 뒤 본문 1회 수정 |
plugins/cc-dev/rules/zenhub-conventions.md → "Issue Body HTML Contract" |
새 표면을 추가할 때는 표면 문서에 무엇을 싣는지만 쓰고, 1~11 절은 여기를 링크한다.
1. 저장소 파일은 저장소 권한을 따른다 ⚠️#
작업 문서는 대상 저장소에 커밋된다. 그 저장소를 읽을 수 있는 사람은 모두 읽는다 — 공개 저장소면 공개 게시다(포크·클론·미러 포함).
- git 이력은 영구다. 다음 커밋에서 지워도 이력·PR diff·포크·다른 사람의 클론에 남는다. 되돌리기가 외부 게시물보다 어렵다 — 새어 나간 시크릿은 삭제가 아니라 회전(rotate) 으로 대응한다.
- 절대 싣지 않는다: 토큰·시크릿·API 키, 내부 전용 URL·호스트명, 고객/개인 데이터(로그·스크린샷· 원문 인용 속에 섞인 것 포함), 아직 공개되지 않은 보안 취약점의 재현 절차.
-
판정이 애매하면 싣지 않고 저장소 경로만 가리킨다 (
.claude/..., 파일:줄 참조). 링크가 부족해서 사람이 한 번 더 묻는 실패가, 시크릿이 이력에 영구히 남는 실패보다 싸다.
2. 위치와 이름은 고정 — 갱신은 같은 파일#
| 항목 | 규칙 |
|---|---|
| 경로 | §0 표 그대로. {scope} 는 기능 스코프, 번호는 이슈 번호다 (PR 번호는 PR 을 만들기 전이라 아직 없다) |
| 찾기 |
scope 추론은 세션마다 갈릴 수 있으므로
번호 글롭
으로 찾는다:
.claude/docs/*/issue-{number}.html
·
.claude/docs/*/pr-work-{number}.html
|
| 갱신 | 같은 파일을 고친다. 새 경로 금지 — 표면의 링크가 낡은 판을 가리키게 된다 |
<title> | 갱신 간 고정 |
3. 기계가 읽으면 본문, 사람만 읽으면 HTML#
자동 파이프라인이 파싱하는 것(AC·범위·DoD·우선순위 표·Closes #·부모/자식 참조·테스트 판정)은
언제나 원본 표면(이슈 본문 / PR body)에 남는다. HTML 은 사람이 읽는 서술만 담는다.
- 본문은 HTML 없이도 읽혀야 한다 — 짧은 요약(2~3줄)은 본문에 남긴다. 이슈 상세 기획은 브랜치가 생기기 전까지 로컬에만 있어(§6) 그동안 아무도 열 수 없다.
- AC 는 본문에서만 파싱한다. HTML 을 AC 의 출처로 쓰지 않는다 — 다른 워크트리·머신에는 없을 수 있다.
- 애매하면 본문에 남긴다.
4. 자기완결 문서 — 감싸 주는 골격이 없다#
파일을 브라우저로 여는 것이 전부라, 자동으로 붙는 문서 골격이 없다. 필요한 것은 전부 파일 안에 쓴다.
-
<!doctype html>·<meta charset="utf-8">· viewport ·<title>을 직접 쓴다. -
CSS·JS 는 인라인, 이미지는 압축한 data URI. 외부 CDN·웹폰트·원격 이미지에 기대지 않는다 —
오프라인에서도, 몇 년 뒤에도 같은 모습이어야 한다. 다이어그램은 인라인 SVG 로 그린다
(mermaid 같은 스크립트 렌더러를 싣지 않는다). 다른 페이지로 가는
<a href>링크는 괜찮다. - 라이트/다크(
prefers-color-scheme) 둘 다, 좁은 화면(모바일 폭)에서 페이지 가로 스크롤 없이 읽혀야 한다. - 크기는 작게 — 권장 1MB 이하. git 에 영구히 쌓인다. 스크린샷은 JPEG/WebP 로 줄이고(가로 1200px 안팎), 그래도 크면 장수를 줄이거나 빼고 로컬 경로만 적는다.
최소 골격 (여기서 시작해 섹션만 채운다):
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>#{number} {작업내역|상세 기획} — {제목}</title>
<style>
:root { color-scheme: light dark;
--fg: #1f2328; --muted: #59636e; --bg: #ffffff; --line: #d1d9e0; --code: #f6f8fa; }
@media (prefers-color-scheme: dark) {
:root { --fg: #e6edf3; --muted: #9198a1; --bg: #0d1117; --line: #3d444d; --code: #151b23; }
}
body { margin: 0; background: var(--bg); color: var(--fg);
font: 16px/1.6 system-ui, -apple-system, "Apple SD Gothic Neo", sans-serif; }
main { max-width: 72ch; margin: 0 auto; padding: 24px 16px; }
table { border-collapse: collapse; display: block; overflow-x: auto; }
th, td { border: 1px solid var(--line); padding: 6px 10px; text-align: left; }
pre, code { background: var(--code); border-radius: 6px; }
pre { padding: 12px; overflow-x: auto; }
img, svg { max-width: 100%; height: auto; }
.muted { color: var(--muted); }
</style>
</head>
<body><main>
<h1>{제목}</h1>
<p class="muted">#{number} · {branch} → {base}</p>
<!-- 섹션은 표면별 SoT 가 정한다 -->
</main></body>
</html>
5. 절차 — 작성 → 점검 → 커밋 → push → 표면 기록 (순서 고정)#
- 작성 — §2 경로에 §4 형식으로 쓴다.
-
커밋 전 점검 — 하나라도 걸리면 커밋하지 않고 고친다(못 고치면 §10 폴백):
DOC=".claude/docs/{scope}/{file}.html" # ① 시크릿·토큰 패턴 (§1) — 걸리면 그 부분을 지운다. 패턴에 안 걸리는 내부 URL·개인 데이터는 사람이 본다 grep -nE 'gh[pousr]_[A-Za-z0-9]{20,}|github_pat_|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY|xox[abpr]-|sk-[A-Za-z0-9]{20,}' "$DOC" # ② 외부 자원 (§4) — 출력이 있으면 자기완결 위반 grep -nE '<script[^>]* src=|<link[^>]* href="https?:|<img[^>]* src="https?:|url\(.?https?:|@import' "$DOC" # ③ 크기 (§4) — 1MB 초과면 이미지부터 줄인다 [ "$(wc -c < "$DOC")" -le 1048576 ] || echo "⚠️ 1MB 초과" # ④ ignore 대상인가 — 대상이면 `git add -f` 로 넣지 않는다(저장소의 의도를 뒤집지 않는다) → §10 폴백 git check-ignore -q "$DOC" && echo "ignore 대상" -
커밋 — 작업 문서만 담은 문서 커밋. 메시지는 저장소 커밋 규약(commit-msg 훅)을 따른다
(예:
docs({scope}): 📝 작업내역 (#{n})).Closes #같은 자동 종료 키워드는 넣지 않는다. - push
- 표면 기록 — §7 링크 블록(경로 + GitHub 링크)을 표면에 남긴다.
| 표면 | 언제 커밋하나 | 표면 기록 |
|---|---|---|
| PR 작업내역 | PR 을 만들기 직전, 로컬 게이트를 통과한 PR 브랜치에 | 링크 블록을 넣은 채로 PR 을 생성한다 — 사후 수정 없음 |
| 이슈 상세 기획 | 그 이슈의 작업 브랜치를 확보한 직후 — 새로 만든 브랜치면 첫 커밋, 이미 있던 브랜치를 채택했으면 그 위 다음 커밋 | 이슈 본문을 한 번 수정해 링크 블록을 덧붙인다 — 자리는 통일 템플릿 1번(H1 바로 아래) |
- 브랜치 확보(생성·push·대장 기록)와 문서 커밋은 분리한다. 브랜치를 먼저 확보하고 그 위에 문서 커밋을 얹는다. 둘을 섞으면, 두 머신이 같은 계층 브랜치를 동시에 만들 때 같은 경로를 서로 다른 내용으로 더해 경합 해소 rebase 가 충돌한다. 문서 커밋의 push 가 거부되면 원격을 받아 보고, 문서가 이미 있으면 내 문서 커밋은 버린다(먼저 올라간 쪽이 정본).
- 멱등 — 표면에 이미 링크 블록이 있으면 아무것도 하지 않는다(재개·다른 머신이 이미 붙였다). 같은 파일이 그 브랜치에 이미 커밋돼 있으면(앞선 실행이 본문 수정 직전에 끊겼다) 커밋은 건너뛰고 표면 기록만 한다.
-
작업 문서만 담은 커밋의 push 는 소스 게이트(예:
run.mdStep 8.5 Per-Push Gate)를 다시 돌리지 않는다 — 게이트가 판정하는 소스가 바뀌지 않았다. 소스가 한 줄이라도 섞였으면 돌린다.
6. 대기 중인 이슈 HTML — 브랜치가 아직 없을 때#
계획만 세우는 단계(/cc-dev:zenhub:breakdown · /cc-dev:go --plan-only · /cc-dev:zenhub:triage)에는
커밋할 브랜치가 없다(기본 브랜치에 직접 커밋하지 않는다). 이 상태는 degradation 이 아니라 대기다.
- HTML 은 §2 경로에 로컬로만 쓴다(추적하지 않는다). 이슈 본문은 계약 블록 + 짧은 요약만.
-
브랜치를 만드는 쪽(
/cc-dev:runStep 4 ·/cc-dev:batchPhase 1.5)이 번호 글롭으로 그 파일을 찾아 §5 절차로 첫 커밋에 넣고 링크를 붙인다. -
워커 워크트리로 넘길 때는 복사가 아니라 이동한다. 원본이 넘겨준 쪽 트리에 남아 있으면, 그 파일이
커밋되어 돌아오는 머지 후
git pull이untracked working tree files would be overwritten으로 멈춘다. 이미 멈췄다면 커밋된 판이 정본이다 — 로컬 사본을 치우고(삭제·이동) 다시 pull 한다. - 로컬 파일이 없으면(다른 워크트리·머신) 이슈 본문에 서술이 20행 넘게 남아 있을 때만 그것으로 다시 만들고, 아니면 건너뛴다 — 비차단, 로그 한 줄. 짧은 문서에 링크를 다는 것은 오버헤드다.
브랜치를 확보한 쪽이 부르는 단일 정의다(run.md Step 4-5 · batch.md Phase 1.5-2.5 — 둘에 복제하지 않는다):
async function attachIssueDetailDoc(issue, { narrative = null, pending }) {
if (bodyHasDetailLink(issue)) return "skip:linked"; // 멱등 — 이미 붙었다
if (issue.issueType === "Sub-task") return "skip:n/a"; // 계층별 적용 (zenhub-conventions)
const doc = narrative && lineCount(narrative) > 20
? writeDoc(issue, narrative) // 이번 실행이 들고 온 서술
: findLocal(pending) // 계획 단계가 남긴 대기 파일 (번호 글롭)
?? rebuildFromBody(issue); // 본문 서술이 20행 넘을 때만, 아니면 null
if (!doc) { log("ℹ️ 상세 기획 HTML 건너뜀 — 원고 없음"); return "skip:no-source"; }
const r = await commitAndPush(doc); // §5 점검 → 문서 커밋 → push (이미 커밋돼 있으면 커밋만 건너뜀)
return r.ok
? editIssueBodyOnce(issue, linkBlock(doc, r.branch)) // H1 바로 아래 — §7
: editIssueBodyOnce(issue, inlineMarkdown(doc)); // §10 폴백 (r.reason 을 로그에)
}
7. 링크 블록 (고정 형식 — 표면 공통)#
## 📄 {작업내역 | 상세 기획}
- 경로: `.claude/docs/{scope}/{file}.html`
- 링크: https://github.com/{owner}/{repo}/blob/{branch}/.claude/docs/{scope}/{file}.html
↳ {HTML 에 담긴 섹션 목록}
> GitHub 은 HTML 을 소스로 보여 준다 — 렌더된 화면은 체크아웃에서 `open .claude/docs/{scope}/{file}.html` 로 연다.
{owner}/{repo}—gh repo view --json nameWithOwner -q .nameWithOwner{branch}— 문서를 커밋한 브랜치. PR 이면 head 브랜치, 이슈면 그 이슈의 작업 브랜치- ⚠️ 브랜치 링크는 머지 후 브랜치가 지워지면 404 가 된다. 경로가 영구 식별자다 — 머지되면 같은 경로가 base 브랜치에 있다. 그래서 둘을 함께 적는다.
- 공유 안내 문구(비공개·공유 켜기)는 없다 — 저장소 권한이 곧 공개 범위다(§1).
8. 커밋 수로 판정하는 게이트는 작업 문서 커밋을 세지 않는다 ⚠️#
문서 커밋은 PR 브랜치·컨테이너 브랜치에 "커밋이 있다"는 사실을 만든다. 그대로 세면 구현 커밋이 없는
PR 이 "커밋 0건" 게이트를 통과하고, 자식이 다른 부모로 머지돼 비어 있는 컨테이너 브랜치가 "빈 diff" 로
드러나지 않는다. 커밋 수·빈 diff 로 판정하는 자리는 이 두 종류의 파일만 바꾼 커밋을 빼고 센다 —
run.md Step 0.5(계층 브랜치 "커밋 0개" 판정)·Step 9 Pre-PR 4번·S12b, batch.md
Phase 3-4.95,
skills/branch-hierarchy R4(작업이 쌓인 후보)·R7(retarget 후 커밋 0건):
git rev-list --count "origin/${BASE}..${HEAD_REF}" -- . \
':(exclude,glob).claude/docs/*/pr-work-*.html' \
':(exclude,glob).claude/docs/*/issue-*.html'
같은 디렉터리의 다른 산출물(design-decisions.md 등)은 구현의 일부이므로 그대로 센다.
9. 갱신 — 실시간 상태는 싣지 않는다#
- 실시간 CI 상태를 싣지 않는다. 문서를 고치면 커밋 → push → CI 재실행 → 문서가 다시 낡는 순환이 된다. CI 결과의 정본은 PR 체크다. 커밋 시점에 확정된 사실(PR 전 로컬 게이트 결과 등)만 싣는다.
- 재작업 push 때 문서를 고쳐야 하면 코드 수정과 같은 커밋에 넣는다. 문서만을 위한 추가 커밋·push 는 하지 않는다 — 열린 PR 의 브랜치라 그 push 가 CI 를 한 번 더 돌린다. 바뀐 것이 없으면 손대지 않는다.
- 이슈 상세 기획도 같은 원칙이다 — 스펙이 바뀌면 같은 파일을 고치되, 그 브랜치에 열린 PR 이 있으면 다음 코드 커밋에 함께 넣는다. 표면의 링크 블록은 다시 고치지 않는다(경로가 그대로다).
10. Degradation Contract — 비차단#
작업 문서가 워크플로를 막는 일은 없다.
| 상황 | 행동 |
|---|---|
| 계획 단계라 브랜치가 아직 없다 | degradation 이 아니다 — §6 대기 |
| 커밋·push 불가 — 작업 브랜치를 확보하지 못함 · 문서 커밋 push 실패(1회 재시도 후) · 줄여도 1MB 를 넘는 파일 · 경로가 ignore 대상 |
서술을
마크다운으로 표면에 인라인
하고 계속한다 (PR: 본문
## 📄 작업내역 (마크다운 폴백)
, 이슈: 본문 1회 수정으로
## 📄 상세 기획
아래 인라인)
|
| 로컬 파일 없음 (다른 워크트리·머신) | §6-4 |
| 표면 기록 실패 (이슈 본문 수정 실패) | 1회 재시도 → 실패하면 경로를 로그에 남기고 계속. 파일은 번호 글롭(§2)으로 찾을 수 있다 |
폴백할 때 로그 한 줄을 남긴다:
ℹ️ 작업 문서 HTML 미커밋 — 마크다운으로 인라인 (사유: 브랜치 없음 | push 실패 | 파일 과대 | ignore 대상)
11. 금지 사항 요약#
- ❌ §1 의 민감정보 싣기 — 이력은 영구다
- ❌ 기계가 파싱하는 계약(AC·DoD·우선순위 표)을 HTML 로만 두기
- ❌ 실시간 CI 상태 싣기 · 문서만 고치는 추가 커밋/push
- ❌ 갱신 시 새 경로 (링크가 갈라진다)
- ❌ 외부 CDN·웹폰트·원격 이미지·스크립트 렌더러 (자기완결 위반)
- ❌ 기본 브랜치에 직접 커밋 ·
git add -f로 ignore 우회 - ❌ 커밋 수 게이트에서 작업 문서 커밋 세기 (§8)
- ❌ 문서 커밋·push 실패를 이유로 워크플로 중단
- ❌ claude.ai
Artifact도구로 발행 — 이 규약이 그 방식을 대체했다 - ❌ 이 문서의 규약을 다른 파일에 복제 (참조만)