Files
Gnuboard7/AGENTS.md
T
HeuJung bd215bc586 feat(identity): NHN KCP 휴대폰 본인확인 플러그인 추가
메일 인증 대신 휴대폰 본인확인을 쓸 수 있게 하는 플러그인을 추가한다.
가입·비밀번호 재설정·성인인증 등 코어 IDV 가 강제하는 모든 지점에서 동작하며,
테스트 모드로 계약 없이 전 흐름을 확인할 수 있다.

구현·검수 과정에서 드러난 저장소 전역 결함을 함께 처리했다.

- 외래키 컬럼의 한국어 comment 가 방출되지 않던 문제 (소스 38건 교정 +
 기설치본 백필 업그레이드 스텝 7종). ->comment 를 ->constrained 뒤에
 체인하면 예외 없이 통과하면서 comment 가 조용히 사라진다
- 로케일 전환 후 확장 액션 핸들러가 소실되던 문제 (플러그인 3종에
 재등록 진입점 노출)
- 본인인증 상태 폴링 응답이 누적 시도 횟수를 함께 내보내던 문제
- 인증 안내 문구와 실제 진행 방식의 폭 경계 불일치 (768~1023px).
 엔진과 같은 값을 같은 방법으로 읽도록 교정
- transition_overlay_target 이 replace 없이 무효이던 관리자 목록 40곳.
 로딩 표시가 나오지 않아도 경고가 남지 않아 화면을 봐야만 알 수 있었다

두 인증 플러그인의 코어 최소 요구 버전을 7.0.6 으로 올린다. 운영 모드
자격증명 필수 검사가 7.0.6 의 설정 저장 검증 표면에 의존하며, 그보다 낮은
코어에서는 그 검사가 조용히 통과해 빈 자격증명으로 저장할 수 있었다.

재발 차단으로 정적 검사 룰 4종을 신설하고, 검사 도구가 미커밋 신규 확장을
"검사 파일 0" 으로 통과시키던 사각을 함께 막았다.
2026-07-31 00:32:46 +09:00

