Skip to content

Commit 30e5759

Browse files
박은우박은우
authored andcommitted
docs(harness): add documentation verification
1 parent ee9cb14 commit 30e5759

6 files changed

Lines changed: 193 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ ADR 작성 기준:
5555
- `npm run build`: production build 생성
5656
- `npm run lint`: source file ESLint 실행
5757
- `npm run lint:css:syntax`: CSS syntax rule 검사
58+
- `npm run verify:docs`: ADR과 핵심 문서 하네스 검사
5859
- `npm run test:unit`: Vitest unit test 실행
5960
- `npm run test:components`: component 중심 Vitest test 실행
6061
- `npm run test:e2e`: Playwright scenario 실행
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# 0010. Targeted Documentation Harness를 사용한다
2+
3+
Date: 2026-05-07
4+
Status: Accepted
5+
6+
## 배경
7+
8+
ADR 작성 조건이 `AGENTS.md``docs/adr/README.md`에 추가되면서, 문서 규칙도
9+
반복적으로 검증할 필요가 생겼다. 하지만 기존 `npm run lint:md`는 전체
10+
`**/*.md``**/*.mdx`를 검사한다. 과거 글과 일부 guide에 남아 있는
11+
markdownlint 이슈 때문에, 현재 상태에서는 문서 변경 검증용 harness로 쓰기
12+
어렵다.
13+
14+
문서 작업마다 전체 markdownlint 실패를 무시하면, 실제로 중요한 ADR/규칙 문서
15+
오류도 함께 묻힌다.
16+
17+
## 결정
18+
19+
핵심 문서만 대상으로 하는 `npm run verify:docs`를 추가한다. 이 command는
20+
`internal/scripts/verify-docs.mjs`를 실행하며 다음을 검증한다.
21+
22+
- `docs/README.md`에 등록된 명시적 문서 경로가 실제로 존재하는지 확인한다.
23+
- `AGENTS.md``docs/adr/README.md`가 같은 ADR 작성 조건을 포함하는지
24+
확인한다.
25+
- `docs/adr/*.md` 파일이 ADR 인덱스에 등록되어 있는지 확인한다.
26+
- `AGENTS.md`, `ARCHITECTURE.md`, `docs/README.md`, `docs/adr/*.md`
27+
markdownlint를 실행한다.
28+
29+
`npm run test:ci`에도 `npm run verify:docs`를 포함한다.
30+
31+
## 결과
32+
33+
- ADR과 저장소 규칙 문서는 전체 Markdown 부채와 분리해 안정적으로 검증된다.
34+
- ADR 파일을 추가하고 인덱스 갱신을 잊는 실수를 줄인다.
35+
- 문서 인덱스의 stale path를 더 빨리 발견할 수 있다.
36+
- 전체 Markdown lint 부채는 별도 정리 과제로 남는다.
37+
38+
## 검토한 대안
39+
40+
- 기존 `npm run lint:md`만 사용: 범위가 넓고 현재 실패가 많아 문서 변경용
41+
harness로 신뢰하기 어렵다.
42+
- 모든 Markdown lint 이슈를 먼저 정리: 이상적이지만 이번 목표보다 범위가
43+
크고 글 콘텐츠까지 광범위하게 건드린다.
44+
- PR 리뷰 체크리스트에만 의존: 자동 검증이 없어 반복 실수를 줄이기 어렵다.
45+
46+
## 관련 히스토리
47+
48+
- `ee9cb14` (2026-05-07): ADR 문서 체계와 ADR 작성 조건 추가.

docs/adr/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Architecture Decision Records
22

3-
Last updated: 2026-05-06
3+
Last updated: 2026-05-07
44

