Files
HeuJung b46396b079 fix(harness): CHANGELOG 한 버전 섹션 안 카테고리 헤딩 중복 검출
이슈 브랜치마다 working 버전 섹션 머리에 자기 ### Fixed 블록을 얹고
병합이 양쪽을 이어 붙여 sirsoft-ecommerce 1.2.1 에 Fixed·Changed 가
각 2회 남았다. 기존 판정기는 [Unreleased] 오용만 봐서 어느 훅에서도
검출되지 않았다.

- check-changelog-section: 카테고리 헤딩 중복·버전 헤더 중복·허용 밖
 카테고리 축 추가. working 섹션(+[Unreleased])만 차단, 공개 배포된
 과거 섹션은 notice (--notices). 모집단에 lang-packs 포함
- validate-runner: 공개 CHANGELOG 편집 직후 그 파일만 검사해 즉시 알림
- stop-guard 7.6: 판정기·픽스처 편집 시 픽스처 자동 실행 (저장소
 위반 0건이라 전수 실행이 공허 통과)
- 픽스처 13건, 규정 4문서, coverage 항목
- ecommerce 1.2.1 두 번째 Fixed·Changed 블록을 첫 블록에 병합 (항목 삭제 0)
2026-09-07 08:05:55 +09:00

13 KiB

Changelog 규칙 (Changelog Rules)

코어 및 확장(모듈, 플러그인, 템플릿)의 변경사항을 CHANGELOG.md에 기록하는 규정

TL;DR (5초 요약)

