# 확장 개발자 문서 (Extension Documentation) > 번들 확장이 갖추는 `AGENTS.md` · `README.md` · `docs/**` 의 역할 경계, 골격, 자동 생성 규약, 갱신 의무. ## TL;DR (5초 요약) ```text 1. 확장마다 AGENTS.md(개발자·에이전트용) + README.md(사람용) + docs/(상세) 를 갖는다 2. 같은 사실은 한쪽만 SSoT — 소개·기능·요구사항은 README, 설계 의도·확장점·동반 의무는 AGENTS 3. 코드에서 실측되는 표는 `php artisan ext:docgen` 이 @generated 블록 안쪽만 교체한다 4. 블록 밖 전부가 사람 영역 — 생성기는 그 자리를 절대 건드리지 않으며 파괴적 재생성 플래그가 없다 5. 사람만 쓸 수 있는 다섯 자리는 TODO 마커로 남는다 (의도 · 흐름 · 금지패턴 · 사용방법 · 트러블슈팅) ``` ## 목차 - [1. 왜 두 문서인가](#1-왜-두-문서인가) - [2. 파일 배치](#2-파일-배치) - [3. 역할 경계와 SSoT](#3-역할-경계와-ssot) - [4. 자동 생성 블록 규약](#4-자동-생성-블록-규약) - [5. 미채움 마커](#5-미채움-마커) - [6. `ext:docgen` 사용법](#6-extdocgen-사용법) - [7. 갱신 의무](#7-갱신-의무) - [8. 체크리스트](#8-체크리스트) --- ## 1. 왜 두 문서인가 `docs/api/**` 는 "엔드포인트가 무엇을 받고 무엇을 돌려주는가" 만 답한다. 확장을 수정하려는 사람이 실제로 필요로 하는 것은 그 앞의 질문이다 — **왜 이렇게 설계됐는가 / 어디를 확장해야 하는가 / 무엇을 건드리면 안 되는가.** 그 답이 코드 안에만 있으면 세 가지가 생긴다. - 확장을 수정할 때마다 `src/` 전체를 훑어 구조를 재발견한다. - 확장이 발행하는 훅이 코드 안에만 있어 확장점이 사실상 비공개가 된다. - 확장 저장소만 받은 제3자에게는 참고할 문서가 `CHANGELOG.md` 뿐이다. 독자가 둘이므로 문서도 둘이다. 도입을 검토하고 운영하는 사람은 "무엇을 해결해 주는가" 를 묻고, 확장을 고치는 사람은 "어디를 어떻게 건드리는가" 를 묻는다. 한 문서에 섞으면 양쪽 모두에게 길어진다. ## 2. 파일 배치 ```text {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의 확장이며 어떤 유형인지 알 수 있으면 충분하다. ```markdown # 그누보드7 페이지 모듈 **그누보드7 모듈 · sirsoft-page** 정적 페이지(정보/정책/안내) 관리 모듈 ``` 제목 조립 규칙의 단일 출처는 골격 생성기가 쓰는 헬퍼이고, 계약 테스트가 **번들 확장** 전수와 생성기 출력을 같은 헬퍼로 대조한다 — 규칙을 문서·생성기·검사에 각각 적으면 한쪽만 고쳐 어긋난다. ## 4. 자동 생성 블록 규약 생성기는 **마커 안쪽만** 쓴다. ```markdown | 훅 이름 | 유형 | 발행 위치 | | ... | 이 훅은 ... (사람이 쓰는 영역 — 생성기가 건드리지 않는다) ``` 블록 밖 전부가 사람 영역이다. 계약은 세 가지다. 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` 사용법 ```bash php artisan ext:docgen {--scope=all : all | module:{id} | plugin:{id} | template:{id}} {--init : 문서가 없는 확장에 골격 파일 생성 (기존 파일은 건너뜀)} {--check : 생성하지 않고 누락·드리프트만 리포트} {--json : 기계 판독 출력} {--dry-run : 대상과 실측 집계만 출력} ``` 전형적인 흐름: ```bash # 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 커맨드로만 한다 (문서만 바뀌었다면 빌드는 불필요하다). ```bash 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. 체크리스트 ```text □ AGENTS.md · README.md · docs/ 필수 문서가 유형에 맞게 있는가? □ 필수 섹션 헤딩이 모두 있는가? □ 자동 생성 블록 마커가 모두 있는가? □ `ext:docgen --check` 가 드리프트 0 인가? □ TODO 마커 다섯 자리를 코드 근거로 채웠는가? (추측 서술 금지) □ mermaid 다이어그램이 실제로 렌더되는가? (눈으로 확인) □ 배지 값이 manifest 와 일치하는가? (생성기가 채우므로 직접 쓰지 않는다) □ README 와 AGENTS 가 같은 사실을 각자 서술하고 있지는 않은가? (SSoT 한쪽 + 링크) □ `5. 수정 시 동반 의무` 가 이 확장에 실제로 걸리는 것만 담고 있는가? □ 버전 상향 시 CHANGELOG 에 기재했는가? ``` --- ## 내부 작업 단계·심사 어휘를 남기지 않는다 확장은 각자 자기 저장소로 배포되고, 그것만 내려받은 개발자에게는 그 문서가 유일한 안내다. 거기에 내부 작업 맥락이 남으면 읽는 쪽은 해석할 근거가 없다. | 남기지 않는다 | 대신 | |---|---| | 작업 단계 (`Phase 3`, `1차 작업`, `다음 단계에서 추가`) | 지금 무엇이 있는지만 적는다. 계획은 문서가 아니라 이슈가 담는다 | | 심사 판정 (`— 정당`, `타당함`, `검토 결과 통과`) | 판정이 아니라 사실을 적는다 | | 작업 이력 (`이번 라운드에서`, `재검 결과`) | 결과만 남긴다 | | 항목 ID 나열로 규모를 설명 | 개수와 목록은 생성기가 실측해 싣는다 | 이 문구들의 공통점은 **쓸 때는 정확했다가 나중에 거짓이 된다**는 것이다. "다음 단계에서 추가" 라고 적어 둔 항목이 실제로 들어온 뒤에도 문구는 그대로 남고, 오류도 경고도 나지 않는다. 문서가 자기 자신을 반박한 채 계속 읽힐 뿐이다. 코드에서 실측되는 부분은 생성기가 유지하므로 이 문제가 없다. 위험한 자리는 **사람이 쓴 자유 텍스트**이며, 그중에서도 생성기가 문서로 옮겨 싣는 필드가 가장 위험하다 — 한 곳의 낡음이 여러 문서로 퍼진다. 그래서 확장 문서의 편집기 스펙 한 줄 요약은 스펙 파일의 설명을 옮기지 않고 실측에서 만든다. ## 관련 문서 - [module-basics.md](module-basics.md) — 모듈 개발 기초 - [plugin-development.md](plugin-development.md) — 플러그인 개발 가이드 - [template-basics.md](template-basics.md) — 템플릿 시스템 기초 - [hooks.md](hooks.md) — 훅 시스템 (발행/구독 규약) - [changelog-rules.md](changelog-rules.md) — CHANGELOG 규칙 - [../backend/api-documentation.md](../backend/api-documentation.md) — API 레퍼런스 문서 규정