Community라이팅 & 에디팅github.com

BESSER-PEARL/BESSER-Skills

BESSER에 기여하기 위한 에이전트 스킬 — UML 클래스 모델링, 코드 생성기, 문제 해결, 기여자 워크플로우에 대한 심층 BESSER 지식을 AI 코딩 에이전트에 제공합니다.

BESSER-Skills란 무엇인가요?

이 스킬은 AI 코딩 에이전트(예: Claude Code, Cursor, Codex)가 BESSER 프로젝트에 효과적으로 기여할 수 있도록 설계된 포괄적인 가이드입니다. BESSER는 도메인 모델링, 특히 UML 클래스 다이어그램 기반의 코드 생성을 위한 Python 프레임워크로, 이 스킬은 새로운 생성기 추가, 메타모델/서브-DSL 설계, 테스트, 문서화 등 주요 기여 시나리오를 단계별로 설명합니다. 구체적으로는 스캐폴딩 스크립트를 통한 생성기 보일러플레이트 자동 생성, Jinja2 템플릿 작성 규칙, 웹 편집기 백엔드 등록 방법, pytest를 활용한 테스트 작성 패턴, Sphinx 기반 문서 구조 등을 상세히 다룹니다. 또한 Conventional Commit 메시지 규칙, PR 워크플로우, 크로스-레포 변경 시 서브모듈 처리 방법, CI/CD 및 릴리스 절차까지 포함되어 있어, 에이전트가 사람의 개입 없이도 BESSER 저장소에 독립적으로 기여할 수 있도록 지원합니다.

지원 대상✓Claude Code~Codex CLI✓Cursor✓Windsurf
npx skills add BESSER-PEARL/BESSER-Skills

Installed? Explore more 라이팅 & 에디팅 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

BESSER에 기여하기

이 스킬은 BESSER 코드베이스에 기여하기 위한 절차별 워크플로우를 다룹니다. 아키텍처 세부 사항과 코드 규칙에 대해서는 컨텍스트에 자동으로 로드되는 프로젝트의 CLAUDE.md 파일도 함께 참조하세요.


개발 환경 설정

git clone https://github.com/<your-username>/BESSER.git
cd BESSER
python -m venv venv
# Windows: venv\Scripts\activate
# macOS/Linux: source venv/bin/activate
pip install -r requirements.txt
pip install -r docs/requirements.txt  # 문서 빌드용
pip install -e .                       # 편집 가능한 설치

확인: python tests/BUML/metamodel/structural/library/library.py

Python 3.10+가 필요합니다 (Django 5.x 의존성 때문).


새 생성기 추가하기

이것은 가장 일반적인 기여 유형입니다. 보일러플레이트를 생략하려면 번들로 제공되는 스캐폴드를 실행하세요:

python scripts/scaffold_generator.py <name> <path/to/BESSER/repo>
# 예: python scripts/scaffold_generator.py graphql ~/code/BESSER

이 명령은 즉시 실행 가능한 작업 스텁 패키지(생성기 클래스 + Jinja 템플릿 + pytest)를 생성합니다. 실제 로직을 구현하고, 웹 편집기 설정에 생성기를 등록하고, 문서를 추가해야 합니다 — 아래 단계에서 다룹니다.

수동으로 수행하려면 다음 6단계를 따르세요:

1단계: 생성기 패키지 만들기

besser/generators/my_generator/
  __init__.py               # 내보내기: from .my_generator import MyGenerator
  my_generator.py           # 생성기 클래스
  templates/                # Jinja2 템플릿
    output_template.py.j2

기존 간단한 생성기(예: python_classes/)에서 스캐폴드를 복사하세요.

2단계: GeneratorInterface 구현하기

# besser/generators/my_generator/my_generator.py
import os
from jinja2 import Environment, FileSystemLoader
from besser.generators import GeneratorInterface

class MyGenerator(GeneratorInterface):

    def __init__(self, model, output_dir: str = None):
        super().__init__(model, output_dir)

    def generate(self):
        """B-UML 모델에서 출력을 생성합니다."""
        templates_path = os.path.join(os.path.dirname(__file__), "templates")
        env = Environment(
            loader=FileSystemLoader(templates_path),
            trim_blocks=True,
            lstrip_blocks=True,
        )
        template = env.get_template("output_template.py.j2")
        output = template.render(
            classes=self.model.get_classes(),
            associations=self.model.associations,
        )
        file_path = self.build_generation_path("my_output.py")
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(output)