55
이 디렉터리는 커밋 히스토리에서 복원한 아키텍처 의사결정을 기록한다.
66
ADR은 AI 협업 가이드와 별개의 문서다. 사람이 결정했든 AI가 초안을
@@ -27,6 +27,7 @@ ADR은 AI 협업 가이드와 별개의 문서다. 사람이 결정했든 AI가
2727
| [0007](0007-use-umami-and-supabase-view-counts.md) | Accepted | Umami 분석과 Supabase 조회수를 함께 사용한다 |
2828
| [0008](0008-enforce-publication-policy-in-content-ingestion.md) | Accepted | 콘텐츠 수집 경로에서 공개 정책을 강제한다 |
2929
| [0009](0009-use-app-shell-for-primary-navigation.md) | Accepted | 주요 탐색과 레이아웃에 AppShell을 사용한다 |
30+
| [0010](0010-use-targeted-documentation-harness.md) | Accepted | 핵심 문서 검증에 targeted documentation harness를 사용한다 |
3031

3132
## 작성 조건
3233

docs/guides/testing-guide.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- `npm run test:components` : UI 컴포넌트 중심 테스트 실행
1212
- `npm run test:smoke` : 유닛 smoke + Playwright smoke
1313
- `npm run test:e2e` : 전체 Playwright 시나리오
14+
- `npm run verify:docs` : ADR과 핵심 문서 하네스 검사
1415

1516
## 단위 테스트 체크리스트
1617

@@ -55,6 +56,24 @@
5556
- `src/styles/tokens.test.ts`
5657
- `src/styles/globals.test.ts`
5758

59+
## 문서 하네스
60+
61+
`npm run verify:docs`는 전체 Markdown 문서를 한 번에 검사하지 않는다. 현재
62+
저장소에는 과거 글과 일부 guide에 남은 markdownlint 이슈가 있기 때문이다.
63+
대신 의사결정과 작업 경계를 잡는 핵심 문서만 안정적으로 검증한다.
64+
65+
검증 범위:
66+
67+
1. `docs/README.md`에 명시된 문서 경로가 실제로 존재하는지 확인한다.
68+
2. `AGENTS.md``docs/adr/README.md`가 같은 ADR 작성 조건을 포함하는지
69+
확인한다.
70+
3. `docs/adr/*.md` 파일이 ADR 인덱스에 등록되어 있는지 확인한다.
71+
4. `AGENTS.md`, `ARCHITECTURE.md`, `docs/README.md`, `docs/adr/*.md`
72+
markdownlint를 실행한다.
73+
74+
문서 변경이 ADR, 저장소 규칙, 문서 인덱스에 닿으면 최소 검증으로
75+
`npm run verify:docs`를 실행한다.
76+
5877
## Playwright 체크리스트
5978

6079
### 1) 모바일 내비 회귀

