Files
Gnuboard7/docs/extension/extension-documentation.md
T
HeuJung 9d3b15e300 feat(core,extensions): 확장 진입 문서 제목에 그누보드7 확장 유형 표기
번들 확장 README 제목이 manifest 확장명 그대로(「게시판」)라 그 문서만 연
사람이 그누보드7의 확장인지, 모듈인지 템플릿인지 알 수 없었다.
README · AGENTS.md · docs/README.md 진입 문서 60개의 제목을
「그누보드7 {확장명} {유형}」 으로 바꾸고, 조립 규칙을 ExtensionInventory::docTitle
한 곳에 두어 골격 생성기와 계약 테스트가 같은 헬퍼를 쓰게 했다.
확장명이 이미 유형으로 끝나면 겹쳐 붙이지 않는다.

공개 규정에는 번들 확장 전용 관례임을 주어로 명시하고 제3자 확장에는
요구하지 않는다는 문장을 두었다. 언어팩은 README 가 없어 대상 밖이다.
2026-09-04 16:40:42 +09:00

20 KiB

확장 개발자 문서 (Extension Documentation)

번들 확장이 갖추는 AGENTS.md · README.md · docs/** 의 역할 경계, 골격, 자동 생성 규약, 갱신 의무.

TL;DR (5초 요약)

1. 확장마다 AGENTS.md(개발자·에이전트용) + README.md(사람용) + docs/(상세) 를 갖는다
2. 같은 사실은 한쪽만 SSoT — 소개·기능·요구사항은 README, 설계 의도·확장점·동반 의무는 AGENTS
3. 코드에서 실측되는 표는 `php artisan ext:docgen` 이 @generated 블록 안쪽만 교체한다
4. 블록 밖 전부가 사람 영역 — 생성기는 그 자리를 절대 건드리지 않으며 파괴적 재생성 플래그가 없다
5. 사람만 쓸 수 있는 다섯 자리는 TODO 마커로 남는다 (의도 · 흐름 · 금지패턴 · 사용방법 · 트러블슈팅)

목차


1. 왜 두 문서인가

docs/api/** 는 "엔드포인트가 무엇을 받고 무엇을 돌려주는가" 만 답한다. 확장을 수정하려는 사람이 실제로 필요로 하는 것은 그 앞의 질문이다 — 왜 이렇게 설계됐는가 / 어디를 확장해야 하는가 / 무엇을 건드리면 안 되는가.

그 답이 코드 안에만 있으면 세 가지가 생긴다.

  • 확장을 수정할 때마다 src/ 전체를 훑어 구조를 재발견한다.
  • 확장이 발행하는 훅이 코드 안에만 있어 확장점이 사실상 비공개가 된다.
  • 확장 저장소만 받은 제3자에게는 참고할 문서가 CHANGELOG.md 뿐이다.

독자가 둘이므로 문서도 둘이다. 도입을 검토하고 운영하는 사람은 "무엇을 해결해 주는가" 를 묻고, 확장을 고치는 사람은 "어디를 어떻게 건드리는가" 를 묻는다. 한 문서에 섞으면 양쪽 모두에게 길어진다.

2. 파일 배치

{ext}/
├─ AGENTS.md              에이전트·확장개발자 진입점
├─ README.md              사람(도입검토자·운영자) 진입점 — 한국어
├─ CHANGELOG.md           변경 이력
└─ docs/
   ├─ README.md           문서 통합 목차 + 실측 집계
   ├─ architecture.md     설계 의도 · 계층 지도 · 디렉토리 맵
   ├─ extension-points.md 발행/구독 훅 · 미들웨어 · 채널 · 스케줄     [모듈·플러그인]
   ├─ data-model.md       모델 · 소유 테이블 · 마이그레이션 · Enum    [모듈·플러그인]
   ├─ settings.md         설정 스키마 · 권한 · 메뉴 · 라우트 · 의존   [모듈·플러그인]
   ├─ frontend.md         레이아웃 · 핸들러 · 전역 진입점 · 에셋      [모듈·플러그인]
   ├─ components.md       제공 컴포넌트                              [템플릿]
   ├─ layouts.md          레이아웃 목록 · 라우트 매핑                 [템플릿]
   ├─ handlers.md         템플릿 전용 핸들러 · 부트스트랩             [템플릿]
   ├─ editor-spec.md      레이아웃 편집기 선언 · 프리뷰 샘플 커버리지
   └─ api/                API 레퍼런스 (별도 체계)

템플릿은 API·모델·훅 축이 사실상 비므로 골격이 다르다. ext:docgen 이 manifest 유형을 보고 골격을 고르므로 유형을 직접 지정할 필요가 없다.

docs/editor-spec.md 만은 세 유형 공통이다. 편집기 스펙(editor-spec.json)을 두지 않는 확장에도 문서를 두는 것은, 미보유가 정상일 수 있고 그 정상 여부를 적을 자리가 필요하기 때문이다 — "이 확장은 왜 스펙이 없어도 되는가 / 언제 필요해지는가" 가 어디에도 없으면 다음 사람이 그 부재를 누락으로 오해하거나 반대로 필요한 시점을 놓친다.

이 문서들은 {ext}/ 안에 있으므로 릴리즈 페이로드에 실리는 공개 배포물이다. 확장 저장소를 받은 사람이 그대로 읽는다.

3. 역할 경계와 SSoT

같은 사실은 한쪽만 SSoT 로 두고 반대쪽은 링크한다. 다만 두 문서 모두 자기 독자에게는 자족적이어야 한다 — "저쪽을 보라" 만 남기면 어느 쪽도 읽히지 않는다.

README.md (사람) AGENTS.md (에이전트·확장개발자)
확장 소개 · 해결하는 문제 (SSoT) 개발 의도 · 설계 원칙 (SSoT)
핵심 기능 목록 (SSoT) 아키텍처 · 계층 지도 · 디렉토리 맵
동작 방식 다이어그램 (운영자 눈높이) 핵심 흐름 (코드 경로 · 계층 통과 순서)
요구사항 · 의존성 · 연동 확장 (SSoT) 도메인 모델 · 소유 테이블 요약
설치 · 활성화 확장점: 발행 훅 · 구독 훅 · 필터 (SSoT)
관리자 설정 화면 사용법 (템플릿은 이 자리에 제공 컴포넌트 요약) 라우트 · 권한 · 설정 스키마 요약
운영 트러블슈팅 프론트 진입점 · 레이아웃 · 핸들러
라이선스 수정 시 동반 의무 체크리스트

필수 섹션 (유형별)

ext:docgen --check 가 요구하는 헤딩이다. 낱말이 본문 어딘가에 있는 것으로는 충족되지 않고 헤딩이어야 한다.

문서 필수 섹션
AGENTS.md TL;DR (5초 요약) · 1. 이 확장은 무엇인가 · 2. 디렉토리 지도 · 3. 핵심 흐름 · 4. 확장점 · 5. 수정 시 동반 의무 · 6. 금지 패턴 · 7. 테스트 실행 · 8. 문서 목차
README.md 소개 · 주요 기능 · 동작 방식 · 요구 사항 · 설치 · 관리자 설정(템플릿은 제공 컴포넌트) · 사용 방법 · 다른 확장과의 연동 · 문서 · 트러블슈팅 · 변경 이력 · 라이선스
docs/README.md 문서 목차
docs/architecture.md 설계 의도 · 계층 지도 · 디렉토리
docs/extension-points.md (모듈·플러그인) 발행 훅 · 구독 훅 · 훅 리스너 · 레이아웃 확장 · 미들웨어 · 브로드캐스트 채널 · 스케줄 · 알림 정의
docs/data-model.md (모듈·플러그인) 모델 · 소유 테이블 · 마이그레이션 · Enum · Repository
docs/settings.md (모듈·플러그인) 설정 스키마 · 권한 · 메뉴 · 라우트 · 의존 관계
docs/frontend.md (모듈·플러그인) 레이아웃 · 액션 핸들러 · 전역 진입점 · 에셋
docs/components.md (템플릿) 제공 컴포넌트
docs/layouts.md (템플릿) 레이아웃 목록 · 라우트 매핑
docs/handlers.md (템플릿) 템플릿 전용 핸들러 · 부트스트랩
docs/editor-spec.md 선언 요약 · 선언 블록 · 컴포넌트 팔레트 · 샘플 데이터와 페이지 상태 · 수정 시 동반 의무

목록의 SSoT 는 생성기이므로, 직접 옮겨 적기보다 ext:docgen --init 이 만든 골격에서 시작하는 편이 어긋나지 않는다.

AGENTS.md 의 5. 수정 시 동반 의무

이 절이 문서의 실효성 핵심이다. 코어 횡단 규정 중 그 확장에 실제로 걸리는 것만 추린다. 전부 나열하면 체크리스트 전체가 형식적으로 읽히고, 정작 걸리는 항목이 묻힌다.

예를 들어 이커머스는 통화 스냅샷과 주문 통화 전 사슬이, 게시판은 비밀글 게이트의 하위 리소스 재적용이, 결제 플러그인은 청구 금액 계약과 샌드박스 실호출이 그 자리에 온다.

ext:docgen --init 이 확장이 실제로 보유한 표면(마이그레이션 · 발행 훅 · 라우트 · 레이아웃 · 빌드 산출물 · 다국어)만 골라 초안을 만든다. 사람은 거기에 그 확장 고유의 항목을 보탠다.

시각 자료

스크린샷을 두지 않고 mermaid 다이어그램과 표로 대체한다. 이미지 파일이 없으므로 릴리즈 용량이 늘지 않고, UI 가 바뀔 때마다 다시 촬영할 의무도 없다. GitHub 이 mermaid 를 네이티브로 렌더한다.

mermaid 문법 오류는 렌더 시점에만 드러나므로, 새 형식을 도입할 때는 실제 렌더를 눈으로 확인한다.

README 첫 화면

확장명은 히어로 이미지 배지가 아니라 평범한 H1 제목으로 적는다. 확장은 서로 대등하게 병렬로 존재하는 구성요소이고, 각자가 코어와 같은 히어로 브랜딩을 달면 그 확장 하나가 독립 프로젝트인 것처럼 보인다. @generated:badges 블록의 버전·유형·코어 제약·라이선스 배지는 manifest 에서 오는 정보 표시이므로 그대로 둔다.

번들 확장의 제목은 확장명만이 아니라 「그누보드7 {확장명} {유형}」 이다 — 「그누보드7 페이지 모듈」·「그누보드7 Basic 템플릿」·「그누보드7 GDPR 플러그인」. 확장명만 제목으로 두면(# 페이지) 그 문서만 연 사람이 이것이 그누보드7의 확장인지, 모듈인지 템플릿인지 알 수 없다. 확장명이 이미 유형으로 끝나면(「Hello 모듈」) 유형을 겹쳐 붙이지 않는다. 진입 문서 세 곳이 같은 제목을 쓴다 — README.md 는 그 제목 그대로, AGENTS.md 는 — 에이전트 가이드, docs/README.md 는 개발자 문서 를 뒤에 붙인다. docs/ 안의 하위 문서(아키텍처·데이터 모델 등)는 이미 상위 문서 안에 있으므로 확장명만 쓴다.

제3자 확장에는 이 표기를 요구하지 않는다. 확장의 이름과 브랜딩은 그 저작자의 것이다. ext:docgen --init 이 같은 형식을 기본 제목으로 깔아 주지만 바꿔도 된다 — 제목만 보고도 그누보드7의 확장이며 어떤 유형인지 알 수 있으면 충분하다.

# 그누보드7 페이지 모듈

**그누보드7 모듈 · sirsoft-page**
정적 페이지(정보/정책/안내) 관리 모듈

제목 조립 규칙의 단일 출처는 골격 생성기가 쓰는 헬퍼이고, 계약 테스트가 번들 확장 전수와 생성기 출력을 같은 헬퍼로 대조한다 — 규칙을 문서·생성기·검사에 각각 적으면 한쪽만 고쳐 어긋난다.

4. 자동 생성 블록 규약

생성기는 마커 안쪽만 쓴다.

<!-- @generated:hooks-published START — ext:docgen 이 갱신. 이 블록 안은 직접 수정하지 않는다 -->
| 훅 이름 | 유형 | 발행 위치 |
| ... |
<!-- @generated:hooks-published END -->

<!-- @intent START -->
이 훅은 ... (사람이 쓰는 영역 — 생성기가 건드리지 않는다)
<!-- @intent END -->

블록 밖 전부가 사람 영역이다. 계약은 세 가지다.

  1. 블록 안쪽 교체가 유일한 쓰기 동작이다. 그래서 --force 같은 파괴적 플래그가 없다. 신규 파일 생성만 --init 으로 분리되어 있고, 그 경로도 기존 파일을 덮어쓰지 않는다.
  2. 문서에 없는 블록 키는 주입하지 않는다. 생성기가 임의 위치에 표를 끼워 넣으면 사람이 잡아 둔 문서 구조가 흔들린다. 대신 누락으로 보고하여 사람이 마커 놓을 자리를 정한다.
  3. 재실행은 멱등이다. 같은 코드 상태에서 두 번 돌리면 한 글자도 바뀌지 않는다.

@intent 블록은 사람 영역임을 눈에 띄게 표시하는 장치일 뿐이며, 생성기는 @generated 마커만 찾는다.

블록 목록

블록 키 내용 출처
badges 버전 · 유형 · 코어 제약 · 라이선스 · 의존 배지 manifest
requirements 코어 버전 · PHP · 의존 확장 · 외부 호스트 manifest · composer.json
install 설치 · 활성화 · 업데이트 커맨드 manifest
integrations · dependencies 정방향/역방향 의존 확장 번들 전수 교차 스캔
docs-index · doc-toc 문서 목차와 작성 상태 파일 존재 여부
stats 훅 · 구독 훅 · 라우트 · 모델 · 테이블 · 마이그레이션 · 레이아웃 · 핸들러 8지표 실측 집계 전 수집기
directory-map 경로별 역할과 수정 시 절차 디렉토리 구조
extension-points-summary 확장점 종류별 개수와 상세 링크 훅·선언형 표면
hooks-published · hooks-subscribed · listeners 발행/구독 훅과 리스너 소스 스캔
layout-extensions · middleware · channels · schedules · notifications 선언형 확장점 진입 클래스 getter
models · tables · migrations · enums · repositories 데이터 모델 소스 파싱
settings-schema · settings-summary · permissions · menus · routes 설정 스키마 · README 용 설정 요약(키·의미·기본값) · 권한 · 메뉴 · 라우트 진입 클래스 getter
layouts · handlers · frontend-entry · assets 프론트 표면 레이아웃/TS 스캔
components · layout-map 템플릿 컴포넌트·라우트 매핑 컴포넌트 스캔 · routes.json
test-commands 테스트 종류별 개수와 실행 명령 테스트 경로 스캔

5. 미채움 마커

생성기가 채울 수 없는 자리 — 코드에서 실측되지 않는 왜 — 에는 다섯 종류의 마커가 남는다.

마커 자리 주 위치
TODO: 의도 설계 의도 · 소개 · 주요 기능 AGENTS 1, README 소개·주요 기능
TODO: 흐름 핵심 흐름 · 동작 방식 다이어그램 AGENTS 3, README 동작 방식
TODO: 금지패턴 금지 패턴 표 AGENTS 6
TODO: 사용방법 운영자 사용 시나리오 README 사용 방법
TODO: 트러블슈팅 증상 → 원인 → 조치 README 트러블슈팅

마커 종류를 다섯으로 고정하는 이유는 잔량을 집계할 수 있게 하기 위해서다. 자유 문구로 남기면 어느 자리가 비었는지 셀 수 없다.

의도 와 흐름 은 두 문서 모두에 나타난다 — 같은 축을 다른 독자에게 서술하는 자리이기 때문이다. 나머지 셋은 한쪽에만 있다.

골격을 만든 직후에는 마커가 반드시 존재하며, 그 상태로 커밋하는 것이 정상 흐름이다. 결함이 아니라 집필 진행 상태다. 다만 잔량이 늘어나는 것은 회귀다.

6. ext:docgen 사용법

php artisan ext:docgen
    {--scope=all : all | module:{id} | plugin:{id} | template:{id}}
    {--init      : 문서가 없는 확장에 골격 파일 생성 (기존 파일은 건너뜀)}
    {--check     : 생성하지 않고 누락·드리프트만 리포트}
    {--json      : 기계 판독 출력}
    {--dry-run   : 대상과 실측 집계만 출력}

전형적인 흐름:

# 1. 대상과 실측 규모 확인
php artisan ext:docgen --scope=module:sirsoft-board --dry-run

# 2. 골격 생성 (없는 문서만)
php artisan ext:docgen --scope=module:sirsoft-board --init

# 3. TODO 마커 자리를 코드 근거로 채운다 (사람)

# 4. 표면을 고친 뒤 자동 생성 블록 갱신
php artisan ext:docgen --scope=module:sirsoft-board

# 5. 문서와 코드가 어긋나지 않는지 확인
php artisan ext:docgen --check

작업 위치는 언제나 _bundled 다. 활성 디렉토리 반영은 update 커맨드로만 한다 (문서만 바뀌었다면 빌드는 불필요하다).

php artisan {module|plugin|template}:update {id} --force

수집 대상은 _bundled 소스다

선언형 표면(라우트 · 권한 · 메뉴 · 훅 리스너 · 설정 스키마 등)은 진입 클래스의 getter 를 실제로 호출해 읽는다. 정규식으로 소스를 긁는 방식과 달리 상속 기본값까지 정확히 반영된다.

같은 확장이 활성 디렉토리에서 이미 부팅되어 있으면 PHP 는 같은 클래스를 다시 정의할 수 없으므로, 진입 클래스명만 바꿔 _bundled 파일을 메모리에 다시 읽는다. 그래서 활성 디렉토리에 반영하기 전이라도 _bundled 의 변경이 문서에 그대로 나타난다.

getter 하나가 실패해도 나머지 수집은 계속되며, 실패 사유가 리포트에 드러난다 — 조용한 누락이 없다.

7. 갱신 의무

확장 표면을 바꾸면 같은 작업 단위에서 문서를 함께 갱신한다.

바꾼 것 해야 할 것
발행 훅 추가 · 이름 변경 ext:docgen 재실행 — 다른 확장이 잡는 계약이 바뀐다
라우트 · 권한 · 메뉴 · 설정 스키마 ext:docgen 재실행
모델 · 마이그레이션 ext:docgen 재실행 + 업그레이드 스텝
레이아웃 · 핸들러 · 에셋 ext:docgen 재실행
설계 방침 · 계층 구조 블록 밖 서술을 직접 갱신 (생성기가 채우지 못한다)
새로 발견한 오용 6. 금지 패턴 에 행 추가

자동 생성 블록이 아무리 정확해도 블록 밖 서술이 낡으면 문서 전체가 의심스러워진다. 생성기 재실행은 갱신의 절반이다.

신규 확장

새 확장을 만들면 php artisan ext:docgen --scope={type}:{id} --init 로 문서 골격을 먼저 만든다. 확장이 문서를 갖고 태어나야 하며, 만들자마자 TODO 마커 자리를 채우는 것이 첫 작업이다.

8. 체크리스트

□ AGENTS.md · README.md · docs/ 필수 문서가 유형에 맞게 있는가?
□ 필수 섹션 헤딩이 모두 있는가?
□ 자동 생성 블록 마커가 모두 있는가?
□ `ext:docgen --check` 가 드리프트 0 인가?
□ TODO 마커 다섯 자리를 코드 근거로 채웠는가? (추측 서술 금지)
□ mermaid 다이어그램이 실제로 렌더되는가? (눈으로 확인)
□ 배지 값이 manifest 와 일치하는가? (생성기가 채우므로 직접 쓰지 않는다)
□ README 와 AGENTS 가 같은 사실을 각자 서술하고 있지는 않은가? (SSoT 한쪽 + 링크)
□ `5. 수정 시 동반 의무` 가 이 확장에 실제로 걸리는 것만 담고 있는가?
□ 버전 상향 시 CHANGELOG 에 기재했는가?

내부 작업 단계·심사 어휘를 남기지 않는다

확장은 각자 자기 저장소로 배포되고, 그것만 내려받은 개발자에게는 그 문서가 유일한 안내다. 거기에 내부 작업 맥락이 남으면 읽는 쪽은 해석할 근거가 없다.

남기지 않는다 대신
작업 단계 (Phase 3, 1차 작업, 다음 단계에서 추가) 지금 무엇이 있는지만 적는다. 계획은 문서가 아니라 이슈가 담는다
심사 판정 (— 정당, 타당함, 검토 결과 통과) 판정이 아니라 사실을 적는다
작업 이력 (이번 라운드에서, 재검 결과) 결과만 남긴다
항목 ID 나열로 규모를 설명 개수와 목록은 생성기가 실측해 싣는다

이 문구들의 공통점은 쓸 때는 정확했다가 나중에 거짓이 된다는 것이다. "다음 단계에서 추가" 라고 적어 둔 항목이 실제로 들어온 뒤에도 문구는 그대로 남고, 오류도 경고도 나지 않는다. 문서가 자기 자신을 반박한 채 계속 읽힐 뿐이다.

코드에서 실측되는 부분은 생성기가 유지하므로 이 문제가 없다. 위험한 자리는 사람이 쓴 자유 텍스트이며, 그중에서도 생성기가 문서로 옮겨 싣는 필드가 가장 위험하다 — 한 곳의 낡음이 여러 문서로 퍼진다. 그래서 확장 문서의 편집기 스펙 한 줄 요약은 스펙 파일의 설명을 옮기지 않고 실측에서 만든다.

관련 문서