1070 lines
67 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 그누보드7 Development Guide
> 이 문서는 그누보드7 오픈소스 CMS 프로젝트의 개발 가이드입니다. AI 에이전트 및 외부 기여자를 위한 참고 자료입니다.
## 빠른 참조 - 상세 가이드 문서
<!-- AUTO-GENERATED-START: docs-quick-reference -->
### 백엔드 [backend/](docs/backend/) (32개)
| 문서 | 설명 | TL;DR 핵심 |
|------|------|-----------|
| [activity-log-hooks.md](docs/backend/activity-log-hooks.md) | 활동 로그 훅 레퍼런스 (Activity Log Hooks Reference) | 코어 66훅 + 이커머스 92훅 + 게시판 32훅 + 페이지 8훅 = 총 198훅 |
| [activity-log.md](docs/backend/activity-log.md) | 활동 로그 시스템 (Activity Log System) | Monolog 기반: Service 훅 → Listener → Log::channel('activity... |
| [admin-settings-access.md](docs/backend/admin-settings-access.md) | Admin 환경설정 값 접근 (`g7_core_settings` vs `config()`) | 동기화 SSoT: storage/app/settings/*.json → SettingsServicePr... |
| [api-documentation.md](docs/backend/api-documentation.md) | API 레퍼런스 문서 규정 (API Documentation) | 모든 API 엔드포인트는 레퍼런스 문서 필수 — 메서드/URI/파라미터/응답 필드 + 요청·응답 예시 ... |
| [api-resources.md](docs/backend/api-resources.md) | API 리소스 | Resource: BaseApiResource 상속 필수 / Collection: BaseApiColl... |
| [authentication.md](docs/backend/authentication.md) | 인증 및 세션 처리 | Laravel Sanctum 토큰 전용 인증 (Bearer 토큰만 사용) |
| [broadcasting.md](docs/backend/broadcasting.md) | Broadcasting (실시간 이벤트) | Laravel Reverb 사용 (WebSocket) |
| [console-confirm.md](docs/backend/console-confirm.md) | 콘솔 yes/no 프롬프트 (ConsoleConfirm) | 콘솔 커맨드의 yes/no 프롬프트는 $this->unifiedConfirm() 사용 — Laravel... |
| [controllers.md](docs/backend/controllers.md) | 컨트롤러 계층 구조 | AdminBaseController / AuthBaseController / PublicBaseCont... |
| [core-config.md](docs/backend/core-config.md) | 코어 설정 (config/core.php) | config/core.php = 코어 권한/역할/메뉴/메일템플릿의 SSoT (Single Source ... |
| [core-update-system.md](docs/backend/core-update-system.md) | 코어 업데이트 시스템 (Core Update System) | 코어 업그레이드 스텝: upgrades/ 디렉토리 (프로젝트 루트), 네임스페이스 App\Upgrades |
| [data-sync-helpers.md](docs/backend/data-sync-helpers.md) | 데이터 동기화 Helper (Data Sync Helpers) | 모든 데이터 동기화는 Service/Seeder 가 Helper 를 호출해 수행 (직접 Model 조작... |
| [dto.md](docs/backend/dto.md) | DTO (Data Transfer Object) 사용 규칙 | DTO 두 패턴 — Value Object(불변 1회 전달) vs Data Carrier(다단계 변형/... |
| [enum.md](docs/backend/enum.md) | Enum 사용 규칙 | 상태/타입/분류 = Enum 필수 (PHP 8.1+ Backed Enum) |
| [exceptions.md](docs/backend/exceptions.md) | Custom Exception 다국어 처리 | 예외 메시지 하드코딩 금지 → __() 함수 필수 |
| [geoip.md](docs/backend/geoip.md) | GeoIP 시스템 (MaxMind GeoLite2) | MaxMind GeoLite2-City DB 기반 IP → 타임존 감지 (SetTimezone 미들웨어... |
| [identity-messages.md](docs/backend/identity-messages.md) | 본인인증 메시지 템플릿 시스템 (Identity Messages) | 알림 시스템(notification_*)과 완전 분리된 IDV 전용 템플릿 인프라 |
| [identity-policies.md](docs/backend/identity-policies.md) | 본인인증 정책 시스템 (Identity Policies) | - |
| [identity-providers.md](docs/backend/identity-providers.md) | IDV Provider 작성 가이드 (Identity Verification Providers) | VerificationProviderInterface 구현 + IdentityProviderManage... |
| [language-pack-service.md](docs/backend/language-pack-service.md) | LanguagePackService (백엔드 Service 레이어) | LanguagePackService 가 install/activate/deactivate/uninsta... |
| [middleware.md](docs/backend/middleware.md) | 미들웨어 등록 규칙 | 인증 필요 미들웨어 → 전역 등록 금지! |
| [notification-system.md](docs/backend/notification-system.md) | 알림 시스템 (Notification System) | GenericNotification 범용 클래스 1개로 모든 알림 처리 (개별 클래스 불필요) |
| [response-helper.md](docs/backend/response-helper.md) | API 응답 규칙 (ResponseHelper) | 모든 API 응답은 ResponseHelper 사용 |
| [routing.md](docs/backend/routing.md) | 라우트 네이밍 및 경로 | 모든 라우트는 name() 필수: ->name('api.users.index') |
| [search-system.md](docs/backend/search-system.md) | Scout 검색 엔진 시스템 (Search System) | Laravel Scout + DatabaseFulltextEngine: MySQL FULLTEXT + ... |
| [seo-system.md](docs/backend/seo-system.md) | SEO 페이지 생성기 시스템 (SEO Page Generator) | SeoMiddleware: 봇 요청 감지 → ?locale= 파라미터 해석 → SeoRenderer가 ... |
| [service-provider.md](docs/backend/service-provider.md) | 서비스 프로바이더 안전성 | DB 접근 전 .env 파일 존재 확인 필수 |
| [service-repository.md](docs/backend/service-repository.md) | Service-Repository 패턴 | RepositoryInterface 주입 필수 (구체 클래스 직접 주입 금지) |
| [settings-multilingual-enrichment.md](docs/backend/settings-multilingual-enrichment.md) | Settings 카탈로그 다국어 자동 보강 | settings JSON 의 다국어 카탈로그 라벨(_cached_name 등)은 카탈로그 빌드 시점에 보강 |
| [translatable-seeders.md](docs/backend/translatable-seeders.md) | 다국어 시더 인터페이스 (Translatable Seeders) | 다국어 JSON 컬럼(name 등)을 시드하는 확장 entity 시더는 TranslatableSeede... |
| [user-overrides.md](docs/backend/user-overrides.md) | 사용자 수정 보존 (HasUserOverrides Trait) | 모델에 `use HasUserOverrides;` + `protected array $trackable... |
| [validation.md](docs/backend/validation.md) | 검증 (Validation) | 필수: FormRequest에서 검증 (Service에 검증 로직 배치 금지) |
### 프론트엔드 [frontend/](docs/frontend/) (50개)
| 문서 | 설명 | TL;DR 핵심 |
|------|------|-----------|
| [actions-g7core-api.md](docs/frontend/actions-g7core-api.md) | 액션 시스템 - G7Core API (React 컴포넌트용) | - |
| [actions-handlers-navigation.md](docs/frontend/actions-handlers-navigation.md) | 액션 핸들러 - 네비게이션 | - |
| [actions-handlers-state.md](docs/frontend/actions-handlers-state.md) | 액션 핸들러 - 상태 관리 | - |
| [actions-handlers-ui.md](docs/frontend/actions-handlers-ui.md) | 액션 핸들러 - UI 인터랙션 | - |
| [actions-handlers.md](docs/frontend/actions-handlers.md) | 액션 핸들러 - 핸들러별 상세 사용법 | navigate: 페이지 이동 (path, query, mergeQuery 옵션) |
| [actions.md](docs/frontend/actions.md) | 액션 핸들러 가이드 | 구조: type 또는 event(이벤트), handler(핸들러명), params(옵션) |
| [auth-system.md](docs/frontend/auth-system.md) | 인증 시스템 (AuthManager) | AuthManager: 싱글톤 인증 상태 관리 클래스 |
| [component-props-composite.md](docs/frontend/component-props-composite.md) | 컴포넌트 Props 레퍼런스 - Composite | FileUploader: autoUpload, uploadTriggerEvent, imageCompre... |
| [component-props.md](docs/frontend/component-props.md) | 컴포넌트 Props 레퍼런스 | - |
| [components-advanced.md](docs/frontend/components-advanced.md) | 컴포넌트 고급 기능 | - |
| [components-patterns.md](docs/frontend/components-patterns.md) | 컴포넌트 패턴 및 다국어 | - |
| [components-types.md](docs/frontend/components-types.md) | 컴포넌트 타입별 개발 규칙 | - |
| [components.md](docs/frontend/components.md) | 컴포넌트 개발 규칙 | HTML 태그 직접 사용 금지 (<div> → Div, <button> → Button) |
| [dark-mode.md](docs/frontend/dark-mode.md) | 다크 모드 지원 (engine-v1.1.0+) | Tailwind dark: variant 사용 (예: bg-white dark:bg-gray-800) |
| [data-binding-i18n.md](docs/frontend/data-binding-i18n.md) | 데이터 바인딩 - 다국어 처리 | - |
| [data-binding.md](docs/frontend/data-binding.md) | 데이터 바인딩 및 표현식 | API 데이터: {{user.name}}, URL 파라미터: {{route.id}} |
| [data-sources-advanced.md](docs/frontend/data-sources-advanced.md) | 데이터 소스 - 고급 기능 | - |
| [data-sources.md](docs/frontend/data-sources.md) | 데이터 소스 (Data Sources) | data_sources 배열에 API 정의: id, endpoint, method |
| [editors.md](docs/frontend/editors.md) | 에디터 컴포넌트 가이드 | HtmlEditor: HTML/텍스트 편집, 게시판/상품 설명 등 사용 |
| [g7core-api-advanced.md](docs/frontend/g7core-api-advanced.md) | G7Core 전역 API 레퍼런스 - 고급 | - |
| [g7core-api.md](docs/frontend/g7core-api.md) | G7Core 전역 API 레퍼런스 | G7Core.state: get/set/subscribe 전역 상태 관리 |
| [g7core-helpers.md](docs/frontend/g7core-helpers.md) | G7Core 헬퍼 API | - |
| [identity-guard-interceptor.md](docs/frontend/identity-guard-interceptor.md) | IdentityGuardInterceptor — 코어 본인인증 인터셉터 레퍼런스 | ActionDispatcher.handleApiCall 응답 후처리에서 isIdentityRequire... |
| [identity-verification-ui.md](docs/frontend/identity-verification-ui.md) | 본인인증(IDV) 공통 UI 가이드 | 모든 IDV 강제 지점은 동일한 428 응답 형식을 공유 (코어 9 + 게시판 4 + 이커머스 4 + N) |
| [layout-json-components-loading.md](docs/frontend/layout-json-components-loading.md) | 레이아웃 JSON - 데이터 로딩 및 생명주기 | - |
| [layout-json-components-rendering.md](docs/frontend/layout-json-components-rendering.md) | 레이아웃 JSON - 조건부/반복 렌더링 | - |
| [layout-json-components-slots.md](docs/frontend/layout-json-components-slots.md) | 레이아웃 JSON - 슬롯 시스템 | - |
| [layout-json-components.md](docs/frontend/layout-json-components.md) | 레이아웃 JSON - 컴포넌트 (반복 렌더링, Blur, 생명주기, 슬롯) | if: 조건부 렌더링 (type: "conditional" 사용 금지!) |
| [layout-json-features-actions.md](docs/frontend/layout-json-features-actions.md) | 레이아웃 JSON - 초기화, 모달, 액션, 스크립트 | - |
| [layout-json-features-error.md](docs/frontend/layout-json-features-error.md) | 레이아웃 JSON - 에러 핸들링 | - |
| [layout-json-features-styling.md](docs/frontend/layout-json-features-styling.md) | 레이아웃 JSON - 스타일 및 계산된 값 | - |
| [layout-json-features.md](docs/frontend/layout-json-features.md) | 레이아웃 JSON - 기능 (에러 핸들링, 초기화, 모달, 액션) | classMap: 조건부 CSS 클래스 (key → variants 매핑) |
| [layout-json-inheritance.md](docs/frontend/layout-json-inheritance.md) | 레이아웃 JSON - 상속 (Extends, Partial, 병합) | extends: 베이스 레이아웃 상속 (type: "slot" 위치에 삽입) |
| [layout-json.md](docs/frontend/layout-json.md) | 레이아웃 JSON 스키마 | HTML 태그 직접 사용 금지 → 기본 컴포넌트 사용 (Div, Button, Span) |
| [layout-testing.md](docs/frontend/layout-testing.md) | 그누보드7 레이아웃 파일 렌더링 테스트 가이드 | createLayoutTest()로 테스트 헬퍼 생성, mockApi()로 API 응답 모킹 |
| [modal-usage.md](docs/frontend/modal-usage.md) | Modal 컴포넌트 사용 가이드 | modals 섹션 모달은 openModal 핸들러로 열고, closeModal 핸들러로 닫음 |
| [responsive-layout.md](docs/frontend/responsive-layout.md) | 반응형 레이아웃 개발 (engine-v1.1.0+) | responsive 속성: 컴포넌트 레벨 breakpoint 오버라이드 (권장) |
| [security.md](docs/frontend/security.md) | 보안 및 검증 | 레이아웃 JSON: FormRequest + Custom Rule 10종 검증 (서버 사전 차단) |
| [state-management-advanced.md](docs/frontend/state-management-advanced.md) | 상태 관리 - 고급 기능 | - |
| [state-management-forms.md](docs/frontend/state-management-forms.md) | 상태 관리 - 폼 자동 바인딩 및 setState | - |
| [state-management.md](docs/frontend/state-management.md) | 전역 상태 관리 | 전역 상태: _global.속성명 (앱 전체 공유, 페이지 이동 시 유지) |
| [tailwind-safelist.md](docs/frontend/tailwind-safelist.md) | Tailwind Safelist 가이드 | Tailwind는 빌드 시 사용된 클래스만 CSS에 포함 |
| [template-development.md](docs/frontend/template-development.md) | 템플릿 개발 가이드라인 | 디렉토리: templates/[vendor-template]/ (예: sirsoft-admin_basic) |
| [template-handlers.md](docs/frontend/template-handlers.md) | 템플릿 전용 핸들러 | setLocale: 앱 언어 변경 — 엔진 빌트인 (ActionDispatcher) |
| [components.md](docs/frontend/templates/sirsoft-admin_basic/components.md) | sirsoft-admin_basic 컴포넌트 | Basic 37개: HTML 래핑 (Div, Button, Input, Select, Form, A, ... |
| [handlers.md](docs/frontend/templates/sirsoft-admin_basic/handlers.md) | sirsoft-admin_basic 핸들러 | setLocale: 앱 언어 변경 (locale 파라미터) |
| [layouts.md](docs/frontend/templates/sirsoft-admin_basic/layouts.md) | sirsoft-admin_basic 레이아웃 | 베이스: _admin_base.json (사이드바 + 헤더 + 콘텐츠 슬롯) |
| [components.md](docs/frontend/templates/sirsoft-basic/components.md) | sirsoft-basic 컴포넌트 | Basic 26개: HTML 래핑 (Div, Button, Input, Select, Form, A, ... |
| [handlers.md](docs/frontend/templates/sirsoft-basic/handlers.md) | sirsoft-basic 핸들러 | setTheme/initTheme: 다크/라이트 모드 전환 (admin과 동일 키 공유) |
| [layouts.md](docs/frontend/templates/sirsoft-basic/layouts.md) | sirsoft-basic 레이아웃 | 베이스: _user_base.json (헤더 + 푸터 + 모바일 네비 + 콘텐츠 슬롯) |
### 확장 시스템 [extension/](docs/extension/) (30개)
| 문서 | 설명 | TL;DR 핵심 |
|------|------|-----------|
| [cache-driver.md](docs/extension/cache-driver.md) | 캐시 드라이버 시스템 (CacheInterface) | 모든 캐시 저장은 CacheInterface 사용 (Cache:: 직접 호출 금지) |
| [changelog-rules.md](docs/extension/changelog-rules.md) | Changelog 규칙 (Changelog Rules) | 확장/코어 버전 업 시 CHANGELOG.md에 변경사항 기록 필수 (미기록 시 버전 업 불가) |
| [editor-spec.md](docs/extension/editor-spec.md) | 편집기 스펙 (editor-spec.json) | editor-spec.json = 편집기 팔레트/스타일 컨트롤/중첩 규칙/샘플 데이터/레시피의 선언 (... |
| [extension-manager.md](docs/extension/extension-manager.md) | ExtensionManager (확장 관리자) | composer.json 수정 없음 - 런타임 오토로드 방식 사용 |
| [extension-update-system.md](docs/extension/extension-update-system.md) | 확장 업데이트 시스템 (Extension Update System) | 업데이트 감지 우선순위: GitHub > _bundled (2단계, _pending 미참여) |
| [hooks.md](docs/extension/hooks.md) | 훅 시스템 (Hook System) | Action 훅: doAction() - 부가 작업 (로그, 알림, 캐시) |
| [language-packs.md](docs/extension/language-packs.md) | 언어팩 시스템 (Language Packs) | 코어/번들 확장의 lang/{ko,en}/ 는 가상 보호 행으로 자동 노출 (DB 없이 항상 activ... |
| [layout-extensions.md](docs/extension/layout-extensions.md) | 레이아웃 확장 시스템 (Layout Extensions) | - |
| [menus.md](docs/extension/menus.md) | 메뉴 시스템 | 구조: User → Role → role_menus 피벗 → Menu |
| [module-assets.md](docs/extension/module-assets.md) | 모듈 프론트엔드 에셋 시스템 | module.json에 에셋 매니페스트 정의 (js, css, loading strategy) |
| [module-basics.md](docs/extension/module-basics.md) | 모듈 개발 기초 | 디렉토리: vendor-module (예: sirsoft-ecommerce) |
| [module-commands.md](docs/extension/module-commands.md) | 모듈 Artisan 커맨드 | 목록: php artisan module:list |
| [module-i18n.md](docs/extension/module-i18n.md) | 모듈 다국어 시스템 | 백엔드: /lang/{locale}/*.php → __('vendor-module::key') |
| [module-identity-settings.md](docs/extension/module-identity-settings.md) | 모듈/플러그인 본인인증(IDV) 설정 통합 가이드 | 정책/목적/메시지: module.php::getIdentity{Policies,Purposes,Mess... |
| [module-layouts.md](docs/extension/module-layouts.md) | 모듈 레이아웃 시스템 | 위치: modules/_bundled/vendor-module/resources/layouts/admi... |
| [module-routing.md](docs/extension/module-routing.md) | 모듈 라우트 규칙 | URL prefix 자동: /api/modules/[vendor-module]/... |
| [module-settings.md](docs/extension/module-settings.md) | 모듈 환경설정 시스템 개발 가이드 | - |
| [permissions.md](docs/extension/permissions.md) | 권한 시스템 | 구조: User → Role → Permission (기능 레벨) |
| [plugin-development.md](docs/extension/plugin-development.md) | 플러그인 개발 가이드 | 디렉토리: plugins/vendor-plugin (예: sirsoft-payment) |
| [sample-extensions.md](docs/extension/sample-extensions.md) | 학습용 샘플 확장 (Sample Extensions) | 샘플 확장 4종: gnuboard7-hello_module / _plugin / _admin_templ... |
| [storage-driver.md](docs/extension/storage-driver.md) | 스토리지 드라이버 시스템 (StorageInterface) | 모든 파일 저장은 StorageInterface 사용 (Storage::disk() 직접 호출 금지) |
| [template-basics.md](docs/extension/template-basics.md) | 템플릿 시스템 기초 | 타입: Admin (관리자용), User (일반사용자용) |
| [template-caching.md](docs/extension/template-caching.md) | 템플릿 캐싱 전략 | - |
| [template-commands.md](docs/extension/template-commands.md) | 템플릿 Artisan 커맨드 | 목록: php artisan template:list |
| [template-idv-bootstrap.md](docs/extension/template-idv-bootstrap.md) | 템플릿 IDV launcher 등록 가이드 | 템플릿 부트스트랩(initTemplate)에서 window.G7Core.identity.setLaunc... |
| [template-routing.md](docs/extension/template-routing.md) | 템플릿 라우트/언어 파일 규칙 | - |
| [template-security.md](docs/extension/template-security.md) | 템플릿 보안 정책 | - |
| [template-workflow.md](docs/extension/template-workflow.md) | 템플릿 개발 워크플로우 | 필수 파일: template.json, routes.json, _base.json, errors/{40... |
| [upgrade-step-guide.md](docs/extension/upgrade-step-guide.md) | 업그레이드 스텝 작성 가이드 (Upgrade Step Guide) | upgrade step 이 실행되는 환경은 경로에 따라 다르다 — 섹션 9 "업그레이드 경로" 먼저 읽기 |
| [vendor-bundle.md](docs/extension/vendor-bundle.md) | Vendor 번들 시스템 (Vendor Bundle System) | - |
### 공통 (5개)
| 문서 | 설명 | TL;DR 핵심 |
|------|------|-----------|
| [cheatsheet.md](docs/cheatsheet.md) | 그누보드7 자주 쓰는 명령어 치트시트 | _bundled에서 레이아웃 JSON만 수정 → 확장 업데이트(--force)만 실행 (빌드 불필요) |
| [database-guide.md](docs/database-guide.md) | 그누보드7 데이터베이스 개발 가이드 | 마이그레이션: 한국어 comment 필수, down() 구현 필수 |
| [requirements.md](docs/requirements.md) | 그누보드7 시스템 요구사항 (System Requirements) | PHP 8.2+ 필수 |
| [testing-guide.md](docs/testing-guide.md) | 그누보드7 테스트 가이드 | 테스트 통과 = 작업 완료 (작성만으로 불충분!) |
| [e2e-testing.md](docs/testing/e2e-testing.md) | 그누보드7 Playwright E2E 테스트 가이드 | - |
### API 레퍼런스 진입점
> 엔드포인트별 요청 파라미터·응답 필드·요청/응답 예시. 공통 규약(Bearer 토큰 인증, 응답 봉투, 페이지네이션, 401/403/422/428)은 진입점 문서 상단에 정리되어 있다.
| 대상 | 진입점 | 문서/엔드포인트 |
|------|--------|----------------|
| 코어 | [docs/backend/api/README.md](docs/backend/api/README.md) | 35 / 291 |
### 확장 API 레퍼런스 (12개 확장, 자동 스캔)
> 각 확장이 소유하는 API 문서 목차. `php artisan api:docgen` 이 생성하며, 이 표는 `{modules,plugins}/_bundled/*/docs/api/README.md` 를 패턴 스캔해 자동 편입된다(확장명 하드코딩 없음).
| 확장 | 유형 | API 문서 목차 | 문서/엔드포인트 |
|------|------|--------------|----------------|
| `gnuboard7-hello_module` | 모듈 | [docs/api/](modules/_bundled/gnuboard7-hello_module/docs/api/README.md) | 1 / 2 |
| `sirsoft-board` | 모듈 | [docs/api/](modules/_bundled/sirsoft-board/docs/api/README.md) | 10 / 80 |
| `sirsoft-ecommerce` | 모듈 | [docs/api/](modules/_bundled/sirsoft-ecommerce/docs/api/README.md) | 33 / 231 |
| `sirsoft-page` | 모듈 | [docs/api/](modules/_bundled/sirsoft-page/docs/api/README.md) | 2 / 17 |
| `sirsoft-ckeditor5` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-ckeditor5/docs/api/README.md) | 2 / 2 |
| `sirsoft-gdpr` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-gdpr/docs/api/README.md) | 4 / 15 |
| `sirsoft-marketing` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-marketing/docs/api/README.md) | 2 / 2 |
| `sirsoft-pay_kginicis` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-pay_kginicis/docs/api/README.md) | 5 / 22 |
| `sirsoft-pay_nhnkcp` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-pay_nhnkcp/docs/api/README.md) | 0 / 0 |
| `sirsoft-pay_nicepayments` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-pay_nicepayments/docs/api/README.md) | 0 / 0 |
| `sirsoft-verification_kginicis` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-verification_kginicis/docs/api/README.md) | 1 / 1 |
| `sirsoft-verification_nhnkcp` | 플러그인 | [docs/api/](plugins/_bundled/sirsoft-verification_nhnkcp/docs/api/README.md) | 1 / 1 |
<!-- AUTO-GENERATED-END: docs-quick-reference -->
---
## 프로젝트 개요
**프로젝트명**: 그누보드7
**목적**: 오픈소스 CMS 플랫폼
**설계 원칙**: 코어 수정 최소화, 모듈화, 플러그인 시스템, 템플릿 시스템, 동적 로딩
---
## 버전 동기화 의무
코어 또는 번들 확장의 공개 표면을 수정할 때, 그 변경의 영향 범위에 있는 다른 확장의 버전 제약(`g7_version`, `dependencies.{modules|plugins}`)을 함께 갱신한다.
### ① 코어 → 확장 동기화 (`requires.g7_version`)
- 트리거: 코어 공개 확장 표면(`app/Extension/Abstract*`, `HookManager`, `ExtensionManager`, `ModuleManager`, `PluginManager`, `TemplateManager`, `app/Contracts/Extension/**`, `app/Extension/Helpers/**`, `app/Seo/Contracts/**`, `app/ActivityLog/**` 공개 API, 루트 `CHANGELOG.md` Added/Changed/Removed) 수정
- 조치: 영향 받는 번들 확장의 `g7_version` 상향 + 각 확장 CHANGELOG 에 변경 기재
### ② 확장 → 확장 동기화 (`dependencies.{modules|plugins}`)
- 트리거: 번들 모듈/플러그인의 공개 Service/Contract/Repository/Model/Route, 발행 훅·이벤트, CHANGELOG 수정
- 조치: 그 확장에 의존하는 다른 번들 확장 전수 스캔 → 최소 버전 제약 상향 여부 판정
### 판정 순서
1. 기존 소비자 API 시그니처/동작을 건드렸는가 → 소비 확장 최소 버전 상향
2. 새 공개 API 가 도입되었는가 → 후보 확장 전수 스캔 후 검토
3. 의존 관계 B 의 공개 API 가 변경되었는가 → A 의 `dependencies.B` 상향
4. 동기화 대상이 없다면 그 근거("순수 내부 리팩토링" 등)를 변경 이력에 기록
> 상세: [changelog-rules.md](docs/extension/changelog-rules.md) "코어 버전 제약 정책"
---
## CRITICAL RULES - 절대 금지 패턴 (DO NOT)
### API/핸들러 호출
| 금지 | 올바른 사용 |
|------|------------|
| `G7Core.actions.execute` | `G7Core.dispatch` |
| `G7Core.api.call` | `G7Core.dispatch({ handler: 'apiCall', ... })` |
| `handler: "api"` | `handler: "apiCall"` |
| `handler: "nav"` | `handler: "navigate"` |
| `handler: "setLocalState"` | `handler: "setState"` + `target: "local"` |
| `navigate` + `replace: true` (URL만 변경 시) | `handler: "replaceUrl"` |
| `navigate` `params.path: "back"` (동작 키워드로 착각) | `handler: "navigateBack"` — path 는 주소로 해석되어 조용히 `/back` 으로 이동한다 |
| `navigate` `params.url` / `href` / `to` 로 목적지 전달 | `params.path` (또는 액션 `target`) — 엔진은 이 둘만 읽는다. 다른 이름은 무시되어 목적지가 `undefined` 가 되고, 예외도 404 도 없이 버튼만 동작하지 않는다 |
| apiCall `params.target` (params 내부) | `target` 은 액션 top-level. params 내부 위치 시 URL 미해석 |
| apiCall `params.onSuccess` / `params.onError` (params 내부) | 액션 top-level. params 내부면 무시됨 |
| `refetchDataSource` `params.id` | `params.dataSourceId` 사용 |
| `handler: "showToast"` | `handler: "toast"` |
| 모달 안에서 부모 `_local.*` 참조 | 데이터소스 응답 필드 또는 `_global` 사용 (모달은 별도 컨텍스트) |
### 데이터 바인딩
| 금지 | 올바른 사용 |
|------|------------|
| `{{products.data}}` | `{{products?.data?.data}}` (배열 경로 확인) |
| `{{value}}` | `{{value ?? ''}}` (fallback 필수) |
| `{{error.data}}` | `{{error.errors}}` (API 응답 구조) |
| `{{error.data?.errors ?? {}}}` | `{{error.errors}}` (`{}}}` 파서 모호성 회피) |
| `$value` (이벤트 값) | `$event.target.value` |
| `{{props.xxx}}` (Partial) | data_sources ID 직접 참조 |
| `{{$response.xxx}}` (onSuccess) | `{{response.xxx}}` ($ 접두사 없음) |
### iteration/반복 렌더링
| 금지 | 올바른 사용 |
|------|------------|
| `"item"`, `"index"` | `"item_var"`, `"index_var"` |
| iteration 내 if 순서 무시 | if가 iteration보다 먼저 평가됨 |
### 컴포넌트 Props
| 금지 | 올바른 사용 |
|------|------------|
| `Icon className="w-4 h-4"` | `className="text-base"` (아래 등가표) 또는 `size` prop |
| `Select valueKey/labelKey` | computed로 `{ value, label }` 변환 |
| Form 내 `Button` type 없음 | `type="button"` 명시 (submit 방지) |
| `options={{options}}` | `options={{options ?? []}}` (fallback) |
Icon 은 `<i>` 글리프라 박스 크기가 곧 `font-size` 다. `w-N h-N` 은 박스만 정하고 글리프는 부모 `font-size` 를 상속하므로 어긋난다. 기존 `w-N h-N` 을 옮길 때는 아래 등가표를 쓴다 (Chrome 실측).
| `w-N h-N` | px | 등가 `className` |
|---|---|---|
| `w-3 h-3` | 12 | `text-xs` |
| `w-3.5 h-3.5` | 14 | `text-sm` |
| `w-4 h-4` | 16 | `text-base` |
| `w-5 h-5` | 20 | `text-xl` |
| `w-6 h-6` | 24 | `text-2xl` |
| `w-12 h-12` | 48 | `text-5xl` |
`size` prop 은 Font Awesome `fa-*` 클래스로 매핑되며 등가가 아니다 — `size="sm"` → `fa-sm` → `font-size: 0.875em`(상대값) + `line-height` 붕괴로 16px 이 12.25×0.88px 이 된다. 새 아이콘에는 써도 되지만, 기존 `w-N h-N` 의 치환용으로는 쓰지 않는다.
### 상태 관리
| 금지 | 올바른 사용 |
|------|------------|
| 스냅샷 기반 setState | 함수형 업데이트 또는 `stateRef.current` |
| closeModal 후 setState | setState 후 closeModal (순서 중요) |
| sortable 내 폼 자동바인딩 | `parentFormContextProp={undefined}` |
| await 후 캡처된 상태 사용 | await 후 `G7Core.state.getLocal()` 재조회 |
| setState params 키에 `{{}}` 사용 | 키는 정적 경로만, 배열 조작은 `.map()`/`.filter()` |
### 핸들러 정의
| 금지 | 올바른 사용 |
|------|------------|
| `{{handler()}}` (표현식에서 호출) | `actions: [{ handler: "xxx" }]` |
### globalHeaders 사용 (engine-v1.16.0+)
| 금지 | 올바른 사용 |
|------|------------|
| `"globalHeaders": { "X-Key": "value" }` | `"globalHeaders": [{ "pattern": "*", "headers": {...} }]` |
| 모든 API에 개별 headers 설정 | globalHeaders로 공통 헤더 정의 |
| pattern 없이 헤더 정의 | pattern 필수 (`*`, `/api/shop/*` 등) |
### 인증/리다이렉트 규칙 (engine-v1.47.0+)
| 금지 | 올바른 사용 |
|------|------------|
| 모듈/플러그인에서 `AuthManager.updateConfig()` 호출 | 템플릿 부트스트랩(`initTemplate`)에서만 호출 |
| `AuthManager.updateConfig({ loginPath: 'https://...' })` (외부 origin) | `loginPath` 는 `/` 로 시작하는 동일 origin path-only |
| `AuthManager.updateConfig({ loginPath: '//evil.com/...' })` (protocol-relative) | `//` 시작 금지 (open redirect 방지) |
| 401 에러 페이지(`errors/401.json`)에서 직접 로그인 리다이렉트 구현 | 코어 `TemplateApp.showRouteError` 가드에 위임 (자동 처리) |
### 정적 확장자 라우트 / 자산 URL 생성
| 금지 | 올바른 사용 |
|------|------------|
| `Route::get('{id}/routes.json', ...)` (`.js`/`.css`/`.json`/`.map` 단일 등록) | `Route::dualSuffix('{id}/routes', 'json', ...)` — 확장자 형태 + 확장자 없는 형태 동시 등록 |
| `Route::get('bundle.js', ...)` (접미사가 종류를 구분해 제거 불가) | `Route::dualSuffixSegment('bundle', 'js', ...)` (`bundle.js` + `bundle/js`) |
| `Route::get('assets/{id}/{path}', ...)` (와일드카드 자산) | `Route::dualAsset('assets/{id}', ...)` (`.../{path}` + `?file=` 쿼리) |
| 서버에서 `'/api/templates/assets/'.$id.'/'.$path` 문자열 조립 | `App\Support\AssetUrl::templateAsset($id, $path)` |
| 프론트에서 `` `/api/templates/${id}/routes.json` `` 템플릿 리터럴 조립 | `resources/js/core/support/assetUrl.ts` 의 `suffixed()` / `templateAsset()` 등 |
정규식 location 은 프리픽스 location 보다 먼저 매칭되므로, 정적 최적화 블록(`location ~* \.(js|css|json)$`)이 있는 서버에서는 확장자 붙은 동적 응답이 `try_files ... /index.php` 폴백 기회 없이 404 가 된다. 서버측 `AssetUrl` 과 프론트측 `assetUrl.ts` 는 동일 규칙을 공유하므로 한쪽만 바꾸면 그 자산만 404 가 된다. 상세: [routing.md](docs/backend/routing.md) "정적 확장자로 끝나는 동적 엔드포인트", [api/README.md](docs/backend/api/README.md) "자산 URL 이중 모드".
### 목록 컨텍스트 왕복 (list context round-trip)
페이지네이션 목록 화면과 그에 딸린 상세·형제 상세·작성/수정 폼·확인 모달은 하나의 목록 클러스터다. 이 클러스터 안에서의 이동은 URL 목록 상태(`page`/`search`/`category`/`filters[*]`/정렬/`per_page`)를 손실 없이 보존해야 한다.
| 금지 | 올바른 사용 |
|------|------------|
| 클러스터 내 navigate 에 `mergeQuery` 누락 | `"params": { "path": "…", "mergeQuery": true, "query": {} }` |
| 이전글/다음글 등 형제 상세 이동만 규약에서 누락 | 목록 진입 / 목록 복귀 / 형제 이동 / 폼 취소 / 삭제 후 복귀 전 leg 동일 적용 |
| 현재 값을 그대로 다시 넘기는 키 열거 (`{"del": "{{query.del ?? ''}}"}`) | `mergeQuery` 가 이미 전부 나른다 — 열거는 중복이자 누락 위험 |
| 덮어쓸 키만 남기지 않고 필터 키 전부 재열거 | 값을 바꿔야 하는 키만 남긴다 (페이지 되돌림은 `{"page": ""}`) |
| 새로고침 버튼에 `mergeQuery: false` | 새로고침은 보던 목록을 다시 부르는 것 — 병합 유지 |
| `mergeQuery` 를 표현식으로 분기 (`"{{cond}}"`) | boolean 리터럴 고정 — 분기마다 보존 여부가 갈리면 한쪽이 조용히 상태를 떨군다 |
| `"path": "/board/{slug}/write?parent_id={{id}}"` (인라인 쿼리스트링) | 인라인 쿼리는 병합 시 버려진다 → `query` 객체로 옮긴다 |
| `mergeQuery: true` + `query` 키 생략 | 의도를 드러내도록 `"query": {}` 를 함께 둔다 |
| `"query": []` (배열 리터럴) | `"query": {}` — 동작은 같아 조용히 통과하지만, 나중에 덮어쓸 키를 넣으면 그 값이 버려진다 |
| 목적지가 표현식이라 판정 불가한 이동을 무표시로 둠 (`"{{_global.shopBase}}/products"`) | 클러스터 내 이동이면 `mergeQuery: true`, 밖으로 나가는 이동이면 예외 주석으로 의도를 명시 |
| 의도적 리셋(검색·필터 초기화 / 탭 전환 / 프리셋 적용)에 `mergeQuery: true` | 리셋은 병합하지 않는다 — 병합하면 초기화 버튼이 아무 일도 하지 않는다 |
| 탭 전환(`onTabChange`)이나 겹치지 않는 다른 목록으로의 이동에 `mergeQuery: true` | 목록 정체성이 다르면 승계하지 않는다 — 남의 검색어·페이지가 얹혀 빈 화면이 열린다 |
| 면제 주석은 "병합하지 않는다" 인데 코드는 `mergeQuery: true` | 주석과 코드를 일치시킨다 (주석은 사실이 아니라 선언일 뿐) |
| 검색 실행·페이지 이동 액션에서 `query` 키를 비움 | 값을 바꾸는 액션은 그 값을 직접 넘긴다 (`{"page": "{{$args[0]}}"}`) — 병합만으로는 새 값이 전달되지 않는다 |
| `path` 없이 `query` 만 바꾸는 액션에 `mergeQuery` 누락 (탭 전환 `{"tab": …}`, 항목 선택 `{"id": …, "mode": "view"}`) | `path` 생략은 "현재 주소에 작용" 이라 목록 화면 자신이 대상 — `mergeQuery: true` 없으면 지금 걸린 목록 상태가 통째로 날아간다 |
의도적 리셋(검색 초기화 / 필터 초기화 / 탭 전환 / 프리셋 적용 / 다른 목록으로의 이동)은 예외다. 그 경우 액션 노드 `comment` 에 `audit:allow layout-list-context-navigate-merge-query <사유>` 를 남겨 의도를 코드에 기록한다. 상세: [actions-handlers-navigation.md "목록 컨텍스트 왕복 규약"](docs/frontend/actions-handlers-navigation.md)
### 중첩 리소스 스코프 / 계층 무결성
| 금지 | 올바른 사용 |
|------|------------|
| 중첩 라우트의 상위 리소스 ID 를 받아만 두고 조회에 미반영 | Repository where 절에 상위 스코프 반영(SSoT) + Service 가 상위 ID 전달 → 교차 접근 시 404 |
| `$request->except(...)` / `->all()` 결과를 Service 쓰기 메서드로 전달 | `$request->validated()` 기준 (FormRequest 미정의 필드가 `$fillable` 로 새는 것 차단) |
| 요청 배열 항목의 `Rule::exists` 에 상위 스코프 미부착 | `Rule::exists(Model::class,'id')->where('order_id', $order->id)` → 422 |
| 수정/순서변경 FormRequest 의 `parent_id` 에 `Rule::exists` 만 부착 | 자손 전체를 검사하는 순환 방지 Rule 부착 (자기참조만 막는 Rule 은 `A→B→A` 통과) |
| 같은 리소스의 두 엔드포인트가 서로 다른 검증 강도 | 부모 변경 경로 전부 동일 강도 — 약한 쪽이 우회로가 된다 |
| 설정값이 정하는 한계를 Service 에서 리터럴로 재클램프 | Service 는 계산만, 상한 검증은 Rule 단일 책임 (이중 클램프 시 깊이 제한이 통째로 무력화) |
| 계층 재귀(path/depth 재계산)에 방문 ID 가드 없음 | 방문 집합으로 유한 종료 — 검증 우회 경로/오염 데이터에서도 무한 루프 금지 |
> 상세: [validation.md "계층 리소스 순환 참조" / "배열 항목의 상위 스코프"](docs/backend/validation.md), [service-repository.md "중첩 리소스 스코프" / "설정 기반 한계값"](docs/backend/service-repository.md)
### Listener 데이터 접근
| 금지 | 올바른 사용 |
|------|------------|
| Listener 에서 `Model::query/find/where/create` 직접 호출 | Repository 인터페이스 주입 후 위임 |
| Listener 에서 `DB::table()->update(...)` | Repository 의 도메인 의도 메서드 (recalculate*/anonymize* 등) |
| Listener 에서 `$row->save()` / `saveQuietly()` / `delete()` | Repository 의 update/save/delete 호출 |
| Listener 생성자에 구체 Repository 직접 주입 | Repository Interface 주입 |
| Listener 에서 `request()` / `$_POST` 직접 접근 | Service 가 검증 후 도메인 객체로 전달 받기 |
| Filter 훅에 `'type' => 'filter'` 누락 | type 명시 필수 (반환값 무시 회귀 차단) |
| Listener 가 `HookListenerInterface` 미구현 (auto-discovery 대상) | implements + `getSubscribedHooks()` 정적 메서드 |
> 상세: [hooks.md "Listener 데이터 접근 규정"](docs/extension/hooks.md), [service-repository.md](docs/backend/service-repository.md)
---
## 템플릿 엔진 내부 버전 (engine-v1.x.x)
코드와 문서에서 `engine-v1.x.x` 형태의 버전은 **템플릿 엔진의 내부 개발 이력**입니다.
그누보드7 공식 버전(`config/app.php`)과는 무관합니다.
- **CHANGELOG**: `resources/js/core/template-engine/CHANGELOG.md`
- **표기법**: `engine-v1.X.Y` (engine- 접두사 필수, `v1.X.Y` 단독 사용 금지)
- **사용처**: @since JSDoc, 인라인 주석, 규정 문서
### 엔진 CHANGELOG 반영 규칙
| 트리거 | 필수 작업 |
|--------|----------|
| `resources/js/core/template-engine/**` 기능 추가 시 | 마이너 버전 업 (engine-v1.X+1.0) + CHANGELOG 기록 |
| `resources/js/core/template-engine/**` 버그 수정 시 | 패치 버전 업 (engine-v1.X.Y+1) + CHANGELOG 기록 |
| `resources/js/core/*.ts` (TemplateApp, G7CoreGlobals 등) 버그 수정 시 | CHANGELOG `[Unreleased]` 또는 해당 버전에 기록 |
| 코드에 `@since` 추가 시 | CHANGELOG 해당 버전에 항목 추가 |
| 규정 문서에 엔진 버전 표기 시 | `engine-v1.X.Y` 형식 사용 |
### 엔진 CHANGELOG 대상 범위
엔진 CHANGELOG에 기록하는 대상은 **엔진 코어 코드**의 변경사항입니다:
| 포함 (엔진 코드) | 제외 (비엔진 코드) |
|------------------|---------------------|
| `resources/js/core/template-engine/**` | `templates/**/src/components/**` (템플릿 컴포넌트) |
| `resources/js/core/TemplateApp.ts` | `modules/**/resources/layouts/**` (모듈 레이아웃) |
| `resources/js/core/G7CoreGlobals.ts` | `resources/layouts/**` (코어 레이아웃 JSON) |
| `resources/js/core/template-engine.ts` | `docs/**` (규정 문서) |
| `resources/js/core/types/` (엔진 타입) | 백엔드 PHP 코드 |
### 엔진 CHANGELOG 작성 형식
Keep a Changelog 표준:
- `### Added` — 새 기능
- `### Fixed` — 버그 수정
- `### Changed` — 기존 기능 변경
- `### Deprecated` — 곧 제거될 기능
- `### Removed` — 제거된 기능
### CHANGELOG 항목 작성 규칙
엔진 코드 수정 후 CHANGELOG 미기록 시 작업 미완료로 간주합니다.
**항목 형식**: `- 수정 내용 요약 (수정 파일명)`
```markdown
# 좋은 예
- setState dot notation 멀티 키 병합 시 이전 키 변경 유실 방지 (ActionDispatcher)
- blocking 데이터소스 + errorHandling 데드락 — fallback 동기 적용, 에러핸들러 비동기 실행
# 나쁜 예
- 버그 수정 ← 무엇을 수정했는지 불명확
- ActionDispatcher.ts 수정 ← 파일명만으로는 변경 내용 파악 불가
```
**패치 버전 항목 형식**: `- (engine-v1.X.Y) 수정 내용 (파일명)`
```markdown
- (engine-v1.17.5) dataKey 자동 바인딩 컴포넌트에서 setState 호출 시 stale 값 방지
```
**버전 결정 기준**:
| 상황 | 버전 처리 |
|------|----------|
| 새 기능 추가 (핸들러, 속성, API) | 마이너 버전 업: `engine-v1.X+1.0` |
| 기존 기능 버그 수정 | 패치 버전 업: `engine-v1.X.Y+1` |
| 특정 버전에 귀속 불가한 수정 | `[Unreleased]` 섹션에 기록 |
| 릴리스 시 | `[Unreleased]` → `[engine-v1.X.0]`으로 이동 |
**대규모 Fixed 섹션 카테고리 분류** (항목 10개 초과 시):
```markdown
### Fixed
#### 상태 동기화
- 항목 1
- 항목 2
#### 캐시
- 항목 3
```
---
## 공개 CHANGELOG 작성 규칙
코어(`CHANGELOG.md`) 및 확장(`modules/*/CHANGELOG.md`, `plugins/*/CHANGELOG.md`, `templates/*/CHANGELOG.md`)의 릴리즈 CHANGELOG 작성 규칙입니다.
### 톤과 표현
- 사용자/개발자가 읽는 문서이므로 **사용자 관점**으로 작성
- "~할 수 있도록 개선", "~하도록 변경", "~문제 수정" 톤 사용
- 각 불릿은 **1~2줄**로 간결하게
- 내부 구현 상세(클래스명, 파일 경로, 테스트 건수, 훅 체인, DI 패턴)는 포함하지 않음
- 이슈 번호(`#123`)는 포함하지 않음
### 포함/제외 대상
| 포함 | 제외 |
|------|------|
| 사용자에게 보이는 기능 추가/변경 | 내부 파일 경로, 클래스/메서드명 |
| API 변경 (엔드포인트, 파라미터) | 테스트 건수/파일명 |
| 기존 기능의 버그 수정 | 리팩토링 세부사항 |
| 성능 개선 (체감 가능한 것) | 내부 규정/문서 변경 |
| Breaking Change | 내부 작업 이슈 번호 단독 (예: `refs #347`) |
| 엔진 버전 참조 (engine-v1.X.Y) | 코드 패턴 설명 |
| **공개 제보자 attribution** (`(#N @login 님께서 제보해주셨습니다.)`) | — |
| **KISA 등 공식 보안 채널** (`(KISA 측에서 제보해주셨습니다 — KVE-XXXX-XXXXX)`) | — |
### 공개 제보자 attribution
공개 저장소(GitHub) 이슈로 제보·건의된 항목이 출시 CHANGELOG 에 반영되면, 항목 끝에 공개 이슈 번호와 제보자 GitHub 핸들 멘션을 부착합니다.
- 형식: `- (본문) (#N @login 님께서 제보해주셨습니다.)` 또는 `... 건의해주셨습니다.`
- 톤: 버그 리포트는 "제보", 제안형 개선 요청은 "건의"
- 다중 매칭: `(#A @x, #B @y 님께서 제보해주셨습니다.)` / 혼재: `(#A @x 님께서 제보해주시고, #B @y 님께서 건의해주셨습니다.)`
- KISA 등 공식 보안 채널: GitHub 멘션 없이 텍스트 "KISA 측에서" + 공개 가능한 식별자만
### 신규 기능의 버그 수정 제외 규칙
해당 릴리즈에서 **새로 도입한 기능**의 개발 중 버그 수정은 Fixed에 기록하지 않습니다. 사용자 관점에서 그 기능은 해당 릴리즈에서 처음 제공되므로, "추가했다가 고쳤다"는 내부 개발 이력일 뿐입니다.
**판단 기준**: 해당 버그가 **이전 릴리즈에도 존재했던 기능**에서 발생한 것인지 확인
### 기능 그룹핑
Added/Changed/Fixed 내 항목이 10개를 초과하면 `####` 서브 헤딩으로 기능 단위 분류합니다.
### Keep a Changelog 형식
- `## [버전] - YYYY-MM-DD` 헤더 필수
- `### Added` / `### Changed` / `### Fixed` / `### Removed` 카테고리 사용
- 최신 버전이 파일 상단
---
## 레이아웃 JSON 구현 규칙
```
1. 새로운 기능 사용 전 → 반드시 해당 규정 문서에서 지원 여부 확인
2. 지원되지 않는 문법 사용 금지 → 추측/가정으로 구현하지 않음
3. 불확실한 경우 → 기존 레이아웃 패턴 참조
4. 규정 문서에 없는 기능 → 절대 사용 금지
```
### 레이아웃 작성 체크리스트
```
□ 레이아웃 구조가 layout-json.md 스키마와 일치하는가?
□ 사용할 컴포넌트가 components.md에 정의되어 있는가?
□ 컴포넌트 props가 component-props.md에 정의된 것만 사용하는가?
□ 사용할 핸들러가 actions.md에 정의되어 있는가?
□ 핸들러의 params 구조가 actions-handlers.md와 일치하는가?
□ 데이터 바인딩 문법이 data-binding.md에 정의된 형식인가?
□ 다크 모드 클래스가 dark-mode.md 규칙을 따르는가?
□ 기존 유사 레이아웃에서 동일 패턴이 사용되고 있는가?
```
### 주의 사항
```text
필수: 규정 문서에 정의된 핸들러/props/바인딩 문법만 사용 (API 응답 구조도 확인 후 바인딩)
필수: Partial은 컴포넌트 치환만 수행 (computed, data_sources, modals, state 미지원)
필수: data_sources ID 고유성 유지, 조건부 렌더링은 if 속성만 사용 (type: "conditional" 미지원)
```
---
## 테스트 프로토콜
```text
기능 구현 = 테스트 코드 작성 필수
신규 기능 / 도메인 표면 변경 = 시나리오 매니페스트(tests/scenarios/<feature>.yaml) 작성 의무 — 입력 axis cross product + 후속 효과 체인 전수 커버
테스트 통과 = 작업 완료 (작성만으로 불충분!)
기존 테스트 있음 → 변경사항 반영하여 수정 후 실행
기능 구현 시 관련된 모든 계층(백엔드+프론트엔드+레이아웃 렌더링) 테스트 필수
주의: 모듈/플러그인 프론트엔드 테스트는 독립 vitest.config.ts 사용 (루트 config 포함 금지)
필수: 도메인 매트릭스 = 테스트 위치/형식 가이드. 입력 조합 망라 의무는 시나리오 매니페스트가 SSoT
필수: 버그 수정은 먼저 실패하는 회귀 테스트 → fail 확인 → 수정 → green 4단계
필수: 테스트 중 발견한 무관 에러도 같은 세션에서 처리 (stale test 또는 로직 수정)
필수: 릴리스 전 composer test-smoke 통과
```
> 상세: [docs/testing-guide.md](docs/testing-guide.md) — 기능 단위 시나리오 매트릭스, 도메인 매트릭스, Pre-release Smoke Suite, 회귀 테스트 4단계, 무관 에러 처리
### 그누보드7 레이아웃 렌더링 테스트
```text
그누보드7 레이아웃 테스트는 브라우저 기반 E2E가 아님!
Vitest + createLayoutTest() 유틸리티 사용 → 추가 인프라 불필요
"인프라 부족" 이유로 레이아웃 테스트 건너뛰기 절대 금지
레이아웃 테스트는 해당 레이아웃이 속한 확장 디렉토리에 작성
모듈 테스트: modules/_bundled/{id}/resources/js/__tests__/layouts/
템플릿 테스트: templates/_bundled/{id}/__tests__/layouts/
코어 테스트: resources/js/core/template-engine/__tests__/layouts/
```
| 특성 | 설명 |
|------|------|
| **테스트 환경** | Vitest (jsdom) - 브라우저 불필요 |
| **렌더링** | DynamicRenderer를 통한 실제 React 렌더링 |
| **유틸리티** | `createLayoutTest()` - 이미 구축됨 |
| **API 모킹** | `mockApi()` - fetch 자동 모킹 |
| **상태 관리** | `getState()`, `setState()` - 즉시 사용 가능 |
| **액션 트리거** | `triggerAction()` - 핸들러 실행 |
```typescript
import { createLayoutTest, screen } from '../utils/layoutTestUtils';
const testUtils = createLayoutTest(layoutJson);
testUtils.mockApi('products', { response: { data: [] } });
await testUtils.render();
expect(screen.getByTestId('element')).toBeInTheDocument();
testUtils.cleanup();
```
### 테스트 작성 트리거
| 수정 대상 | 테스트 파일 위치 | 테스트 유형 |
|----------|-----------------|-------------|
| `app/Models/*.php` | `tests/Unit/Models/*Test.php` | 모델 메서드, 관계, 스코프 |
| `app/Services/*.php` | `tests/Unit/Services/*Test.php` | 비즈니스 로직 |
| `app/Enums/*.php` | `tests/Unit/Enums/*Test.php` | Enum 메서드 |
| `app/Http/Controllers/**/*.php` | `tests/Feature/**/*Test.php` | API 엔드포인트 |
| `database/migrations/*.php` | 해당 모델/서비스 테스트에서 검증 | 스키마 변경 |
| `templates/**/src/components/**/*.tsx` | `templates/**/__tests__/*.test.tsx` | 컴포넌트 |
| `resources/js/core/**/*.ts` | `resources/js/core/__tests__/*.test.ts` | 템플릿 엔진 |
| `resources/layouts/**/*.json` | `resources/js/core/template-engine/__tests__/layouts/*.test.tsx` | 코어 레이아웃 렌더링 |
| `modules/**/resources/layouts/**/*.json` | `modules/_bundled/{id}/resources/js/__tests__/layouts/*.test.tsx` | 모듈 레이아웃 렌더링 |
| `templates/**/layouts/**/*.json` | `templates/_bundled/{id}/__tests__/layouts/*.test.tsx` | 템플릿 레이아웃 렌더링 |
### 기능 구현 시 전 계층 테스트
| 작업 유형 | 백엔드 (PHPUnit) | 프론트엔드 (Vitest) | 레이아웃 렌더링 (Vitest) |
| ---------- | ----------------- | ------------------- | ---------------------- |
| 새 화면 구현 | API 엔드포인트 테스트 | 컴포넌트 테스트 | 레이아웃 JSON 렌더링 테스트 |
| 기존 화면 수정 | 변경된 API 테스트 | 변경된 컴포넌트 테스트 | 레이아웃 렌더링 회귀 테스트 |
| 데이터 흐름 변경 | Service/Repository 테스트 | 상태 관리 테스트 | 데이터 바인딩 렌더링 테스트 |
### Windows 환경 테스트 규칙
```text
프론트엔드 (npm/Vitest) → PowerShell 래퍼 필수
백엔드 (PHPUnit/Laravel) → Bash 직접 실행
```
**프론트엔드 (템플릿 디렉토리에서 실행 권장)**:
```bash
# 템플릿 디렉토리에서 실행 (해당 템플릿만 테스트)
cd templates/sirsoft-admin_basic
powershell -Command "npm run test:run" # 전체
powershell -Command "npm run test:run -- DataGrid" # 특정 테스트
# 루트에서 실행 (모든 테스트)
powershell -Command "npm run test:run"
powershell -Command "npm run test:run -- template-engine" # 코어 테스트
```
**백엔드**:
```bash
php artisan test
php artisan test --filter=TestName
```
**_bundled 확장 테스트 (활성 디렉토리 복사 불필요)**:
```bash
# _bundled 모듈 테스트 직접 실행
php vendor/bin/phpunit modules/_bundled/sirsoft-ecommerce/tests
php vendor/bin/phpunit --filter=ShippingPolicyControllerTest modules/_bundled/sirsoft-ecommerce/tests
# _bundled 모듈 프론트엔드 테스트
cd modules/_bundled/sirsoft-ecommerce
powershell -Command "npm run test:run"
```
### 필수 준수 사항
```text
필수: 기능 구현 시 모든 계층(백엔드+프론트엔드+레이아웃) 테스트 포함
필수: 테스트 통과 확인 후 완료 선언 (기존 테스트 유지 — 삭제/skip 금지)
필수: createLayoutTest() 유틸리티 활용 (추가 인프라 불필요)
```
> 상세: [testing-guide.md](docs/testing-guide.md) | [layout-testing.md](docs/frontend/layout-testing.md)
---
## npm install 규칙
기본 `npm install`은 `package-lock.json`을 자동 수정할 수 있으므로, lock 파일 변경 의도가 없는 의존성 복구나 작업 환경 재구성에는 `npm install --package-lock=false`를 사용합니다.
| 상황 | 권장 명령어 | 비고 |
| ---- | ----------- | ---- |
| 누락 의존성 복구 / 작업 환경 재구성 | `npm install --package-lock=false` | lock 파일 변경 없이 설치 |
| clean install | `npm ci` | `package.json`과 `package-lock.json`이 동기화된 경우 |
| 의존성 신규 추가/업데이트 | `npm install <pkg>` | lock 변경이 작업 범위에 포함된 경우만 |
lock 파일 변경 의도가 없는 상황에서 `npm install` 단독 실행을 피합니다. `module.json`, `plugin.json`, `template.json`의 `version`을 바꾸면 해당 확장의 `package.json`, `package-lock.json`, `composer.json` 버전도 함께 동기화합니다. 의존성 재설치 없이 lock 파일의 version 필드만 갱신할 때는 `npm install --package-lock-only`를 사용합니다.
---
## 핵심 원칙
### 1. 동적 로딩
```
절대 금지: composer.json에 모듈/플러그인 하드코딩
필수: /modules와 /plugins 디렉토리 스캔으로 자동 발견
```
### 2. 코어 수정 최소화
- 모든 확장은 모듈/플러그인으로 구현
- 훅 시스템을 통한 기능 추가
- 서비스 계층에서 훅 실행
### 3. 계층 분리
```
Controller → Request → Service → RepositoryInterface → Repository → Model
```
### 4. Repository 인터페이스
```
절대 금지: Repository 구체 클래스 직접 타입힌트
필수: Repository 인터페이스를 통한 DI
필수: CoreServiceProvider에서 인터페이스-구현체 바인딩
```
---
## 기술 스택
### 백엔드
- **PHP**: 8.2+
- **Laravel**: 12.x
- **데이터베이스**: MySQL 8.0
- **인증**: Laravel Sanctum 4.x
- **테스트**: PHPUnit 11.x
- **코드 스타일**: Laravel Pint (PSR-12)
---
## 아키텍처 패턴
### 디렉토리 구조 개요
```text
/
├── /app # 코어 애플리케이션
├── /modules # 모듈 디렉토리
│ ├── _bundled/ # 선탑재 확장 소스 (Git 추적)
│ ├── _pending/ # 외부 다운로드 대기소 (Git 제외)
│ └── vendor-module/ # 활성 설치 디렉토리 (Git 제외)
├── /plugins # 플러그인 디렉토리 (동일 구조)
├── /templates # 템플릿 디렉토리 (동일 구조)
├── /resources/js/core/ # 코어 렌더링 엔진
└── /public/build/ # Vite 빌드 결과
```
### 네이밍 규칙
| 항목 | 디렉토리명 | 네임스페이스 |
|------|-----------|-------------|
| 모듈 | `sirsoft-ecommerce` | `Modules\Sirsoft\Ecommerce\` |
| 플러그인 | `sirsoft-payment` | `Plugins\Sirsoft\Payment\` |
| 템플릿 | `sirsoft-admin_basic` | - |
---
## 백엔드 개발 - 핵심 요약
> 상세: [docs/backend/](docs/backend/) | [database-guide.md](docs/database-guide.md)
```text
절대 금지: Service 클래스에 검증 로직 구현 → FormRequest + Custom Rule 사용
절대 금지: FormRequest authorize()에서 인증/권한 로직 → permission 미들웨어 사용
필수: __() 함수를 사용한 다국어 처리
필수: 상태/타입/분류는 Enum으로 정의
절대 금지: 인증 필요 미들웨어를 append()로 전역 등록 → appendToGroup('api') 사용
절대 금지: DB CASCADE에 의존한 삭제 → Service에서 명시적 삭제 (훅/파일/로깅 보장)
절대 금지: 로케일 하드코딩 → config('app.supported_locales') 사용
필수: 마이그레이션 한국어 comment 필수, down() 구현 필수
필수: FK 컬럼의 ->comment() 는 ->constrained()/->references()/->on() 앞에 둔다 (뒤에 두면 comment 가 컬럼이 아닌 FK 정의에 부착되어 조용히 사라진다)
필수: 소스 교정만으로는 기설치본이 낫지 않는다 — 마이그레이션은 재실행되지 않으므로 업그레이드 스텝 백필을 함께 작성
주의: ResponseHelper::success($messageKey, $data) — 메시지가 첫 번째 인수
```
> 상세 규칙 (API 리소스, ServiceProvider, validation, 인증, 활동 로그 등): [docs/backend/](docs/backend/) 각 문서 참조
### 컨트롤러 계층
```text
BaseApiController (최상위)
├── AdminBaseController (관리자 전용)
├── AuthBaseController (인증된 사용자)
└── PublicBaseController (공개 API)
```
### 파사드 사용
```text
✅ use Illuminate\Support\Facades\Log; → Log::info()
❌ \Log::info(), auth()->user() 금지
```
---
## 프론트엔드/템플릿 시스템
> 상세: [docs/frontend/](docs/frontend/)
```text
필수: 기본 컴포넌트만 사용 (Div, Button, H2 등 — HTML 태그 직접 사용 금지)
필수: 집합 컴포넌트 재사용 우선
필수: 다크 모드 light/dark variant 함께 지정
필수: HtmlEditor 사용 (RichTextEditor 미구현)
```
---
## 확장 시스템 빠른 참조
> 상세: [docs/extension/](docs/extension/)
```text
필수: 모든 확장 작업은 _bundled 디렉토리에서만 수행 (활성 디렉토리 직접 수정 금지)
필수: 프로덕션 반영은 update 커맨드로만 수행 (_bundled → 활성 디렉토리)
필수: 확장 코드 변경 시 manifest 버전 업 (미변경 시 업데이트 감지 불가)
필수: 버전 업 시 CHANGELOG.md 기록 — Keep a Changelog 표준 (미기록 시 버전 업 불가)
필수: StorageInterface 사용 (Storage::disk() 직접 호출 금지)
필수: ActionDispatcher 에 핸들러를 등록하는 확장은 재등록 진입점을 window 전역에 고정 이름으로 노출 — 모듈 window.__[Name].initModule, 플러그인 window.__[Name].initPlugin (미노출 시 로케일 전환 후 해당 확장 액션이 전부 무반응, 에러·토스트 없음). 진입점은 핸들러 재등록만 수행
필수: 확장 미들웨어는 getMiddleware() 로 부착 대상(targets) 명시 선언 (self-gate) — SP Kernel 미들웨어 그룹 직접 조작·라우트 파일 자기 미들웨어 FQCN 부착 금지, 무규율 전역 개입 금지
필수: 코어 레이아웃에 모듈 UI 주입은 layout_extensions만 사용
필수: 모든 확장 작업은 Artisan 커맨드로 수행
```
> 상세 규칙 (플러그인 의존성, 훅 시스템, 버전 동기화, 업그레이드 스텝 등): [docs/extension/](docs/extension/) 각 문서 참조
### 확장 타입 요약
| 타입 | 네이밍 | 네임스페이스 | 예시 |
|------|--------|-------------|------|
| 모듈 | vendor-module | Modules\Vendor\Module\ | sirsoft-ecommerce |
| 플러그인 | vendor-plugin | Plugins\Vendor\Plugin\ | sirsoft-payment |
| 템플릿 | vendor-template | - | sirsoft-admin_basic |
---
## 한국어 사용 규칙
```
한국어: 사용자 대상 텍스트, 주석, 문서, 커밋 메시지, DB comment
영어: 변수명, 함수명, 클래스명
Laravel 기본 메서드 주석은 영어 유지 (up(), down() 등)
```
---
## 코드 품질
### Laravel Pint
```bash
vendor/bin/pint --dirty
```
### PHPDoc
```php
/**
* 상품을 생성합니다.
*
* @param array $data 상품 생성 데이터
* @return Product 생성된 상품 모델
* @throws \Exception 생성 실패 시
*/
public function createProduct(array $data): Product
```
---
## 빌드 vs 확장 업데이트
| 수정 파일 유형 | 필요한 작업 |
|---------------|-------------|
| `*.json` (레이아웃만) | `{type}:update {id} --force` 실행 |
| `*.tsx`, `*.ts` + `*.json` | `{type}:build` + `{type}:update {id} --force` |
| `*.tsx`, `*.ts`만 | `{type}:build` + `{type}:update {id} --force` |
| `lang-packs/_bundled/**` (번들 언어팩 콘텐츠/버전) | `language-pack:update {id} --force` (빌드 불필요) |
```bash
# 확장 업데이트 (_bundled → 활성 반영)
php artisan template:update sirsoft-admin_basic --force
php artisan module:update sirsoft-ecommerce --force
php artisan plugin:update sirsoft-payment --force
php artisan language-pack:update g7-core-ja --force
```
번들 언어팩도 `_bundled` 는 배포 원본일 뿐이다. 설치본(`lang-packs/{id}/`)을 갱신하지 않으면 새로 추가한 번역 키가 런타임에 존재하지 않아 해당 로케일이 조용히 기준 로케일로 폴백한다.
### 코어 3-번들 구조 + 공유 런타임 (engine-v1.51.0+)
`core:build` 는 코어 프론트엔드를 3개 IIFE 번들로 빌드한다:
| 번들 | 로드 시점 | vite config |
|------|----------|-------------|
| `template-engine.min.js` | 모든 페이지 (동기 `<script>`) | `vite.config.core.js` |
| `layout-editor.min.js` | `/admin/layout-editor/*` 진입 시 런타임 주입 | `vite.config.editor.js` |
| `devtools.min.js` | 디버그 모드에서만 런타임 주입 | `vite.config.devtools.js` |
lazy 번들(편집기/devtools)이 코어 런타임(DynamicRenderer·엔진 싱글톤·React Context·DevTools 코어)을 재사용할 때는 재번들하지 않고 `window.G7Core.__runtime` 을 빌려 쓴다 — React/컨텍스트/싱글톤 인스턴스 동일성이 강제되기 때문(사본이 둘이면 "Invalid hook call"·컨텍스트 미매칭). 메인 번들이 `G7CoreGlobals` 에서 공유 대상을 `G7Core.__runtime` 에 노출하고, lazy 번들 vite config 는 React 4종(`react`/`react-dom`/`react-dom/client`/`react/jsx-runtime`)을 external→window 로, 코어 런타임 모듈을 `resolveId` 플러그인으로 `__runtime-shims/` 로 치환한다.
### 확장 번들 병합 (서버측 concat)
활성 모듈/플러그인의 프론트엔드 IIFE JS·CSS 는 타입별로 서버에서 하나의 번들로 병합해 서빙한다(`/api/{modules,plugins}/bundle.{js,css}?v={version}`). `ExtensionBundleService` 가 정렬·필터·concat·캐시를 전담하고, 프론트는 `window.G7Config.bundleUrls` 를 읽어 모듈 번들 → 플러그인 번들 순으로 로드한다. 병합 규율:
- priority 순서는 선언형 — 실행 순서는 오직 manifest `loading.priority` 오름차순(`uasort`). 특정 확장 이름을 지목하는 분기를 두지 않는다.
- IIFE 사이는 `\n;\n`(JS)/`\n`(CSS) 로 잇는다. 미사용 시 ASI 경계가 깨져 번들 전체 파싱 에러가 난다.
- 소스맵은 prod strip, dev 는 개별 에셋 서빙 절대 URL 로 rewrite. 개별 에셋 서빙 라우트(`*.map` 포함)는 존치한다.
- 번들 URL 은 반드시 same-origin(`/api/...`). 외부 origin/CDN·protocol-relative 는 gdpr preblocker 에 자기 차단된다.
- 확장 에셋 절대경로는 `getBuiltAssetAbsolutePaths()`(=`getModulePath()`/`getPluginPath()`) 만 쓴다. `base_path("modules"|"plugins")` 직접 조립은 `_bundled` 경로 오해석 → 빈 번들.
- concat 루프는 확장별 try/catch — 실패 확장만 skip 하고 나머지 병합을 지속한다.
- 번들 파일명에 확장 캐시 버전을 포함(`{type}.{version}.{js,css}`). 조합 변경 시 version bump → 새 파일명 → 자동 재생성. 구파일 GC 는 `ext-bundles:cleanup` + `{module,plugin,template}:cache-clear` 가 담당한다. prod 은 version-in-path 디스크 캐시, 비프로덕션은 매 요청 concat.
### 빌드 명령어 (Artisan)
```bash
# 코어 템플릿 엔진 (resources/js/core/template-engine/**)
php artisan core:build # 기본: 템플릿 엔진만 빌드
php artisan core:build --full # 전체 빌드 (npm run build)
php artisan core:build --watch # 파일 감시 모드
# 모듈 빌드 (기본: _bundled 디렉토리)
php artisan module:build sirsoft-ecommerce # _bundled에서 빌드
php artisan module:build --all # 모든 _bundled 모듈 빌드
php artisan module:build sirsoft-ecommerce --watch # 활성 디렉토리에서 watch
php artisan module:build sirsoft-ecommerce --active # 활성 디렉토리에서 빌드
# 템플릿 빌드 (기본: _bundled 디렉토리)
php artisan template:build sirsoft-admin_basic # _bundled에서 빌드
php artisan template:build --all # 모든 _bundled 템플릿 빌드
php artisan template:build sirsoft-admin_basic --watch # 활성 디렉토리에서 watch
php artisan template:build sirsoft-admin_basic --active # 활성 디렉토리에서 빌드
# 플러그인 빌드 (기본: _bundled 디렉토리)
php artisan plugin:build sirsoft-payment # _bundled에서 빌드
php artisan plugin:build --all # 모든 _bundled 플러그인 빌드
php artisan plugin:build sirsoft-payment --watch # 활성 디렉토리에서 watch
php artisan plugin:build sirsoft-payment --active # 활성 디렉토리에서 빌드
```
> **빌드 원칙**: 기본값은 `_bundled` 디렉토리. 빌드 결과물은 빌드 경로 내에만 남음.
> 활성 디렉토리 반영은 `update` 커맨드로만 수행. `--watch` 모드는 실시간 개발용으로 활성 디렉토리를 자동 사용.
---
## 확장 시스템 Artisan 명령어
```bash
# 코어 업데이트
php artisan core:check-updates # 코어 업데이트 확인
php artisan core:update [--force] [--no-backup] [--no-maintenance] # 코어 업데이트 실행
php artisan core:execute-upgrade-steps --from=X.Y.Z --to=A.B.C [--force] # 업그레이드 스텝 단독 실행 (HANDOFF 안내/수동 복구용 — 사전·사후 단계 자동 수행)
# 모듈
php artisan module:list
php artisan module:install [identifier]
php artisan module:activate [identifier]
php artisan module:deactivate [identifier]
php artisan module:uninstall [identifier]
php artisan module:composer-install [identifier?] [--all]
php artisan module:cache-clear [identifier?]
php artisan module:seed [identifier] [--sample] [--count=key=value]
php artisan module:check-updates [identifier?]
php artisan module:update [identifier] [--force] [--source=auto|bundled|github]
# 플러그인
php artisan plugin:list
php artisan plugin:install [identifier]
php artisan plugin:activate [identifier]
php artisan plugin:deactivate [identifier]
php artisan plugin:uninstall [identifier]
php artisan plugin:composer-install [identifier?] [--all]
php artisan plugin:cache-clear [identifier?]
php artisan plugin:seed [identifier] [--sample] [--count=key=value]
php artisan plugin:check-updates [identifier?]
php artisan plugin:update [identifier] [--force] [--source=auto|bundled|github]
# 템플릿
php artisan template:list
php artisan template:install [identifier]
php artisan template:activate [identifier]
php artisan template:deactivate [identifier]
php artisan template:uninstall [identifier]
php artisan template:cache-clear
php artisan template:check-updates [identifier?]
php artisan template:update [identifier] [--layout-strategy=overwrite] [--force] [--source=auto|bundled|github]
# Composer 의존성 (모듈/플러그인별 독립 vendor/)
php artisan extension:composer-install
# 오토로드
php artisan extension:update-autoload
```
---
## SEO Artisan 커맨드
```bash
php artisan seo:warmup [--layout=]
php artisan seo:clear [--layout=]
php artisan seo:stats
php artisan seo:generate-sitemap [--sync]
```
---
## 코드 스타일/마이그레이션 명령어
```bash
# 코드 스타일 (Laravel Pint)
vendor/bin/pint --dirty
# 마이그레이션
php artisan make:migration create_[table]_table
php artisan migrate
php artisan migrate:rollback
```
---
## 파일 유형별 규정 확인
파일 수정 **전** 해당 규정 파일을 먼저 확인합니다:
| 수정 대상 파일 패턴 | 작업 전 필수 참조 |
| ------------------- | ------------------ |
| `app/Http/Controllers/**` | [controllers.md](docs/backend/controllers.md), [api-documentation.md](docs/backend/api-documentation.md) |
| `app/Services/**` | [service-repository.md](docs/backend/service-repository.md) |
| `app/Http/Requests/**` | [validation.md](docs/backend/validation.md) |
| `app/Repositories/**` | [service-repository.md](docs/backend/service-repository.md) |
| `app/Http/Resources/**` | [api-resources.md](docs/backend/api-resources.md) |
| `app/**/DTO/**`, `modules/**/src/DTO/**`, `plugins/**/src/DTO/**` | [dto.md](docs/backend/dto.md) |
| `database/migrations/**` | [database-guide.md](docs/database-guide.md) |
| `database/seeders/**` | [database-guide.md](docs/database-guide.md) |
| `resources/layouts/**/*.json` | [layout-json.md](docs/frontend/layout-json.md) |
| `templates/**/layouts/**/*.json` | [layout-json.md](docs/frontend/layout-json.md) |
| `templates/**/src/components/**/*.tsx` | [components.md](docs/frontend/components.md) |
| `modules/**/Listeners/**` | [hooks.md](docs/extension/hooks.md) |
| `plugins/**/Listeners/**` | [hooks.md](docs/extension/hooks.md) |
| `lang/{ko,en}/**/*.php` | [database-guide.md](docs/database-guide.md) (다국어 섹션) — 코어 백엔드 다국어 |
| `lang/{ko,en}.json`, `lang/partial/{ko,en}/**` | [data-binding-i18n.md](docs/frontend/data-binding-i18n.md) — 코어 프론트엔드 다국어 (`$t:core.*`) |
| `lang/**` | [database-guide.md](docs/database-guide.md) (다국어 섹션) |
| `routes/**` | [routing.md](docs/backend/routing.md) |
| `app/Seo/**` | [seo-system.md](docs/backend/seo-system.md) |
---
## 참고 파일 위치
- **AbstractModule**: `app/Extension/AbstractModule.php`
- **HookManager**: `app/Extension/HookManager.php`
- **ModuleManager**: `app/Extension/ModuleManager.php`
- **PluginManager**: `app/Extension/PluginManager.php`
- **TemplateManager**: `app/Extension/TemplateManager.php`
- **CoreStorageDriver**: `app/Extension/Storage/CoreStorageDriver.php`
- **ResponseHelper**: `app/Helpers/ResponseHelper.php`
- **ExtensionStatusGuard**: `app/Extension/Helpers/ExtensionStatusGuard.php`
- **ExtensionBackupHelper**: `app/Extension/Helpers/ExtensionBackupHelper.php`
- **ExtensionPendingHelper**: `app/Extension/Helpers/ExtensionPendingHelper.php`
- **ExtensionRoleSyncHelper**: `app/Extension/Helpers/ExtensionRoleSyncHelper.php`
- **ExtensionMenuSyncHelper**: `app/Extension/Helpers/ExtensionMenuSyncHelper.php`
- **SettingsMigrator**: `app/Extension/Helpers/SettingsMigrator.php`
- **UpgradeStepInterface**: `app/Contracts/Extension/UpgradeStepInterface.php`
- **UpgradeContext**: `app/Extension/UpgradeContext.php`
- **SeoRenderer**: `app/Seo/SeoRenderer.php`
- **SeoMiddleware**: `app/Seo/SeoMiddleware.php`
- **SeoCacheManager**: `app/Seo/SeoCacheManager.php`
- **SeoServiceProvider**: `app/Seo/SeoServiceProvider.php`
- **SitemapContributorInterface**: `app/Seo/Contracts/SitemapContributorInterface.php`
- **SitemapGenerator**: `app/Seo/SitemapGenerator.php`
- **ActivityLogChannel**: `app/ActivityLog/ActivityLogChannel.php`
- **ActivityLogHandler**: `app/ActivityLog/ActivityLogHandler.php`
- **ActivityLogProcessor**: `app/ActivityLog/ActivityLogProcessor.php`
- **ResolvesActivityLogType**: `app/ActivityLog/Traits/ResolvesActivityLogType.php`
- **ChangeDetector**: `app/ActivityLog/ChangeDetector.php`
- **CoreActivityLogListener**: `app/Listeners/CoreActivityLogListener.php`