internal/scripts/verify-docs.mjs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { spawnSync } from 'child_process';
4+
5+
const ROOT = process.cwd();
6+
const ADR_DIR = path.join(ROOT, 'docs', 'adr');
7+
const DOCS_INDEX = path.join(ROOT, 'docs', 'README.md');
8+
const ADR_INDEX = path.join(ADR_DIR, 'README.md');
9+
const AGENTS = path.join(ROOT, 'AGENTS.md');
10+
11+
const adrTriggerRules = [
12+
'선택지가 2개 이상이고 트레이드오프가 존재한 경우.',
13+
'반복적으로 따라야 할 규칙이나 경계를 정의한 경우.',
14+
'테스트 전략이나 검증 방식이 결정의 핵심이었던 경우.',
15+
];
16+
17+
const markdownlintTargets = [
18+
'AGENTS.md',
19+
'ARCHITECTURE.md',
20+
'docs/README.md',
21+
...fs
22+
.readdirSync(ADR_DIR)
23+
.filter((fileName) => fileName.endsWith('.md'))
24+
.sort()
25+
.map((fileName) => path.join('docs', 'adr', fileName)),
26+
];
27+
28+
function readText(filePath) {
29+
return fs.readFileSync(filePath, 'utf8');
30+
}
31+
32+
function assertCondition(condition, message) {
33+
if (!condition) {
34+
throw new Error(message);
35+
}
36+
}
37+
38+
function assertFileExists(relativePath) {
39+
assertCondition(
40+
fs.existsSync(path.join(ROOT, relativePath)),
41+
`Missing docs index target: ${relativePath}`
42+
);
43+
}
44+
45+
function verifyDocsIndexTargets() {
46+
const docsIndex = readText(DOCS_INDEX);
47+
const linkedPaths = [...docsIndex.matchAll(/`([^`]+)`/g)]
48+
.map((match) => match[1])
49+
.filter((entry) => !entry.includes('*'))
50+
.filter((entry) => entry.endsWith('.md') || entry.endsWith('.sql'));
51+
52+
linkedPaths.forEach(assertFileExists);
53+
}
54+
55+
function verifyAdrTriggerRules() {
56+
const agents = readText(AGENTS);
57+
const adrIndex = readText(ADR_INDEX);
58+
59+
adrTriggerRules.forEach((rule) => {
60+
assertCondition(
61+
agents.includes(rule),
62+
`AGENTS.md is missing ADR trigger rule: ${rule}`
63+
);
64+
assertCondition(
65+
adrIndex.includes(rule),
66+
`docs/adr/README.md is missing ADR trigger rule: ${rule}`
67+
);
68+
});
69+
}
70+
71+
function verifyAdrIndexEntries() {
72+
const adrIndex = readText(ADR_INDEX);
73+
const adrFiles = fs
74+
.readdirSync(ADR_DIR)
75+
.filter((fileName) => /^\d{4}-.+\.md$/.test(fileName))
76+
.sort();
77+
78+
adrFiles.forEach((fileName) => {
79+
assertCondition(
80+
adrIndex.includes(`](${fileName})`),
81+
`docs/adr/README.md does not list ${fileName}`
82+
);
83+
});
84+
}
85+
86+
function runMarkdownlint() {
87+
const result = spawnSync(
88+
path.join(ROOT, 'node_modules', '.bin', 'markdownlint-cli2'),
89+
[
90+
'--config',
91+
path.join('internal', 'config', '.markdownlint-cli2.jsonc'),
92+
...markdownlintTargets,
93+
],
94+
{
95+
cwd: ROOT,
96+
stdio: 'inherit',
97+
}
98+
);
99+
100+
assertCondition(
101+
result.status === 0,
102+
`markdownlint failed with exit code ${result.status ?? 'unknown'}`
103+
);
104+
}
105+
106+
function main() {
107+
verifyDocsIndexTargets();
108+
verifyAdrTriggerRules();
109+
verifyAdrIndexEntries();
110+
runMarkdownlint();
111+
console.log('Documentation verification passed.');
112+
}
113+
114+
try {
115+
main();
116+
} catch (error) {
117+
console.error(
118+
error instanceof Error ? error.message : 'Documentation verification failed.'
119+
);
120+
process.exit(1);
121+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
"start": "next start",
1212
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
1313
"lint:css:syntax": "node internal/scripts/check-css-syntax.mjs",
14+
"verify:docs": "node internal/scripts/verify-docs.mjs",
1415
"analyze": "ANALYZE=true next build --webpack",
1516
"test": "vitest",
1617
"test:unit": "vitest run",
1718
"test:components": "vitest run src/features/**/*.test.ts src/features/**/*.test.tsx src/shared/**/*.test.ts src/shared/**/*.test.tsx",
1819
"test:coverage": "vitest run --coverage",
19-
"test:ci": "npm run lint && npm run lint:css:syntax && npm run test:unit && npm run test:e2e",
20+
"test:ci": "npm run lint && npm run lint:css:syntax && npm run verify:docs && npm run test:unit && npm run test:e2e",
2021
"test:e2e": "npx playwright test",
2122
"test:smoke": "vitest run src/shared/**/*.test.ts src/app/actions/*.test.ts && npx playwright test --grep @smoke",
2223
"new-post": "node internal/scripts/posts/new-post.js",

0 commit comments

Comments
 (0)