1. 확장/코어 버전 업 시 CHANGELOG.md에 변경사항 기록 필수 (미기록 시 버전 업 불가)
2. 형식: Keep a Changelog 표준 (## [버전] - 날짜 / ### 카테고리 / - 항목) — 한 버전 안에 같은 카테고리 헤딩은 한 번만
3. 허용 카테고리: Added, Changed, Deprecated, Removed, Fixed, Security
4. 확장 위치: 각 확장의 루트 디렉토리 (modules/_bundled/vendor-module/CHANGELOG.md)
5. 코어 위치: 프로젝트 루트 /CHANGELOG.md (코어 버전 변경 시 필수)

목차

  1. CHANGELOG.md 작성 규칙
  2. 카테고리 정의
  3. 파일 위치
  4. ChangelogParser 헬퍼
  5. API 엔드포인트
  6. 관리 화면 표시
  7. 템플릿 예시
  8. 코어 버전 제약 정책
  9. 다국어 파일 변경과 CHANGELOG

1. CHANGELOG.md 작성 규칙

필수 사항

확장 버전 업 시 CHANGELOG.md에 변경사항 기록 필수
미기록 시 버전 업 작업 불완전으로 간주
릴리스 태깅 전 composer test-smoke 통과 필수 (Installation 스위트)
  • 형식: Keep a Changelog 표준 준수
  • 버전 관리: Semantic Versioning 준수
  • 작성 언어: 한국어 (확장 대상이 한국어 사용자)
  • 날짜 형식: YYYY-MM-DD (ISO 8601)
  • 최신 버전: 파일 상단에 위치 (역순)

릴리스 전 Smoke Suite 통과

코어 또는 번들 확장의 버전 bump 시 다음을 충족해야 합니다:

# 릴리스 전 필수 검증
composer test-smoke

# 첫 실패에서 중단 (CI 모드)
composer test-smoke-ci
  • composer test-smoke는 tests/Feature/Installation/ 및 각 번들 확장의 tests/Feature/Installation/ 디렉토리를 실행합니다.
  • 스모크 통과 없이 릴리스 태깅을 진행하면 beta.2 #12 유형(마이그레이션–Repository 결합 누락) 회귀를 놓칠 수 있습니다.
  • 상세: testing-guide.md#pre-release-smoke-suite

버전 섹션 형식

## [버전] - YYYY-MM-DD

예: ## [0.1.2] - 2026-02-25

항목 형식

### 카테고리
- 변경사항 설명

한 버전 섹션 안의 카테고리는 한 번만

브랜치마다 버전 섹션 머리에 자기 ### Fixed 블록을 새로 얹으면, 병합이 양쪽을 그대로 이어 붙여 한 버전 안에 같은 카테고리 헤딩이 두 번 남는다. 오류도 경고도 없고, 렌더된 문서에서 같은 제목이 두 번 보이는 것이 유일한 증상이다.

  • 새 항목은 이미 있는 카테고리 블록 끝에 추가한다 (같은 카테고리 헤딩을 새로 만들지 않는다)
  • 병합 뒤 두 번째 블록이 보이면 그 항목을 첫 블록에 합치고 헤딩만 지운다 (항목 삭제 금지)
  • 같은 버전 헤더(## [1.2.1])도 한 번만 — 뒤 섹션의 내용을 앞 섹션에 합친다
  • 카테고리 헤딩은 §2 의 6종만 쓴다. 기능 단위 분류는 #### 서브 헤딩으로 둔다
  • 이미 공개 배포된 과거 버전 섹션은 소급 수정하지 않는다

번들 확장은 정적 검사가 working 버전 섹션(+[Unreleased])에서 이 조건을 차단한다. 제3자 확장에는 강제하지 않는다.


2. 카테고리 정의

카테고리 설명 사용 시점
Added 새 기능 추가 새로운 기능, 화면, API 엔드포인트 추가
Changed 기존 기능 변경 기존 동작 수정, 성능 개선, UI 변경
Deprecated 향후 제거 예정 향후 삭제될 기능 경고
Removed 기능 제거 기존 기능/API 삭제
Fixed 버그 수정 기존 버그, 오류 수정
Security 보안 취약점 수정 보안 관련 수정

3. 파일 위치

확장 (모듈/플러그인/템플릿)

modules/_bundled/vendor-module/CHANGELOG.md     # 모듈 (예: sirsoft-board)
plugins/_bundled/vendor-plugin/CHANGELOG.md     # 플러그인 (예: sirsoft-tosspayments)
templates/_bundled/vendor-template/CHANGELOG.md # 템플릿 (예: sirsoft-admin_basic)
  • _bundled 디렉토리에서 작업 (활성 디렉토리 직접 수정 금지)
  • 업데이트 프로세스를 통해 활성 디렉토리로 복사

코어

/CHANGELOG.md                                   # 프로젝트 루트
  • 코어 버전 변경 시 CHANGELOG.md에 변경사항 기록 필수 (미기록 시 버전 업 불가)
  • 확장과 동일한 Keep a Changelog 형식 사용
  • 코어 버전 변경 대상 파일: config/app.php, .env.example, .env.testing
  • 버전 변경 시 반드시 CHANGELOG.md에 해당 버전 섹션 추가 후 버전 파일 수정

4. ChangelogParser 헬퍼

파일: app/Extension/Helpers/ChangelogParser.php

주요 메서드

메서드 설명 반환
parse(string $filePath) CHANGELOG.md 전체 파싱 array (버전별 구조화 배열)
getVersionRange(string $filePath, string $from, string $to) 특정 범위 엔트리 추출 array (from 초과 ~ to 이하)
resolveChangelogPath(string $basePath, string $identifier, ?string $source) 소스별 경로 결정 ?string

반환 형식

[
  [
    'version' => '0.1.2',
    'date' => '2026-02-25',
    'categories' => [
      ['name' => 'Added', 'items' => ['새 기능 A']],
      ['name' => 'Fixed', 'items' => ['버그 B 수정']],
    ],
  ],
]

5. API 엔드포인트

GET /api/admin/modules/{identifier}/changelog
GET /api/admin/plugins/{identifier}/changelog
GET /api/admin/templates/{identifier}/changelog

쿼리 파라미터

파라미터 설명 기본값
source CHANGELOG.md 읽을 위치 active
from_version 이 버전 초과 항목만 반환 -
to_version 이 버전 이하만 반환 -

사용 시나리오

  • 상세 모달: GET .../changelog (전체 조회)
  • 업데이트 모달: GET .../changelog?source=bundled&from_version=0.1.1&to_version=0.1.2 (변경분)

6. 관리 화면 표시

업데이트 모달

  • 전역 상태: _global.updateChangelog (3개 확장 타입 공통)
  • 표시 조건: updateChangelog && updateChangelog.length > 0
  • 표시 위치: 버전 정보 아래, 외부 링크(github_changelog_url) 위
  • 스타일: blue 배경 (bg-blue-50 dark:bg-blue-900/20)
  • 우선순위: 인라인 changelog > 외부 링크 (둘 다 있으면 둘 다 표시)

상세 모달

  • 전역 상태: _global.moduleChangelog, _global.pluginChangelog, _global.templateChangelog (타입별)
  • 섹션 ID: changelog_section
  • 아이콘: clock-rotate-left
  • empty state: _global.xxxChangelog가 비어있으면 "변경 내역 정보가 없습니다." 표시

메인 리스트 액션 흐름

# 업데이트 버튼 클릭 시:
setState(selectedX) → apiCall(changelog?source=bundled&from_version=...&to_version=...)
  → onSuccess: setState(updateChangelog) + openModal(update_modal)
  → onError: setState(updateChangelog=[]) + openModal(update_modal)

# 상세 보기 버튼 클릭 시:
setState(selectedX) → apiCall(changelog)
  → onSuccess: setState(xChangelog) + openModal(detail_modal)
  → onError: setState(xChangelog=[]) + openModal(detail_modal)

7. 템플릿 예시

새 확장을 만들 때 아래 템플릿을 사용합니다:

# Changelog

이 프로젝트의 모든 주요 변경사항을 기록합니다.
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,
[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.

## [0.1.0] - YYYY-MM-DD

### Added
- 초기 기능 구현

8. 코어 버전 제약 정책

확장 manifest(module.json, plugin.json, template.json)의 g7_version 과 dependencies.{modules|plugins} 버전 제약 작성 규칙.

표기 규칙

  • 형식: >=X.Y.Z[-prerelease] 통일 (공백 없음). 예: >=7.0.0-beta.2, >=1.0.0-beta.2
  • 캐럿(^), 틸드(~), 엄격 일치(=) 사용 금지
  • placeholder 금지: 실존하지 않는 버전(0.1.0, 0.0.1 등) 을 적어 사실상 "아무 버전이나 허용" 상태로 두지 않음

g7_version — 코어 최소 요구 버전

  • 확장이 실제로 의존하는 코어 API/기능의 최초 도입 버전을 최소값으로 기재
  • 확장 version 이 bump 될 때 g7_version 재검토 필수
  • 번들 확장은 일반적으로 코어의 현재 릴리스와 같은 단계(beta.X, rc.X, X.Y.Z)를 하한으로 둠
  • 예: 알림 시스템 3계층(NotificationDefinition/Template) 을 사용하는 모듈은 최소 >=7.0.0-beta.2

dependencies.{modules|plugins} — 확장 간 버전 제약

  • A 확장이 B 확장의 공개 Service/Contract/Model/Route/훅을 사용한다면 dependencies.{type}.B: ">=X.Y.Z" 기재
  • B 에서 공개 표면이 변경되거나 새 API 가 도입되어 A 가 그것을 소비하게 되면 A 의 최소 버전 제약을 그 API 최초 도입 버전으로 상향
  • 실존하지 않는 placeholder 버전(>=0.1.0 등)은 금지 — 버전 게이팅 무효화 유발

버전 제약 재검토 트리거

다음 이벤트 발생 시 관련 확장 manifest 를 전수 재검토한다.

이벤트 재검토 대상
코어 공개 확장 표면 변경 (AbstractModule/AbstractPlugin/HookManager/Contracts 등) 모든 번들 확장의 g7_version
코어 minor/major/beta 번호 변경 번들 확장 전체 g7_version
번들 모듈/플러그인의 공개 Service/Contract/Model/Route/훅/CHANGELOG 변경 해당 확장을 dependencies 에 선언한 모든 확장

CHANGELOG 기재

  • g7_version 상향: ### Changed 에 - 코어 최소 요구 버전을 X.Y.Z 로 상향
  • dependencies.{id} 상향: ### Changed 에 - {id} 의존성 버전 제약을 실제 릴리스 버전에 맞춰 정비 또는 - {id} 최소 버전을 X.Y.Z 로 상향 (변경 이유에 따라)

9. 다국어 파일 변경과 CHANGELOG

호스트(코어/모듈/플러그인/템플릿)의 다국어 파일을 변경하면 그 호스트의 CHANGELOG 에도 사용자 관점 항목을 기재한다.

다국어 변경은 대부분 사용자가 화면에서 읽는 문장의 변경이다.

변경 유형 사용자에게 보이는 결과
누락 키 추가 원문 키(sirsoft-ecommerce::messages.settings.fetch_success)가 그대로 보이던 화면이 정상 문구로 바뀐다
문구 정정 잘못된 안내(예: 실제 입력 한도와 다른 글자 수)가 고쳐진다
오탈자 정정 화면 용어가 바뀐다

이들 변경은 diff 상 lang/ 아래에만 남아 CHANGELOG 를 쓰는 시점에 눈에 띄지 않는다. 실제로 이슈 #78 에서 코어·이커머스·게시판·kginicis 4개 호스트의 문구 수정이 기록 없이 누락됐고, 그중에는 로그인/역할 권한 거절 응답에 원문 키가 노출되던 사용자 관점 결함도 있었다.

기재 예시

### Fixed

- 일부 안내가 실제 문장 대신 내부 식별자로 표시되던 문제를 수정했습니다. 로그인하지 않았거나 역할 권한이 없어 요청이 거절될 때가 해당됩니다.

면제

사용자에게 노출되지 않는 문구(내부 커맨드 출력, 테스트 픽스처 등)는 변경 파일 상단에 사유와 함께 면제 주석을 둔다.

// changelog:allow 개발용 Artisan 커맨드 출력 문구 — 사용자 화면 비노출

자동 검증

정적 검사가 다음 두 가지를 본다.

  • 변경셋에 호스트 lang 파일이 있는데 그 호스트 CHANGELOG 가 없으면 경고한다.
  • 같은 레벨의 중복 배열 키는 차단한다. 뒤 선언이 앞을 덮어 앞 블록 전용 키가 런타임에 소실되고 원문 키가 노출되기 때문이다. 정정은 항상 두 블록 병합 — 한쪽만 삭제하면 그쪽 키가 사라진다.

언어팩 패키지(lang-packs/_bundled/**)는 별도 검사가 담당하므로 본 절의 대상이 아니다.


관련 파일

  • app/Extension/Helpers/ChangelogParser.php — Changelog 파서
  • app/Http/Requests/Extension/ChangelogRequest.php — FormRequest 검증
  • app/Http/Controllers/Api/Admin/ModuleController.php — changelog() 메서드
  • app/Http/Controllers/Api/Admin/PluginController.php — changelog() 메서드
  • app/Http/Controllers/Api/Admin/TemplateController.php — changelog() 메서드
  • app/Services/ModuleService.php — getModuleChangelog() 메서드
  • app/Services/PluginService.php — getPluginChangelog() 메서드
  • app/Services/TemplateService.php — getTemplateChangelog() 메서드