주요 요구사항:

  • super().__init__(model, output_dir) 호출 — 이렇게 하면 self.model과 self.output_dir이 설정됩니다.
  • 올바른 출력 파일 경로를 얻으려면 self.build_generation_path(filename)을 사용하세요.
  • 디렉토리 경로만 필요한 경우 self.build_generation_dir()을 사용하세요.
  • Jinja2 템플릿은 생성기와 함께 templates/ 하위 디렉토리에 있어야 합니다.
  • 출력은 결정적이어야 합니다 — 동일한 모델은 매번 동일한 출력을 생성해야 합니다 (타임스탬프, 난수 값 등 금지).
  • 재사용 가능한 헬퍼는 생성기가 아닌 besser/utilities/에 위치해야 합니다.

3단계: Jinja2 템플릿 작성하기

{# besser/generators/my_generator/templates/output_template.py.j2 #}
# Generated by BESSER MyGenerator
{% for cls in classes %}
class {{ cls.name }}:
    {% for attr in cls.attributes %}
    {{ attr.name }}: {{ attr.type.name }}
    {% endfor %}

{% endfor %}

JSX/React를 생성하는 경우 충돌을 피하기 위해 비표준 구분 기호를 사용하세요:

env = Environment(
    loader=FileSystemLoader(templates_path),
    variable_start_string="[[",
    variable_end_string="]]",
)

4단계: 웹 편집기 백엔드에 등록하기

besser/utilities/web_modeling_editor/backend/config/generators.py에 생성기를 추가하세요:

from besser.generators.my_generator import MyGenerator

GENERATORS = {
    # ... 기존 생성기 ...
    "my_generator": GeneratorInfo(
        generator_class=MyGenerator,
        output_type="file",           # "file" (단일 파일) 또는 "zip" (다중 파일)
        file_extension=".py",         # 출력 파일 확장자
        category="my_category",       # 그룹화 레이블
        requires_class_diagram=True,  # DomainModel이 필요한 경우 True
    ),
}

생성기가 특정 파일 이름을 생성하는 경우 같은 파일의 get_filename_for_generator()도 업데이트하세요.

5단계: 테스트 작성하기

tests/generators/my_generator/test_my_generator.py를 만드세요:

import os
import shutil
import pytest
from besser.BUML.metamodel.structural import (
    DomainModel, Class, Property, BinaryAssociation,
    Multiplicity, StringType, IntegerType,
)
from besser.generators.my_generator import MyGenerator


@pytest.fixture
def domain_model():
    """작은 테스트 모델을 만듭니다."""
    name_prop = Property(name="name", type=StringType)
    age_prop = Property(name="age", type=IntegerType)
    person = Class(name="Person", attributes={name_prop, age_prop})
    model = DomainModel(name="TestModel", types={person})
    return model


@pytest.fixture
def output_dir(tmp_path):
    return str(tmp_path)


def test_generate_creates_output_file(domain_model, output_dir):
    gen = MyGenerator(model=domain_model, output_dir=output_dir)
    gen.generate()
    assert os.path.exists(os.path.join(output_dir, "my_output.py"))


def test_generate_contains_class_name(domain_model, output_dir):
    gen = MyGenerator(model=domain_model, output_dir=output_dir)
    gen.generate()
    with open(os.path.join(output_dir, "my_output.py")) as f:
        content = f.read()
    assert "Person" in content
    assert "name" in content
    assert "age" in content

코드베이스 전체에서 사용되는 테스트 패턴:

  • 출력 디렉토리에는 tmp_path 또는 tmpdir fixture를 사용하세요 (자동 정리됨).
  • 작은 인라인 모델을 fixture로 빌드하세요 — 외부 파일에 의존하지 마세요.
  • 구조(파일 존재, 클래스 이름 포함)와 내용(비즈니스 로직)을 모두 테스트하세요.
  • import 가능한 Python을 생성하는 생성기의 경우 importlib을 사용하여 생성된 코드를 동적으로 가져와 테스트하세요.
  • pytest tmp fixture를 사용하지 않는 경우 shutil.rmtree()로 정리하세요.

6단계: 문서화하기

docs/source/generators/my_generator.rst에 문서를 추가하세요:

MyGenerator
===========

MyGenerator는 B-UML 도메인 모델로부터 ...를 생성합니다.

필수 조건
-------------

- BESSER 설치
- ...

사용법
-----

.. code-block:: python

   from besser.generators.my_generator import MyGenerator

   gen = MyGenerator(model=my_model, output_dir="./output")
   gen.generate()

출력
------

생성기는 ``my_output.py``라는 단일 파일을 생성하며, 여기에는 ...가 포함됩니다.

docs/source/generators.rst의 생성기 인덱스에 추가하세요.


새 메타모델 / 서브-DSL 추가하기

1단계: 메타모델 설계하기

besser/BUML/metamodel/your_dsl/에 새 클래스를 만드세요:

besser/BUML/metamodel/your_dsl/
  __init__.py
  your_dsl.py    # 메타모델 클래스

기존 패턴을 따르세요:

  • 기본 클래스로 NamedElement 또는 Element를 확장하세요.
  • getter/setter 검증과 함께 비공개 속성을 사용하세요.
  • 기존 메타모델 패키지와 일관된 이름을 유지하세요.

2단계: 컨버터 추가하기 (웹 편집기 통합이 필요한 경우)

컨버터는 프론트엔드 JSON과 B-UML 객체 간의 변환을 수행합니다:

besser/utilities/web_modeling_editor/backend/services/converters/
  json_to_buml/process_your_dsl.py    # JSON → BUML
  buml_to_json/your_dsl_to_json.py    # BUML → JSON

컨버터는 대칭적이어야 합니다: 한 방향에서 지원되는 모든 기능은 다른 방향에서도 작동해야 합니다. 라운드-트립을 테스트하세요: JSON → BUML → JSON은 원본과 동일해야 합니다.

3단계: 유효성 검사 추가하기

메타모델에 setter가 강제하는 것 이상의 제약 조건이 있는 경우:

def validate(self, raise_exception=True):
    errors = []
    warnings = []
    # 구조적 규칙 확인
    if some_bad_condition:
        errors.append("설명적인 오류 메시지")
    result = {"success": len(errors) == 0, "errors": errors, "warnings": warnings}
    if raise_exception and errors:
        raise ValueError("\n".join(errors))
    return result

4단계: 백엔드에 연결하기

  • besser/utilities/web_modeling_editor/backend/backend.py에 엔드포인트를 추가하세요.
  • backend/models/에 Pydantic 요청/응답 모델을 추가하세요.
  • 해당하는 경우 /validate-diagram 아래에 유효성 검사 라우트를 추가하세요.

5단계: 테스트 작성하기

tests/BUML/metamodel/your_dsl/에 테스트를 배치하세요:

  • 유효한 모델 생성 테스트.
  • 잘못된 모델 테스트 (ValueError, TypeError 예상).
  • 해당하는 경우 라운드-트립 컨버터 테스트.

6단계: 문서화하기

  • 메타모델 문서: docs/source/buml_language/your_dsl.rst
  • docs/source/buml_language.rst 인덱스에 추가하세요.

테스트

모든 테스트 실행

python -m pytest

특정 테스트 실행

# 디렉토리별
python -m pytest tests/generators/sqlalchemy/

# 키워드별
python -m pytest -k "test_django"

# 자세한 출력 포함
python -m pytest -v tests/BUML/metamodel/structural/

# 첫 번째 실패에서 중지
python -m pytest -x

테스트 규칙

  • 테스트 파일: test_*.py
  • 테스트 함수: test_*
  • 재사용 가능한 모델과 디렉토리에는 @pytest.fixture를 사용하세요.
  • 출력 디렉토리에는 tmp_path를 사용하세요 (pytest에 의해 자동 정리됨).
  • 파일 존재 여부뿐만 아니라 특정 내용을 단언하세요.
  • 오류 조건은 pytest.raises(ValueError)로 테스트하세요.

테스트할 내용

메타모델 변경의 경우: 생성 검증, setter 제약 조건, 잘못된 입력에 대한 예상 오류, validate() 결과.

생성기의 경우: 출력 파일 존재, 출력의 주요 내용(클래스 이름, 관계 매핑), 엣지 케이스(빈 모델, 깊은 상속, 다대다).

컨버터의 경우: 라운드-트립 정확도(JSON → BUML → JSON이 동등해야 함).


문서 빌드

cd docs

# Windows:
make.bat html

# macOS/Linux:
make html

# 보기:
# 브라우저에서 docs/build/html/index.html 열기

문서는 Furo 테마와 함께 Sphinx를 사용합니다. 소스 파일은 docs/source/ 디렉토리의 reStructuredText(.rst)입니다.

문서 구조

경로내용
docs/source/buml_language/메타모델 문서
docs/source/generators/생성기 문서
docs/source/web_editor.rst웹 편집기 API 문서
docs/source/utilities/유틸리티 문서
docs/source/contributing/기여자 가이드
docs/source/releases/릴리스 노트
docs/source/_static/공유 이미지 및 에셋
docs/source/img/다이어그램 및 스크린샷

상호 참조

상호 링크에 Sphinx 역할을 사용하세요:

  • :doc:\generators/django`` — 다른 문서 페이지로 링크
  • :mod:\besser.generators.django`` — 모듈 API로 링크
  • :class:\besser.BUML.metamodel.structural.Class`` — 클래스 API로 링크
  • :ref:\label-name`` — 레이블이 지정된 섹션으로 링크

코드 스타일

  • PEP 8, 4-스페이스 들여쓰기.
  • 120자 줄 제한 (pyproject.toml의 [tool.pylint.'FORMAT']에 설정됨).
  • 공개 API에 대한 타입 힌트.
  • PEP 257 독스트링.
  • 임포트 순서: 표준 라이브러리, 서드파티, 로컬 모듈.
  • snake_case 함수/변수, PascalCase 클래스, UPPER_CASE 상수.
  • 와일드카드 임포트 금지, 암시적 재내보내기 금지.
  • 린팅: pylint (ruff/black/pre-commit 훅은 현재 구성되지 않음).

커밋 및 PR 규칙

커밋 메시지

Conventional Commits를 사용하세요:

feat: AWS EKS용 Terraform 생성기 추가
fix: 0..* 연관에 대한 다중성 구문 분석 수정
refactor: 공유 템플릿 헬퍼를 besser.utilities로 추출
docs: QiskitGenerator 사용법 문서화
test: GUI 모델 컨버터에 대한 라운드-트립 테스트 추가

Pull Request 워크플로우

  1. 토픽 브랜치를 만드세요: git checkout -b feature/add-my-generator.
  2. 집중적이고 논리적으로 그룹화된 커밋을 만드세요.
  3. 로컬에서 테스트를 실행하세요: python -m pytest.
  4. 문서를 빌드하세요: cd docs && make html.
  5. 최신 master에 리베이스하세요: git fetch origin && git rebase origin/master.
  6. 푸시하고 master에 대한 PR을 여세요.
  7. PR 템플릿을 작성하세요: 설명, 실행된 테스트, 추가 컨텍스트.
  8. 병합을 위해 메인테이너 승인 2개가 필요합니다 (14일 후에는 승인 1개로 충분).

크로스-레포 변경 (BESSER + 프론트엔드)

변경 사항이 BESSER와 웹 편집기 프론트엔드 모두에 영향을 미치는 경우:

  1. WME 저장소에서 WME 변경 사항을 구현하고 커밋하세요.
  2. 이 저장소에서 BESSER 변경 사항을 구현하세요.
  3. 서브모듈 포인터를 업데이트하세요:
    cd besser/utilities/web_modeling_editor/frontend
    git fetch && git checkout <commit>
    cd ../../../..
    git add besser/utilities/web_modeling_editor/frontend
    
  4. 리뷰어가 올바른 순서로 병합할 수 있도록 두 PR을 연결하세요.

CI / 릴리스

  • CI: 자동화된 테스트 워크플로우가 없습니다 — 테스트는 PR 전에 로컬에서 실행됩니다.
  • 릴리스: GitHub 릴리스를 생성하여 트리거됩니다. python-publish.yml 워크플로우가 PYPI_API_TOKEN을 사용하여 PyPI에 빌드하고 게시합니다.
  • 버전: setup.cfg의 [metadata] version에 정의됩니다.
  • 릴리스 노트: docs/source/releases/ 디렉토리에 RST 파일로 추가됩니다.

일반적인 기여 함정

  1. 로직을 중복하지 마세요 — 공유 헬퍼는 개별 생성기가 아닌 besser/utilities/에 위치해야 합니다.
  2. 결정성을 유지하세요 — 생성기는 동일한 입력에 대해 동일한 출력을 생성해야 합니다.
  3. 컨버터를 대칭적으로 유지하세요 — JSON → BUML이 기능을 지원하면 BUML → JSON도 지원해야 합니다.
  4. 문서를 업데이트하세요 — 백엔드 변경은 대개 docs/source/ 업데이트가 필요합니다.
  5. 명시적으로 필요하지 않는 한 프론트엔드 서브모듈을 건드리지 마세요 — UI 변경은 업스트림 WME 저장소로 이동합니다.
  6. 리소스를 정리하세요 — 임시 디렉토리와 파일 핸들에는 항상 try/finally를 사용하세요.
  7. 라운드-트립을 테스트하세요 — 특히 컨버터의 경우 (JSON → BUML → JSON은 항등적이어야 함).

관련 스킬